diff --git a/js/public/CharacterEncoding.h b/js/public/CharacterEncoding.h index b1cc513c511c..e3dae37c9054 100644 --- a/js/public/CharacterEncoding.h +++ b/js/public/CharacterEncoding.h @@ -119,7 +119,7 @@ class UTF8CharsZ : public mozilla::RangedPtr * to others. This differs from UTF8CharsZ in that the chars are * const and it allows assignment. */ -class ConstUTF8CharsZ +class JS_PUBLIC_API(ConstUTF8CharsZ) { const char* data_; diff --git a/js/src/builtin/Reflect.cpp b/js/src/builtin/Reflect.cpp index 9b005cf9f170..f2f35915e932 100644 --- a/js/src/builtin/Reflect.cpp +++ b/js/src/builtin/Reflect.cpp @@ -46,30 +46,6 @@ Reflect_deleteProperty(JSContext* cx, unsigned argc, Value* vp) return true; } -/* ES6 26.1.6 Reflect.get(target, propertyKey [, receiver]) */ -static bool -Reflect_get(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - - // Step 1. - RootedObject obj(cx, NonNullObjectArg(cx, "`target`", "Reflect.get", args.get(0))); - if (!obj) - return false; - - // Steps 2-3. - RootedValue propertyKey(cx, args.get(1)); - RootedId key(cx); - if (!ToPropertyKey(cx, propertyKey, &key)) - return false; - - // Step 4. - RootedValue receiver(cx, args.length() > 2 ? args[2] : args.get(0)); - - // Step 5. - return GetProperty(cx, obj, receiver, key, args.rval()); -} - /* ES6 26.1.8 Reflect.getPrototypeOf(target) */ bool js::Reflect_getPrototypeOf(JSContext* cx, unsigned argc, Value* vp) @@ -210,7 +186,7 @@ static const JSFunctionSpec methods[] = { JS_SELF_HOSTED_FN("construct", "Reflect_construct", 2, 0), JS_SELF_HOSTED_FN("defineProperty", "Reflect_defineProperty", 3, 0), JS_FN("deleteProperty", Reflect_deleteProperty, 2, 0), - JS_FN("get", Reflect_get, 2, 0), + JS_SELF_HOSTED_FN("get", "Reflect_get", 2, 0), JS_SELF_HOSTED_FN("getOwnPropertyDescriptor", "Reflect_getOwnPropertyDescriptor", 2, 0), JS_INLINABLE_FN("getPrototypeOf", Reflect_getPrototypeOf, 1, 0, ReflectGetPrototypeOf), JS_SELF_HOSTED_FN("has", "Reflect_has", 2, 0), diff --git a/js/src/builtin/Reflect.js b/js/src/builtin/Reflect.js index e6e52a5abf35..93a603c3fdea 100644 --- a/js/src/builtin/Reflect.js +++ b/js/src/builtin/Reflect.js @@ -127,10 +127,30 @@ function Reflect_getOwnPropertyDescriptor(target, propertyKey) { // 26.1.8 Reflect.has ( target, propertyKey ) function Reflect_has(target, propertyKey) { // Step 1. - if (!IsObject(target)) + if (!IsObject(target)) { ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT_ARG, "`target`", "Reflect.has", ToSource(target)); + } // Steps 2-3 are identical to the runtime semantics of the "in" operator. return propertyKey in target; } + +// ES2018 draft rev 0525bb33861c7f4e9850f8a222c89642947c4b9c +// 26.1.5 Reflect.get ( target, propertyKey [ , receiver ] ) +function Reflect_get(target, propertyKey/*, receiver*/) { + // Step 1. + if (!IsObject(target)) { + ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT_ARG, "`target`", "Reflect.get", + ToSource(target)); + } + + // Step 3 (reordered). + if (arguments.length > 2) { + // Steps 2, 4. + return getPropertySuper(target, propertyKey, arguments[2]); + } + + // Steps 2, 4. + return target[propertyKey]; +} diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index 2bbb42ad410a..a22f335763b4 100644 --- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -60,6 +60,8 @@ enum AssignmentOperator { AOP_LSH, AOP_RSH, AOP_URSH, /* binary */ AOP_BITOR, AOP_BITXOR, AOP_BITAND, + /* short-circuit */ + AOP_COALESCE, AOP_OR, AOP_AND, AOP_LIMIT }; @@ -116,19 +118,22 @@ enum PropKind { }; static const char* const aopNames[] = { - "=", /* AOP_ASSIGN */ - "+=", /* AOP_PLUS */ - "-=", /* AOP_MINUS */ - "*=", /* AOP_STAR */ - "/=", /* AOP_DIV */ - "%=", /* AOP_MOD */ - "**=", /* AOP_POW */ - "<<=", /* AOP_LSH */ - ">>=", /* AOP_RSH */ - ">>>=", /* AOP_URSH */ - "|=", /* AOP_BITOR */ - "^=", /* AOP_BITXOR */ - "&=" /* AOP_BITAND */ + "=", /* AOP_ASSIGN */ + "+=", /* AOP_PLUS */ + "-=", /* AOP_MINUS */ + "*=", /* AOP_STAR */ + "/=", /* AOP_DIV */ + "%=", /* AOP_MOD */ + "**=", /* AOP_POW */ + "<<=", /* AOP_LSH */ + ">>=", /* AOP_RSH */ + ">>>=", /* AOP_URSH */ + "|=", /* AOP_BITOR */ + "^=", /* AOP_BITXOR */ + "&=", /* AOP_BITAND */ + "\?\?=", /* AOP_COALESCE */ + "||=", /* AOP_OR */ + "&&=", /* AOP_AND */ }; static const char* const binopNames[] = { @@ -1919,6 +1924,12 @@ ASTSerializer::aop(ParseNodeKind kind) return AOP_BITXOR; case ParseNodeKind::BitAndAssign: return AOP_BITAND; + case ParseNodeKind::CoalesceAssignExpr: + return AOP_COALESCE; + case ParseNodeKind::OrAssignExpr: + return AOP_OR; + case ParseNodeKind::AndAssignExpr: + return AOP_AND; default: return AOP_ERR; } @@ -2925,6 +2936,9 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst) case ParseNodeKind::Assign: case ParseNodeKind::AddAssign: case ParseNodeKind::SubAssign: + case ParseNodeKind::CoalesceAssignExpr: + case ParseNodeKind::OrAssignExpr: + case ParseNodeKind::AndAssignExpr: case ParseNodeKind::BitOrAssign: case ParseNodeKind::BitXorAssign: case ParseNodeKind::BitAndAssign: diff --git a/js/src/ds/InlineTable.h b/js/src/ds/InlineTable.h index b8326b169b94..b017333c4204 100644 --- a/js/src/ds/InlineTable.h +++ b/js/src/ds/InlineTable.h @@ -23,7 +23,7 @@ template -class InlineTable +class InlineTable : private AllocPolicy { private: using TablePtr = typename Table::Ptr; @@ -103,7 +103,8 @@ class InlineTable static const size_t SizeOfInlineEntries = sizeof(InlineEntry) * InlineEntries; explicit InlineTable(AllocPolicy a = AllocPolicy()) - : inlNext_(0), + : AllocPolicy(a), + inlNext_(0), inlCount_(0), table_(a) { } @@ -304,6 +305,10 @@ class InlineTable MOZ_ASSERT(!p.found()); MOZ_ASSERT(uintptr_t(inlineEnd()) == uintptr_t(p.inlAddPtr_)); + + if (!this->checkSimulatedOOM()) + return false; + addPtr->update(mozilla::Forward(key), mozilla::Forward(args)...); ++inlCount_; diff --git a/js/src/ds/Nestable.h b/js/src/ds/Nestable.h index c02264abcea1..50712cb7f094 100644 --- a/js/src/ds/Nestable.h +++ b/js/src/ds/Nestable.h @@ -7,6 +7,9 @@ #ifndef ds_Nestable_h #define ds_Nestable_h +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" + namespace js { // A base class for nestable structures. diff --git a/js/src/frontend/BytecodeControlStructures.cpp b/js/src/frontend/BytecodeControlStructures.cpp new file mode 100644 index 000000000000..a75ff0651708 --- /dev/null +++ b/js/src/frontend/BytecodeControlStructures.cpp @@ -0,0 +1,101 @@ +/* -*- 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(&bce->innermostNestableControl), + kind_(kind), + emitterScope_(bce->innermostEmitterScopeNoCheck()) +{} + +BreakableControl::BreakableControl(BytecodeEmitter* bce, StatementKind kind) + : NestableControl(bce, kind) +{ + MOZ_ASSERT(is()); +} + +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* enclosingLoop = findNearest(enclosing()); + + stackDepth_ = bce->stackDepth; + loopDepth_ = enclosingLoop ? enclosingLoop->loopDepth_ + 1 : 1; + + int loopSlots; + if (loopKind == StatementKind::Spread) { + loopSlots = 3; + } else if (loopKind == StatementKind::ForInLoop || loopKind == StatementKind::ForOfLoop) { + loopSlots = 2; + } else { + loopSlots = 0; + } + + MOZ_ASSERT(loopSlots <= stackDepth_); + + if (enclosingLoop) { + canIonOsr_ = (enclosingLoop->canIonOsr_ && + stackDepth_ == enclosingLoop->stackDepth_ + loopSlots); + } else { + canIonOsr_ = stackDepth_ == loopSlots; + } +} + +bool +LoopControl::emitSpecialBreakForDone(BytecodeEmitter* bce) +{ + // This doesn't pop stack values, nor handle any other controls. + // Should be called on the toplevel of the loop. + MOZ_ASSERT(bce->stackDepth == stackDepth_); + MOZ_ASSERT(bce->innermostNestableControl == this); + + if (!bce->newSrcNote(SRC_BREAK)) + return false; + if (!bce->emitJump(JSOP_GOTO, &breaks)) + return false; + + return true; +} + +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()); +} diff --git a/js/src/frontend/BytecodeControlStructures.h b/js/src/frontend/BytecodeControlStructures.h new file mode 100644 index 000000000000..784088617afc --- /dev/null +++ b/js/src/frontend/BytecodeControlStructures.h @@ -0,0 +1,174 @@ +/* -*- 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 +#include + +#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 +{ + StatementKind kind_; + + // The innermost scope when this was pushed. + EmitterScope* emitterScope_; + + protected: + NestableControl(BytecodeEmitter* bce, StatementKind kind); + + public: + using Nestable::enclosing; + using Nestable::findNearest; + + StatementKind kind() const { + return kind_; + } + + EmitterScope* emitterScope() const { + return emitterScope_; + } + + template bool is() const; + + template T& as() { + MOZ_ASSERT(this->is()); + return static_cast(*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() 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() 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 emitSpecialBreakForDone(BytecodeEmitter* bce); + MOZ_MUST_USE bool patchBreaksAndContinues(BytecodeEmitter* bce); +}; +template <> +inline bool +NestableControl::is() 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() const +{ + return kind_ == StatementKind::Try || kind_ == StatementKind::Finally; +} + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_BytecodeControlStructures_h */ diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 1adaa4b9b657..48b8cdbbedb6 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -29,7 +29,21 @@ #include "jsutil.h" #include "ds/Nestable.h" +#include "frontend/BytecodeControlStructures.h" +#include "frontend/CallOrNewEmitter.h" +#include "frontend/ElemOpEmitter.h" +#include "frontend/EmitterScope.h" +#include "frontend/ExpressionStatementEmitter.h" +#include "frontend/ForOfLoopControl.h" +#include "frontend/IfEmitter.h" +#include "frontend/LabelEmitter.h" // LabelEmitter +#include "frontend/NameOpEmitter.h" +#include "frontend/ObjectEmitter.h" // PropertyEmitter, ObjectEmitter, ClassEmitter +#include "frontend/OptionalEmitter.h" #include "frontend/Parser.h" +#include "frontend/PropOpEmitter.h" +#include "frontend/TDZCheckCache.h" +#include "frontend/TryEmitter.h" #include "frontend/TokenStream.h" #include "vm/Debugger.h" #include "vm/GeneratorObject.h" @@ -58,2278 +72,11 @@ using mozilla::PodCopy; using mozilla::Some; using mozilla::Unused; -class BreakableControl; -class LabelControl; -class LoopControl; -class ForOfLoopControl; -class TryFinallyControl; - -static bool -ParseNodeRequiresSpecialLineNumberNotes(ParseNode* pn) -{ - return pn->getKind() == ParseNodeKind::While || pn->getKind() == ParseNodeKind::For; -} - -// A cache that tracks superfluous TDZ checks. -// -// Each basic block should have a TDZCheckCache in scope. Some NestableControl -// subclasses contain a TDZCheckCache. -class BytecodeEmitter::TDZCheckCache : public Nestable -{ - PooledMapPtr cache_; - - MOZ_MUST_USE bool ensureCache(BytecodeEmitter* bce) { - return cache_ || cache_.acquire(bce->cx); - } - - public: - explicit TDZCheckCache(BytecodeEmitter* bce) - : Nestable(&bce->innermostTDZCheckCache), - cache_(bce->cx->frontendCollectionPool()) - { } - - Maybe needsTDZCheck(BytecodeEmitter* bce, JSAtom* name); - MOZ_MUST_USE bool noteTDZCheck(BytecodeEmitter* bce, JSAtom* name, MaybeCheckTDZ check); -}; - -class BytecodeEmitter::NestableControl : public Nestable -{ - StatementKind kind_; - - // The innermost scope when this was pushed. - EmitterScope* emitterScope_; - - protected: - NestableControl(BytecodeEmitter* bce, StatementKind kind) - : Nestable(&bce->innermostNestableControl), - kind_(kind), - emitterScope_(bce->innermostEmitterScopeNoCheck()) - { } - - public: - using Nestable::enclosing; - using Nestable::findNearest; - - StatementKind kind() const { - return kind_; - } - - EmitterScope* emitterScope() const { - return emitterScope_; - } - - template - bool is() const; - - template - T& as() { - MOZ_ASSERT(this->is()); - return static_cast(*this); - } -}; - -// Template specializations are disallowed in different namespaces; specialize -// all the NestableControl subtypes up front. -namespace js { -namespace frontend { - -template <> -bool -BytecodeEmitter::NestableControl::is() const -{ - return StatementKindIsUnlabeledBreakTarget(kind_) || kind_ == StatementKind::Label; -} - -template <> -bool -BytecodeEmitter::NestableControl::is() const -{ - return kind_ == StatementKind::Label; -} - -template <> -bool -BytecodeEmitter::NestableControl::is() const -{ - return StatementKindIsLoop(kind_); -} - -template <> -bool -BytecodeEmitter::NestableControl::is() const -{ - return kind_ == StatementKind::ForOfLoop; -} - -template <> -bool -BytecodeEmitter::NestableControl::is() const -{ - return kind_ == StatementKind::Try || kind_ == StatementKind::Finally; -} - -} // namespace frontend -} // namespace js - -class BreakableControl : public BytecodeEmitter::NestableControl -{ - public: - // Offset of the last break. - JumpList breaks; - - BreakableControl(BytecodeEmitter* bce, StatementKind kind) - : NestableControl(bce, kind) - { - MOZ_ASSERT(is()); - } - - MOZ_MUST_USE bool patchBreaks(BytecodeEmitter* bce) { - return bce->emitJumpTargetAndPatch(breaks); - } -}; - -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) - : BreakableControl(bce, StatementKind::Label), - label_(bce->cx, label), - startOffset_(startOffset) - { } - - HandleAtom label() const { - return label_; - } - - ptrdiff_t startOffset() const { - return startOffset_; - } -}; - -class LoopControl : public BreakableControl -{ - // Loops' children are emitted in dominance order, so they can always - // have a TDZCheckCache. - BytecodeEmitter::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) - : BreakableControl(bce, loopKind), - tdzCache_(bce), - continueTarget({ -1 }) - { - MOZ_ASSERT(is()); - - LoopControl* enclosingLoop = findNearest(enclosing()); - - stackDepth_ = bce->stackDepth; - loopDepth_ = enclosingLoop ? enclosingLoop->loopDepth_ + 1 : 1; - - int loopSlots; - if (loopKind == StatementKind::Spread) - loopSlots = 3; - else if (loopKind == StatementKind::ForInLoop || loopKind == StatementKind::ForOfLoop) - loopSlots = 2; - else - loopSlots = 0; - - MOZ_ASSERT(loopSlots <= stackDepth_); - - if (enclosingLoop) { - canIonOsr_ = (enclosingLoop->canIonOsr_ && - stackDepth_ == enclosingLoop->stackDepth_ + loopSlots); - } else { - canIonOsr_ = stackDepth_ == loopSlots; - } - } - - uint32_t loopDepth() const { - return loopDepth_; - } - - bool canIonOsr() const { - return canIonOsr_; - } - - MOZ_MUST_USE bool emitSpecialBreakForDone(BytecodeEmitter* bce) { - // This doesn't pop stack values, nor handle any other controls. - // Should be called on the toplevel of the loop. - MOZ_ASSERT(bce->stackDepth == stackDepth_); - MOZ_ASSERT(bce->innermostNestableControl == this); - - if (!bce->newSrcNote(SRC_BREAK)) - return false; - if (!bce->emitJump(JSOP_GOTO, &breaks)) - return false; - - return true; - } - - MOZ_MUST_USE bool patchBreaksAndContinues(BytecodeEmitter* bce) { - MOZ_ASSERT(continueTarget.offset != -1); - if (!patchBreaks(bce)) - return false; - bce->patchJumpsToTarget(continues, continueTarget); - return true; - } -}; - -class TryFinallyControl : public BytecodeEmitter::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) - : NestableControl(bce, kind), - emittingSubroutine_(false) - { - MOZ_ASSERT(is()); - } - - void setEmittingSubroutine() { - emittingSubroutine_ = true; - } - - bool emittingSubroutine() const { - return emittingSubroutine_; - } -}; - -static inline void -MarkAllBindingsClosedOver(LexicalScope::Data& data) -{ - BindingName* names = data.names; - for (uint32_t i = 0; i < data.length; i++) - names[i] = BindingName(names[i].name(), true); -} - -// A scope that introduces bindings. -class BytecodeEmitter::EmitterScope : public Nestable -{ - // 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 nameCache_; - - // If this scope's cache does not include free names, such as the - // global scope, the NameLocation to return. - Maybe 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) { - return nameCache_.acquire(bce->cx); - } - - template - MOZ_MUST_USE bool checkSlotLimits(BytecodeEmitter* bce, const BindingIter& bi) { - if (bi.nextFrameSlot() >= LOCALNO_LIMIT || - bi.nextEnvironmentSlot() >= ENVCOORD_SLOT_LIMIT) - { - bce->reportError(nullptr, JSMSG_TOO_MANY_LOCALS); - return false; - } - return true; - } - - MOZ_MUST_USE bool checkEnvironmentChainLength(BytecodeEmitter* bce) { - uint32_t hops; - if (EmitterScope* emitterScope = enclosing(&bce)) - hops = emitterScope->environmentChainLength_; - else - hops = bce->sc->compilationEnclosingScope()->environmentChainLength(); - - if (hops >= ENVCOORD_HOPS_LIMIT - 1) { - bce->reportError(nullptr, JSMSG_TOO_DEEP, js_function_str); - return false; - } - - environmentChainLength_ = mozilla::AssertedCast(hops + 1); - return true; - } - - void updateFrameFixedSlots(BytecodeEmitter* bce, const BindingIter& bi) { - nextFrameSlot_ = bi.nextFrameSlot(); - if (nextFrameSlot_ > bce->maxFixedSlots) - bce->maxFixedSlots = nextFrameSlot_; - MOZ_ASSERT_IF(bce->sc->isFunctionBox() && - (bce->sc->asFunctionBox()->isStarGenerator() || - bce->sc->asFunctionBox()->isLegacyGenerator() || - bce->sc->asFunctionBox()->isAsync()), - bce->maxFixedSlots == 0); - } - - MOZ_MUST_USE bool putNameInCache(BytecodeEmitter* bce, JSAtom* name, NameLocation loc) { - NameLocationMap& cache = *nameCache_; - NameLocationMap::AddPtr p = cache.lookupForAdd(name); - MOZ_ASSERT(!p); - if (!cache.add(p, name, loc)) { - ReportOutOfMemory(bce->cx); - return false; - } - return true; - } - - Maybe lookupInCache(BytecodeEmitter* bce, JSAtom* name) { - if (NameLocationMap::Ptr p = nameCache_->lookup(name)) - return Some(p->value().wrapped); - if (fallbackFreeNameLocation_ && nameCanBeFree(bce, name)) - return fallbackFreeNameLocation_; - return Nothing(); - } - - friend bool BytecodeEmitter::needsImplicitThis(); - - EmitterScope* enclosing(BytecodeEmitter** bce) const { - // There is an enclosing scope with access to the same frame. - if (EmitterScope* inFrame = enclosingInFrame()) - return inFrame; - - // We are currently compiling the enclosing script, look in the - // enclosing BCE. - if ((*bce)->parent) { - *bce = (*bce)->parent; - return (*bce)->innermostEmitterScopeNoCheck(); - } - - return nullptr; - } - - Scope* enclosingScope(BytecodeEmitter* bce) const { - if (EmitterScope* es = enclosing(&bce)) - return es->scope(bce); - - // The enclosing script is already compiled or the current script is the - // global script. - return bce->sc->compilationEnclosingScope(); - } - - static bool nameCanBeFree(BytecodeEmitter* bce, JSAtom* name) { - // '.generator' cannot be accessed by name. - return name != bce->cx->names().dotGenerator; - } - - static NameLocation searchInEnclosingScope(JSAtom* name, Scope* scope, uint8_t hops); - NameLocation searchAndCache(BytecodeEmitter* bce, JSAtom* name); - - template - MOZ_MUST_USE bool internScope(BytecodeEmitter* bce, ScopeCreator createScope); - template - 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) - : Nestable(&bce->innermostEmitterScope_), - nameCache_(bce->cx->frontendCollectionPool()), - hasEnvironment_(false), - environmentChainLength_(0), - nextFrameSlot_(0), - scopeIndex_(ScopeNote::NoScopeIndex), - noteIndex_(ScopeNote::NoScopeNoteIndex) - { } - - void dump(BytecodeEmitter* bce); - - MOZ_MUST_USE bool enterLexical(BytecodeEmitter* bce, ScopeKind kind, - Handle bindings); - MOZ_MUST_USE bool enterNamedLambda(BytecodeEmitter* bce, FunctionBox* funbox); - MOZ_MUST_USE bool enterComprehensionFor(BytecodeEmitter* bce, - Handle 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 { - return bce->scopeList.vector[index()]; - } - - 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::enclosing(); - } - - NameLocation lookup(BytecodeEmitter* bce, JSAtom* name) { - if (Maybe loc = lookupInCache(bce, name)) - return *loc; - return searchAndCache(bce, name); - } - - Maybe locationBoundInScope(BytecodeEmitter* bce, JSAtom* name, - EmitterScope* target); -}; - -void -BytecodeEmitter::EmitterScope::dump(BytecodeEmitter* bce) -{ - fprintf(stdout, "EmitterScope [%s] %p\n", ScopeKindString(scope(bce)->kind()), this); - - for (NameLocationMap::Range r = nameCache_->all(); !r.empty(); r.popFront()) { - const NameLocation& l = r.front().value(); - - JSAutoByteString bytes; - if (!AtomToPrintableString(bce->cx, r.front().key(), &bytes)) - return; - if (l.kind() != NameLocation::Kind::Dynamic) - fprintf(stdout, " %s %s ", BindingKindString(l.bindingKind()), bytes.ptr()); - else - fprintf(stdout, " %s ", bytes.ptr()); - - switch (l.kind()) { - case NameLocation::Kind::Dynamic: - fprintf(stdout, "dynamic\n"); - break; - case NameLocation::Kind::Global: - fprintf(stdout, "global\n"); - break; - case NameLocation::Kind::Intrinsic: - fprintf(stdout, "intrinsic\n"); - break; - case NameLocation::Kind::NamedLambdaCallee: - fprintf(stdout, "named lambda callee\n"); - break; - case NameLocation::Kind::Import: - fprintf(stdout, "import\n"); - break; - case NameLocation::Kind::ArgumentSlot: - fprintf(stdout, "arg slot=%u\n", l.argumentSlot()); - break; - case NameLocation::Kind::FrameSlot: - fprintf(stdout, "frame slot=%u\n", l.frameSlot()); - break; - case NameLocation::Kind::EnvironmentCoordinate: - fprintf(stdout, "environment hops=%u slot=%u\n", - l.environmentCoordinate().hops(), l.environmentCoordinate().slot()); - break; - case NameLocation::Kind::DynamicAnnexBVar: - fprintf(stdout, "dynamic annex b var\n"); - break; - } - } - - fprintf(stdout, "\n"); -} - -template -bool -BytecodeEmitter::EmitterScope::internScope(BytecodeEmitter* bce, ScopeCreator createScope) -{ - RootedScope enclosing(bce->cx, enclosingScope(bce)); - Scope* scope = createScope(bce->cx, enclosing); - if (!scope) - return false; - hasEnvironment_ = scope->hasEnvironment(); - scopeIndex_ = bce->scopeList.length(); - return bce->scopeList.append(scope); -} - -template -bool -BytecodeEmitter::EmitterScope::internBodyScope(BytecodeEmitter* bce, ScopeCreator createScope) -{ - MOZ_ASSERT(bce->bodyScopeIndex == UINT32_MAX, "There can be only one body scope"); - bce->bodyScopeIndex = bce->scopeList.length(); - return internScope(bce, createScope); -} - -bool -BytecodeEmitter::EmitterScope::appendScopeNote(BytecodeEmitter* bce) -{ - MOZ_ASSERT(ScopeKindIsInBody(scope(bce)->kind()) && enclosingInFrame(), - "Scope notes are not needed for body-level scopes."); - noteIndex_ = bce->scopeNoteList.length(); - return bce->scopeNoteList.append(index(), bce->offset(), bce->inPrologue(), - enclosingInFrame() ? enclosingInFrame()->noteIndex() - : ScopeNote::NoScopeNoteIndex); -} - -#ifdef DEBUG -static bool -NameIsOnEnvironment(Scope* scope, JSAtom* name) -{ - for (BindingIter bi(scope); bi; bi++) { - // If found, the name must already be on the environment or an import, - // or else there is a bug in the closed-over name analysis in the - // Parser. - if (bi.name() == name) { - BindingLocation::Kind kind = bi.location().kind(); - - if (bi.hasArgumentSlot()) { - JSScript* script = scope->as().script(); - if (!script->strict() && !script->functionHasParameterExprs()) { - // Check for duplicate positional formal parameters. - for (BindingIter bi2(bi); bi2 && bi2.hasArgumentSlot(); bi2++) { - if (bi2.name() == name) - kind = bi2.location().kind(); - } - } - } - - return kind == BindingLocation::Kind::Global || - kind == BindingLocation::Kind::Environment || - kind == BindingLocation::Kind::Import; - } - } - - // If not found, assume it's on the global or dynamically accessed. - return true; -} -#endif - -/* static */ NameLocation -BytecodeEmitter::EmitterScope::searchInEnclosingScope(JSAtom* name, Scope* scope, uint8_t hops) -{ - for (ScopeIter si(scope); si; si++) { - MOZ_ASSERT(NameIsOnEnvironment(si.scope(), name)); - - bool hasEnv = si.hasSyntacticEnvironment(); - - switch (si.kind()) { - case ScopeKind::Function: - if (hasEnv) { - JSScript* script = si.scope()->as().script(); - if (script->funHasExtensibleScope()) - return NameLocation::Dynamic(); - - for (BindingIter bi(si.scope()); bi; bi++) { - if (bi.name() != name) - continue; - - BindingLocation bindLoc = bi.location(); - if (bi.hasArgumentSlot() && - !script->strict() && - !script->functionHasParameterExprs()) - { - // Check for duplicate positional formal parameters. - for (BindingIter bi2(bi); bi2 && bi2.hasArgumentSlot(); bi2++) { - if (bi2.name() == name) - bindLoc = bi2.location(); - } - } - - MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment); - return NameLocation::EnvironmentCoordinate(bi.kind(), hops, bindLoc.slot()); - } - } - break; - - case ScopeKind::FunctionBodyVar: - case ScopeKind::ParameterExpressionVar: - case ScopeKind::Lexical: - case ScopeKind::NamedLambda: - case ScopeKind::StrictNamedLambda: - case ScopeKind::SimpleCatch: - case ScopeKind::Catch: - if (hasEnv) { - for (BindingIter bi(si.scope()); bi; bi++) { - if (bi.name() != name) - continue; - - // The name must already have been marked as closed - // over. If this assertion is hit, there is a bug in the - // name analysis. - BindingLocation bindLoc = bi.location(); - MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment); - return NameLocation::EnvironmentCoordinate(bi.kind(), hops, bindLoc.slot()); - } - } - break; - - case ScopeKind::Module: - if (hasEnv) { - for (BindingIter bi(si.scope()); bi; bi++) { - if (bi.name() != name) - continue; - - BindingLocation bindLoc = bi.location(); - - // Imports are on the environment but are indirect - // bindings and must be accessed dynamically instead of - // using an EnvironmentCoordinate. - if (bindLoc.kind() == BindingLocation::Kind::Import) { - MOZ_ASSERT(si.kind() == ScopeKind::Module); - return NameLocation::Import(); - } - - MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment); - return NameLocation::EnvironmentCoordinate(bi.kind(), hops, bindLoc.slot()); - } - } - break; - - case ScopeKind::Eval: - case ScopeKind::StrictEval: - // As an optimization, if the eval doesn't have its own var - // environment and its immediate enclosing scope is a global - // scope, all accesses are global. - if (!hasEnv && si.scope()->enclosing()->is()) - return NameLocation::Global(BindingKind::Var); - return NameLocation::Dynamic(); - - case ScopeKind::Global: - return NameLocation::Global(BindingKind::Var); - - case ScopeKind::With: - case ScopeKind::NonSyntactic: - return NameLocation::Dynamic(); - - case ScopeKind::WasmInstance: - case ScopeKind::WasmFunction: - MOZ_CRASH("No direct eval inside wasm functions"); - } - - if (hasEnv) { - MOZ_ASSERT(hops < ENVCOORD_HOPS_LIMIT - 1); - hops++; - } - } - - MOZ_CRASH("Malformed scope chain"); -} - -NameLocation -BytecodeEmitter::EmitterScope::searchAndCache(BytecodeEmitter* bce, JSAtom* name) -{ - Maybe loc; - uint8_t hops = hasEnvironment() ? 1 : 0; - DebugOnly inCurrentScript = enclosingInFrame(); - - // Start searching in the current compilation. - for (EmitterScope* es = enclosing(&bce); es; es = es->enclosing(&bce)) { - loc = es->lookupInCache(bce, name); - if (loc) { - if (loc->kind() == NameLocation::Kind::EnvironmentCoordinate) - *loc = loc->addHops(hops); - break; - } - - if (es->hasEnvironment()) - hops++; - -#ifdef DEBUG - if (!es->enclosingInFrame()) - inCurrentScript = false; -#endif - } - - // If the name is not found in the current compilation, walk the Scope - // chain encompassing the compilation. - if (!loc) { - inCurrentScript = false; - loc = Some(searchInEnclosingScope(name, bce->sc->compilationEnclosingScope(), hops)); - } - - // Each script has its own frame. A free name that is accessed - // from an inner script must not be a frame slot access. If this - // assertion is hit, it is a bug in the free name analysis in the - // parser. - MOZ_ASSERT_IF(!inCurrentScript, loc->kind() != NameLocation::Kind::FrameSlot); - - // It is always correct to not cache the location. Ignore OOMs to make - // lookups infallible. - if (!putNameInCache(bce, name, *loc)) - bce->cx->recoverFromOutOfMemory(); - - return *loc; -} - -Maybe -BytecodeEmitter::EmitterScope::locationBoundInScope(BytecodeEmitter* bce, JSAtom* name, - EmitterScope* target) -{ - // The target scope must be an intra-frame enclosing scope of this - // one. Count the number of extra hops to reach it. - uint8_t extraHops = 0; - for (EmitterScope* es = this; es != target; es = es->enclosingInFrame()) { - if (es->hasEnvironment()) - extraHops++; - } - - // Caches are prepopulated with bound names. So if the name is bound in a - // particular scope, it must already be in the cache. Furthermore, don't - // consult the fallback location as we only care about binding names. - Maybe loc; - if (NameLocationMap::Ptr p = target->nameCache_->lookup(name)) { - NameLocation l = p->value().wrapped; - if (l.kind() == NameLocation::Kind::EnvironmentCoordinate) - loc = Some(l.addHops(extraHops)); - else - loc = Some(l); - } - return loc; -} - -bool -BytecodeEmitter::EmitterScope::deadZoneFrameSlotRange(BytecodeEmitter* bce, uint32_t slotStart, - uint32_t slotEnd) -{ - // Lexical bindings throw ReferenceErrors if they are used before - // initialization. See ES6 8.1.1.1.6. - // - // For completeness, lexical bindings are initialized in ES6 by calling - // InitializeBinding, after which touching the binding will no longer - // throw reference errors. See 13.1.11, 9.2.13, 13.6.3.4, 13.6.4.6, - // 13.6.4.8, 13.14.5, 15.1.8, and 15.2.0.15. - if (slotStart != slotEnd) { - if (!bce->emit1(JSOP_UNINITIALIZED)) - return false; - for (uint32_t slot = slotStart; slot < slotEnd; slot++) { - if (!bce->emitLocalOp(JSOP_INITLEXICAL, slot)) - return false; - } - if (!bce->emit1(JSOP_POP)) - return false; - } - - return true; -} - -bool -BytecodeEmitter::EmitterScope::deadZoneFrameSlots(BytecodeEmitter* bce) -{ - return deadZoneFrameSlotRange(bce, frameSlotStart(), frameSlotEnd()); -} - -bool -BytecodeEmitter::EmitterScope::enterLexical(BytecodeEmitter* bce, ScopeKind kind, - Handle bindings) -{ - MOZ_ASSERT(kind != ScopeKind::NamedLambda && kind != ScopeKind::StrictNamedLambda); - MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); - - if (!ensureCache(bce)) - return false; - - // Marks all names as closed over if the the context requires it. This - // cannot be done in the Parser as we may not know if the context requires - // all bindings to be closed over until after parsing is finished. For - // example, legacy generators require all bindings to be closed over but - // it is unknown if a function is a legacy generator until the first - // 'yield' expression is parsed. - // - // This is not a problem with other scopes, as all other scopes with - // bindings are body-level. At the time of their creation, whether or not - // the context requires all bindings to be closed over is already known. - if (bce->sc->allBindingsClosedOver()) - MarkAllBindingsClosedOver(*bindings); - - // Resolve bindings. - TDZCheckCache* tdzCache = bce->innermostTDZCheckCache; - uint32_t firstFrameSlot = frameSlotStart(); - BindingIter bi(*bindings, firstFrameSlot, /* isNamedLambda = */ false); - for (; bi; bi++) { - if (!checkSlotLimits(bce, bi)) - return false; - - NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location()); - if (!putNameInCache(bce, bi.name(), loc)) - return false; - - if (!tdzCache->noteTDZCheck(bce, bi.name(), CheckTDZ)) - return false; - } - - updateFrameFixedSlots(bce, bi); - - // Create and intern the VM scope. - auto createScope = [kind, bindings, firstFrameSlot](JSContext* cx, - HandleScope enclosing) - { - return LexicalScope::create(cx, kind, bindings, firstFrameSlot, enclosing); - }; - if (!internScope(bce, createScope)) - return false; - - if (ScopeKindIsInBody(kind) && hasEnvironment()) { - // After interning the VM scope we can get the scope index. - if (!bce->emitInternedScopeOp(index(), JSOP_PUSHLEXICALENV)) - return false; - } - - // Lexical scopes need notes to be mapped from a pc. - if (!appendScopeNote(bce)) - return false; - - // Put frame slots in TDZ. Environment slots are poisoned during - // environment creation. - // - // This must be done after appendScopeNote to be considered in the extent - // of the scope. - if (!deadZoneFrameSlotRange(bce, firstFrameSlot, frameSlotEnd())) - return false; - - return checkEnvironmentChainLength(bce); -} - -bool -BytecodeEmitter::EmitterScope::enterNamedLambda(BytecodeEmitter* bce, FunctionBox* funbox) -{ - MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); - MOZ_ASSERT(funbox->namedLambdaBindings()); - - if (!ensureCache(bce)) - return false; - - // See comment in enterLexical about allBindingsClosedOver. - if (funbox->allBindingsClosedOver()) - MarkAllBindingsClosedOver(*funbox->namedLambdaBindings()); - - BindingIter bi(*funbox->namedLambdaBindings(), LOCALNO_LIMIT, /* isNamedLambda = */ true); - MOZ_ASSERT(bi.kind() == BindingKind::NamedLambdaCallee); - - // The lambda name, if not closed over, is accessed via JSOP_CALLEE and - // not a frame slot. Do not update frame slot information. - NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location()); - if (!putNameInCache(bce, bi.name(), loc)) - return false; - - bi++; - MOZ_ASSERT(!bi, "There should be exactly one binding in a NamedLambda scope"); - - auto createScope = [funbox](JSContext* cx, HandleScope enclosing) { - ScopeKind scopeKind = - funbox->strict() ? ScopeKind::StrictNamedLambda : ScopeKind::NamedLambda; - return LexicalScope::create(cx, scopeKind, funbox->namedLambdaBindings(), - LOCALNO_LIMIT, enclosing); - }; - if (!internScope(bce, createScope)) - return false; - - return checkEnvironmentChainLength(bce); -} - -bool -BytecodeEmitter::EmitterScope::enterComprehensionFor(BytecodeEmitter* bce, - Handle bindings) -{ - if (!enterLexical(bce, ScopeKind::Lexical, bindings)) - return false; - - // For comprehensions, initialize all lexical names up front to undefined - // because they're now a dead feature and don't interact properly with - // TDZ. - auto nop = [](BytecodeEmitter*, const NameLocation&, bool) { - return true; - }; - - if (!bce->emit1(JSOP_UNDEFINED)) - return false; - - RootedAtom name(bce->cx); - for (BindingIter bi(*bindings, frameSlotStart(), /* isNamedLambda = */ false); bi; bi++) { - name = bi.name(); - if (!bce->emitInitializeName(name, nop)) - return false; - } - - if (!bce->emit1(JSOP_POP)) - return false; - - return true; -} - -bool -BytecodeEmitter::EmitterScope::enterParameterExpressionVar(BytecodeEmitter* bce) -{ - MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); - - if (!ensureCache(bce)) - return false; - - // Parameter expressions var scopes have no pre-set bindings and are - // always extensible, as they are needed for eval. - fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); - - // Create and intern the VM scope. - uint32_t firstFrameSlot = frameSlotStart(); - auto createScope = [firstFrameSlot](JSContext* cx, HandleScope enclosing) { - return VarScope::create(cx, ScopeKind::ParameterExpressionVar, - /* data = */ nullptr, firstFrameSlot, - /* needsEnvironment = */ true, enclosing); - }; - if (!internScope(bce, createScope)) - return false; - - MOZ_ASSERT(hasEnvironment()); - if (!bce->emitInternedScopeOp(index(), JSOP_PUSHVARENV)) - return false; - - // The extra var scope needs a note to be mapped from a pc. - if (!appendScopeNote(bce)) - return false; - - return checkEnvironmentChainLength(bce); -} - -bool -BytecodeEmitter::EmitterScope::enterFunction(BytecodeEmitter* bce, FunctionBox* funbox) -{ - MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); - - // If there are parameter expressions, there is an extra var scope. - if (!funbox->hasExtraBodyVarScope()) - bce->setVarEmitterScope(this); - - if (!ensureCache(bce)) - return false; - - // Resolve body-level bindings, if there are any. - auto bindings = funbox->functionScopeBindings(); - Maybe lastLexicalSlot; - if (bindings) { - NameLocationMap& cache = *nameCache_; - - BindingIter bi(*bindings, funbox->hasParameterExprs); - for (; bi; bi++) { - if (!checkSlotLimits(bce, bi)) - return false; - - NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location()); - NameLocationMap::AddPtr p = cache.lookupForAdd(bi.name()); - - // The only duplicate bindings that occur are simple formal - // parameters, in which case the last position counts, so update the - // location. - if (p) { - MOZ_ASSERT(bi.kind() == BindingKind::FormalParameter); - MOZ_ASSERT(!funbox->hasDestructuringArgs); - MOZ_ASSERT(!funbox->hasRest()); - p->value() = loc; - continue; - } - - if (!cache.add(p, bi.name(), loc)) { - ReportOutOfMemory(bce->cx); - return false; - } - } - - updateFrameFixedSlots(bce, bi); - } else { - nextFrameSlot_ = 0; - } - - // If the function's scope may be extended at runtime due to sloppy direct - // eval and there is no extra var scope, any names beyond the function - // scope must be accessed dynamically as we don't know if the name will - // become a 'var' binding due to direct eval. - if (!funbox->hasParameterExprs && funbox->hasExtensibleScope()) - fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); - - // In case of parameter expressions, the parameters are lexical - // bindings and have TDZ. - if (funbox->hasParameterExprs && nextFrameSlot_) { - uint32_t paramFrameSlotEnd = 0; - for (BindingIter bi(*bindings, true); bi; bi++) { - if (!BindingKindIsLexical(bi.kind())) - break; - - NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location()); - if (loc.kind() == NameLocation::Kind::FrameSlot) { - MOZ_ASSERT(paramFrameSlotEnd <= loc.frameSlot()); - paramFrameSlotEnd = loc.frameSlot() + 1; - } - } - - if (!deadZoneFrameSlotRange(bce, 0, paramFrameSlotEnd)) - return false; - } - - // Create and intern the VM scope. - auto createScope = [funbox](JSContext* cx, HandleScope enclosing) { - RootedFunction fun(cx, funbox->function()); - return FunctionScope::create(cx, funbox->functionScopeBindings(), - funbox->hasParameterExprs, - funbox->needsCallObjectRegardlessOfBindings(), - fun, enclosing); - }; - if (!internBodyScope(bce, createScope)) - return false; - - return checkEnvironmentChainLength(bce); -} - -bool -BytecodeEmitter::EmitterScope::enterFunctionExtraBodyVar(BytecodeEmitter* bce, FunctionBox* funbox) -{ - MOZ_ASSERT(funbox->hasParameterExprs); - MOZ_ASSERT(funbox->extraVarScopeBindings() || - funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings()); - MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); - - // The extra var scope is never popped once it's entered. It replaces the - // function scope as the var emitter scope. - bce->setVarEmitterScope(this); - - if (!ensureCache(bce)) - return false; - - // Resolve body-level bindings, if there are any. - uint32_t firstFrameSlot = frameSlotStart(); - if (auto bindings = funbox->extraVarScopeBindings()) { - BindingIter bi(*bindings, firstFrameSlot); - for (; bi; bi++) { - if (!checkSlotLimits(bce, bi)) - return false; - - NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location()); - if (!putNameInCache(bce, bi.name(), loc)) - return false; - } - - updateFrameFixedSlots(bce, bi); - } else { - nextFrameSlot_ = firstFrameSlot; - } - - // If the extra var scope may be extended at runtime due to sloppy - // direct eval, any names beyond the var scope must be accessed - // dynamically as we don't know if the name will become a 'var' binding - // due to direct eval. - if (funbox->hasExtensibleScope()) - fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); - - // Create and intern the VM scope. - auto createScope = [funbox, firstFrameSlot](JSContext* cx, HandleScope enclosing) { - return VarScope::create(cx, ScopeKind::FunctionBodyVar, - funbox->extraVarScopeBindings(), firstFrameSlot, - funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings(), - enclosing); - }; - if (!internScope(bce, createScope)) - return false; - - if (hasEnvironment()) { - if (!bce->emitInternedScopeOp(index(), JSOP_PUSHVARENV)) - return false; - } - - // The extra var scope needs a note to be mapped from a pc. - if (!appendScopeNote(bce)) - return false; - - return checkEnvironmentChainLength(bce); -} - -class DynamicBindingIter : public BindingIter -{ - public: - explicit DynamicBindingIter(GlobalSharedContext* sc) - : BindingIter(*sc->bindings) - { } - - explicit DynamicBindingIter(EvalSharedContext* sc) - : BindingIter(*sc->bindings, /* strict = */ false) - { - MOZ_ASSERT(!sc->strict()); - } - - JSOp bindingOp() const { - switch (kind()) { - case BindingKind::Var: - return JSOP_DEFVAR; - case BindingKind::Let: - return JSOP_DEFLET; - case BindingKind::Const: - return JSOP_DEFCONST; - default: - MOZ_CRASH("Bad BindingKind"); - } - } -}; - -bool -BytecodeEmitter::EmitterScope::enterGlobal(BytecodeEmitter* bce, GlobalSharedContext* globalsc) -{ - MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); - - bce->setVarEmitterScope(this); - - if (!ensureCache(bce)) - return false; - - if (bce->emitterMode == BytecodeEmitter::SelfHosting) { - // In self-hosting, it is incorrect to consult the global scope because - // self-hosted scripts are cloned into their target compartments before - // they are run. Instead of Global, Intrinsic is used for all names. - // - // Intrinsic lookups are redirected to the special intrinsics holder - // in the global object, into which any missing values are cloned - // lazily upon first access. - fallbackFreeNameLocation_ = Some(NameLocation::Intrinsic()); - - auto createScope = [](JSContext* cx, HandleScope enclosing) { - MOZ_ASSERT(!enclosing); - return &cx->global()->emptyGlobalScope(); - }; - return internBodyScope(bce, createScope); - } - - // Resolve binding names and emit DEF{VAR,LET,CONST} prologue ops. - if (globalsc->bindings) { - for (DynamicBindingIter bi(globalsc); bi; bi++) { - NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location()); - JSAtom* name = bi.name(); - if (!putNameInCache(bce, name, loc)) - return false; - - // Define the name in the prologue. Do not emit DEFVAR for - // functions that we'll emit DEFFUN for. - if (bi.isTopLevelFunction()) - continue; - - if (!bce->emitAtomOp(name, bi.bindingOp())) - return false; - } - } - - // Note that to save space, we don't add free names to the cache for - // global scopes. They are assumed to be global vars in the syntactic - // global scope, dynamic accesses under non-syntactic global scope. - if (globalsc->scopeKind() == ScopeKind::Global) - fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var)); - else - fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); - - auto createScope = [globalsc](JSContext* cx, HandleScope enclosing) { - MOZ_ASSERT(!enclosing); - return GlobalScope::create(cx, globalsc->scopeKind(), globalsc->bindings); - }; - return internBodyScope(bce, createScope); -} - -bool -BytecodeEmitter::EmitterScope::enterEval(BytecodeEmitter* bce, EvalSharedContext* evalsc) -{ - MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); - - bce->setVarEmitterScope(this); - - if (!ensureCache(bce)) - return false; - - // For simplicity, treat all free name lookups in eval scripts as dynamic. - fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); - - // Create the `var` scope. Note that there is also a lexical scope, created - // separately in emitScript(). - auto createScope = [evalsc](JSContext* cx, HandleScope enclosing) { - ScopeKind scopeKind = evalsc->strict() ? ScopeKind::StrictEval : ScopeKind::Eval; - return EvalScope::create(cx, scopeKind, evalsc->bindings, enclosing); - }; - if (!internBodyScope(bce, createScope)) - return false; - - if (hasEnvironment()) { - if (!bce->emitInternedScopeOp(index(), JSOP_PUSHVARENV)) - return false; - } else { - // Resolve binding names and emit DEFVAR prologue ops if we don't have - // an environment (i.e., a sloppy eval not in a parameter expression). - // Eval scripts always have their own lexical scope, but non-strict - // scopes may introduce 'var' bindings to the nearest var scope. - // - // TODO: We may optimize strict eval bindings in the future to be on - // the frame. For now, handle everything dynamically. - if (!hasEnvironment() && evalsc->bindings) { - for (DynamicBindingIter bi(evalsc); bi; bi++) { - MOZ_ASSERT(bi.bindingOp() == JSOP_DEFVAR); - - if (bi.isTopLevelFunction()) - continue; - - if (!bce->emitAtomOp(bi.name(), JSOP_DEFVAR)) - return false; - } - } - - // As an optimization, if the eval does not have its own var - // environment and is directly enclosed in a global scope, then all - // free name lookups are global. - if (scope(bce)->enclosing()->is()) - fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var)); - } - - return true; -} - -bool -BytecodeEmitter::EmitterScope::enterModule(BytecodeEmitter* bce, ModuleSharedContext* modulesc) -{ - MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); - - bce->setVarEmitterScope(this); - - if (!ensureCache(bce)) - return false; - - // Resolve body-level bindings, if there are any. - TDZCheckCache* tdzCache = bce->innermostTDZCheckCache; - Maybe firstLexicalFrameSlot; - if (ModuleScope::Data* bindings = modulesc->bindings) { - BindingIter bi(*bindings); - for (; bi; bi++) { - if (!checkSlotLimits(bce, bi)) - return false; - - NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location()); - if (!putNameInCache(bce, bi.name(), loc)) - return false; - - if (BindingKindIsLexical(bi.kind())) { - if (loc.kind() == NameLocation::Kind::FrameSlot && !firstLexicalFrameSlot) - firstLexicalFrameSlot = Some(loc.frameSlot()); - - if (!tdzCache->noteTDZCheck(bce, bi.name(), CheckTDZ)) - return false; - } - } - - updateFrameFixedSlots(bce, bi); - } else { - nextFrameSlot_ = 0; - } - - // Modules are toplevel, so any free names are global. - fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var)); - - // Put lexical frame slots in TDZ. Environment slots are poisoned during - // environment creation. - if (firstLexicalFrameSlot) { - if (!deadZoneFrameSlotRange(bce, *firstLexicalFrameSlot, frameSlotEnd())) - return false; - } - - // Create and intern the VM scope. - auto createScope = [modulesc](JSContext* cx, HandleScope enclosing) { - return ModuleScope::create(cx, modulesc->bindings, modulesc->module(), enclosing); - }; - if (!internBodyScope(bce, createScope)) - return false; - - return checkEnvironmentChainLength(bce); -} - -bool -BytecodeEmitter::EmitterScope::enterWith(BytecodeEmitter* bce) -{ - MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); - - if (!ensureCache(bce)) - return false; - - // 'with' make all accesses dynamic and unanalyzable. - fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); - - auto createScope = [](JSContext* cx, HandleScope enclosing) { - return WithScope::create(cx, enclosing); - }; - if (!internScope(bce, createScope)) - return false; - - if (!bce->emitInternedScopeOp(index(), JSOP_ENTERWITH)) - return false; - - if (!appendScopeNote(bce)) - return false; - - return checkEnvironmentChainLength(bce); -} - -bool -BytecodeEmitter::EmitterScope::leave(BytecodeEmitter* bce, bool nonLocal) -{ - // If we aren't leaving the scope due to a non-local jump (e.g., break), - // we must be the innermost scope. - MOZ_ASSERT_IF(!nonLocal, this == bce->innermostEmitterScopeNoCheck()); - - ScopeKind kind = scope(bce)->kind(); - switch (kind) { - case ScopeKind::Lexical: - case ScopeKind::SimpleCatch: - case ScopeKind::Catch: - if (!bce->emit1(hasEnvironment() ? JSOP_POPLEXICALENV : JSOP_DEBUGLEAVELEXICALENV)) - return false; - break; - - case ScopeKind::With: - if (!bce->emit1(JSOP_LEAVEWITH)) - return false; - break; - - case ScopeKind::ParameterExpressionVar: - MOZ_ASSERT(hasEnvironment()); - if (!bce->emit1(JSOP_POPVARENV)) - return false; - break; - - case ScopeKind::Function: - case ScopeKind::FunctionBodyVar: - case ScopeKind::NamedLambda: - case ScopeKind::StrictNamedLambda: - case ScopeKind::Eval: - case ScopeKind::StrictEval: - case ScopeKind::Global: - case ScopeKind::NonSyntactic: - case ScopeKind::Module: - break; - - case ScopeKind::WasmInstance: - case ScopeKind::WasmFunction: - MOZ_CRASH("No wasm function scopes in JS"); - } - - // Finish up the scope if we are leaving it in LIFO fashion. - if (!nonLocal) { - // Popping scopes due to non-local jumps generate additional scope - // notes. See NonLocalExitControl::prepareForNonLocalJump. - if (ScopeKindIsInBody(kind)) { - // The extra function var scope is never popped once it's pushed, - // so its scope note extends until the end of any possible code. - uint32_t offset = kind == ScopeKind::FunctionBodyVar ? UINT32_MAX : bce->offset(); - bce->scopeNoteList.recordEnd(noteIndex_, offset, bce->inPrologue()); - } - } - - return true; -} - -Maybe -BytecodeEmitter::TDZCheckCache::needsTDZCheck(BytecodeEmitter* bce, JSAtom* name) -{ - if (!ensureCache(bce)) - return Nothing(); - - CheckTDZMap::AddPtr p = cache_->lookupForAdd(name); - if (p) - return Some(p->value().wrapped); - - MaybeCheckTDZ rv = CheckTDZ; - for (TDZCheckCache* it = enclosing(); it; it = it->enclosing()) { - if (it->cache_) { - if (CheckTDZMap::Ptr p2 = it->cache_->lookup(name)) { - rv = p2->value(); - break; - } - } - } - - if (!cache_->add(p, name, rv)) { - ReportOutOfMemory(bce->cx); - return Nothing(); - } - - return Some(rv); -} - -bool -BytecodeEmitter::TDZCheckCache::noteTDZCheck(BytecodeEmitter* bce, JSAtom* name, - MaybeCheckTDZ check) -{ - if (!ensureCache(bce)) - return false; - - CheckTDZMap::AddPtr p = cache_->lookupForAdd(name); - if (p) { - MOZ_ASSERT(!check, "TDZ only needs to be checked once per binding per basic block."); - p->value() = check; - } else { - if (!cache_->add(p, name, check)) - return false; - } - - return true; -} - -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 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) - : 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; - } - - bool emitJumpOverCatchAndFinally() { - if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_)) - return false; - return true; - } - - bool 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; - } - - private: - bool 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; - } - - public: - bool 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; - } - - private: - bool emitCatchEnd(bool hasNext) { - MOZ_ASSERT(state_ == Catch); - - if (!controlInfo_) - return true; - - // gosub , 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; - } - - public: - bool emitFinally(const Maybe& 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; - } - - private: - bool 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; - } - - public: - bool 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; - } -}; - -class MOZ_STACK_CLASS IfThenElseEmitter -{ - BytecodeEmitter* bce_; - JumpList jumpAroundThen_; - JumpList jumpsAroundElse_; - unsigned noteIndex_; - int32_t thenDepth_; -#ifdef DEBUG - int32_t pushed_; - bool calculatedPushed_; -#endif - enum State { - Start, - If, - Cond, - IfElse, - Else, - End - }; - State state_; - - public: - explicit IfThenElseEmitter(BytecodeEmitter* bce) - : bce_(bce), - noteIndex_(-1), - thenDepth_(0), -#ifdef DEBUG - pushed_(0), - calculatedPushed_(false), -#endif - state_(Start) - {} - - ~IfThenElseEmitter() - {} - - private: - bool emitIf(State nextState) { - MOZ_ASSERT(state_ == Start || state_ == Else); - MOZ_ASSERT(nextState == If || nextState == IfElse || nextState == Cond); - - // Clear jumpAroundThen_ offset that points previous JSOP_IFEQ. - if (state_ == Else) - jumpAroundThen_ = JumpList(); - - // Emit an annotated branch-if-false around the then part. - SrcNoteType type = nextState == If ? SRC_IF : nextState == IfElse ? SRC_IF_ELSE : SRC_COND; - if (!bce_->newSrcNote(type, ¬eIndex_)) - return false; - if (!bce_->emitJump(JSOP_IFEQ, &jumpAroundThen_)) - return false; - - // To restore stack depth in else part, save depth of the then part. -#ifdef DEBUG - // If DEBUG, this is also necessary to calculate |pushed_|. - thenDepth_ = bce_->stackDepth; -#else - if (nextState == IfElse || nextState == Cond) - thenDepth_ = bce_->stackDepth; -#endif - state_ = nextState; - return true; - } - - public: - bool emitIf() { - return emitIf(If); - } - - bool emitCond() { - return emitIf(Cond); - } - - bool emitIfElse() { - return emitIf(IfElse); - } - - bool emitElse() { - MOZ_ASSERT(state_ == IfElse || state_ == Cond); - - calculateOrCheckPushed(); - - // Emit a jump from the end of our then part around the else part. The - // patchJumpsToTarget call at the bottom of this function will fix up - // the offset with jumpsAroundElse value. - if (!bce_->emitJump(JSOP_GOTO, &jumpsAroundElse_)) - return false; - - // Ensure the branch-if-false comes here, then emit the else. - if (!bce_->emitJumpTargetAndPatch(jumpAroundThen_)) - return false; - - // Annotate SRC_IF_ELSE or SRC_COND with the offset from branch to - // jump, for IonMonkey's benefit. We can't just "back up" from the pc - // of the else clause, because we don't know whether an extended - // jump was required to leap from the end of the then clause over - // the else clause. - if (!bce_->setSrcNoteOffset(noteIndex_, 0, - jumpsAroundElse_.offset - jumpAroundThen_.offset)) - { - return false; - } - - // Restore stack depth of the then part. - bce_->stackDepth = thenDepth_; - state_ = Else; - return true; - } - - bool emitEnd() { - MOZ_ASSERT(state_ == If || state_ == Else); - - calculateOrCheckPushed(); - - if (state_ == If) { - // No else part, fixup the branch-if-false to come here. - if (!bce_->emitJumpTargetAndPatch(jumpAroundThen_)) - return false; - } - - // Patch all the jumps around else parts. - if (!bce_->emitJumpTargetAndPatch(jumpsAroundElse_)) - return false; - - state_ = End; - return true; - } - - void calculateOrCheckPushed() { -#ifdef DEBUG - if (!calculatedPushed_) { - pushed_ = bce_->stackDepth - thenDepth_; - calculatedPushed_ = true; - } else { - MOZ_ASSERT(pushed_ == bce_->stackDepth - thenDepth_); - } -#endif - } - -#ifdef DEBUG - int32_t pushed() const { - return pushed_; - } - - int32_t popped() const { - return -pushed_; - } -#endif -}; - -// OOOEEE start - -class MOZ_RAII BytecodeEmitter::OptionalEmitter { - public: - enum class Kind { - // Requires two values on the stack - Reference, - // Requires one value on the stack - Other - }; - - private: - BytecodeEmitter* bce_; - - BytecodeEmitter::TDZCheckCache tdzCache_; - - // jumpTarget for the fake label over the optional chaining code - JumpList top_; - - // BreakableControl target for the break from inside the optional chaining code - BreakableControl breakInfo_; - - int32_t initialDepth_; - - // JSOp is the op code to be emitted, Kind is if we are dealing with a - // reference (in which case we need two elements on the stack) or other value - // (which needs one element on the stack) - JSOp op_; - Kind kind_; - - public: - OptionalEmitter(BytecodeEmitter* bce, int32_t initialDepth, JSOp op = JSOP_UNDEFINED, Kind kind = Kind::Other) - : bce_(bce), tdzCache_(bce), - breakInfo_(bce, StatementKind::Label), - initialDepth_(initialDepth), op_(op), kind_(kind) {} - - bool emitOptionalJumpLabel() { - - return bce_->emitJump(JSOP_LABEL, &top_); - } - - bool emitJumpShortCircuit() { - MOZ_ASSERT(initialDepth_ + 1 == bce_->stackDepth); - IfThenElseEmitter ifEmitter(bce_); - if (!bce_->emitPushNotUndefinedOrNull()) - return false; - - if (!bce_->emit1(JSOP_NOT)) - return false; - - if (!ifEmitter.emitIf()) - return false; - - // Perform ShortCircuiting code and break - if (!bce_->emit1(JSOP_POP)) - return false; - - if (!bce_->emit1(op_)) - return false; - - if (kind_ == Kind::Reference) { - if (!bce_->emit1(op_)) - return false; - } - - if (!bce_->emitGoto(&breakInfo_, &breakInfo_.breaks, SRC_BREAK2LABEL)) - return false; - - if (!ifEmitter.emitEnd()) { - return false; - } - return true; - } - - bool emitJumpShortCircuitForCall() { - int32_t depth = bce_->stackDepth; - MOZ_ASSERT(initialDepth_ + 2 == depth); - if (!bce_->emit1(JSOP_SWAP)) - return false; - - IfThenElseEmitter ifEmitter(bce_); - if (!bce_->emitPushNotUndefinedOrNull()) - return false; - - if (!bce_->emit1(JSOP_NOT)) - return false; - - if (!ifEmitter.emitIf()) - return false; - - // Perform ShortCircuiting code for Call and break - if (!bce_->emit1(JSOP_POP)) - return false; - - if (!bce_->emit1(JSOP_POP)) - return false; - - if (!bce_->emit1(op_)) - return false; - - if (kind_ == Kind::Reference) { - if (!bce_->emit1(op_)) - return false; - } - - if (!bce_->emitGoto(&breakInfo_, &breakInfo_.breaks, SRC_BREAK2LABEL)) - return false; - - if (!ifEmitter.emitEnd()) - return false; - - bce_->stackDepth = depth; - - if (!bce_->emit1(JSOP_SWAP)) - return false; - return true; - } - - bool emitOptionalJumpTarget() { - - // Patch the JSOP_LABEL offset. - JumpTarget brk{ bce_->lastNonJumpTargetOffset() }; - bce_->patchJumpsToTarget(top_, brk); - - // Patch the emitGoto() offset. - if (!breakInfo_.patchBreaks(bce_)) - return false; - - // XXX: Commented out due to workaround for missing JSOP_GOTO functionality - /*// reset stack depth to the depth when we jumped - bce_->stackDepth = initialDepth_ + 1;*/ - - return true; - } -}; - -// OOOEEE end - -class ForOfLoopControl : public LoopControl +static bool +ParseNodeRequiresSpecialLineNumberNotes(ParseNode* pn) { - using EmitterScope = BytecodeEmitter::EmitterScope; - - // 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 }); - // } - // } - Maybe 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) - : LoopControl(bce, StatementKind::ForOfLoop), - iterDepth_(iterDepth), - numYieldsAtBeginCodeNeedingIterClose_(UINT32_MAX), - allowSelfHosted_(allowSelfHosted), - iterKind_(iterKind) - { - } - - bool 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 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; - - IfThenElseEmitter ifIteratorIsNotClosed(bce); - if (!ifIteratorIsNotClosed.emitIf()) // 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; - - IfThenElseEmitter ifGeneratorClosing(bce); - if (!bce->emit1(JSOP_ISGENCLOSING)) // ITER ... FTYPE FVALUE CLOSING - return false; - if (!ifGeneratorClosing.emitIf()) // ITER ... FTYPE FVALUE - return false; - if (!bce->emitDupAt(slotFromTop + 1)) // ITER ... FTYPE FVALUE ITER - return false; - if (!emitIteratorCloseInInnermostScope(bce, CompletionKind::Normal)) - return false; - if (!ifGeneratorClosing.emitEnd()) // ITER ... FTYPE FVALUE - return false; - } - - if (!tryCatch_->emitEnd()) - return false; - - tryCatch_.reset(); - numYieldsAtBeginCodeNeedingIterClose_ = UINT32_MAX; - - return true; - } - - bool emitIteratorCloseInInnermostScope(BytecodeEmitter* bce, - CompletionKind completionKind = CompletionKind::Normal) { - return emitIteratorCloseInScope(bce, *bce->innermostEmitterScope(), completionKind); - } - - bool 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 emitPrepareForNonLocalJumpFromScope(BytecodeEmitter* bce, - EmitterScope& currentScope, - bool isTarget) { - // Pop unnecessary value 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 - 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 - // an undefined to balance the stack. - if (!bce->emit1(JSOP_UNDEFINED)) // UNDEF UNDEF - return false; - } else { - if (!bce->emit1(JSOP_POP)) // - return false; - } - - return true; - } -}; + return pn->getKind() == ParseNodeKind::While || pn->getKind() == ParseNodeKind::For; +} BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, const EitherParser& parser, SharedContext* sc, @@ -2391,13 +138,6 @@ BytecodeEmitter::init() return atomIndices.acquire(cx); } -template bool */> -BytecodeEmitter::NestableControl* -BytecodeEmitter::findInnermostNestableControl(Predicate predicate) const -{ - return NestableControl::findNearest(innermostNestableControl, predicate); -} - template T* BytecodeEmitter::findInnermostNestableControl() const @@ -2421,7 +161,7 @@ BytecodeEmitter::lookupName(JSAtom* name) Maybe BytecodeEmitter::locationOfNameBoundInScope(JSAtom* name, EmitterScope* target) { - return innermostEmitterScope()->locationBoundInScope(this, name, target); + return innermostEmitterScope()->locationBoundInScope(name, target); } Maybe @@ -2430,7 +170,7 @@ BytecodeEmitter::locationOfNameBoundInFunctionScope(JSAtom* name, EmitterScope* EmitterScope* funScope = source; while (!funScope->scope(this)->is()) funScope = funScope->enclosingInFrame(); - return source->locationBoundInScope(this, name, funScope); + return source->locationBoundInScope(name, funScope); } bool @@ -2575,27 +315,6 @@ BytecodeEmitter::emitJumpTarget(JumpTarget* target) return true; } -void -JumpList::push(jsbytecode* code, ptrdiff_t jumpOffset) -{ - SET_JUMP_OFFSET(&code[jumpOffset], offset - jumpOffset); - offset = jumpOffset; -} - -void -JumpList::patchAll(jsbytecode* code, JumpTarget target) -{ - ptrdiff_t delta; - for (ptrdiff_t jumpOffset = offset; jumpOffset != -1; jumpOffset += delta) { - jsbytecode* pc = &code[jumpOffset]; - MOZ_ASSERT(IsJumpOpcode(JSOp(*pc)) || JSOp(*pc) == JSOP_LABEL); - delta = GET_JUMP_OFFSET(pc); - MOZ_ASSERT(delta < 0); - ptrdiff_t span = target.offset - jumpOffset; - SET_JUMP_OFFSET(pc, span); - } -} - bool BytecodeEmitter::emitJumpNoFallthrough(JSOp op, JumpList* jump) { @@ -2660,33 +379,54 @@ BytecodeEmitter::emitJumpTargetAndPatch(JumpList jump) return true; } +bool +BytecodeEmitter::emitCall(JSOp op, uint16_t argc, const Maybe& sourceCoordOffset) +{ + if (sourceCoordOffset.isSome()) { + if (!updateSourceCoordNotes(*sourceCoordOffset)) + return false; + } + return emit3(op, ARGC_LO(argc), ARGC_HI(argc)); +} + bool BytecodeEmitter::emitCall(JSOp op, uint16_t argc, ParseNode* pn) { if (pn && !updateSourceCoordNotes(pn->pn_pos.begin)) return false; - return emit3(op, ARGC_HI(argc), ARGC_LO(argc)); + return emitCall(op, argc, pn ? Some(pn->pn_pos.begin) : Nothing()); } bool -BytecodeEmitter::emitDupAt(unsigned slotFromTop) +BytecodeEmitter::emitDupAt(unsigned slotFromTop, unsigned count) { MOZ_ASSERT(slotFromTop < unsigned(stackDepth)); - if (slotFromTop == 0) + MOZ_ASSERT(slotFromTop + 1 >= count); + + if (slotFromTop == 0 && count == 1) { return emit1(JSOP_DUP); + } + + if (slotFromTop == 1 && count == 2) { + return emit1(JSOP_DUP2); + } if (slotFromTop >= JS_BIT(24)) { reportError(nullptr, JSMSG_TOO_MANY_LOCALS); return false; } - ptrdiff_t off; - if (!emitN(JSOP_DUPAT, 3, &off)) - return false; + for (unsigned i = 0; i < count; i++) { + ptrdiff_t off; + if (!emitN(JSOP_DUPAT, 3, &off)) { + return false; + } + + jsbytecode* pc = code(off); + SET_UINT24(pc, slotFromTop); + } - jsbytecode* pc = code(off); - SET_UINT24(pc, slotFromTop); return true; } @@ -2705,6 +445,30 @@ BytecodeEmitter::emitPopN(unsigned n) return emitUint16Operand(JSOP_POPN, n); } +bool +BytecodeEmitter::emitPickN(uint8_t n) +{ + MOZ_ASSERT(n != 0); + + if (n == 1) { + return emit1(JSOP_SWAP); + } + + return emit2(JSOP_PICK, n); +} + +bool +BytecodeEmitter::emitUnpickN(uint8_t n) +{ + MOZ_ASSERT(n != 0); + + if (n == 1) { + return emit1(JSOP_SWAP); + } + + return emit2(JSOP_UNPICK, n); +} + bool BytecodeEmitter::emitCheckIsObj(CheckIsObjectKind kind) { @@ -2848,7 +612,7 @@ bool BytecodeEmitter::emitUint16Operand(JSOp op, uint32_t operand) { MOZ_ASSERT(operand <= UINT16_MAX); - if (!emit3(op, UINT16_HI(operand), UINT16_LO(operand))) + if (!emit3(op, UINT16_LO(operand), UINT16_HI(operand))) return false; checkTypeSet(op); return true; @@ -2895,7 +659,7 @@ class NonLocalExitControl NonLocalExitControl(const NonLocalExitControl&) = delete; - MOZ_MUST_USE bool leaveScope(BytecodeEmitter::EmitterScope* scope); + MOZ_MUST_USE bool leaveScope(EmitterScope* scope); public: NonLocalExitControl(BytecodeEmitter* bce, Kind kind) @@ -2912,7 +676,7 @@ class NonLocalExitControl bce_->stackDepth = savedDepth_; } - MOZ_MUST_USE bool prepareForNonLocalJump(BytecodeEmitter::NestableControl* target); + MOZ_MUST_USE bool prepareForNonLocalJump(NestableControl* target); MOZ_MUST_USE bool prepareForNonLocalJumpToOutermost() { return prepareForNonLocalJump(nullptr); @@ -2920,7 +684,7 @@ class NonLocalExitControl }; bool -NonLocalExitControl::leaveScope(BytecodeEmitter::EmitterScope* es) +NonLocalExitControl::leaveScope(EmitterScope* es) { if (!es->leave(bce_, /* nonLocal = */ true)) return false; @@ -2943,11 +707,8 @@ NonLocalExitControl::leaveScope(BytecodeEmitter::EmitterScope* es) * Emit additional bytecode(s) for non-local jumps. */ bool -NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* target) +NonLocalExitControl::prepareForNonLocalJump(NestableControl* target) { - using NestableControl = BytecodeEmitter::NestableControl; - using EmitterScope = BytecodeEmitter::EmitterScope; - EmitterScope* es = bce_->innermostEmitterScope(); int npops = 0; @@ -3120,7 +881,6 @@ bool BytecodeEmitter::emitAtomOp(JSAtom* atom, JSOp op) { MOZ_ASSERT(atom); - MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM); // .generator lookups should be emitted as JSOP_GETALIASEDVAR instead of // JSOP_GETNAME etc, to bypass |with| objects on the scope chain. @@ -3138,14 +898,15 @@ BytecodeEmitter::emitAtomOp(JSAtom* atom, JSOp op) if (!makeAtomIndex(atom, &index)) return false; - return emitIndexOp(op, index); + return emitAtomOp(index, op); } bool -BytecodeEmitter::emitAtomOp(ParseNode* pn, JSOp op) +BytecodeEmitter::emitAtomOp(uint32_t atomIndex, JSOp op) { - MOZ_ASSERT(pn->pn_atom != nullptr); - return emitAtomOp(pn->pn_atom, op); + MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM); + + return emitIndexOp(op, atomIndex); } bool @@ -3231,19 +992,6 @@ BytecodeEmitter::emitEnvCoordOp(JSOp op, EnvironmentCoordinate ec) return true; } -static JSOp -GetIncDecInfo(ParseNodeKind kind, bool* post) -{ - MOZ_ASSERT(kind == ParseNodeKind::PostIncrement || - kind == ParseNodeKind::PreIncrement || - kind == ParseNodeKind::PostDecrement || - kind == ParseNodeKind::PreDecrement); - *post = kind == ParseNodeKind::PostIncrement || kind == ParseNodeKind::PostDecrement; - return (kind == ParseNodeKind::PostIncrement || kind == ParseNodeKind::PreIncrement) - ? JSOP_ADD - : JSOP_SUB; -} - JSOp BytecodeEmitter::strictifySetNameOp(JSOp op) { @@ -3415,6 +1163,9 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) case ParseNodeKind::Assign: case ParseNodeKind::AddAssign: case ParseNodeKind::SubAssign: + case ParseNodeKind::CoalesceAssignExpr: + case ParseNodeKind::OrAssignExpr: + case ParseNodeKind::AndAssignExpr: case ParseNodeKind::BitOrAssign: case ParseNodeKind::BitXorAssign: case ParseNodeKind::BitAndAssign: @@ -3930,259 +1681,31 @@ BytecodeEmitter::emitFinishIteratorResult(bool done) return false; if (!emitIndex32(JSOP_INITPROP, value_id)) - return false; - if (!emit1(done ? JSOP_TRUE : JSOP_FALSE)) - return false; - if (!emitIndex32(JSOP_INITPROP, done_id)) - return false; - return true; -} - -bool -BytecodeEmitter::emitGetNameAtLocation(JSAtom* name, const NameLocation& loc, bool callContext) -{ - switch (loc.kind()) { - case NameLocation::Kind::Dynamic: - if (!emitAtomOp(name, JSOP_GETNAME)) - return false; - break; - - case NameLocation::Kind::Global: - if (!emitAtomOp(name, JSOP_GETGNAME)) - return false; - break; - - case NameLocation::Kind::Intrinsic: - if (!emitAtomOp(name, JSOP_GETINTRINSIC)) - return false; - break; - - case NameLocation::Kind::NamedLambdaCallee: - if (!emit1(JSOP_CALLEE)) - return false; - break; - - case NameLocation::Kind::Import: - if (!emitAtomOp(name, JSOP_GETIMPORT)) - return false; - break; - - case NameLocation::Kind::ArgumentSlot: - if (!emitArgOp(JSOP_GETARG, loc.argumentSlot())) - return false; - break; - - case NameLocation::Kind::FrameSlot: - if (loc.isLexical()) { - if (!emitTDZCheckIfNeeded(name, loc)) - return false; - } - if (!emitLocalOp(JSOP_GETLOCAL, loc.frameSlot())) - return false; - break; - - case NameLocation::Kind::EnvironmentCoordinate: - if (loc.isLexical()) { - if (!emitTDZCheckIfNeeded(name, loc)) - return false; - } - if (!emitEnvCoordOp(JSOP_GETALIASEDVAR, loc.environmentCoordinate())) - return false; - break; - - case NameLocation::Kind::DynamicAnnexBVar: - MOZ_CRASH("Synthesized vars for Annex B.3.3 should only be used in initialization"); - } - - // Need to provide |this| value for call. - if (callContext) { - switch (loc.kind()) { - case NameLocation::Kind::Dynamic: { - JSOp thisOp = needsImplicitThis() ? JSOP_IMPLICITTHIS : JSOP_GIMPLICITTHIS; - if (!emitAtomOp(name, thisOp)) - return false; - break; - } - - case NameLocation::Kind::Global: - if (!emitAtomOp(name, JSOP_GIMPLICITTHIS)) - return false; - break; - - case NameLocation::Kind::Intrinsic: - case NameLocation::Kind::NamedLambdaCallee: - case NameLocation::Kind::Import: - case NameLocation::Kind::ArgumentSlot: - case NameLocation::Kind::FrameSlot: - case NameLocation::Kind::EnvironmentCoordinate: - if (!emit1(JSOP_UNDEFINED)) - return false; - break; - - case NameLocation::Kind::DynamicAnnexBVar: - MOZ_CRASH("Synthesized vars for Annex B.3.3 should only be used in initialization"); - } - } - - return true; -} - -bool -BytecodeEmitter::emitGetName(ParseNode* pn, bool callContext) -{ - return emitGetName(pn->name(), callContext); -} - -template -bool -BytecodeEmitter::emitSetOrInitializeNameAtLocation(HandleAtom name, const NameLocation& loc, - RHSEmitter emitRhs, bool initialize) -{ - bool emittedBindOp = false; - - switch (loc.kind()) { - case NameLocation::Kind::Dynamic: - case NameLocation::Kind::Import: - case NameLocation::Kind::DynamicAnnexBVar: { - uint32_t atomIndex; - if (!makeAtomIndex(name, &atomIndex)) - return false; - if (loc.kind() == NameLocation::Kind::DynamicAnnexBVar) { - // Annex B vars always go on the nearest variable environment, - // even if lexical environments in between contain same-named - // bindings. - if (!emit1(JSOP_BINDVAR)) - return false; - } else { - if (!emitIndexOp(JSOP_BINDNAME, atomIndex)) - return false; - } - emittedBindOp = true; - if (!emitRhs(this, loc, emittedBindOp)) - return false; - if (!emitIndexOp(strictifySetNameOp(JSOP_SETNAME), atomIndex)) - return false; - break; - } - - case NameLocation::Kind::Global: { - JSOp op; - uint32_t atomIndex; - if (!makeAtomIndex(name, &atomIndex)) - return false; - if (loc.isLexical() && initialize) { - // INITGLEXICAL always gets the global lexical scope. It doesn't - // need a BINDGNAME. - MOZ_ASSERT(innermostScope()->is()); - op = JSOP_INITGLEXICAL; - } else { - if (!emitIndexOp(JSOP_BINDGNAME, atomIndex)) - return false; - emittedBindOp = true; - op = strictifySetNameOp(JSOP_SETGNAME); - } - if (!emitRhs(this, loc, emittedBindOp)) - return false; - if (!emitIndexOp(op, atomIndex)) - return false; - break; - } - - case NameLocation::Kind::Intrinsic: - if (!emitRhs(this, loc, emittedBindOp)) - return false; - if (!emitAtomOp(name, JSOP_SETINTRINSIC)) - return false; - break; - - case NameLocation::Kind::NamedLambdaCallee: - if (!emitRhs(this, loc, emittedBindOp)) - return false; - // Assigning to the named lambda is a no-op in sloppy mode but - // throws in strict mode. - if (sc->strict() && !emit1(JSOP_THROWSETCALLEE)) - return false; - break; - - case NameLocation::Kind::ArgumentSlot: { - // If we assign to a positional formal parameter and the arguments - // object is unmapped (strict mode or function with - // default/rest/destructing args), parameters do not alias - // arguments[i], and to make the arguments object reflect initial - // parameter values prior to any mutation we create it eagerly - // whenever parameters are (or might, in the case of calls to eval) - // assigned. - FunctionBox* funbox = sc->asFunctionBox(); - if (funbox->argumentsHasLocalBinding() && !funbox->hasMappedArgsObj()) - funbox->setDefinitelyNeedsArgsObj(); - - if (!emitRhs(this, loc, emittedBindOp)) - return false; - if (!emitArgOp(JSOP_SETARG, loc.argumentSlot())) - return false; - break; - } - - case NameLocation::Kind::FrameSlot: { - JSOp op = JSOP_SETLOCAL; - if (!emitRhs(this, loc, emittedBindOp)) - return false; - if (loc.isLexical()) { - if (initialize) { - op = JSOP_INITLEXICAL; - } else { - if (loc.isConst()) - op = JSOP_THROWSETCONST; - - if (!emitTDZCheckIfNeeded(name, loc)) - return false; - } - } - if (!emitLocalOp(op, loc.frameSlot())) - return false; - if (op == JSOP_INITLEXICAL) { - if (!innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ)) - return false; - } - break; - } - - case NameLocation::Kind::EnvironmentCoordinate: { - JSOp op = JSOP_SETALIASEDVAR; - if (!emitRhs(this, loc, emittedBindOp)) - return false; - if (loc.isLexical()) { - if (initialize) { - op = JSOP_INITALIASEDLEXICAL; - } else { - if (loc.isConst()) - op = JSOP_THROWSETALIASEDCONST; + return false; + if (!emit1(done ? JSOP_TRUE : JSOP_FALSE)) + return false; + if (!emitIndex32(JSOP_INITPROP, done_id)) + return false; + return true; +} - if (!emitTDZCheckIfNeeded(name, loc)) - return false; - } - } - if (loc.bindingKind() == BindingKind::NamedLambdaCallee) { - // Assigning to the named lambda is a no-op in sloppy mode and throws - // in strict mode. - op = JSOP_THROWSETALIASEDCONST; - if (sc->strict() && !emitEnvCoordOp(op, loc.environmentCoordinate())) - return false; - } else { - if (!emitEnvCoordOp(op, loc.environmentCoordinate())) - return false; - } - if (op == JSOP_INITALIASEDLEXICAL) { - if (!innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ)) - return false; - } - break; - } +bool +BytecodeEmitter::emitGetNameAtLocation(JSAtom* name, const NameLocation& loc) +{ + NameOpEmitter noe(this, name, loc, NameOpEmitter::Kind::Get); + if (!noe.emitGet()) { + return false; } return true; } +bool +BytecodeEmitter::emitGetName(ParseNode* pn) +{ + return emitGetName(pn->name()); +} + bool BytecodeEmitter::emitTDZCheckIfNeeded(JSAtom* name, const NameLocation& loc) { @@ -4243,7 +1766,7 @@ BytecodeEmitter::emitPropLHS(ParseNode* pn) do { /* Walk back up the list, emitting annotated name ops. */ - if (!emitAtomOp(pndot->pn_right, JSOP_GETPROP)) + if (!emitAtomOp(pndot->pn_right->pn_atom, JSOP_GETPROP)) return false; /* Reverse the pn_left link again. */ @@ -4258,127 +1781,36 @@ BytecodeEmitter::emitPropLHS(ParseNode* pn) return emitTree(pn2); } -bool -BytecodeEmitter::emitSuperPropLHS(ParseNode* superBase, bool isCall) -{ - if (!emitGetThisForSuperBase(superBase)) - return false; - if (isCall && !emit1(JSOP_DUP)) - return false; - if (!emit1(JSOP_SUPERBASE)) - return false; - return true; -} - -bool -BytecodeEmitter::emitPropOp(ParseNode* pn, JSOp op) -{ - MOZ_ASSERT(pn->isArity(PN_BINARY)); - - if (!emitPropLHS(pn)) - return false; - - if (op == JSOP_CALLPROP && !emit1(JSOP_DUP)) - return false; - - if (!emitAtomOp(pn->pn_right, op)) - return false; - - if (op == JSOP_CALLPROP && !emit1(JSOP_SWAP)) - return false; - - return true; -} - -bool -BytecodeEmitter::emitSuperPropOp(ParseNode* pn, JSOp op, bool isCall) -{ - ParseNode* base = &pn->as().expression(); - if (!emitSuperPropLHS(base, isCall)) - return false; - - if (!emitAtomOp(pn->pn_right, op)) - return false; - - if (isCall && !emit1(JSOP_SWAP)) - return false; - - return true; -} - bool BytecodeEmitter::emitPropIncDec(ParseNode* pn) { - MOZ_ASSERT(pn->pn_kid->isKind(ParseNodeKind::Dot)); - - bool post; - bool isSuper = pn->pn_kid->as().isSuper(); - JSOp binop = GetIncDecInfo(pn->getKind(), &post); + PropertyAccess* prop = &pn->pn_kid->as(); + bool isSuper = prop->isSuper(); + ParseNodeKind kind = pn->getKind(); + PropOpEmitter poe(this, + kind == ParseNodeKind::PostIncrement ? PropOpEmitter::Kind::PostIncrement + : kind == ParseNodeKind::PreIncrement ? PropOpEmitter::Kind::PreIncrement + : kind == ParseNodeKind::PostDecrement ? PropOpEmitter::Kind::PostDecrement + : PropOpEmitter::Kind::PreDecrement, + isSuper + ? PropOpEmitter::ObjKind::Super + : PropOpEmitter::ObjKind::Other); + if (!poe.prepareForObj()) { + return false; + } if (isSuper) { - ParseNode* base = &pn->pn_kid->as().expression(); - if (!emitSuperPropLHS(base)) // THIS OBJ - return false; - if (!emit1(JSOP_DUP2)) // THIS OBJ THIS OBJ + ParseNode* base = &prop->expression(); + if (!emitGetThisForSuperBase(base)) { // THIS return false; + } } else { - if (!emitPropLHS(pn->pn_kid)) // OBJ - return false; - if (!emit1(JSOP_DUP)) // OBJ OBJ - return false; - } - if (!emitAtomOp(pn->pn_kid->pn_right, isSuper ? JSOP_GETPROP_SUPER : JSOP_GETPROP)) // OBJ V - return false; - if (!emit1(JSOP_POS)) // OBJ N - return false; - if (post && !emit1(JSOP_DUP)) // OBJ N? N - return false; - if (!emit1(JSOP_ONE)) // OBJ N? N 1 - return false; - if (!emit1(binop)) // OBJ N? N+1 - return false; - - if (post) { - if (!emit2(JSOP_PICK, 2 + isSuper)) // N? N+1 OBJ - return false; - if (!emit1(JSOP_SWAP)) // N? OBJ N+1 - return false; - if (isSuper) { - if (!emit2(JSOP_PICK, 3)) // N THIS N+1 OBJ - return false; - if (!emit1(JSOP_SWAP)) // N THIS OBJ N+1 - return false; + if (!emitPropLHS(prop)) { + return false; // OBJ } } - - JSOp setOp = isSuper ? sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER - : sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP; - if (!emitAtomOp(pn->pn_kid->pn_right, setOp)) // N? N+1 - return false; - if (post && !emit1(JSOP_POP)) // RESULT + if (!poe.emitIncDec(prop->key().pn_atom)) { // RESULT return false; - - return true; -} - -bool -BytecodeEmitter::emitGetNameAtLocationForCompoundAssignment(JSAtom* name, const NameLocation& loc) -{ - if (loc.kind() == NameLocation::Kind::Dynamic) { - // For dynamic accesses we need to emit GETBOUNDNAME instead of - // GETNAME for correctness: looking up @@unscopables on the - // environment chain (due to 'with' environments) must only happen - // once. - // - // GETBOUNDNAME uses the environment already pushed on the stack from - // the earlier BINDNAME. - if (!emit1(JSOP_DUP)) // ENV ENV - return false; - if (!emitAtomOp(name, JSOP_GETBOUNDNAME)) // ENV V - return false; - } else { - if (!emitGetNameAtLocation(name, loc)) // ENV? V - return false; } return true; @@ -4389,110 +1821,17 @@ BytecodeEmitter::emitNameIncDec(ParseNode* pn) { MOZ_ASSERT(pn->pn_kid->isKind(ParseNodeKind::Name)); - bool post; - JSOp binop = GetIncDecInfo(pn->getKind(), &post); - - auto emitRhs = [pn, post, binop](BytecodeEmitter* bce, const NameLocation& loc, - bool emittedBindOp) - { - JSAtom* name = pn->pn_kid->name(); - if (!bce->emitGetNameAtLocationForCompoundAssignment(name, loc)) // ENV? V - return false; - if (!bce->emit1(JSOP_POS)) // ENV? N - return false; - if (post && !bce->emit1(JSOP_DUP)) // ENV? N? N - return false; - if (!bce->emit1(JSOP_ONE)) // ENV? N? N 1 - return false; - if (!bce->emit1(binop)) // ENV? N? N+1 - return false; - - if (post && emittedBindOp) { - if (!bce->emit2(JSOP_PICK, 2)) // N? N+1 ENV? - return false; - if (!bce->emit1(JSOP_SWAP)) // N? ENV? N+1 - return false; - } - - return true; - }; - - if (!emitSetName(pn->pn_kid, emitRhs)) - return false; - - if (post && !emit1(JSOP_POP)) - return false; - - return true; -} - -bool -BytecodeEmitter::emitElemOperands(ParseNode* pn, EmitElemOption opts) -{ - MOZ_ASSERT(pn->isArity(PN_BINARY)); - - if (!emitTree(pn->pn_left)) - return false; - - if (opts == EmitElemOption::IncDec) { - if (!emit1(JSOP_CHECKOBJCOERCIBLE)) - return false; - } else if (opts == EmitElemOption::Call) { - if (!emit1(JSOP_DUP)) - return false; - } - - if (!emitTree(pn->pn_right)) - return false; - - if (opts == EmitElemOption::Set) { - if (!emit2(JSOP_PICK, 2)) - return false; - } else if (opts == EmitElemOption::IncDec || opts == EmitElemOption::CompoundAssign) { - if (!emit1(JSOP_TOID)) - return false; - } - return true; -} - -bool -BytecodeEmitter::emitSuperElemOperands(ParseNode* pn, EmitElemOption opts) -{ - MOZ_ASSERT(pn->isKind(ParseNodeKind::Elem) && pn->as().isSuper()); - - // The ordering here is somewhat screwy. We need to evaluate the propval - // first, by spec. Do a little dance to not emit more than one JSOP_THIS. - // Since JSOP_THIS might throw in derived class constructors, we cannot - // just push it earlier as the receiver. We have to swap it down instead. - - if (!emitTree(pn->pn_right)) - return false; - - // We need to convert the key to an object id first, so that we do not do - // it inside both the GETELEM and the SETELEM. - if (opts == EmitElemOption::IncDec || opts == EmitElemOption::CompoundAssign) { - if (!emit1(JSOP_TOID)) - return false; - } - - if (!emitGetThisForSuperBase(pn->pn_left)) + ParseNodeKind kind = pn->getKind(); + NameNode* name = &pn->pn_kid->as(); + NameOpEmitter noe(this, name->pn_atom, + kind == ParseNodeKind::PostIncrement ? NameOpEmitter::Kind::PostIncrement + : kind == ParseNodeKind::PreIncrement ? NameOpEmitter::Kind::PreIncrement + : kind == ParseNodeKind::PostDecrement ? NameOpEmitter::Kind::PostDecrement + : NameOpEmitter::Kind::PreDecrement); + if (!noe.emitIncDec()) { return false; - - if (opts == EmitElemOption::Call) { - if (!emit1(JSOP_SWAP)) - return false; - - // We need another |this| on top, also - if (!emitDupAt(1)) - return false; } - if (!emit1(JSOP_SUPERBASE)) - return false; - - if (opts == EmitElemOption::Set && !emit2(JSOP_PICK, 3)) - return false; - return true; } @@ -4507,33 +1846,38 @@ BytecodeEmitter::emitElemOpBase(JSOp op) } bool -BytecodeEmitter::emitElemOp(ParseNode* pn, JSOp op) +BytecodeEmitter::emitElemObjAndKey(PropertyByValue* elem, bool isSuper, ElemOpEmitter& eoe) { - EmitElemOption opts = EmitElemOption::Get; - if (op == JSOP_CALLELEM) - opts = EmitElemOption::Call; - else if (op == JSOP_SETELEM || op == JSOP_STRICTSETELEM) - opts = EmitElemOption::Set; - - return emitElemOperands(pn, opts) && emitElemOpBase(op); -} + if (isSuper) { + if (!eoe.prepareForObj()) { // + return false; + } + UnaryNode* base = &elem->expression().as(); + if (!emitGetThisForSuperBase(base)) { // THIS + return false; + } + if (!eoe.prepareForKey()) { // THIS + return false; + } + if (!emitTree(&elem->key())) { // THIS KEY + return false; + } -bool -BytecodeEmitter::emitSuperElemOp(ParseNode* pn, JSOp op, bool isCall) -{ - EmitElemOption opts = EmitElemOption::Get; - if (isCall) - opts = EmitElemOption::Call; - else if (op == JSOP_SETELEM_SUPER || op == JSOP_STRICTSETELEM_SUPER) - opts = EmitElemOption::Set; + return true; + } - if (!emitSuperElemOperands(pn, opts)) + if (!eoe.prepareForObj()) { // return false; - if (!emitElemOpBase(op)) + } + if (!emitTree(&elem->expression())) { // OBJ return false; - - if (isCall && !emit1(JSOP_SWAP)) + } + if (!eoe.prepareForKey()) { // OBJ? OBJ + return false; + } + if (!emitTree(&elem->key())) { // OBJ? OBJ KEY return false; + } return true; } @@ -4541,74 +1885,27 @@ BytecodeEmitter::emitSuperElemOp(ParseNode* pn, JSOp op, bool isCall) bool BytecodeEmitter::emitElemIncDec(ParseNode* pn) { - MOZ_ASSERT(pn->pn_kid->isKind(ParseNodeKind::Elem)); - - bool isSuper = pn->pn_kid->as().isSuper(); - - // We need to convert the key to an object id first, so that we do not do - // it inside both the GETELEM and the SETELEM. This is done by - // emit(Super)ElemOperands. - if (isSuper) { - if (!emitSuperElemOperands(pn->pn_kid, EmitElemOption::IncDec)) - return false; - } else { - if (!emitElemOperands(pn->pn_kid, EmitElemOption::IncDec)) - return false; - } - - bool post; - JSOp binop = GetIncDecInfo(pn->getKind(), &post); - - JSOp getOp; - if (isSuper) { - // There's no such thing as JSOP_DUP3, so we have to be creative. - // Note that pushing things again is no fewer JSOps. - if (!emitDupAt(2)) // KEY THIS OBJ KEY - return false; - if (!emitDupAt(2)) // KEY THIS OBJ KEY THIS - return false; - if (!emitDupAt(2)) // KEY THIS OBJ KEY THIS OBJ - return false; - getOp = JSOP_GETELEM_SUPER; - } else { - // OBJ KEY - if (!emit1(JSOP_DUP2)) // OBJ KEY OBJ KEY - return false; - getOp = JSOP_GETELEM; - } - if (!emitElemOpBase(getOp)) // OBJ KEY V - return false; - if (!emit1(JSOP_POS)) // OBJ KEY N - return false; - if (post && !emit1(JSOP_DUP)) // OBJ KEY N? N - return false; - if (!emit1(JSOP_ONE)) // OBJ KEY N? N 1 - return false; - if (!emit1(binop)) // OBJ KEY N? N+1 - return false; - - if (post) { - if (isSuper) { - // We have one more value to rotate around, because of |this| - // on the stack - if (!emit2(JSOP_PICK, 4)) - return false; - } - if (!emit2(JSOP_PICK, 3 + isSuper)) // KEY N N+1 OBJ - return false; - if (!emit2(JSOP_PICK, 3 + isSuper)) // N N+1 OBJ KEY - return false; - if (!emit2(JSOP_PICK, 2 + isSuper)) // N OBJ KEY N+1 - return false; + PropertyByValue* elemExpr = &pn->pn_kid->as(); + bool isSuper = elemExpr->isSuper(); + ParseNodeKind kind = pn->getKind(); + ElemOpEmitter eoe(this, + kind == ParseNodeKind::PostIncrement ? ElemOpEmitter::Kind::PostIncrement + : kind == ParseNodeKind::PreIncrement ? ElemOpEmitter::Kind::PreIncrement + : kind == ParseNodeKind::PostDecrement ? ElemOpEmitter::Kind::PostDecrement + : ElemOpEmitter::Kind::PreDecrement, + isSuper + ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other); + if (!emitElemObjAndKey(elemExpr, isSuper, eoe)) { // [Super] + // // THIS KEY + // // [Other] + // // OBJ KEY + return false; + } + if (!eoe.emitIncDec()) { // RESULT + return false; } - JSOp setOp = isSuper ? (sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER) - : (sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM); - if (!emitElemOpBase(setOp)) // N? N+1 - return false; - if (post && !emit1(JSOP_POP)) // RESULT - return false; - return true; } @@ -5075,21 +2372,6 @@ BytecodeEmitter::emitSetThis(ParseNode* pn) MOZ_ASSERT(pn->pn_left->isKind(ParseNodeKind::Name)); RootedAtom name(cx, pn->pn_left->name()); - auto emitRhs = [&name, pn](BytecodeEmitter* bce, const NameLocation&, bool) { - // Emit the new |this| value. - if (!bce->emitTree(pn->pn_right)) - return false; - // Get the original |this| and throw if we already initialized - // it. Do *not* use the NameLocation argument, as that's the special - // lexical location below to deal with super() semantics. - if (!bce->emitGetName(name)) - return false; - if (!bce->emit1(JSOP_CHECKTHISREINIT)) - return false; - if (!bce->emit1(JSOP_POP)) - return false; - return true; - }; // The 'this' binding is not lexical, but due to super() semantics this // initialization needs to be treated as a lexical one. @@ -5106,7 +2388,32 @@ BytecodeEmitter::emitSetThis(ParseNode* pn) lexicalLoc = loc; } - return emitSetOrInitializeNameAtLocation(name, lexicalLoc, emitRhs, true); + NameOpEmitter noe(this, name, lexicalLoc, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { // + return false; + } + + // Emit the new |this| value. + if (!emitTree(pn->pn_right)) // NEWTHIS + return false; + + // Get the original |this| and throw if we already initialized + // it. Do *not* use the NameLocation argument, as that's the special + // lexical location below to deal with super() semantics. + if (!emitGetName(name)) { // NEWTHIS THIS + return false; + } + if (!emit1(JSOP_CHECKTHISREINIT)) { // NEWTHIS THIS + return false; + } + if (!emit1(JSOP_POP)) { // NEWTHIS + return false; + } + if (!noe.emitAssignment()) { // NEWTHIS + return false; + } + + return true; } bool @@ -5237,64 +2544,16 @@ BytecodeEmitter::emitFunctionScript(ParseNode* body) if (!JSScript::fullyInitFromEmitter(cx, script, this)) return false; - // URL and source map information must be set before firing - // Debugger::onNewScript. Only top-level functions need this, as compiling - // the outer scripts of nested functions already processed the source. - if (emitterMode != LazyFunction && !parent) { - if (!maybeSetDisplayURL() || !maybeSetSourceMap()) - return false; - - tellDebuggerAboutCompiledScript(cx); - } - - return true; -} - -template -bool -BytecodeEmitter::emitDestructuringDeclsWithEmitter(ParseNode* pattern, NameEmitter emitName) -{ - if (pattern->isKind(ParseNodeKind::Array)) { - for (ParseNode* element = pattern->pn_head; element; element = element->pn_next) { - if (element->isKind(ParseNodeKind::Elision)) - continue; - ParseNode* target = element; - if (element->isKind(ParseNodeKind::Spread)) { - target = element->pn_kid; - } - if (target->isKind(ParseNodeKind::Assign)) - target = target->pn_left; - if (target->isKind(ParseNodeKind::Name)) { - if (!emitName(this, target)) - return false; - } else { - if (!emitDestructuringDeclsWithEmitter(target, emitName)) - return false; - } - } - return true; - } - - MOZ_ASSERT(pattern->isKind(ParseNodeKind::Object)); - for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) { - MOZ_ASSERT(member->isKind(ParseNodeKind::MutateProto) || - member->isKind(ParseNodeKind::Colon) || - member->isKind(ParseNodeKind::Shorthand)); - - ParseNode* target = member->isKind(ParseNodeKind::MutateProto) - ? member->pn_kid - : member->pn_right; + // URL and source map information must be set before firing + // Debugger::onNewScript. Only top-level functions need this, as compiling + // the outer scripts of nested functions already processed the source. + if (emitterMode != LazyFunction && !parent) { + if (!maybeSetDisplayURL() || !maybeSetSourceMap()) + return false; - if (target->isKind(ParseNodeKind::Assign)) - target = target->pn_left; - if (target->isKind(ParseNodeKind::Name)) { - if (!emitName(this, target)) - return false; - } else { - if (!emitDestructuringDeclsWithEmitter(target, emitName)) - return false; - } + tellDebuggerAboutCompiledScript(cx); } + return true; } @@ -5325,28 +2584,64 @@ BytecodeEmitter::emitDestructuringLHSRef(ParseNode* target, size_t* emitted) switch (target->getKind()) { case ParseNodeKind::Dot: { - if (target->as().isSuper()) { - if (!emitSuperPropLHS(&target->as().expression())) + PropertyAccess* prop = &target->as(); + bool isSuper = prop->isSuper(); + PropOpEmitter poe(this, + PropOpEmitter::Kind::SimpleAssignment, + isSuper + ? PropOpEmitter::ObjKind::Super + : PropOpEmitter::ObjKind::Other); + if (!poe.prepareForObj()) { + return false; + } + if (isSuper) { + UnaryNode* base = &prop->expression().as(); + if (!emitGetThisForSuperBase(base)) { // THIS SUPERBASE return false; + } + // SUPERBASE is pushed onto THIS in poe.prepareForRhs below. *emitted = 2; } else { - if (!emitTree(target->pn_left)) + if (!emitTree(&prop->expression())) { // OBJ return false; + } *emitted = 1; } + if (!poe.prepareForRhs()) { // [Super] + // // THIS SUPERBASE + // // [Other] + // // OBJ + return false; + } break; } case ParseNodeKind::Elem: { - if (target->as().isSuper()) { - if (!emitSuperElemOperands(target, EmitElemOption::Ref)) - return false; + PropertyByValue* elem = &target->as(); + bool isSuper = elem->isSuper(); + ElemOpEmitter eoe(this, + ElemOpEmitter::Kind::SimpleAssignment, + isSuper + ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other); + if (!emitElemObjAndKey(elem, isSuper, eoe)) { // [Super] + // // THIS KEY + // // [Other] + // // OBJ KEY + return false; + } + if (isSuper) { + // SUPERBASE is pushed onto KEY in eoe.prepareForRhs below. *emitted = 3; } else { - if (!emitElemOperands(target, EmitElemOption::Ref)) - return false; *emitted = 2; } + if (!eoe.prepareForRhs()) { // [Super] + // // THIS KEY SUPERBASE + // // [Other] + // // OBJ KEY + return false; + } break; } @@ -5386,38 +2681,13 @@ BytecodeEmitter::emitSetOrInitializeDestructuring(ParseNode* target, Destructuri } else { switch (target->getKind()) { case ParseNodeKind::Name: { - auto emitSwapScopeAndRhs = [](BytecodeEmitter* bce, const NameLocation&, - bool emittedBindOp) - { - if (emittedBindOp) { - // This is like ordinary assignment, but with one - // difference. - // - // In `a = b`, we first determine a binding for `a` (using - // JSOP_BINDNAME or JSOP_BINDGNAME), then we evaluate `b`, - // then a JSOP_SETNAME instruction. - // - // In `[a] = [b]`, per spec, `b` is evaluated first, then - // we determine a binding for `a`. Then we need to do - // assignment-- but the operands are on the stack in the - // wrong order for JSOP_SETPROP, so we have to add a - // JSOP_SWAP. - // - // In the cases where we are emitting a name op, emit a - // swap because of this. - return bce->emit1(JSOP_SWAP); - } - - // In cases of emitting a frame slot or environment slot, - // nothing needs be done. - return true; - }; - RootedAtom name(cx, target->name()); + NameLocation loc; + NameOpEmitter::Kind kind; switch (flav) { case DestructuringDeclaration: - if (!emitInitializeName(name, emitSwapScopeAndRhs)) - return false; + loc = lookupName(name); + kind = NameOpEmitter::Kind::Initialize; break; case DestructuringFormalParameterInVarScope: { @@ -5426,45 +2696,89 @@ BytecodeEmitter::emitSetOrInitializeDestructuring(ParseNode* target, Destructuri // the function scope. The innermost scope is the var scope, // and its enclosing scope is the function scope. EmitterScope* funScope = innermostEmitterScope()->enclosingInFrame(); - NameLocation paramLoc = *locationOfNameBoundInScope(name, funScope); - if (!emitSetOrInitializeNameAtLocation(name, paramLoc, emitSwapScopeAndRhs, true)) - return false; + loc = *locationOfNameBoundInScope(name, funScope); + kind = NameOpEmitter::Kind::Initialize; break; } case DestructuringAssignment: - if (!emitSetName(name, emitSwapScopeAndRhs)) - return false; + loc = lookupName(name); + kind = NameOpEmitter::Kind::SimpleAssignment; break; } + NameOpEmitter noe(this, name, loc, kind); + if (!noe.prepareForRhs()) { // V ENV? + return false; + } + if (noe.emittedBindOp()) { + // This is like ordinary assignment, but with one difference. + // + // In `a = b`, we first determine a binding for `a` (using + // JSOP_BINDNAME or JSOP_BINDGNAME), then we evaluate `b`, then + // a JSOP_SETNAME instruction. + // + // In `[a] = [b]`, per spec, `b` is evaluated first, then we + // determine a binding for `a`. Then we need to do assignment-- + // but the operands are on the stack in the wrong order for + // JSOP_SETPROP, so we have to add a JSOP_SWAP. + // + // In the cases where we are emitting a name op, emit a swap + // because of this. + if (!emit1(JSOP_SWAP)) { // ENV V + return false; + } + } else { + // In cases of emitting a frame slot or environment slot, + // nothing needs be done. + } + if (!noe.emitAssignment()) { // V + return false; + } + break; } case ParseNodeKind::Dot: { // The reference is already pushed by emitDestructuringLHSRef. - JSOp setOp; - if (target->as().isSuper()) - setOp = sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER; - else - setOp = sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP; - if (!emitAtomOp(target->pn_right, setOp)) + // // [Super] + // // THIS SUPERBASE VAL + // // [Other] + // // OBJ VAL + PropertyAccess* prop = &target->as(); + bool isSuper = prop->isSuper(); + PropOpEmitter poe(this, + PropOpEmitter::Kind::SimpleAssignment, + isSuper + ? PropOpEmitter::ObjKind::Super + : PropOpEmitter::ObjKind::Other); + if (!poe.skipObjAndRhs()) { return false; + } + if (!poe.emitAssignment(prop->key().pn_atom)) { + return false; // VAL + } break; } case ParseNodeKind::Elem: { // The reference is already pushed by emitDestructuringLHSRef. - if (target->as().isSuper()) { - JSOp setOp = sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER; - // emitDestructuringLHSRef already did emitSuperElemOperands - // part of emitSuperElemOp. Perform remaining part here. - if (!emitElemOpBase(setOp)) - return false; - } else { - JSOp setOp = sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM; - if (!emitElemOpBase(setOp)) - return false; + // // [Super] + // // THIS KEY SUPERBASE VAL + // // [Other] + // // OBJ KEY VAL + PropertyByValue* elem = &target->as(); + bool isSuper = elem->isSuper(); + ElemOpEmitter eoe(this, + ElemOpEmitter::Kind::SimpleAssignment, + isSuper + ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other); + if (!eoe.skipObjAndKeyAndRhs()) { + return false; + } + if (!eoe.emitAssignment()) { // VAL + return false; } break; } @@ -5480,8 +2794,9 @@ BytecodeEmitter::emitSetOrInitializeDestructuring(ParseNode* target, Destructuri } // Pop the assigned value. - if (!emit1(JSOP_POP)) + if (!emit1(JSOP_POP)) { // !STACK EMPTY! return false; + } } return true; @@ -5556,121 +2871,84 @@ BytecodeEmitter::emitIteratorCloseInScope(EmitterScope& currentScope, ".close() on iterators is prohibited in self-hosted code because it " "can run user-modifiable iteration code"); - // Generate inline logic corresponding to IteratorClose (ES 7.4.6). + // Generate inline logic corresponding to IteratorClose (ES2021 7.4.6) and + // AsyncIteratorClose (ES2021 7.4.7). Steps numbers apply to both operations. // // Callers need to ensure that the iterator object is at the top of the // stack. + // For non-Throw completions, we emit the equivalent of: + // + // var returnMethod = GetMethod(iterator, "return"); + // if (returnMethod !== undefined) { + // var innerResult = [Await] Call(returnMethod, iterator); + // CheckIsObj(innerResult); + // } + // + // Whereas for Throw completions, we emit: + // + // try { + // var returnMethod = GetMethod(iterator, "return"); + // if (returnMethod !== undefined) { + // [Await] Call(returnMethod, iterator); + // } + // } catch {} + + Maybe tryCatch; + + if (completionKind == CompletionKind::Throw) { + tryCatch.emplace(this, TryEmitter::Kind::TryCatch, + TryEmitter::ControlKind::NonSyntactic); + + if (!tryCatch->emitTry()) { // ... ITER + return false; + } + } + if (!emit1(JSOP_DUP)) // ... ITER ITER return false; - // Step 3. + // Steps 1-2 are assertions, step 3 is implicit. + + // Step 4. // // Get the "return" method. if (!emitAtomOp(cx->names().return_, JSOP_CALLPROP)) // ... ITER RET return false; - // Step 4. + // Step 5. // // Do nothing if "return" is undefined or null. - IfThenElseEmitter ifReturnMethodIsDefined(this); + InternalIfEmitter ifReturnMethodIsDefined(this); if (!emitPushNotUndefinedOrNull()) // ... ITER RET NOT-UNDEF-OR-NULL return false; - if (!ifReturnMethodIsDefined.emitIfElse()) // ... ITER RET + if (!ifReturnMethodIsDefined.emitThenElse()) // ... ITER RET return false; - if (completionKind == CompletionKind::Throw) { - // 7.4.6 IteratorClose ( iterator, completion ) - // ... - // 3. Let return be ? GetMethod(iterator, "return"). - // 4. If return is undefined, return Completion(completion). - // 5. Let innerResult be Call(return, iterator, « »). - // 6. If completion.[[Type]] is throw, return Completion(completion). - // 7. If innerResult.[[Type]] is throw, return - // Completion(innerResult). - // - // For CompletionKind::Normal case, JSOP_CALL for step 5 checks if RET - // is callable, and throws if not. Since step 6 doesn't match and - // error handling in step 3 and step 7 can be merged. - // - // For CompletionKind::Throw case, an error thrown by JSOP_CALL for - // step 5 is ignored by try-catch. So we should check if RET is - // callable here, outside of try-catch, and the throw immediately if - // not. - CheckIsCallableKind kind = CheckIsCallableKind::IteratorReturn; - if (!emitCheckIsCallable(kind)) // ... ITER RET - return false; - } - - // Steps 5, 8. + // Steps 5.c, 7. // - // Call "return" if it is not undefined or null, and check that it returns - // an Object. + // Call the "return" method. if (!emit1(JSOP_SWAP)) // ... RET ITER return false; - Maybe tryCatch; - - if (completionKind == CompletionKind::Throw) { - tryCatch.emplace(this, TryEmitter::TryCatch, TryEmitter::DontUseRetVal, - TryEmitter::DontUseControl); - - // Mutate stack to balance stack for try-catch. - if (!emit1(JSOP_UNDEFINED)) // ... RET ITER UNDEF - return false; - if (!tryCatch->emitTry()) // ... RET ITER UNDEF - return false; - if (!emitDupAt(2)) // ... RET ITER UNDEF RET - return false; - if (!emitDupAt(2)) // ... RET ITER UNDEF RET ITER - return false; - } - - if (!emitCall(JSOP_CALL, 0)) // ... ... RESULT + if (!emitCall(JSOP_CALL, 0)) // ... RESULT return false; checkTypeSet(JSOP_CALL); + // 7.4.7 AsyncIteratorClose, step 5.d. if (iterKind == IteratorKind::Async) { if (completionKind != CompletionKind::Throw) { // Await clobbers rval, so save the current rval. - if (!emit1(JSOP_GETRVAL)) // ... ... RESULT RVAL + if (!emit1(JSOP_GETRVAL)) // ... RESULT RVAL return false; - if (!emit1(JSOP_SWAP)) // ... ... RVAL RESULT + if (!emit1(JSOP_SWAP)) // ... RVAL RESULT return false; } - if (!emitAwaitInScope(currentScope)) // ... ... RVAL? RESULT - return false; - } - - if (completionKind == CompletionKind::Throw) { - if (!emit1(JSOP_SWAP)) // ... RET ITER RESULT UNDEF - return false; - if (!emit1(JSOP_POP)) // ... RET ITER RESULT - return false; - - if (!tryCatch->emitCatch()) // ... RET ITER RESULT - return false; - - // Just ignore the exception thrown by call and await. - if (!emit1(JSOP_EXCEPTION)) // ... RET ITER RESULT EXC - return false; - if (!emit1(JSOP_POP)) // ... RET ITER RESULT - return false; - - if (!tryCatch->emitEnd()) // ... RET ITER RESULT - return false; - - // Restore stack. - if (!emit2(JSOP_UNPICK, 2)) // ... RESULT RET ITER - return false; - if (!emitPopN(2)) // ... RESULT - return false; - } else { - if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // ... RVAL? RESULT + if (!emitAwaitInScope(currentScope)) // ... RVAL? RESULT return false; - if (iterKind == IteratorKind::Async) { + if (completionKind != CompletionKind::Throw) { if (!emit1(JSOP_SWAP)) // ... RESULT RVAL return false; if (!emit1(JSOP_SETRVAL)) // ... RESULT @@ -5678,6 +2956,16 @@ BytecodeEmitter::emitIteratorCloseInScope(EmitterScope& currentScope, } } + // Step 6 (Handled in caller). + + // Step 8. + if (completionKind != CompletionKind::Throw) { + // Check that the "return" result is an object. + if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) { // ... RESULT + return false; + } + } + if (!ifReturnMethodIsDefined.emitElse()) // ... ITER RET return false; @@ -5687,6 +2975,23 @@ BytecodeEmitter::emitIteratorCloseInScope(EmitterScope& currentScope, if (!ifReturnMethodIsDefined.emitEnd()) return false; + if (completionKind == CompletionKind::Throw) { // ... ITER EXC + if (!tryCatch->emitCatch()) { + return false; + } + + // Just ignore the exception thrown by call and await. + if (!emit1(JSOP_POP)) { // ... ITER + return false; + } + + if (!tryCatch->emitEnd()) { // ... ITER + return false; + } + } + + // Step 9 (Handled in caller). + return emit1(JSOP_POP); // ... } @@ -5738,55 +3043,97 @@ BytecodeEmitter::emitDefault(ParseNode* defaultExpr, ParseNode* pattern) } bool -BytecodeEmitter::setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name, - FunctionPrefixKind prefixKind) +BytecodeEmitter::emitAnonymousFunctionWithName(ParseNode* node, HandleAtom name) { - if (maybeFun->isKind(ParseNodeKind::Function)) { - // Function doesn't have 'name' property at this point. - // Set function's name at compile time. - JSFunction* fun = maybeFun->pn_funbox->function(); + MOZ_ASSERT(node->isDirectRHSAnonFunction()); - // Single node can be emitted multiple times if it appears in - // array destructuring default. If function already has a name, - // just return. - if (fun->hasCompileTimeName()) { -#ifdef DEBUG - RootedFunction rootedFun(cx, fun); - JSAtom* funName = NameToFunctionName(cx, name, prefixKind); - if (!funName) - return false; - MOZ_ASSERT(funName == rootedFun->compileTimeName()); -#endif - return true; - } + if (node->isKind(ParseNodeKind::Function)) { + if (!emitTree(node)) { + return false; + } + + // Function doesn't have 'name' property at this point. + // Set function's name at compile time. + return setFunName(node->pn_funbox->function(), name); + } + + MOZ_ASSERT(node->is()); + + return emitClass(node, ClassNameKind::InferredName, name); +} + +bool +BytecodeEmitter::emitAnonymousFunctionWithComputedName(ParseNode* node, + FunctionPrefixKind prefixKind) +{ + MOZ_ASSERT(node->isDirectRHSAnonFunction()); + + if (node->isKind(ParseNodeKind::Function)) { + if (!emitTree(node)) { + // [stack] # !isAsync || !needsHomeObject + // [stack] NAME FUN + // [stack] # isAsync && needsHomeObject + // [stack] NAME UNWRAPPED WRAPPED + return false; + } + unsigned depth = 1; + FunctionBox* funbox = node->pn_funbox; + if (funbox->isAsync() && funbox->needsHomeObject()) { + depth = 2; + } + if (!emitDupAt(depth)) { + // [stack] # !isAsync || !needsHomeObject + // [stack] NAME FUN NAME + // [stack] # isAsync && needsHomeObject + // [stack] NAME UNWRAPPED WRAPPED NAME + return false; + } + if (!emit2(JSOP_SETFUNNAME, uint8_t(prefixKind))) { + // [stack] # !isAsync || !needsHomeObject + // [stack] NAME FUN + // [stack] # isAsync && needsHomeObject + // [stack] NAME UNWRAPPED WRAPPED + return false; + } + return true; + } + + MOZ_ASSERT(node->is()); + MOZ_ASSERT(prefixKind == FunctionPrefixKind::None); + + return emitClass(node, ClassNameKind::ComputedName); +} + +bool +BytecodeEmitter::setFunName(JSFunction* fun, JSAtom* name) +{ + // The inferred name may already be set if this function is an interpreted + // lazy function and we OOM'ed after we set the inferred name the first + // time. + if (fun->hasInferredName()) { + MOZ_ASSERT(fun->isInterpretedLazy()); + MOZ_ASSERT(fun->inferredName() == name); - fun->setCompileTimeName(name); return true; } - uint32_t nameIndex; - if (!makeAtomIndex(name, &nameIndex)) - return false; - if (!emitIndexOp(JSOP_STRING, nameIndex)) // FUN NAME - return false; - uint8_t kind = uint8_t(prefixKind); - if (!emit2(JSOP_SETFUNNAME, kind)) // FUN - return false; + fun->setInferredName(name); return true; } bool BytecodeEmitter::emitInitializer(ParseNode* initializer, ParseNode* pattern) { - if (!emitTree(initializer)) - return false; - - if (!pattern->isInParens() && pattern->isKind(ParseNodeKind::Name) && - initializer->isDirectRHSAnonFunction()) - { + if (initializer->isDirectRHSAnonFunction()) { + MOZ_ASSERT(!pattern->isInParens()); RootedAtom name(cx, pattern->name()); - if (!setOrEmitSetFunName(initializer, name, FunctionPrefixKind::None)) + if (!emitAnonymousFunctionWithName(initializer, name)) { + return false; + } + } else { + if (!emitTree(initializer)) { return false; + } } return true; @@ -5934,7 +3281,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav // Pick the DONE value to the top of the stack. if (emitted) { - if (!emit2(JSOP_PICK, emitted)) // ... OBJ ITER *LREF DONE + if (!emitPickN(emitted)) // ... OBJ ITER *LREF DONE return false; } @@ -5948,12 +3295,12 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav } if (member->isKind(ParseNodeKind::Spread)) { - IfThenElseEmitter ifThenElse(this); + InternalIfEmitter ifThenElse(this); if (!isFirst) { // If spread is not the first element of the pattern, // iterator can already be completed. // ... OBJ ITER *LREF DONE - if (!ifThenElse.emitIfElse()) // ... OBJ ITER *LREF + if (!ifThenElse.emitThenElse()) // ... OBJ ITER *LREF return false; if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ ITER *LREF ARRAY @@ -6003,10 +3350,10 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav MOZ_ASSERT(!member->isKind(ParseNodeKind::Spread)); - IfThenElseEmitter ifAlreadyDone(this); + InternalIfEmitter ifAlreadyDone(this); if (!isFirst) { // ... OBJ ITER *LREF DONE - if (!ifAlreadyDone.emitIfElse()) // ... OBJ ITER *LREF + if (!ifAlreadyDone.emitThenElse()) // ... OBJ ITER *LREF return false; if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER *LREF UNDEF @@ -6038,8 +3385,8 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav if (!emit2(JSOP_UNPICK, emitted + 2)) // ... OBJ ITER DONE *LREF RESULT DONE return false; - IfThenElseEmitter ifDone(this); - if (!ifDone.emitIfElse()) // ... OBJ ITER DONE *LREF RESULT + InternalIfEmitter ifDone(this); + if (!ifDone.emitThenElse()) // ... OBJ ITER DONE *LREF RESULT return false; if (!emit1(JSOP_POP)) // ... OBJ ITER DONE *LREF @@ -6090,8 +3437,8 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav // The last DONE value is on top of the stack. If not DONE, call // IteratorClose. // ... OBJ ITER DONE - IfThenElseEmitter ifDone(this); - if (!ifDone.emitIfElse()) // ... OBJ ITER + InternalIfEmitter ifDone(this); + if (!ifDone.emitThenElse()) // ... OBJ ITER return false; if (!emit1(JSOP_POP)) // ... OBJ return false; @@ -6438,46 +3785,60 @@ BytecodeEmitter::emitSingleDeclaration(ParseNode* declList, ParseNode* decl, MOZ_ASSERT(decl->isKind(ParseNodeKind::Name)); // Nothing to do for initializer-less 'var' declarations, as there's no TDZ. - if (!initializer && declList->isKind(ParseNodeKind::Var)) + if (!initializer && declList->isKind(ParseNodeKind::Var)) { return true; + } - auto emitRhs = [initializer, declList, decl](BytecodeEmitter* bce, const NameLocation&, bool) { - if (!initializer) { - // Lexical declarations are initialized to undefined without an - // initializer. - MOZ_ASSERT(declList->isKind(ParseNodeKind::Let), - "var declarations without initializers handled above, " - "and const declarations must have initializers"); - Unused << declList; // silence clang -Wunused-lambda-capture in opt builds - return bce->emit1(JSOP_UNDEFINED); + NameOpEmitter noe(this, decl->name(), NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { // ENV? + return false; + } + if (!initializer) { + // Lexical declarations are initialized to undefined without an + // initializer. + MOZ_ASSERT(declList->isKind(ParseNodeKind::Let), + "var declarations without initializers handled above, " + "and const declarations must have initializers"); + if (!emit1(JSOP_UNDEFINED)) { // ENV? UNDEF + return false; } - + } else { MOZ_ASSERT(initializer); - return bce->emitInitializer(initializer, decl); - }; - - if (!emitInitializeName(decl, emitRhs)) + if (!emitInitializer(initializer, decl)) { // ENV? V + return false; + } + } + if (!noe.emitAssignment()) { // V + return false; + } + if (!emit1(JSOP_POP)) { // return false; + } - // Pop the RHS. - return emit1(JSOP_POP); + return true; } -static bool -EmitAssignmentRhs(BytecodeEmitter* bce, ParseNode* rhs, uint8_t offset) -{ - // If there is a RHS tree, emit the tree. - if (rhs) - return bce->emitTree(rhs); +bool BytecodeEmitter::emitAssignmentRhs(ParseNode* rhs, + HandleAtom anonFunctionName) { + if (rhs->isDirectRHSAnonFunction()) { + if (anonFunctionName) { + return emitAnonymousFunctionWithName(rhs, anonFunctionName); + } + return emitAnonymousFunctionWithComputedName(rhs, FunctionPrefixKind::None); + } + return emitTree(rhs); +} - // Otherwise the RHS value to assign is already on the stack, i.e., the - // next enumeration value in a for-in or for-of loop. Depending on how - // many other values have been pushed on the stack, we need to get the - // already-pushed RHS value. - if (offset != 1 && !bce->emit2(JSOP_PICK, offset - 1)) - return false; +// The RHS value to assign is already on the stack, i.e., the next enumeration +// value in a for-in or for-of loop. Offset is the location in the stack of the +// already-emitted rhs. If we emitted a BIND[G]NAME, then the scope is on the +// top of the stack and we need to dig one deeper to get the right RHS value. +bool BytecodeEmitter::emitAssignmentRhs(uint8_t offset) { + if (offset != 1) { + return emitPickN(offset - 1); + } - return true; + return true; } static inline JSOp @@ -6497,79 +3858,116 @@ CompoundAssignmentParseNodeKindToJSOp(ParseNodeKind pnk) case ParseNodeKind::DivAssign: return JSOP_DIV; case ParseNodeKind::ModAssign: return JSOP_MOD; case ParseNodeKind::PowAssign: return JSOP_POW; + case ParseNodeKind::CoalesceAssignExpr: + case ParseNodeKind::OrAssignExpr: + case ParseNodeKind::AndAssignExpr: + // Short-circuit assignment operators are handled elsewhere. + [[fallthrough]]; default: MOZ_CRASH("unexpected compound assignment op"); } } bool -BytecodeEmitter::emitAssignment(ParseNode* lhs, ParseNodeKind pnk, ParseNode* rhs) +BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs) { - JSOp op = CompoundAssignmentParseNodeKindToJSOp(pnk); + bool isCompound = compoundOp != JSOP_NOP; // Name assignments are handled separately because choosing ops and when // to emit BINDNAME is involved and should avoid duplication. if (lhs->isKind(ParseNodeKind::Name)) { - auto emitRhs = [op, lhs, rhs](BytecodeEmitter* bce, const NameLocation& lhsLoc, - bool emittedBindOp) - { - // For compound assignments, first get the LHS value, then emit - // the RHS and the op. - if (op != JSOP_NOP) { - if (!bce->emitGetNameAtLocationForCompoundAssignment(lhs->name(), lhsLoc)) - return false; - } + RootedAtom name(cx, lhs->name()); + NameOpEmitter noe(this, name, + isCompound + ? NameOpEmitter::Kind::CompoundAssignment + : NameOpEmitter::Kind::SimpleAssignment); + if (!noe.prepareForRhs()) { // ENV? VAL? + return false; + } - // Emit the RHS. If we emitted a BIND[G]NAME, then the scope is on - // the top of the stack and we need to pick the right RHS value. - if (!EmitAssignmentRhs(bce, rhs, emittedBindOp ? 2 : 1)) + if (rhs) { + if (!emitAssignmentRhs(rhs, name)) { + // ENV? VAL? RHS return false; - - if (!lhs->isInParens() && op == JSOP_NOP && rhs && rhs->isDirectRHSAnonFunction()) { - RootedAtom name(bce->cx, lhs->name()); - if (!bce->setOrEmitSetFunName(rhs, name, FunctionPrefixKind::None)) - return false; } - - // Emit the compound assignment op if there is one. - if (op != JSOP_NOP) { - if (!bce->emit1(op)) - return false; + } else { + uint8_t offset = noe.emittedBindOp() ? 2 : 1; + // Assumption: Things with pre-emitted RHS values never need to be named. + if (!emitAssignmentRhs(offset)) { + // [stack] ENV? VAL? RHS + return false; } + } - return true; - }; + // Emit the compound assignment op if there is one. + if (isCompound) { + if (!emit1(compoundOp)) { // ENV? VAL + return false; + } + } + if (!noe.emitAssignment()) { // VAL + return false; + } - return emitSetName(lhs, emitRhs); + return true; } + Maybe poe; + Maybe eoe; + // Deal with non-name assignments. - uint32_t atomIndex = (uint32_t) -1; uint8_t offset = 1; + RootedAtom anonFunctionName(cx); switch (lhs->getKind()) { - case ParseNodeKind::Dot: - if (lhs->as().isSuper()) { - if (!emitSuperPropLHS(&lhs->as().expression())) + case ParseNodeKind::Dot: { + PropertyAccess* prop = &lhs->as(); + bool isSuper = prop->isSuper(); + poe.emplace(this, + isCompound + ? PropOpEmitter::Kind::CompoundAssignment + : PropOpEmitter::Kind::SimpleAssignment, + isSuper + ? PropOpEmitter::ObjKind::Super + : PropOpEmitter::ObjKind::Other); + if (!poe->prepareForObj()) { + return false; + } + anonFunctionName = &prop->name(); + if (isSuper) { + UnaryNode* base = &prop->expression().as(); + if (!emitGetThisForSuperBase(base)) { // THIS SUPERBASE return false; + } + // SUPERBASE is pushed onto THIS later in poe->emitGet below. offset += 2; } else { - if (!emitTree(lhs->pn_left)) + if (!emitTree(&prop->expression())) { // OBJ return false; + } offset += 1; } - if (!makeAtomIndex(lhs->pn_right->pn_atom, &atomIndex)) - return false; break; + } case ParseNodeKind::Elem: { - MOZ_ASSERT(lhs->isArity(PN_BINARY)); - EmitElemOption opt = op == JSOP_NOP ? EmitElemOption::Get : EmitElemOption::CompoundAssign; - if (lhs->as().isSuper()) { - if (!emitSuperElemOperands(lhs, opt)) - return false; + PropertyByValue* elem = &lhs->as(); + bool isSuper = elem->isSuper(); + eoe.emplace(this, + isCompound + ? ElemOpEmitter::Kind::CompoundAssignment + : ElemOpEmitter::Kind::SimpleAssignment, + isSuper + ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other); + if (!emitElemObjAndKey(elem, isSuper, *eoe)) { // [Super] + // // THIS KEY + // // [Other] + // // OBJ KEY + return false; + } + if (isSuper) { + // SUPERBASE is pushed onto KEY in eoe->emitGet below. offset += 3; } else { - if (!emitElemOperands(lhs, opt)) - return false; offset += 2; } break; @@ -6594,42 +3992,23 @@ BytecodeEmitter::emitAssignment(ParseNode* lhs, ParseNodeKind pnk, ParseNode* rh MOZ_ASSERT(0); } - if (op != JSOP_NOP) { + if (isCompound) { MOZ_ASSERT(rhs); switch (lhs->getKind()) { case ParseNodeKind::Dot: { - JSOp getOp; - if (lhs->as().isSuper()) { - if (!emit1(JSOP_DUP2)) - return false; - getOp = JSOP_GETPROP_SUPER; - } else { - if (!emit1(JSOP_DUP)) - return false; - bool isLength = (lhs->pn_right->pn_atom == cx->names().length); - getOp = isLength ? JSOP_LENGTH : JSOP_GETPROP; - } - if (!emitIndex32(getOp, atomIndex)) + PropertyAccess* prop = &lhs->as(); + if (!poe->emitGet(prop->key().pn_atom)) { // [Super] + // // THIS SUPERBASE PROP + // // [Other] + // // OBJ PROP return false; + } break; } case ParseNodeKind::Elem: { - JSOp elemOp; - if (lhs->as().isSuper()) { - if (!emitDupAt(2)) - return false; - if (!emitDupAt(2)) - return false; - if (!emitDupAt(2)) - return false; - elemOp = JSOP_GETELEM_SUPER; - } else { - if (!emit1(JSOP_DUP2)) - return false; - elemOp = JSOP_GETELEM; - } - if (!emitElemOpBase(elemOp)) + if (!eoe->emitGet()) { // KEY THIS OBJ ELEM return false; + } break; } case ParseNodeKind::Call: @@ -6643,36 +4022,87 @@ BytecodeEmitter::emitAssignment(ParseNode* lhs, ParseNodeKind pnk, ParseNode* rh } } - if (!EmitAssignmentRhs(this, rhs, offset)) - return false; + switch (lhs->getKind()) { + case ParseNodeKind::Dot: + if (!poe->prepareForRhs()) { // [Simple,Super] + // // THIS SUPERBASE + // // [Simple,Other] + // // OBJ + // // [Compound,Super] + // // THIS SUPERBASE PROP + // // [Compound,Other] + // // OBJ PROP + return false; + } + break; + case ParseNodeKind::Elem: + if (!eoe->prepareForRhs()) { // [Simple,Super] + // // THIS KEY SUPERBASE + // // [Simple,Other] + // // OBJ KEY + // // [Compound,Super] + // // THIS KEY SUPERBASE ELEM + // // [Compound,Other] + // // OBJ KEY ELEM + return false; + } + break; + default: + break; + } + + + // Purpose of anonFunctionName: + // In normal property assignments (`obj.x = function(){}`), the anonymous + // function does not have a computed name, and rhs->isDirectRHSAnonFunction() + // will be false (and anonFunctionName will not be used). However, in field + // initializers (`class C { x = function(){} }`), field initialization is + // implemented via a property or elem assignment (where we are now), and + // rhs->isDirectRHSAnonFunction() is set - so we'll assign the name of the + // function. + if (rhs) { + if (!emitAssignmentRhs(rhs, anonFunctionName)) { + // [stack] ... VAL? RHS + return false; + } + } else { + // Assumption: Things with pre-emitted RHS values never need to be named. + if (!emitAssignmentRhs(offset)) { + // [stack] ... VAL? RHS + return false; + } + } /* If += etc., emit the binary operator with a source note. */ - if (op != JSOP_NOP) { - if (!newSrcNote(SRC_ASSIGNOP)) + if (isCompound) { + if (!newSrcNote(SRC_ASSIGNOP)) { return false; - if (!emit1(op)) + } + if (!emit1(compoundOp)) { // ... VAL return false; + } } /* Finally, emit the specialized assignment bytecode. */ switch (lhs->getKind()) { case ParseNodeKind::Dot: { - JSOp setOp = lhs->as().isSuper() ? - (sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER) : - (sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP); - if (!emitIndexOp(setOp, atomIndex)) + PropertyAccess* prop = &lhs->as(); + if (!poe->emitAssignment(prop->key().pn_atom)) { // VAL return false; + } + + poe.reset(); break; } case ParseNodeKind::Call: // We threw above, so nothing to do here. break; case ParseNodeKind::Elem: { - JSOp setOp = lhs->as().isSuper() ? - sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER : - sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM; - if (!emit1(setOp)) + if (!eoe->emitAssignment()) { // VAL return false; + } + + eoe.reset(); break; } case ParseNodeKind::Array: @@ -6837,6 +4267,243 @@ BytecodeEmitter::emitSingletonInitialiser(ParseNode* pn) return emitObjectOp(objbox, JSOP_OBJECT); } +bool BytecodeEmitter::emitShortCircuitAssignment(ParseNode* pn) { + MOZ_ASSERT(pn->isArity(PN_BINARY)); + MOZ_ASSERT(pn->isKind(ParseNodeKind::CoalesceAssignExpr) || + pn->isKind(ParseNodeKind::OrAssignExpr) || + pn->isKind(ParseNodeKind::AndAssignExpr)); + + TDZCheckCache tdzCache(this); + + JSOp op; + switch (pn->getKind()) { + case ParseNodeKind::CoalesceAssignExpr: + op = JSOP_COALESCE; + break; + case ParseNodeKind::OrAssignExpr: + op = JSOP_OR; + break; + case ParseNodeKind::AndAssignExpr: + op = JSOP_AND; + break; + default: + MOZ_CRASH("Unexpected ParseNodeKind"); + } + + ParseNode* lhs = pn->pn_left; + ParseNode* rhs = pn->pn_right; + + // |name| is used within NameOpEmitter, so its lifetime must surpass |noe|. + RootedAtom name(cx); + + // Select the appropriate emitter based on the left-hand side. + Maybe noe; + Maybe poe; + Maybe eoe; + + int32_t depth = stackDepth; + + // Number of values pushed onto the stack in addition to the lhs value. + int32_t numPushed; + + // Evaluate the left-hand side expression and compute any stack values needed + // for the assignment. + switch (lhs->getKind()) { + case ParseNodeKind::Name: { + name = lhs->as().name(); + noe.emplace(this, name, NameOpEmitter::Kind::CompoundAssignment); + + if (!noe->prepareForRhs()) { + // [stack] ENV? LHS + return false; + } + + numPushed = noe->emittedBindOp(); + break; + } + + case ParseNodeKind::Dot: { + PropertyAccess* prop = &lhs->as(); + bool isSuper = prop->isSuper(); + + poe.emplace(this, PropOpEmitter::Kind::CompoundAssignment, + isSuper ? PropOpEmitter::ObjKind::Super + : PropOpEmitter::ObjKind::Other); + + if (!poe->prepareForObj()) { + return false; + } + + if (isSuper) { + UnaryNode* base = &prop->expression().as(); + if (!emitGetThisForSuperBase(base)) { + // [stack] THIS SUPERBASE + return false; + } + } else { + if (!emitTree(&prop->expression())) { + // [stack] OBJ + return false; + } + } + + if (!poe->emitGet(prop->key().pn_atom)) { + // [stack] # if Super + // [stack] THIS SUPERBASE LHS + // [stack] # otherwise + // [stack] OBJ LHS + return false; + } + + if (!poe->prepareForRhs()) { + // [stack] # if Super + // [stack] THIS SUPERBASE LHS + // [stack] # otherwise + // [stack] OBJ LHS + return false; + } + + numPushed = 1 + isSuper; + break; + } + + case ParseNodeKind::Elem: { + PropertyByValue* elem = &lhs->as(); + bool isSuper = elem->isSuper(); + + eoe.emplace(this, ElemOpEmitter::Kind::CompoundAssignment, + isSuper ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other); + + if (!emitElemObjAndKey(elem, isSuper, *eoe)) { + // [stack] # if Super + // [stack] THIS KEY + // [stack] # otherwise + // [stack] OBJ KEY + return false; + } + + if (!eoe->emitGet()) { + // [stack] # if Super + // [stack] THIS KEY SUPERBASE LHS + // [stack] # otherwise + // [stack] OBJ KEY LHS + return false; + } + + if (!eoe->prepareForRhs()) { + // [stack] # if Super + // [stack] THIS KEY SUPERBASE LHS + // [stack] # otherwise + // [stack] OBJ KEY LHS + return false; + } + + numPushed = 2 + isSuper; + break; + } + + default: + MOZ_CRASH(); + } + + MOZ_ASSERT(stackDepth == depth + numPushed + 1); + + // Test for the short-circuit condition. + JumpList jump; + if (!emitJump(op, &jump)) { + // [stack] ... LHS + return false; + } + + // The short-circuit condition wasn't fulfilled, pop the left-hand side value + // which was kept on the stack. + if (!emit1(JSOP_POP)) { + // [stack] ... + return false; + } + + if (!emitAssignmentRhs(rhs, name)) { + // [stack] ... RHS + return false; + } + + // Perform the actual assignment. + switch (lhs->getKind()) { + case ParseNodeKind::Name: { + if (!noe->emitAssignment()) { + // [stack] RHS + return false; + } + break; + } + + case ParseNodeKind::Dot: { + PropertyAccess* prop = &lhs->as(); + + if (!poe->emitAssignment(prop->key().pn_atom)) { + // [stack] RHS + return false; + } + break; + } + + case ParseNodeKind::Elem: { + if (!eoe->emitAssignment()) { + // [stack] RHS + return false; + } + break; + } + + default: + MOZ_CRASH(); + } + + MOZ_ASSERT(stackDepth == depth + 1); + + // Join with the short-circuit jump and pop anything left on the stack. + if (numPushed > 0) { + JumpList jumpAroundPop; + if (!emitJump(JSOP_GOTO, &jumpAroundPop)) { + // [stack] RHS + return false; + } + + if (!emitJumpTargetAndPatch(jump)) { + // [stack] ... LHS + return false; + } + + // Reconstruct the stack depth after the jump. + stackDepth = depth + 1 + numPushed; + + // Move the left-hand side value to the bottom and pop the rest. + if (!emitUnpickN(numPushed)) { + // [stack] LHS ... + return false; + } + if (!emitPopN(numPushed)) { + // [stack] LHS + return false; + } + + if (!emitJumpTargetAndPatch(jumpAroundPop)) { + // [stack] LHS | RHS + return false; + } + } else { + if (!emitJumpTargetAndPatch(jump)) { + // [stack] LHS | RHS + return false; + } + } + + MOZ_ASSERT(stackDepth == depth + 1); + + return true; +} + bool BytecodeEmitter::emitCallSiteObject(ParseNode* pn) { @@ -6884,10 +4551,6 @@ BytecodeEmitter::emitCatch(ParseNode* pn) // We must be nested under a try-finally statement. TryFinallyControl& controlInfo = innermostNestableControl->as(); - /* Pick up the pending exception and bind it to the catch variable. */ - if (!emit1(JSOP_EXCEPTION)) - return false; - /* * Dup the exception object if there is a guard for rethrowing to use * it later when rethrowing or in other catches. @@ -6976,14 +4639,14 @@ BytecodeEmitter::emitTry(ParseNode* pn) TryEmitter::Kind kind; if (catchList) { if (finallyNode) - kind = TryEmitter::TryCatchFinally; + kind = TryEmitter::Kind::TryCatchFinally; else - kind = TryEmitter::TryCatch; + kind = TryEmitter::Kind::TryCatch; } else { MOZ_ASSERT(finallyNode); - kind = TryEmitter::TryFinally; + kind = TryEmitter::Kind::TryFinally; } - TryEmitter tryCatch(this, kind); + TryEmitter tryCatch(this, kind, TryEmitter::ControlKind::Syntactic); if (!tryCatch.emitTry()) return false; @@ -7049,37 +4712,41 @@ BytecodeEmitter::emitTry(ParseNode* pn) bool BytecodeEmitter::emitIf(ParseNode* pn) { - IfThenElseEmitter ifThenElse(this); + IfEmitter ifThenElse(this); if_again: /* Emit code for the condition before pushing stmtInfo. */ - if (!emitTreeInBranch(pn->pn_kid1)) + if (!emitTree(pn->pn_kid1)) return false; ParseNode* elseNode = pn->pn_kid3; if (elseNode) { - if (!ifThenElse.emitIfElse()) + if (!ifThenElse.emitThenElse()) return false; } else { - if (!ifThenElse.emitIf()) + if (!ifThenElse.emitThen()) return false; } /* Emit code for the then part. */ - if (!emitTreeInBranch(pn->pn_kid2)) + if (!emitTree(pn->pn_kid2)) return false; if (elseNode) { - if (!ifThenElse.emitElse()) - return false; - if (elseNode->isKind(ParseNodeKind::If)) { pn = elseNode; + + if (!ifThenElse.emitElseIf()) + return false; + goto if_again; } + if (!ifThenElse.emitElse()) + return false; + /* Emit code for the else part. */ - if (!emitTreeInBranch(elseNode)) + if (!emitTree(elseNode)) return false; } @@ -7280,12 +4947,12 @@ BytecodeEmitter::emitAsyncIterator() if (!emitElemOpBase(JSOP_CALLELEM)) // OBJ ITERFN return false; - IfThenElseEmitter ifAsyncIterIsUndefined(this); + InternalIfEmitter ifAsyncIterIsUndefined(this); if (!emitPushNotUndefinedOrNull()) // OBJ ITERFN !UNDEF-OR-NULL return false; if (!emit1(JSOP_NOT)) // OBJ ITERFN UNDEF-OR-NULL return false; - if (!ifAsyncIterIsUndefined.emitIfElse()) // OBJ ITERFN + if (!ifAsyncIterIsUndefined.emitThenElse()) // OBJ ITERFN return false; if (!emit1(JSOP_POP)) // OBJ @@ -7424,7 +5091,7 @@ BytecodeEmitter::emitInitializeForInOrOfTarget(ParseNode* forHead) // initialization is just assigning the iteration value to a target // expression. if (!parser.isDeclarationList(target)) - return emitAssignment(target, ParseNodeKind::Assign, nullptr); // ... ITERVAL + return emitAssignment(target, JSOP_NOP, nullptr); // ... ITERVAL // Otherwise, per-loop initialization is (possibly) declaration // initialization. If the declaration is a lexical declaration, it must be @@ -7439,26 +5106,30 @@ BytecodeEmitter::emitInitializeForInOrOfTarget(ParseNode* forHead) target = parser.singleBindingFromDeclaration(target); if (target->isKind(ParseNodeKind::Name)) { - auto emitSwapScopeAndRhs = [](BytecodeEmitter* bce, const NameLocation&, - bool emittedBindOp) - { - if (emittedBindOp) { - // Per-iteration initialization in for-in/of loops computes the - // iteration value *before* initializing. Thus the - // initializing value may be buried under a bind-specific value - // on the stack. Swap it to the top of the stack. - MOZ_ASSERT(bce->stackDepth >= 2); - return bce->emit1(JSOP_SWAP); + NameOpEmitter noe(this, target->name(), NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + return false; + } + if (noe.emittedBindOp()) { + // Per-iteration initialization in for-in/of loops computes the + // iteration value *before* initializing. Thus the initializing + // value may be buried under a bind-specific value on the stack. + // Swap it to the top of the stack. + MOZ_ASSERT(stackDepth >= 2); + if (!emit1(JSOP_SWAP)) { + return false; } - - // In cases of emitting a frame slot or environment slot, - // nothing needs be done. - MOZ_ASSERT(bce->stackDepth >= 1); - return true; - }; + } else { + // In cases of emitting a frame slot or environment slot, + // nothing needs be done. + MOZ_ASSERT(stackDepth >= 1); + } + if (!noe.emitAssignment()) { + return false; + } // The caller handles removing the iteration value from the stack. - return emitInitializeName(target, emitSwapScopeAndRhs); + return true; } MOZ_ASSERT(!target->isKind(ParseNodeKind::Assign), @@ -7581,9 +5252,9 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE return false; - IfThenElseEmitter ifDone(this); + InternalIfEmitter ifDone(this); - if (!ifDone.emitIf()) // ITER RESULT + if (!ifDone.emitThen()) // ITER RESULT return false; // Remove RESULT from the stack to release it. @@ -7685,16 +5356,21 @@ BytecodeEmitter::emitForIn(ParseNode* forInLoop, EmitterScope* headLexicalEmitte if (!updateSourceCoordNotes(decl->pn_pos.begin)) return false; - auto emitRhs = [decl, initializer](BytecodeEmitter* bce, const NameLocation&, bool) { - return bce->emitInitializer(initializer, decl); - }; - - if (!emitInitializeName(decl, emitRhs)) + NameOpEmitter noe(this, decl->name(), NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + return false; + } + if (!emitInitializer(initializer, decl)) { + return false; + } + if (!noe.emitAssignment()) { return false; + } // Pop the initializer. - if (!emit1(JSOP_POP)) + if (!emit1(JSOP_POP)) { return false; + } } } } @@ -8127,9 +5803,9 @@ BytecodeEmitter::emitComprehensionForOf(ParseNode* pn) if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE return false; - IfThenElseEmitter ifDone(this); + InternalIfEmitter ifDone(this); - if (!ifDone.emitIf()) // ITER RESULT + if (!ifDone.emitThen()) // ITER RESULT return false; // Remove RESULT from the stack to release it. @@ -8152,7 +5828,7 @@ BytecodeEmitter::emitComprehensionForOf(ParseNode* pn) // Notice: Comprehension for-of doesn't perform IteratorClose, since it's // not in the spec. - if (!emitAssignment(loopVariableName,ParseNodeKind::Assign, nullptr)) // ITER VALUE + if (!emitAssignment(loopVariableName, JSOP_NOP, nullptr)) // ITER VALUE return false; // Remove VALUE from the stack to release it. @@ -8276,7 +5952,7 @@ BytecodeEmitter::emitComprehensionForIn(ParseNode* pn) // Emit code to assign the enumeration value to the left hand side, but // also leave it on the stack. - if (!emitAssignment(forHead->pn_kid2,ParseNodeKind::Assign, nullptr)) + if (!emitAssignment(forHead->pn_kid2, JSOP_NOP, nullptr)) return false; /* The stack should be balanced around the assignment opcode sequence. */ @@ -8361,12 +6037,6 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) // definitions are seen for the second time, we need to emit the // assignment that assigns the function to the outer 'var' binding. if (funbox->isAnnexB) { - auto emitRhs = [&name](BytecodeEmitter* bce, const NameLocation&, bool) { - // The RHS is the value of the lexically bound name in the - // innermost scope. - return bce->emitGetName(name); - }; - // Get the location of the 'var' binding in the body scope. The // name must be found, else there is a bug in the Annex B handling // in Parser. @@ -8388,10 +6058,19 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) sc->asFunctionBox()->hasParameterExprs)); } - if (!emitSetOrInitializeNameAtLocation(name, *lhsLoc, emitRhs, false)) + NameOpEmitter noe(this, name, *lhsLoc, NameOpEmitter::Kind::SimpleAssignment); + if (!noe.prepareForRhs()) { return false; - if (!emit1(JSOP_POP)) + } + if (!emitGetName(name)) { + return false; + } + if (!noe.emitAssignment()) { + return false; + } + if (!emit1(JSOP_POP)) { return false; + } } MOZ_ASSERT_IF(fun->hasScript(), fun->nonLazyScript()); @@ -8547,21 +6226,27 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) // For functions nested within functions and blocks, make a lambda and // initialize the binding name of the function in the current scope. - bool isAsync = funbox->isAsync(); - bool isStarGenerator = funbox->isStarGenerator(); - auto emitLambda = [index, isAsync, isStarGenerator](BytecodeEmitter* bce, - const NameLocation&, bool) { - if (isAsync) { - return bce->emitAsyncWrapper(index, /* needsHomeObject = */ false, - /* isArrow = */ false, isStarGenerator); + NameOpEmitter noe(this, name, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + return false; + } + if (funbox->isAsync()) { + if (!emitAsyncWrapper(index, /* needsHomeObject = */ false, + /* isArrow = */ false, funbox->isStarGenerator())) + { + return false; } - return bce->emitIndexOp(JSOP_LAMBDA, index); - }; - - if (!emitInitializeName(name, emitLambda)) + } else { + if (!emitIndexOp(JSOP_LAMBDA, index)) { + return false; + } + } + if (!noe.emitAssignment()) { return false; - if (!emit1(JSOP_POP)) + } + if (!emit1(JSOP_POP)) { return false; + } } return true; @@ -8605,7 +6290,8 @@ BytecodeEmitter::emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isA // // classObj classCtor classProto // (emitted code) // classObj classCtor classProto unwrapped wrapped // swap // classObj classCtor classProto wrapped unwrapped - // inithomeobject 1 // classObj classCtor classProto wrapped unwrapped + // dupat 2 // classObj classCtor classProto wrapped unwrapped classProto + // inithomeobject // classObj classCtor classProto wrapped unwrapped // // initialize the home object of unwrapped // // with classProto here // pop // classObj classCtor classProto wrapped @@ -8816,10 +6502,26 @@ BytecodeEmitter::emitGetFunctionThis(ParseNode* pn) MOZ_ASSERT(pn->isKind(ParseNodeKind::Name)); MOZ_ASSERT(pn->name() == cx->names().dotThis); - if (!emitTree(pn)) - return false; - if (sc->needsThisTDZChecks() && !emit1(JSOP_CHECKTHIS)) + return emitGetFunctionThis(Some(pn->pn_pos.begin)); +} + +bool +BytecodeEmitter::emitGetFunctionThis(const mozilla::Maybe& offset) +{ + if (offset) { + if (!updateLineNumberNotes(*offset)) { + return false; + } + } + + if (!emitGetName(cx->names().dotThis)) { // THIS return false; + } + if (sc->needsThisTDZChecks()) { + if (!emit1(JSOP_CHECKTHIS)) { // THIS + return false; + } + } return true; } @@ -8828,7 +6530,7 @@ bool BytecodeEmitter::emitGetThisForSuperBase(ParseNode* pn) { MOZ_ASSERT(pn->isKind(ParseNodeKind::SuperBase)); - return emitGetFunctionThis(pn->pn_kid); + return emitGetFunctionThis(pn->pn_kid); // THIS } bool @@ -8836,14 +6538,16 @@ BytecodeEmitter::emitThisLiteral(ParseNode* pn) { MOZ_ASSERT(pn->isKind(ParseNodeKind::This)); - if (ParseNode* thisName = pn->pn_kid) - return emitGetFunctionThis(thisName); + if (ParseNode* thisName = pn->pn_kid) { + return emitGetFunctionThis(thisName); // THIS + } - if (sc->thisBinding() == ThisBinding::Module) - return emit1(JSOP_UNDEFINED); + if (sc->thisBinding() == ThisBinding::Module) { + return emit1(JSOP_UNDEFINED); // UNDEF + } MOZ_ASSERT(sc->thisBinding() == ThisBinding::Global); - return emit1(JSOP_GLOBALTHIS); + return emit1(JSOP_GLOBALTHIS); // THIS } bool @@ -9061,8 +6765,8 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter) int32_t startDepth = stackDepth; MOZ_ASSERT(startDepth >= 2); - TryEmitter tryCatch(this, TryEmitter::TryCatchFinally, TryEmitter::DontUseRetVal, - TryEmitter::DontUseControl); + TryEmitter tryCatch(this, TryEmitter::Kind::TryCatchFinally, + TryEmitter::ControlKind::NonSyntactic); if (!tryCatch.emitJumpOverCatchAndFinally()) // ITER RESULT return false; @@ -9086,12 +6790,12 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter) if (!emitYieldOp(JSOP_YIELD)) // ITER RECEIVED return false; - if (!tryCatch.emitCatch()) // ITER RESULT + if (!tryCatch.emitCatch()) // ITER RESULT EXCEPTION return false; - stackDepth = startDepth; // ITER RESULT - if (!emit1(JSOP_EXCEPTION)) // ITER RESULT EXCEPTION - return false; + // The exception was already pushed by emitCatch(). + MOZ_ASSERT(stackDepth == startDepth + 1); + if (!emitDupAt(2)) // ITER RESULT EXCEPTION ITER return false; if (!emit1(JSOP_DUP)) // ITER RESULT EXCEPTION ITER ITER @@ -9105,8 +6809,8 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter) if (!emit1(JSOP_EQ)) // ITER RESULT EXCEPTION ITER THROW ?EQL return false; - IfThenElseEmitter ifThrowMethodIsNotDefined(this); - if (!ifThrowMethodIsNotDefined.emitIf()) // ITER RESULT EXCEPTION ITER THROW + InternalIfEmitter ifThrowMethodIsNotDefined(this); + if (!ifThrowMethodIsNotDefined.emitThen()) // ITER RESULT EXCEPTION ITER THROW return false; savedDepthTemp = stackDepth; if (!emit1(JSOP_POP)) // ITER RESULT EXCEPTION ITER @@ -9161,10 +6865,10 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter) // Call iterator.return() for receiving a "forced return" completion from // the generator. - IfThenElseEmitter ifGeneratorClosing(this); + InternalIfEmitter ifGeneratorClosing(this); if (!emit1(JSOP_ISGENCLOSING)) // ITER RESULT FTYPE FVALUE CLOSING return false; - if (!ifGeneratorClosing.emitIf()) // ITER RESULT FTYPE FVALUE + if (!ifGeneratorClosing.emitThen()) // ITER RESULT FTYPE FVALUE return false; // Step ii. @@ -9180,7 +6884,7 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter) // Step iii. // // Do nothing if "return" is undefined or null. - IfThenElseEmitter ifReturnMethodIsDefined(this); + InternalIfEmitter ifReturnMethodIsDefined(this); if (!emitPushNotUndefinedOrNull()) // ITER RESULT FTYPE FVALUE ITER RET NOT-UNDEF-OR-NULL return false; @@ -9188,7 +6892,7 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter) // // Call "return" with the argument passed to Generator.prototype.return, // which is currently in rval.value. - if (!ifReturnMethodIsDefined.emitIfElse()) // ITER OLDRESULT FTYPE FVALUE ITER RET + if (!ifReturnMethodIsDefined.emitThenElse()) // ITER OLDRESULT FTYPE FVALUE ITER RET return false; if (!emit1(JSOP_SWAP)) // ITER OLDRESULT FTYPE FVALUE RET ITER return false; @@ -9213,12 +6917,12 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter) // // Check if the returned object from iterator.return() is done. If not, // continuing yielding. - IfThenElseEmitter ifReturnDone(this); + InternalIfEmitter ifReturnDone(this); if (!emit1(JSOP_DUP)) // ITER OLDRESULT FTYPE FVALUE RESULT RESULT return false; if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER OLDRESULT FTYPE FVALUE RESULT DONE return false; - if (!ifReturnDone.emitIfElse()) // ITER OLDRESULT FTYPE FVALUE RESULT + if (!ifReturnDone.emitThenElse()) // ITER OLDRESULT FTYPE FVALUE RESULT return false; if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER OLDRESULT FTYPE FVALUE VALUE return false; @@ -9334,9 +7038,6 @@ BytecodeEmitter::emitExpressionStatement(ParseNode* pn) { MOZ_ASSERT(pn->isKind(ParseNodeKind::ExpressionStatement)); - if (!updateSourceCoordNotes(pn->pn_pos.begin)) - return false; - /* * Top-level or called-from-a-native JS_Execute/EvaluateScript, * debugger, and eval frames may need the value of the ultimate @@ -9373,13 +7074,18 @@ BytecodeEmitter::emitExpressionStatement(ParseNode* pn) } if (useful) { - JSOp op = wantval ? JSOP_SETRVAL : JSOP_POP; - ValueUsage valueUsage = wantval ? ValueUsage::WantValue : ValueUsage::IgnoreValue; MOZ_ASSERT_IF(expr->isKind(ParseNodeKind::Assign), expr->isOp(JSOP_NOP)); - if (!emitTree(expr, valueUsage)) + ValueUsage valueUsage = wantval ? ValueUsage::WantValue : ValueUsage::IgnoreValue; + ExpressionStatementEmitter ese(this, valueUsage); + if (!ese.prepareForExpr(Some(pn->pn_pos.begin))) { return false; - if (!emit1(op)) + } + if (!emitTree(expr, valueUsage)) { + return false; + } + if (!ese.emitEnd()) { return false; + } } else if (pn->isDirectivePrologueMember()) { // Don't complain about directive prologue members; just don't emit // their code. @@ -9423,7 +7129,7 @@ BytecodeEmitter::emitDeleteName(ParseNode* node) ParseNode* nameExpr = node->pn_kid; MOZ_ASSERT(nameExpr->isKind(ParseNodeKind::Name)); - return emitAtomOp(nameExpr, JSOP_DELNAME); + return emitAtomOp(nameExpr->pn_atom, JSOP_DELNAME); } bool @@ -9432,21 +7138,38 @@ BytecodeEmitter::emitDeleteProperty(ParseNode* node) MOZ_ASSERT(node->isKind(ParseNodeKind::DeleteProp)); MOZ_ASSERT(node->isArity(PN_UNARY)); - ParseNode* propExpr = node->pn_kid; - MOZ_ASSERT(propExpr->isKind(ParseNodeKind::Dot)); - - if (propExpr->as().isSuper()) { - // Still have to calculate the base, even though we are are going - // to throw unconditionally, as calculating the base could also - // throw. - if (!emit1(JSOP_SUPERBASE)) + PropertyAccess* propExpr = &node->pn_kid->as(); + PropOpEmitter poe(this, + PropOpEmitter::Kind::Delete, + propExpr->as().isSuper() + ? PropOpEmitter::ObjKind::Super + : PropOpEmitter::ObjKind::Other); + if (propExpr->isSuper()) { + // The expression |delete super.foo;| has to evaluate |super.foo|, + // which could throw if |this| hasn't yet been set by a |super(...)| + // call or the super-base is not an object, before throwing a + // ReferenceError for attempting to delete a super-reference. + UnaryNode* base = &propExpr->expression().as(); + if (!emitGetThisForSuperBase(base)) { // THIS + return false; + } + } else { + if (!poe.prepareForObj()) { + return false; + } + if (!emitPropLHS(propExpr)) { // OBJ return false; + } + } - return emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER); + if (!poe.emitDelete(propExpr->key().pn_atom)) { // [Super] + // // THIS + // // [Other] + // // SUCCEEDED + return false; } - JSOp delOp = sc->strict() ? JSOP_STRICTDELPROP : JSOP_DELPROP; - return emitPropOp(propExpr, delOp); + return true; } bool @@ -9455,27 +7178,46 @@ BytecodeEmitter::emitDeleteElement(ParseNode* node) MOZ_ASSERT(node->isKind(ParseNodeKind::DeleteElem)); MOZ_ASSERT(node->isArity(PN_UNARY)); - ParseNode* elemExpr = node->pn_kid; - MOZ_ASSERT(elemExpr->isKind(ParseNodeKind::Elem)); - - if (elemExpr->as().isSuper()) { - // Still have to calculate everything, even though we're gonna throw - // since it may have side effects - if (!emitTree(elemExpr->pn_right)) + PropertyByValue* elemExpr = &node->pn_kid->as(); + bool isSuper = elemExpr->isSuper(); + ElemOpEmitter eoe(this, + ElemOpEmitter::Kind::Delete, + isSuper + ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other); + if (isSuper) { + // The expression |delete super[foo];| has to evaluate |super[foo]|, + // which could throw if |this| hasn't yet been set by a |super(...)| + // call, or trigger side-effects when evaluating ToPropertyKey(foo), + // or also throw when the super-base is not an object, before throwing + // a ReferenceError for attempting to delete a super-reference. + if (!eoe.prepareForObj()) { // return false; + } - if (!emit1(JSOP_SUPERBASE)) + UnaryNode* base = &elemExpr->expression().as(); + if (!emitGetThisForSuperBase(base)) { // THIS return false; - if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER)) + } + if (!eoe.prepareForKey()) { // THIS return false; - - // Another wrinkle: Balance the stack from the emitter's point of view. - // Execution will not reach here, as the last bytecode threw. - return emit1(JSOP_POP); + } + if (!emitTree(&elemExpr->key())) { // THIS KEY + return false; + } + } else { + if (!emitElemObjAndKey(elemExpr, false, eoe)) { // OBJ KEY + return false; + } + } + if (!eoe.emitDelete()) { // [Super] + // // THIS + // // [Other] + // // SUCCEEDED + return false; } - JSOp delOp = sc->strict() ? JSOP_STRICTDELELEM : JSOP_DELELEM; - return emitElemOp(elemExpr, delOp); + return true; } bool @@ -9541,39 +7283,78 @@ bool BytecodeEmitter::emitDeletePropertyInOptChain(PropertyAccessBase* propExpr, OptionalEmitter& oe) { MOZ_ASSERT_IF(propExpr->is(), !propExpr->as().isSuper()); + PropOpEmitter poe(this, PropOpEmitter::Kind::Delete, + PropOpEmitter::ObjKind::Other); - if (!emitOptionalTree(&propExpr->expression(), oe)) + if (!poe.prepareForObj()) { + // [stack] + return false; + } + if (!emitOptionalTree(&propExpr->expression(), oe)) { + // [stack] OBJ return false; + } if (propExpr->isKind(ParseNodeKind::OptionalDot)) { - if (!oe.emitJumpShortCircuit()) + if (!oe.emitJumpShortCircuit()) { + // [stack] # if Jump + // [stack] UNDEFINED-OR-NULL + // [stack] # otherwise + // [stack] OBJ return false; + } } - // POE.emitDelete, !isSuper - JSOp delOp = sc->strict() ? JSOP_STRICTDELPROP : JSOP_DELPROP; - return emitAtomOp(propExpr->pn_right, delOp); + if (!poe.emitDelete(propExpr->key().pn_atom)) { + // [stack] SUCCEEDED + return false; + } + return true; } bool BytecodeEmitter::emitDeleteElementInOptChain(PropertyByValueBase* elemExpr, OptionalEmitter& oe) { MOZ_ASSERT_IF(elemExpr->is(), !elemExpr->as().isSuper()); + ElemOpEmitter eoe(this, ElemOpEmitter::Kind::Delete, + ElemOpEmitter::ObjKind::Other); + + if (!eoe.prepareForObj()) { + // [stack] + return false; + } - if (!emitOptionalTree(&elemExpr->expression(), oe)) + if (!emitOptionalTree(&elemExpr->expression(), oe)) { + // [stack] OBJ return false; + } if (elemExpr->isKind(ParseNodeKind::OptionalElem)) { - if (!oe.emitJumpShortCircuit()) + if (!oe.emitJumpShortCircuit()) { + // [stack] # if Jump + // [stack] UNDEFINED-OR-NULL + // [stack] # otherwise + // [stack] OBJ return false; + } + } + + if (!eoe.prepareForKey()) { + // [stack] OBJ + return false; + } + + if (!emitTree(&elemExpr->key())) { + // [stack] OBJ KEY + return false; } - if (!emitTree(elemExpr->pn_right)) + if (!eoe.emitDelete()) { + // [stack] SUCCEEDED return false; + } - // EOE.emitDelete, !isSuper - JSOp delOp = sc->strict() ? JSOP_STRICTDELELEM : JSOP_DELELEM; - return emitElemOpBase(delOp); + return true; } static const char * @@ -9769,6 +7550,32 @@ BytecodeEmitter::emitSelfHostedHasOwn(ParseNode* pn) return emit1(JSOP_HASOWN); } +bool +BytecodeEmitter::emitSelfHostedGetPropertySuper(ParseNode* pn) +{ + ParseNode* pn_args = pn->pn_right; + + if (pn_args->pn_count != 3) { + reportError(pn, JSMSG_MORE_ARGS_NEEDED, "getPropertySuper", "3", ""); + return false; + } + + ParseNode* objNode = pn_args->pn_head; + ParseNode* idNode = objNode->pn_next; + ParseNode* receiverNode = idNode->pn_next; + + if (!emitTree(receiverNode)) + return false; + + if (!emitTree(idNode)) + return false; + + if (!emitTree(objNode)) + return false; + + return emitElemOpBase(JSOP_GETELEM_SUPER); +} + bool BytecodeEmitter::isRestParameter(ParseNode* pn) { @@ -9815,19 +7622,21 @@ BytecodeEmitter::isRestParameter(ParseNode* pn) * * See emitCallOrNew and emitOptionalCall for more context. */ -bool BytecodeEmitter::emitOptionalCalleeAndThis(ParseNode* callee, ParseNode* call, - bool isCall, bool isNew, +bool BytecodeEmitter::emitOptionalCalleeAndThis(ParseNode* callee, + ParseNode* call, + CallOrNewEmitter& cone, OptionalEmitter& oe) { if (!CheckRecursionLimit(cx)) { return false; } - bool needsThis = !isCall; - switch (ParseNodeKind kind = callee->getKind()) { case ParseNodeKind::Name: { - if (!emitGetName(callee, isCall)) + RootedAtom nameAtom(cx, callee->as().name()); + if (!cone.emitNameCallee(nameAtom)) { + // [stack] CALLEE THIS return false; + } break; } @@ -9836,8 +7645,11 @@ bool BytecodeEmitter::emitOptionalCalleeAndThis(ParseNode* callee, ParseNode* ca OptionalPropertyAccess* prop = &callee->as(); bool isSuper = false; - if (!emitOptionalDotExpression(prop, isCall, isSuper, oe)) + PropOpEmitter& poe = cone.prepareForPropCallee(isSuper); + if (!emitOptionalDotExpression(prop, poe, isSuper, oe)) { + // [stack] CALLEE THIS return false; + } break; } case ParseNodeKind::Dot: { @@ -9845,8 +7657,11 @@ bool BytecodeEmitter::emitOptionalCalleeAndThis(ParseNode* callee, ParseNode* ca PropertyAccess* prop = &callee->as(); bool isSuper = prop->isSuper(); - if (!emitOptionalDotExpression(prop, isCall, isSuper, oe)) + PropOpEmitter& poe = cone.prepareForPropCallee(isSuper); + if (!emitOptionalDotExpression(prop, poe, isSuper, oe)) { + // [stack] CALLEE THIS return false; + } break; } @@ -9854,146 +7669,143 @@ bool BytecodeEmitter::emitOptionalCalleeAndThis(ParseNode* callee, ParseNode* ca OptionalPropertyByValue* elem = &callee->as(); bool isSuper = false; - if (!emitOptionalElemExpression(elem, isCall, isSuper, oe)) + ElemOpEmitter& eoe = cone.prepareForElemCallee(isSuper); + if (!emitOptionalElemExpression(elem, eoe, isSuper, oe)) { + // [stack] CALLEE THIS return false; + } break; } case ParseNodeKind::Elem: { PropertyByValue* elem = &callee->as(); bool isSuper = elem->isSuper(); - if (!emitOptionalElemExpression(elem, isCall, isSuper, oe)) + ElemOpEmitter& eoe = cone.prepareForElemCallee(isSuper); + if (!emitOptionalElemExpression(elem, eoe, isSuper, oe)) { + // [stack] CALLEE THIS return false; + } break; } case ParseNodeKind::Function: - MOZ_ASSERT(!emittingRunOnceLambda); - if (checkRunOnceContext()) { - emittingRunOnceLambda = true; - if (!emitOptionalTree(callee, oe)) - return false; - emittingRunOnceLambda = false; - } else { - if (!emitOptionalTree(callee, oe)) - return false; + if (!cone.prepareForFunctionCallee()) { + return false; + } + if (!emitOptionalTree(callee, oe)) { + // [stack] CALLEE + return false; } - needsThis = true; break; + case ParseNodeKind::OptionalChain: { - return emitCalleeAndThisForOptionalChain(callee, call, isCall, isNew); - break; + return emitCalleeAndThisForOptionalChain(callee, call, + cone); } default: MOZ_RELEASE_ASSERT(kind != ParseNodeKind::SuperBase); - if (!emitOptionalTree(callee, oe)) - return false; - - needsThis = true; - break; - } - - if (needsThis) { - if (isNew) { - if (!emit1(JSOP_IS_CONSTRUCTING)) { + if (!cone.prepareForOtherCallee()) { return false; } - } else { - if (!emit1(JSOP_UNDEFINED)) { + if (!emitOptionalTree(callee, oe)) { + // [stack] CALLEE return false; } - } + break; + } + + if (!cone.emitThis()) { + // [stack] CALLEE THIS + return false; } return true; } bool -BytecodeEmitter::emitCalleeAndThis(ParseNode* callee, ParseNode* call, bool isCall, bool isNew) +BytecodeEmitter::emitCalleeAndThis(ParseNode* callee, ParseNode* call, CallOrNewEmitter& cone) { - bool needsThis = !isCall; switch (callee->getKind()) { case ParseNodeKind::Name: - if (!emitGetName(callee, isCall)) + if (!cone.emitNameCallee(callee->name())) { // CALLEE THIS return false; + } break; - case ParseNodeKind::Dot: + case ParseNodeKind::Dot: { MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); - if (callee->as().isSuper()) { - if (!emitSuperPropOp(callee, JSOP_GETPROP_SUPER, isCall)) + PropertyAccess* prop = &callee->as(); + bool isSuper = prop->isSuper(); + + PropOpEmitter& poe = cone.prepareForPropCallee(isSuper); + if (!poe.prepareForObj()) { + return false; + } + if (isSuper) { + UnaryNode* base = &prop->expression().as(); + if (!emitGetThisForSuperBase(base)) { // THIS return false; + } } else { - if (!emitPropOp(callee, isCall ? JSOP_CALLPROP : JSOP_GETPROP)) + if (!emitPropLHS(prop)) { // OBJ return false; + } + } + if (!poe.emitGet(prop->key().pn_atom)) { // CALLEE THIS? + return false; } break; - case ParseNodeKind::Elem: + } + case ParseNodeKind::Elem: { MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); - if (callee->as().isSuper()) { - if (!emitSuperElemOp(callee, JSOP_GETELEM_SUPER, isCall)) - return false; - } else { - if (!emitElemOp(callee, isCall ? JSOP_CALLELEM : JSOP_GETELEM)) - return false; - if (isCall) { - if (!emit1(JSOP_SWAP)) - return false; - } + PropertyByValue* elem = &callee->as(); + bool isSuper = elem->isSuper(); + + ElemOpEmitter& eoe = cone.prepareForElemCallee(isSuper); + if (!emitElemObjAndKey(elem, isSuper, eoe)) { // [Super] + // // THIS? THIS KEY + // // [needsThis,Other] + // // OBJ? OBJ KEY + return false; + } + if (!eoe.emitGet()) { // CALLEE? THIS + return false; } break; + } case ParseNodeKind::Function: - /* - * Top level lambdas which are immediately invoked should be - * treated as only running once. Every time they execute we will - * create new types and scripts for their contents, to increase - * the quality of type information within them and enable more - * backend optimizations. Note that this does not depend on the - * lambda being invoked at most once (it may be named or be - * accessed via foo.caller indirection), as multiple executions - * will just cause the inner scripts to be repeatedly cloned. - */ - MOZ_ASSERT(!emittingRunOnceLambda); - if (checkRunOnceContext()) { - emittingRunOnceLambda = true; - if (!emitTree(callee)) - return false; - emittingRunOnceLambda = false; - } else { - if (!emitTree(callee)) - return false; + if (!cone.prepareForFunctionCallee()) { + return false; + } + if (!emitTree(callee)) { // CALLEE + return false; } - needsThis = true; break; case ParseNodeKind::SuperBase: MOZ_ASSERT(call->isKind(ParseNodeKind::SuperCall)); MOZ_ASSERT(parser.isSuperBase(callee)); - if (!emit1(JSOP_SUPERFUN)) + if (!cone.emitSuperCallee()) { // CALLEE THIS return false; + } break; case ParseNodeKind::OptionalChain: - return emitCalleeAndThisForOptionalChain(callee, call, isCall, isNew); + return emitCalleeAndThisForOptionalChain(callee, call, cone); break; default: - if (!emitTree(callee)) + if (!cone.prepareForOtherCallee()) { + return false; + } + if (!emitTree(callee)) { return false; - needsThis = true; + } break; } - if (needsThis) { - if (isNew) { - if (!emit1(JSOP_IS_CONSTRUCTING)) { - return false; - } - } else { - if (!emit1(JSOP_UNDEFINED)) { - return false; - } - } + if (!cone.emitThis()) { // CALLEE THIS + return false; } return true; @@ -10005,21 +7817,26 @@ BytecodeEmitter::emitPipeline(ParseNode* pn) MOZ_ASSERT(pn->isArity(PN_LIST)); MOZ_ASSERT(pn->pn_count >= 2); - if (!emitTree(pn->pn_head)) + if (!emitTree(pn->pn_head)) { // ARG return false; + } ParseNode* callee = pn->pn_head->pn_next; - + CallOrNewEmitter cone(this, JSOP_CALL, + CallOrNewEmitter::ArgumentsKind::Other, + ValueUsage::WantValue); do { - if (!emitCalleeAndThis(callee, pn, true, false)) { + if (!emitCalleeAndThis(callee, pn, cone)) { // ARG CALLEE THIS return false; } - - if (!emit2(JSOP_PICK, 2)) + if (!emit2(JSOP_PICK, 2)) { // CALLEE THIS ARG return false; - - if (!emitCall(JSOP_CALL, 1, pn)) + } + if (!cone.emitEnd(1, Some(pn->pn_pos.begin))) { // RVAL return false; + } + + cone.reset(); checkTypeSet(JSOP_CALL); } while ((callee = callee->pn_next)); @@ -10064,60 +7881,35 @@ ParseNode* BytecodeEmitter::getCoordNode(ParseNode* pn, } bool -BytecodeEmitter::emitArguments(ParseNode* pn, bool callop, bool spread) +BytecodeEmitter::emitArguments(ListNode* argsList, bool isCall, bool isSpread, + CallOrNewEmitter& cone) { - uint32_t argc = pn->pn_count; - + uint32_t argc = argsList->pn_count; if (argc >= ARGC_LIMIT) { - parser.reportError(callop ? JSMSG_TOO_MANY_FUN_ARGS : JSMSG_TOO_MANY_CON_ARGS); + reportError(argsList, isCall ? JSMSG_TOO_MANY_FUN_ARGS : JSMSG_TOO_MANY_CON_ARGS); return false; } - - if (!spread) { - for (ParseNode* pn3 = pn->pn_head; pn3; pn3 = pn3->pn_next) { - if (!emitTree(pn3)) + if (!isSpread) { + if (!cone.prepareForNonSpreadArguments()) { // CALLEE THIS + return false; + } + for (ParseNode* arg = argsList->pn_head; arg; arg = arg->pn_next) { + if (!emitTree(arg)) { return false; + } } } else { - ParseNode* args = pn->pn_head; - bool emitOptCode = (argc == 1) && isRestParameter(args->pn_kid); - IfThenElseEmitter ifNotOptimizable(this); - - if (emitOptCode) { - // Emit a preparation code to optimize the spread call with a rest - // parameter: - // - // function f(...args) { - // g(...args); - // } - // - // If the spread operand is a rest parameter and it's optimizable - // array, skip spread operation and pass it directly to spread call - // operation. See the comment in OptimizeSpreadCall in - // Interpreter.cpp for the optimizable conditons. - - if (!emitTree(args->pn_kid)) - return false; - - if (!emit1(JSOP_OPTIMIZE_SPREADCALL)) - return false; - - if (!emit1(JSOP_NOT)) - return false; - - if (!ifNotOptimizable.emitIf()) - return false; - - if (!emit1(JSOP_POP)) + if (cone.wantSpreadOperand()) { + UnaryNode* spreadNode = &argsList->pn_head->as(); + if (!emitTree(spreadNode->pn_kid)) { // CALLEE THIS ARG0 return false; + } } - - if (!emitArray(args, argc)) + if (!cone.emitSpreadArgumentsTest()) { // CALLEE THIS + return false; + } + if (!emitArray(argsList->pn_head, argc)) { // CALLEE THIS ARR return false; - - if (emitOptCode) { - if (!ifNotOptimizable.emitEnd()) - return false; } } @@ -10137,67 +7929,42 @@ bool BytecodeEmitter::emitOptionalCall(BinaryNode* node, OptionalEmitter& oe, * * See CallOrNewEmitter for more context. */ - ParseNode* pn_callee = node->pn_left; - ParseNode* pn_args = node->pn_right; + ParseNode* calleeNode = node->pn_left; + ListNode* argsList = &node->pn_right->as(); bool isSpread = JOF_OPTYPE(node->getOp()) == JOF_BYTE; - JSOp op = node->getOp(); + uint32_t argc = argsList->pn_count; - bool isNewOp = op == JSOP_NEW || op == JSOP_SPREADNEW || - op == JSOP_SUPERCALL || op == JSOP_SPREADSUPERCALL; + CallOrNewEmitter cone( + this, op, + isSpread && (argc == 1) && + isRestParameter(argsList->pn_head->as().pn_kid) + ? CallOrNewEmitter::ArgumentsKind::SingleSpreadRest + : CallOrNewEmitter::ArgumentsKind::Other, + valueUsage); - if (!emitOptionalCalleeAndThis(pn_callee, node, /* isCall = */ true, isNewOp, oe)) + ParseNode* coordNode = getCoordNode(node, calleeNode, argsList); + + if (!emitOptionalCalleeAndThis(calleeNode, node, cone, oe)) { + // [stack] CALLEE THIS return false; + } if (node->isKind(ParseNodeKind::OptionalCall)) { - if (!oe.emitJumpShortCircuitForCall()) + if (!oe.emitJumpShortCircuitForCall()) { + // [stack] CALLEE THIS return false; + } } - if (!emitArguments(pn_args, /* isCall = */ true, isSpread)) + if (!emitArguments(argsList, /* isCall = */ true, isSpread, cone)) { + // [stack] CALLEE THIS ARGS... return false; - - uint32_t argc = pn_args->pn_count; - - if (isNewOp) { - if (!isSpread) { - // Repush the callee as new.target - if (!emitDupAt(argc + 1)) - return false; - } else { - if (!emitDupAt(2)) - return false; - } } - ParseNode* coordNode = getCoordNode(node, pn_callee, pn_args); - - if (!isSpread) { - if (op == JSOP_CALL && valueUsage == ValueUsage::IgnoreValue) { - if (!emitCall(JSOP_CALL_IGNORES_RV, argc, coordNode)) - return false; - checkTypeSet(JSOP_CALL_IGNORES_RV); - } else { - if (!emitCall(op, argc, coordNode)) - return false; - checkTypeSet(op); - } - } else { - if (coordNode) { - if (!updateSourceCoordNotes(coordNode->pn_pos.begin)) - return false; - } - - if (!emit1(op)) - return false; - checkTypeSet(op); - } - if (op == JSOP_EVAL || op == JSOP_STRICTEVAL || - op == JSOP_SPREADEVAL || op == JSOP_STRICTSPREADEVAL) - { - uint32_t lineNum = parser.tokenStream().srcCoords.lineNum(node->pn_pos.begin); - if (!emitUint32Operand(JSOP_LINENO, lineNum)) - return false; + if (!cone.emitEnd(argc, Some(coordNode->pn_pos.begin))) { + // [stack] RVAL + return false; } return true; @@ -10206,9 +7973,6 @@ bool BytecodeEmitter::emitOptionalCall(BinaryNode* node, OptionalEmitter& oe, bool BytecodeEmitter::emitCallOrNew(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::WantValue */) { - bool callop = - pn->isKind(ParseNodeKind::Call) || pn->isKind(ParseNodeKind::TaggedTemplate); - /* * Emit callable invocation or operator new (constructor call) code. * First, emit code for the left operand to evaluate the callable or @@ -10224,95 +7988,69 @@ BytecodeEmitter::emitCallOrNew(ParseNode* pn, ValueUsage valueUsage /* = ValueUs * value required for calls (which non-strict mode functions * will box into the global object). */ - ParseNode* pn_callee = pn->pn_left; - ParseNode* pn_args = pn->pn_right; - - bool spread = JOF_OPTYPE(pn->getOp()) == JOF_BYTE; - - if (pn_callee->isKind(ParseNodeKind::Name) && emitterMode == BytecodeEmitter::SelfHosting && !spread) { + bool isCall = pn->isKind(ParseNodeKind::Call) || + pn->isKind(ParseNodeKind::TaggedTemplate); + ParseNode* calleeNode = pn->pn_left; + ListNode* argsList = &pn->pn_right->as(); + bool isSpread = JOF_OPTYPE(pn->getOp()) == JOF_BYTE; + + if (calleeNode->isKind(ParseNodeKind::Name) && + emitterMode == BytecodeEmitter::SelfHosting && !isSpread) { // Calls to "forceInterpreter", "callFunction", // "callContentFunction", or "resumeGenerator" in self-hosted // code generate inline bytecode. - if (pn_callee->name() == cx->names().callFunction || - pn_callee->name() == cx->names().callContentFunction || - pn_callee->name() == cx->names().constructContentFunction) - { + PropertyName* calleeName = calleeNode->as().name(); + if (calleeName == cx->names().callFunction || + calleeName == cx->names().callContentFunction || + calleeName == cx->names().constructContentFunction) { return emitSelfHostedCallFunction(pn); } - if (pn_callee->name() == cx->names().resumeGenerator) + if (calleeName == cx->names().resumeGenerator) { return emitSelfHostedResumeGenerator(pn); - if (pn_callee->name() == cx->names().forceInterpreter) + } + if (calleeName == cx->names().forceInterpreter) { return emitSelfHostedForceInterpreter(pn); - if (pn_callee->name() == cx->names().allowContentIter) + } + if (calleeName == cx->names().allowContentIter) { return emitSelfHostedAllowContentIter(pn); - if (pn_callee->name() == cx->names().defineDataPropertyIntrinsic && pn_args->pn_count == 3) + } + if (calleeName == cx->names().defineDataPropertyIntrinsic && + argsList->pn_count == 3) { return emitSelfHostedDefineDataProperty(pn); - if (pn_callee->name() == cx->names().hasOwn) + } + if (calleeName == cx->names().hasOwn) { return emitSelfHostedHasOwn(pn); + } + if (calleeName == cx->names().getPropertySuper) { + return emitSelfHostedGetPropertySuper(pn); + } // Fall through } - bool isNewOp = pn->getOp() == JSOP_NEW || pn->getOp() == JSOP_SPREADNEW || - pn->getOp() == JSOP_SUPERCALL || pn->getOp() == JSOP_SPREADSUPERCALL; + JSOp op = pn->getOp(); + uint32_t argc = argsList->pn_count; + CallOrNewEmitter cone( + this, op, + isSpread && (argc == 1) && + isRestParameter(argsList->pn_head->as().pn_kid) + ? CallOrNewEmitter::ArgumentsKind::SingleSpreadRest + : CallOrNewEmitter::ArgumentsKind::Other, + valueUsage); - if (!emitCalleeAndThis(pn_callee, pn, callop, isNewOp)) { + if (!emitCalleeAndThis(calleeNode, pn, cone)) { + // [stack] CALLEE THIS return false; } - - if (!emitArguments(pn_args, callop, spread)) + if (!emitArguments(argsList, isCall, isSpread, cone)) { + // [stack] CALLEE THIS ARGS... return false; - - uint32_t argc = pn_args->pn_count; - - /* - * Emit code for each argument in order, then emit the JSOP_*CALL or - * JSOP_NEW bytecode with a two-byte immediate telling how many args - * were pushed on the operand stack. - */ - if (isNewOp) { - if (pn->isKind(ParseNodeKind::SuperCall)) { - if (!emit1(JSOP_NEWTARGET)) - return false; - } else if (!spread) { - // Repush the callee as new.target - if (!emitDupAt(argc + 1)) - return false; - } else { - if (!emitDupAt(2)) - return false; - } } - ParseNode* coordNode = getCoordNode(pn, pn_callee, pn_args); - - if (!spread) { - if (pn->getOp() == JSOP_CALL && valueUsage == ValueUsage::IgnoreValue) { - if (!emitCall(JSOP_CALL_IGNORES_RV, argc, coordNode)) - return false; - checkTypeSet(JSOP_CALL_IGNORES_RV); - } else { - if (!emitCall(pn->getOp(), argc, coordNode)) - return false; - checkTypeSet(pn->getOp()); - } - } else { - if (coordNode) { - if (!updateSourceCoordNotes(coordNode->pn_pos.begin)) - return false; - } + ParseNode* coordNode = getCoordNode(pn, calleeNode, argsList); - if (!emit1(pn->getOp())) - return false; - checkTypeSet(pn->getOp()); - } - if (pn->isOp(JSOP_EVAL) || - pn->isOp(JSOP_STRICTEVAL) || - pn->isOp(JSOP_SPREADEVAL) || - pn->isOp(JSOP_STRICTSPREADEVAL)) - { - uint32_t lineNum = parser.tokenStream().srcCoords.lineNum(pn->pn_pos.begin); - if (!emitUint32Operand(JSOP_LINENO, lineNum)) - return false; + if (!cone.emitEnd(argc, Some(coordNode->pn_pos.begin))) { + // [stack] RVAL + return false; } return true; @@ -10420,14 +8158,19 @@ bool BytecodeEmitter::emitOptionalTree(ParseNode* pn, OptionalEmitter& oe, case ParseNodeKind::OptionalDot: { OptionalPropertyAccess* prop = &pn->as(); bool isSuper = false; - if (!emitOptionalDotExpression(prop, false, isSuper, oe)) + PropOpEmitter poe(this, PropOpEmitter::Kind::Get, + PropOpEmitter::ObjKind::Other); + if (!emitOptionalDotExpression(prop, poe, isSuper, oe)) return false; break; } case ParseNodeKind::Dot: { PropertyAccess* prop = &pn->as(); bool isSuper = prop->isSuper(); - if (!emitOptionalDotExpression(prop, false, isSuper, oe)) + PropOpEmitter poe(this, PropOpEmitter::Kind::Get, + isSuper ? PropOpEmitter::ObjKind::Super + : PropOpEmitter::ObjKind::Other); + if (!emitOptionalDotExpression(prop, poe, isSuper, oe)) return false; break; } @@ -10435,16 +8178,21 @@ bool BytecodeEmitter::emitOptionalTree(ParseNode* pn, OptionalEmitter& oe, case ParseNodeKind::OptionalElem: { OptionalPropertyByValue* elem = &pn->as(); bool isSuper = false; + ElemOpEmitter eoe(this, ElemOpEmitter::Kind::Get, + ElemOpEmitter::ObjKind::Other); - if (!emitOptionalElemExpression(elem, false, isSuper, oe)) + if (!emitOptionalElemExpression(elem, eoe, isSuper, oe)) return false; break; } case ParseNodeKind::Elem: { PropertyByValue* elem = &pn->as(); bool isSuper = elem->isSuper(); + ElemOpEmitter eoe(this, ElemOpEmitter::Kind::Get, + isSuper ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other); - if (!emitOptionalElemExpression(elem, false, isSuper, oe)) + if (!emitOptionalElemExpression(elem, eoe, isSuper, oe)) return false; break; } @@ -10498,7 +8246,7 @@ bool BytecodeEmitter::emitOptionalTree(ParseNode* pn, OptionalEmitter& oe, // For example `(a?.b)()` and `(a?.b)?.()`. bool BytecodeEmitter::emitCalleeAndThisForOptionalChain(ParseNode* optionalChain, ParseNode* callNode, - bool isCall, bool isNew) { + CallOrNewEmitter& cone) { ParseNode* calleeNode = optionalChain->pn_kid; // Create a new OptionalEmitter, in order to emit the right bytecode @@ -10508,7 +8256,7 @@ bool BytecodeEmitter::emitCalleeAndThisForOptionalChain(ParseNode* optionalChain if (!oe.emitOptionalJumpLabel()) return false; - if (!emitOptionalCalleeAndThis(calleeNode, callNode, isCall, isNew, oe)) + if (!emitOptionalCalleeAndThis(calleeNode, callNode, cone, oe)) return false; // complete the jump if necessary. This will set both the "this" value @@ -10539,84 +8287,92 @@ bool BytecodeEmitter::emitOptionalChain(ParseNode* node, ValueUsage valueUsage) } bool BytecodeEmitter::emitOptionalDotExpression(PropertyAccessBase* prop, - bool isCall, bool isSuper, + PropOpEmitter& poe, bool isSuper, OptionalEmitter& oe) { + if (!poe.prepareForObj()) { + // [stack] + return false; + } + if (isSuper) { UnaryNode* base = &prop->expression().as(); - if (!emitGetThisForSuperBase(base)) + if (!emitGetThisForSuperBase(base)) { + // [stack] OBJ return false; + } } else { - if (!emitOptionalTree(&prop->expression(), oe)) + if (!emitOptionalTree(&prop->expression(), oe)) { + // [stack] OBJ return false; + } } if (prop->isKind(ParseNodeKind::OptionalDot)) { MOZ_ASSERT(!isSuper); - if (!oe.emitJumpShortCircuit()) + if (!oe.emitJumpShortCircuit()) { + // [stack] # if Jump + // [stack] UNDEFINED-OR-NULL + // [stack] # otherwise + // [stack] OBJ return false; + } } - // POE.emitGet - if (isCall && !emit1(JSOP_DUP)) - return false; - - if (isSuper && !emit1(JSOP_SUPERBASE)) - return false; - - JSOp op; - if (isSuper) - op = JSOP_GETPROP_SUPER; - else - op = isCall ? JSOP_CALLPROP : JSOP_GETPROP; - - if (!emitAtomOp(prop->pn_right, op)) - return false; - - if (isCall && !emit1(JSOP_SWAP)) + if (!poe.emitGet(prop->key().pn_atom)) { + // [stack] PROP return false; + } return true; } bool BytecodeEmitter::emitOptionalElemExpression(PropertyByValueBase* elem, - bool isCall, bool isSuper, + ElemOpEmitter& eoe, + bool isSuper, OptionalEmitter& oe) { + if (!eoe.prepareForObj()) { + // [stack] + return false; + } + if (isSuper) { UnaryNode* base = &elem->expression().as(); - if (!emitGetThisForSuperBase(base)) + if (!emitGetThisForSuperBase(base)) { + // [stack] OBJ return false; + } } else { - if (!emitOptionalTree(&elem->expression(), oe)) + if (!emitOptionalTree(&elem->expression(), oe)) { + // [stack] OBJ return false; + } } if (elem->isKind(ParseNodeKind::OptionalElem)) { MOZ_ASSERT(!isSuper); - if (!oe.emitJumpShortCircuit()) + if (!oe.emitJumpShortCircuit()) { + // [stack] # if Jump + // [stack] UNDEFINED-OR-NULL + // [stack] # otherwise + // [stack] OBJ return false; + } } - // EOE.prepareForKey - if (isCall && !emit1(JSOP_DUP)) + if (!eoe.prepareForKey()) { + // [stack] OBJ? OBJ return false; + } - if (!emitTree(elem->pn_right)) + if (!emitTree(&elem->key())) { + // [stack] OBJ? OBJ KEY return false; + } - if (isSuper && !emit1(JSOP_SUPERBASE)) - return false; - - JSOp op; - if (isSuper) - op = JSOP_GETELEM_SUPER; - else - op = isCall ? JSOP_CALLELEM : JSOP_GETELEM; - - if (!emitElemOpBase(op)) + if (!eoe.emitGet()) { + // [stack] ELEM return false; - - if (isCall && !emit1(JSOP_SWAP)) - return false; + } return true; } @@ -10724,32 +8480,18 @@ BytecodeEmitter::emitIncOrDec(ParseNode* pn) // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See // the comment on emitSwitch. MOZ_NEVER_INLINE bool -BytecodeEmitter::emitLabeledStatement(const LabeledStatement* pn) +BytecodeEmitter::emitLabeledStatement(const LabeledStatement* labeledStmt) { - /* - * Emit a JSOP_LABEL instruction. The argument is the offset to the statement - * following the labeled statement. - */ - uint32_t index; - if (!makeAtomIndex(pn->label(), &index)) - return false; - - JumpList top; - if (!emitJump(JSOP_LABEL, &top)) + LabelEmitter label(this); + if (!label.emitLabel(labeledStmt->label())) { return false; - - /* Emit code for the labeled statement. */ - LabelControl controlInfo(this, pn->label(), offset()); - - if (!emitTree(pn->statement())) + } + if (!emitTree(labeledStmt->statement())) { return false; - - /* Patch the JSOP_LABEL offset. */ - JumpTarget brk{ lastNonJumpTargetOffset() }; - patchJumpsToTarget(top, brk); - - if (!controlInfo.patchBreaks(this)) + } + if (!label.emitEnd()) { return false; + } return true; } @@ -10762,17 +8504,17 @@ BytecodeEmitter::emitConditionalExpression(ConditionalExpression& conditional, if (!emitTree(&conditional.condition())) return false; - IfThenElseEmitter ifThenElse(this); + IfEmitter ifThenElse(this); if (!ifThenElse.emitCond()) return false; - if (!emitTreeInBranch(&conditional.thenExpression(), valueUsage)) + if (!emitTree(&conditional.thenExpression(), valueUsage)) return false; if (!ifThenElse.emitElse()) return false; - if (!emitTreeInBranch(&conditional.elseExpression(), valueUsage)) + if (!emitTree(&conditional.elseExpression(), valueUsage)) return false; if (!ifThenElse.emitEnd()) @@ -10783,170 +8525,254 @@ BytecodeEmitter::emitConditionalExpression(ConditionalExpression& conditional, } bool -BytecodeEmitter::emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp, PropListType type) +BytecodeEmitter::emitPropertyList(ParseNode* pn, PropertyEmitter& pe, PropListType type) { - for (ParseNode* propdef = pn->pn_head; propdef; propdef = propdef->pn_next) { - if (!updateSourceCoordNotes(propdef->pn_pos.begin)) - return false; + // [stack] CTOR? OBJ + for (ParseNode* propdef = pn->pn_head; propdef; propdef = propdef->pn_next) { // Handle __proto__: v specially because *only* this form, and no other // involving "__proto__", performs [[Prototype]] mutation. if (propdef->isKind(ParseNodeKind::MutateProto)) { + // [stack] OBJ MOZ_ASSERT(type == ObjectLiteral); - if (!emitTree(propdef->pn_kid)) + if (!pe.prepareForProtoValue(Some(propdef->pn_pos.begin))) { + // [stack] OBJ return false; - objp.set(nullptr); - if (!emit1(JSOP_MUTATEPROTO)) + } + if (!emitTree(propdef->pn_kid)) { + // [stack] OBJ PROTO + return false; + } + if (!pe.emitMutateProto()) { + // [stack] OBJ return false; + } continue; } if (propdef->isKind(ParseNodeKind::Spread)) { MOZ_ASSERT(type == ObjectLiteral); - - if (!emit1(JSOP_DUP)) - return false; - - if (!emitTree(propdef->pn_kid)) - return false; - - if (!emitCopyDataProperties(CopyOption::Unfiltered)) - return false; - - objp.set(nullptr); - continue; - } - - bool extraPop = false; - if (type == ClassBody && propdef->as().isStatic()) { - extraPop = true; - if (!emit1(JSOP_DUP2)) - return false; - if (!emit1(JSOP_POP)) + // [stack] OBJ + if (!pe.prepareForSpreadOperand(Some(propdef->pn_pos.begin))) { + // [stack] OBJ OBJ return false; - } - - /* Emit an index for t[2] for later consumption by JSOP_INITELEM. */ - ParseNode* key = propdef->pn_left; - bool isIndex = false; - if (key->isKind(ParseNodeKind::Number)) { - if (!emitNumberOp(key->pn_dval)) + } + if (!emitTree(propdef->pn_kid)) { + // [stack] OBJ OBJ VAL return false; - isIndex = true; - } else if (key->isKind(ParseNodeKind::ObjectPropertyName) || - key->isKind(ParseNodeKind::String)) - { - // EmitClass took care of constructor already. - if (type == ClassBody && key->pn_atom == cx->names().constructor && - !propdef->as().isStatic()) - { - continue; } - } else { - if (!emitComputedPropertyName(key)) + if (!pe.emitSpread()) { + // [stack] OBJ return false; - isIndex = true; + } + continue; } - /* Emit code for the property initializer. */ - if (!emitTree(propdef->pn_right)) - return false; + BinaryNode* prop = &propdef->as(); + ParseNode* key = prop->pn_left; + ParseNode* propVal = prop->pn_right; JSOp op = propdef->getOp(); - MOZ_ASSERT(op == JSOP_INITPROP || - op == JSOP_INITPROP_GETTER || + MOZ_ASSERT(op == JSOP_INITPROP || op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER); - FunctionPrefixKind prefixKind = op == JSOP_INITPROP_GETTER ? FunctionPrefixKind::Get - : op == JSOP_INITPROP_SETTER ? FunctionPrefixKind::Set - : FunctionPrefixKind::None; + auto emitValue = [this, &key, &propVal, op, &pe]() { + // [stack] CTOR? OBJ CTOR? KEY? + + if (propVal->isDirectRHSAnonFunction()) { + if (key->isKind(ParseNodeKind::Number)) { + MOZ_ASSERT(op == JSOP_INITPROP); + + RootedAtom keyAtom(cx, NumberToAtom(cx, key->pn_dval)); + if (!keyAtom) { + return false; + } + if (!emitAnonymousFunctionWithName(propVal, keyAtom)) { + // [stack] CTOR? OBJ CTOR? KEY VAL + return false; + } + } else if (key->isKind(ParseNodeKind::ObjectPropertyName) || + key->isKind(ParseNodeKind::String)) { + MOZ_ASSERT(op == JSOP_INITPROP); + + RootedAtom keyAtom(cx, key->pn_atom); + if (!emitAnonymousFunctionWithName(propVal, keyAtom)) { + // [stack] CTOR? OBJ CTOR? VAL + return false; + } + } else { + MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName)); + + FunctionPrefixKind prefix = op == JSOP_INITPROP + ? FunctionPrefixKind::None + : op == JSOP_INITPROP_GETTER + ? FunctionPrefixKind::Get + : FunctionPrefixKind::Set; + + if (!emitAnonymousFunctionWithComputedName(propVal, prefix)) { + // [stack] CTOR? OBJ CTOR? KEY VAL + return false; + } + } + } else { + if (!emitTree(propVal)) { + // [stack] CTOR? OBJ CTOR? KEY? VAL + return false; + } + } - if (op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER) - objp.set(nullptr); + if (propVal->isKind(ParseNodeKind::Function) && + propVal->pn_funbox->needsHomeObject()) { + FunctionBox* funbox = propVal->pn_funbox; + MOZ_ASSERT(funbox->function()->allowSuperProperty()); - if (propdef->pn_right->isKind(ParseNodeKind::Function) && - propdef->pn_right->pn_funbox->needsHomeObject()) - { - MOZ_ASSERT(propdef->pn_right->pn_funbox->function()->allowSuperProperty()); - bool isAsync = propdef->pn_right->pn_funbox->isAsync(); - if (isAsync) { - if (!emit1(JSOP_SWAP)) + if (!pe.emitInitHomeObject(funbox->asyncKind())) { + // [stack] CTOR? OBJ CTOR? KEY? FUN return false; + } + } + return true; + }; + + PropertyEmitter::Kind kind = + (type == ClassBody && propdef->as().isStatic()) + ? PropertyEmitter::Kind::Static + : PropertyEmitter::Kind::Prototype; + if (key->isKind(ParseNodeKind::Number)) { + // [stack] CTOR? OBJ + if (!pe.prepareForIndexPropKey(Some(propdef->pn_pos.begin), kind)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + if (!emitNumberOp(key->pn_dval)) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + if (!pe.prepareForIndexPropValue()) { + // [stack] CTOR? OBJ CTOR? KEY + return false; } - if (!emit2(JSOP_INITHOMEOBJECT, isIndex + isAsync)) + if (!emitValue()) { + // [stack] CTOR? OBJ CTOR? KEY VAL return false; - if (isAsync) { - if (!emit1(JSOP_POP)) + } + + switch (op) { + case JSOP_INITPROP: + if (!pe.emitInitIndexProp()) { + // [stack] CTOR? OBJ return false; + } + break; + case JSOP_INITPROP_GETTER: + if (!pe.emitInitIndexGetter()) { + // [stack] CTOR? OBJ + return false; + } + break; + case JSOP_INITPROP_SETTER: + if (!pe.emitInitIndexSetter()) { + // [stack] CTOR? OBJ + return false; + } + break; + default: + MOZ_CRASH("Invalid op"); + } + + continue; + } + + if (key->isKind(ParseNodeKind::ObjectPropertyName) || + key->isKind(ParseNodeKind::String)) { + // [stack] CTOR? OBJ + + // emitClass took care of constructor already. + if (type == ClassBody && key->pn_atom == cx->names().constructor && + !propdef->as().isStatic()) { + continue; } - } - // Class methods are not enumerable. - if (type == ClassBody) { - switch (op) { - case JSOP_INITPROP: op = JSOP_INITHIDDENPROP; break; - case JSOP_INITPROP_GETTER: op = JSOP_INITHIDDENPROP_GETTER; break; - case JSOP_INITPROP_SETTER: op = JSOP_INITHIDDENPROP_SETTER; break; - default: MOZ_CRASH("Invalid op"); + if (!pe.prepareForPropValue(Some(propdef->pn_pos.begin), kind)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + if (!emitValue()) { + // [stack] CTOR? OBJ CTOR? VAL + return false; } - } - if (isIndex) { - objp.set(nullptr); + RootedAtom keyAtom(cx, key->pn_atom); switch (op) { - case JSOP_INITPROP: op = JSOP_INITELEM; break; - case JSOP_INITHIDDENPROP: op = JSOP_INITHIDDENELEM; break; - case JSOP_INITPROP_GETTER: op = JSOP_INITELEM_GETTER; break; - case JSOP_INITHIDDENPROP_GETTER: op = JSOP_INITHIDDENELEM_GETTER; break; - case JSOP_INITPROP_SETTER: op = JSOP_INITELEM_SETTER; break; - case JSOP_INITHIDDENPROP_SETTER: op = JSOP_INITHIDDENELEM_SETTER; break; - default: MOZ_CRASH("Invalid op"); - } - if (propdef->pn_right->isDirectRHSAnonFunction()) { - if (!emitDupAt(1)) + case JSOP_INITPROP: + if (!pe.emitInitProp(keyAtom)) { + // [stack] CTOR? OBJ return false; - if (!emit2(JSOP_SETFUNNAME, uint8_t(prefixKind))) + } + break; + case JSOP_INITPROP_GETTER: + if (!pe.emitInitGetter(keyAtom)) { + // [stack] CTOR? OBJ return false; - } - if (!emit1(op)) - return false; - } else { - MOZ_ASSERT(key->isKind(ParseNodeKind::ObjectPropertyName) || - key->isKind(ParseNodeKind::String)); - - uint32_t index; - if (!makeAtomIndex(key->pn_atom, &index)) - return false; - - if (objp) { - MOZ_ASSERT(type == ObjectLiteral); - MOZ_ASSERT(!IsHiddenInitOp(op)); - MOZ_ASSERT(!objp->inDictionaryMode()); - Rooted id(cx, AtomToId(key->pn_atom)); - if (!NativeDefineProperty(cx, objp, id, UndefinedHandleValue, nullptr, nullptr, - JSPROP_ENUMERATE)) - { + } + break; + case JSOP_INITPROP_SETTER: + if (!pe.emitInitSetter(keyAtom)) { + // [stack] CTOR? OBJ return false; } - if (objp->inDictionaryMode()) - objp.set(nullptr); + break; + default: MOZ_CRASH("Invalid op"); } - if (propdef->pn_right->isDirectRHSAnonFunction()) { - RootedAtom keyName(cx, key->pn_atom); - if (!setOrEmitSetFunName(propdef->pn_right, keyName, prefixKind)) - return false; - } - if (!emitIndex32(op, index)) - return false; + continue; } - if (extraPop) { - if (!emit1(JSOP_POP)) - return false; + MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName)); + + // [stack] CTOR? OBJ + + if (!pe.prepareForComputedPropKey(Some(propdef->pn_pos.begin), kind)) { + // [stack] CTOR? OBJ CTOR? + return false; } - } - return true; + if (!emitTree(key->pn_kid)) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + if (!pe.prepareForComputedPropValue()) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + if (!emitValue()) { + // [stack] CTOR? OBJ CTOR? KEY VAL + return false; + } + + switch (op) { + case JSOP_INITPROP: + if (!pe.emitInitComputedProp()) { + // [stack] CTOR? OBJ + return false; + } + break; + case JSOP_INITPROP_GETTER: + if (!pe.emitInitComputedGetter()) { + // [stack] CTOR? OBJ + return false; + } + break; + case JSOP_INITPROP_SETTER: + if (!pe.emitInitComputedSetter()) { + // [stack] CTOR? OBJ + return false; + } + break; + default: + MOZ_CRASH("Invalid op"); + } + } + return true; } // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See @@ -10954,39 +8780,24 @@ BytecodeEmitter::emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp, MOZ_NEVER_INLINE bool BytecodeEmitter::emitObject(ParseNode* pn) { - if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head && checkSingletonContext()) + if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head && checkSingletonContext()) { return emitSingletonInitialiser(pn); + } - /* - * Emit code for {p:a, '%q':b, 2:c} that is equivalent to constructing - * a new object and defining (in source order) each property on the object - * (or mutating the object's [[Prototype]], in the case of __proto__). - */ - ptrdiff_t offset = this->offset(); - if (!emitNewInit(JSProto_Object)) - return false; - - // Try to construct the shape of the object as we go, so we can emit a - // JSOP_NEWOBJECT with the final shape instead. - // In the case of computed property names and indices, we cannot fix the - // shape at bytecode compile time. When the shape cannot be determined, - // |obj| is nulled out. + // [stack] - // No need to do any guessing for the object kind, since we know the upper - // bound of how many properties we plan to have. - gc::AllocKind kind = gc::GetGCObjectKind(pn->pn_count); - RootedPlainObject obj(cx, NewBuiltinClassInstance(cx, kind, TenuredObject)); - if (!obj) + ObjectEmitter oe(this); + if (!oe.emitObject(pn->pn_count)) { + // [stack] OBJ return false; - - if (!emitPropertyList(pn, &obj, ObjectLiteral)) + } + if (!emitPropertyList(pn, oe, ObjectLiteral)) { + // [stack] OBJ + return false; + } + if (!oe.emitEnd()) { + // [stack] OBJ return false; - - if (obj) { - // The object survived and has a predictable shape: update the original - // bytecode. - if (!replaceNewInitWithNewObject(obj, offset)) - return false; } return true; @@ -11007,10 +8818,7 @@ BytecodeEmitter::replaceNewInitWithNewObject(JSObject* obj, ptrdiff_t offset) MOZ_ASSERT(code[0] == JSOP_NEWINIT); code[0] = JSOP_NEWOBJECT; - code[1] = jsbytecode(index >> 24); - code[2] = jsbytecode(index >> 16); - code[3] = jsbytecode(index >> 8); - code[4] = jsbytecode(index); + SET_UINT32(code, index); return true; } @@ -11257,17 +9065,21 @@ BytecodeEmitter::emitFunctionFormalParametersAndBody(ParseNode *pn) MOZ_ASSERT(name != cx->names().dotThis && name != cx->names().dotGenerator); - NameLocation paramLoc = *locationOfNameBoundInScope(name, &funEmitterScope); - auto emitRhs = [&name, ¶mLoc](BytecodeEmitter* bce, - const NameLocation&, bool) - { - return bce->emitGetNameAtLocation(name, paramLoc); - }; + NameOpEmitter noe(this, name, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + return false; + } - if (!emitInitializeName(name, emitRhs)) + NameLocation paramLoc = *locationOfNameBoundInScope(name, &funEmitterScope); + if (!emitGetNameAtLocation(name, paramLoc)) { return false; - if (!emit1(JSOP_POP)) + } + if (!noe.emitAssignment()) { + return false; + } + if (!emit1(JSOP_POP)) { return false; + } } } } @@ -11403,37 +9215,30 @@ BytecodeEmitter::emitFunctionFormalParameters(ParseNode* pn) return false; } - if (!emit1(JSOP_POP)) + if (!emit1(JSOP_POP)) { return false; - } else { + } + } else if (hasParameterExprs || isRest) { RootedAtom paramName(cx, bindingElement->name()); NameLocation paramLoc = *locationOfNameBoundInScope(paramName, funScope); - + NameOpEmitter noe(this, paramName, paramLoc, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + return false; + } if (hasParameterExprs) { - auto emitRhs = [argSlot, initializer, isRest](BytecodeEmitter* bce, - const NameLocation&, bool) - { - // If we had an initializer or a rest parameter, the value is - // already on the stack. - if (!initializer && !isRest) - return bce->emitArgOp(JSOP_GETARG, argSlot); - return true; - }; - - if (!emitSetOrInitializeNameAtLocation(paramName, paramLoc, emitRhs, true)) - return false; - if (!emit1(JSOP_POP)) - return false; - } else if (isRest) { - // The rest value is already on top of the stack. - auto nop = [](BytecodeEmitter*, const NameLocation&, bool) { - return true; - }; - - if (!emitSetOrInitializeNameAtLocation(paramName, paramLoc, nop, true)) - return false; - if (!emit1(JSOP_POP)) - return false; + // If we had an initializer or a rest parameter, the value is + // already on the stack. + if (!initializer && !isRest) { + if (!emitArgOp(JSOP_GETARG, argSlot)) { + return false; + } + } + } + if (!noe.emitAssignment()) { + return false; + } + if (!emit1(JSOP_POP)) { + return false; } } @@ -11458,14 +9263,19 @@ BytecodeEmitter::emitInitializeFunctionSpecialNames() // call environment. MOZ_ASSERT(bce->lookupName(name).hasKnownSlot()); - auto emitInitial = [op](BytecodeEmitter* bce, const NameLocation&, bool) { - return bce->emit1(op); - }; - - if (!bce->emitInitializeName(name, emitInitial)) + NameOpEmitter noe(bce, name, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + return false; + } + if (!bce->emit1(op)) { + return false; + } + if (!noe.emitAssignment()) { return false; - if (!bce->emit1(JSOP_POP)) + } + if (!bce->emit1(JSOP_POP)) { return false; + } return true; }; @@ -11543,244 +9353,180 @@ BytecodeEmitter::emitFunctionBody(ParseNode* funBody) } bool -BytecodeEmitter::emitLexicalInitialization(ParseNode* pn) +BytecodeEmitter::emitLexicalInitialization(ParseNode* name) { - // The caller has pushed the RHS to the top of the stack. Assert that the - // name is lexical and no BIND[G]NAME ops were emitted. - auto assertLexical = [](BytecodeEmitter*, const NameLocation& loc, bool emittedBindOp) { - MOZ_ASSERT(loc.isLexical()); - MOZ_ASSERT(!emittedBindOp); - return true; - }; - return emitInitializeName(pn, assertLexical); + return emitLexicalInitialization(name->name()); } -// This follows ES6 14.5.14 (ClassDefinitionEvaluation) and ES6 14.5.15 -// (BindingClassDeclarationEvaluation). bool -BytecodeEmitter::emitClass(ParseNode* pn) +BytecodeEmitter::emitLexicalInitialization(JSAtom* name) { - ClassNode& classNode = pn->as(); + NameOpEmitter noe(this, name, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + return false; + } - ClassNames* names = classNode.names(); + // The caller has pushed the RHS to the top of the stack. Assert that the + // name is lexical and no BIND[G]NAME ops were emitted. + MOZ_ASSERT(noe.loc().isLexical()); + MOZ_ASSERT(!noe.emittedBindOp()); - ParseNode* heritageExpression = classNode.heritage(); + if (!noe.emitAssignment()) { + return false; + } + + return true; +} - ParseNode* classMethods = classNode.methodList(); - ParseNode* constructor = nullptr; +static MOZ_ALWAYS_INLINE +CodeNode* FindConstructor(JSContext* cx, ListNode* classMethods) +{ for (ParseNode* mn = classMethods->pn_head; mn; mn = mn->pn_next) { ClassMethod& method = mn->as(); ParseNode& methodName = method.name(); if (!method.isStatic() && (methodName.isKind(ParseNodeKind::ObjectPropertyName) || methodName.isKind(ParseNodeKind::String)) && - methodName.pn_atom == cx->names().constructor) - { - constructor = &method.method(); - break; + methodName.pn_atom == cx->names().constructor) { + return &method.method().as(); } } - bool savedStrictness = sc->setLocalStrictMode(true); - - Maybe tdzCache; - Maybe emitterScope; - if (names) { - tdzCache.emplace(this); - emitterScope.emplace(this); - if (!emitterScope->enterLexical(this, ScopeKind::Lexical, classNode.scopeBindings())) - return false; - } - - // Pseudocode for class declarations: - // - // class extends BaseExpression { - // constructor() { ... } - // ... - // } - // - // - // if defined { - // let heritage = BaseExpression; - // - // if (heritage !== null) { - // funProto = heritage; - // objProto = heritage.prototype; - // } else { - // funProto = %FunctionPrototype%; - // objProto = null; - // } - // } else { - // objProto = %ObjectPrototype%; - // } - // - // let homeObject = ObjectCreate(objProto); - // - // if defined { - // if defined { - // cons = DefineMethod(, proto=homeObject, funProto=funProto); - // } else { - // cons = DefineMethod(, proto=homeObject); - // } - // } else { - // if defined { - // cons = DefaultDerivedConstructor(proto=homeObject, funProto=funProto); - // } else { - // cons = DefaultConstructor(proto=homeObject); - // } - // } - // - // cons.prototype = homeObject; - // homeObject.constructor = cons; - // - // EmitPropertyList(...) + return nullptr; +} - // This is kind of silly. In order to the get the home object defined on - // the constructor, we have to make it second, but we want the prototype - // on top for EmitPropertyList, because we expect static properties to be - // rarer. The result is a few more swaps than we would like. Such is life. - if (heritageExpression) { - IfThenElseEmitter ifThenElse(this); +// This follows ES6 14.5.14 (ClassDefinitionEvaluation) and ES6 14.5.15 +// (BindingClassDeclarationEvaluation). +bool +BytecodeEmitter::emitClass(ParseNode* pn, + ClassNameKind nameKind /* = ClassNameKind::BindingName */, + HandleAtom nameForAnonymousClass /* = nullptr */) +{ + MOZ_ASSERT((nameKind == ClassNameKind::InferredName) == + (nameForAnonymousClass != nullptr)); - if (!emitTree(heritageExpression)) // ... HERITAGE - return false; + ClassNode& classNode = pn->as(); - // Heritage must be null or a non-generator constructor - if (!emit1(JSOP_CHECKCLASSHERITAGE)) // ... HERITAGE - return false; + ParseNode* heritageExpression = classNode.heritage(); + ListNode* classMethods = &classNode.methodList()->as(); + CodeNode* constructor = FindConstructor(cx, classMethods); - // [IF] (heritage !== null) - if (!emit1(JSOP_DUP)) // ... HERITAGE HERITAGE - return false; - if (!emit1(JSOP_NULL)) // ... HERITAGE HERITAGE NULL - return false; - if (!emit1(JSOP_STRICTNE)) // ... HERITAGE NE - return false; + // If |nameKind != ClassNameKind::ComputedName| + // [stack] + // Else + // [stack] NAME - // [THEN] funProto = heritage, objProto = heritage.prototype - if (!ifThenElse.emitIfElse()) - return false; - if (!emit1(JSOP_DUP)) // ... HERITAGE HERITAGE - return false; - if (!emitAtomOp(cx->names().prototype, JSOP_GETPROP)) // ... HERITAGE PROTO - return false; + ClassEmitter ce(this); + RootedAtom innerName(cx); + ClassEmitter::Kind kind = ClassEmitter::Kind::Expression; + if (ClassNames* names = classNode.names()) { + MOZ_ASSERT(nameKind == ClassNameKind::BindingName); + innerName = names->innerBinding()->name(); + MOZ_ASSERT(innerName); - // [ELSE] funProto = %FunctionPrototype%, objProto = null - if (!ifThenElse.emitElse()) - return false; - if (!emit1(JSOP_POP)) // ... - return false; - if (!emit2(JSOP_BUILTINPROTO, JSProto_Function)) // ... PROTO - return false; - if (!emit1(JSOP_NULL)) // ... PROTO NULL - return false; + if (names->outerBinding()) { + MOZ_ASSERT(names->outerBinding()->name()); + MOZ_ASSERT(names->outerBinding()->name() == innerName); + kind = ClassEmitter::Kind::Declaration; + } - // [ENDIF] - if (!ifThenElse.emitEnd()) + if (!ce.emitScopeForNamedClass(classNode.scopeBindings())) { + // [stack] return false; + } + } - if (!emit1(JSOP_OBJWITHPROTO)) // ... HERITAGE HOMEOBJ + // This is kind of silly. In order to the get the home object defined on + // the constructor, we have to make it second, but we want the prototype + // on top for EmitPropertyList, because we expect static properties to be + // rarer. The result is a few more swaps than we would like. Such is life. + bool isDerived = !!heritageExpression; + bool hasNameOnStack = nameKind == ClassNameKind::ComputedName; + if (isDerived) { + if (!emitTree(heritageExpression)) { + // [stack] HERITAGE return false; - if (!emit1(JSOP_SWAP)) // ... HOMEOBJ HERITAGE + } + if (!ce.emitDerivedClass(innerName, nameForAnonymousClass, + hasNameOnStack)) { + // [stack] HERITAGE HOMEOBJ return false; + } } else { - if (!emitNewInit(JSProto_Object)) // ... HOMEOBJ + if (!ce.emitClass(innerName, nameForAnonymousClass, hasNameOnStack)) { + // [stack] HOMEOBJ return false; + } } // Stack currently has HOMEOBJ followed by optional HERITAGE. When HERITAGE // is not used, an implicit value of %FunctionPrototype% is implied. - if (constructor) { - if (!emitFunction(constructor, !!heritageExpression)) // ... HOMEOBJ CONSTRUCTOR + bool needsHomeObject = constructor->pn_funbox->needsHomeObject(); + // HERITAGE is consumed inside emitFunction. + if (!emitFunction(constructor, isDerived)) { + // [stack] HOMEOBJ CTOR return false; - if (constructor->pn_funbox->needsHomeObject()) { - if (!emit2(JSOP_INITHOMEOBJECT, 0)) // ... HOMEOBJ CONSTRUCTOR + } + if (nameKind == ClassNameKind::InferredName) { + if (!setFunName(constructor->pn_funbox->function(), + nameForAnonymousClass)) { return false; + } + } + if (!ce.emitInitConstructor(needsHomeObject)) { + // [stack] CTOR HOMEOBJ + return false; } } else { - // In the case of default class constructors, emit the start and end - // offsets in the source buffer as source notes so that when we - // actually make the constructor during execution, we can give it the - // correct toString output. - ptrdiff_t classStart = ptrdiff_t(pn->pn_pos.begin); - ptrdiff_t classEnd = ptrdiff_t(pn->pn_pos.end); - if (!newSrcNote3(SRC_CLASS_SPAN, classStart, classEnd)) + if (!ce.emitInitDefaultConstructor(Some(classNode.pn_pos.begin), + Some(classNode.pn_pos.end))) { + // [stack] CTOR HOMEOBJ return false; - - JSAtom *name = names ? names->innerBinding()->pn_atom : cx->names().empty; - if (heritageExpression) { - if (!emitAtomOp(name, JSOP_DERIVEDCONSTRUCTOR)) // ... HOMEOBJ CONSTRUCTOR - return false; - } else { - if (!emitAtomOp(name, JSOP_CLASSCONSTRUCTOR)) // ... HOMEOBJ CONSTRUCTOR - return false; } } - - if (!emit1(JSOP_SWAP)) // ... CONSTRUCTOR HOMEOBJ - return false; - - if (!emit1(JSOP_DUP2)) // ... CONSTRUCTOR HOMEOBJ CONSTRUCTOR HOMEOBJ + if (!emitPropertyList(classMethods, ce, ClassBody)) { + // [stack] CTOR HOMEOBJ return false; - if (!emitAtomOp(cx->names().prototype, JSOP_INITLOCKEDPROP)) // ... CONSTRUCTOR HOMEOBJ CONSTRUCTOR - return false; - if (!emitAtomOp(cx->names().constructor, JSOP_INITHIDDENPROP)) // ... CONSTRUCTOR HOMEOBJ - return false; - - RootedPlainObject obj(cx); - if (!emitPropertyList(classMethods, &obj, ClassBody)) // ... CONSTRUCTOR HOMEOBJ - return false; - - if (!emit1(JSOP_POP)) // ... CONSTRUCTOR + } + if (!ce.emitEnd(kind)) { + // [stack] # class declaration + // [stack] + // [stack] # class expression + // [stack] CTOR return false; - - if (names) { - ParseNode* innerName = names->innerBinding(); - if (!emitLexicalInitialization(innerName)) // ... CONSTRUCTOR - return false; - - // Pop the inner scope. - if (!emitterScope->leave(this)) - return false; - emitterScope.reset(); - - ParseNode* outerName = names->outerBinding(); - if (outerName) { - if (!emitLexicalInitialization(outerName)) // ... CONSTRUCTOR - return false; - // Only class statements make outer bindings, and they do not leave - // themselves on the stack. - if (!emit1(JSOP_POP)) // ... - return false; - } } - // The CONSTRUCTOR is left on stack if this is an expression. - - MOZ_ALWAYS_TRUE(sc->setLocalStrictMode(savedStrictness)); - return true; } bool BytecodeEmitter::emitExportDefault(ParseNode* pn) { - if (!emitTree(pn->pn_left)) - return false; + MOZ_ASSERT(pn->isKind(ParseNodeKind::ExportDefault)); - if (pn->pn_right) { - if (!emitLexicalInitialization(pn->pn_right)) + ParseNode* valueNode = pn->pn_left; + if (valueNode->isDirectRHSAnonFunction()) { + MOZ_ASSERT(pn->pn_right); + + HandlePropertyName name = cx->names().default_; + if (!emitAnonymousFunctionWithName(valueNode, name)) { + return false; + } + } else { + if (!emitTree(valueNode)) { return false; + } + } - if (pn->pn_left->isDirectRHSAnonFunction()) { - HandlePropertyName name = cx->names().default_; - if (!setOrEmitSetFunName(pn->pn_left, name, FunctionPrefixKind::None)) - return false; + if (ParseNode* binding = pn->pn_right) { + if (!emitLexicalInitialization(&binding->as())) { + return false; } - if (!emit1(JSOP_POP)) + if (!emit1(JSOP_POP)) { return false; + } } return true; @@ -11940,8 +9686,20 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage:: case ParseNodeKind::DivAssign: case ParseNodeKind::ModAssign: case ParseNodeKind::PowAssign: - if (!emitAssignment(pn->pn_left, pn->getKind(), pn->pn_right)) + if (!emitAssignment(pn->pn_left, + CompoundAssignmentParseNodeKindToJSOp(pn->getKind()), + pn->pn_right)) + { return false; + } + break; + + case ParseNodeKind::CoalesceAssignExpr: + case ParseNodeKind::OrAssignExpr: + case ParseNodeKind::AndAssignExpr: + if (!emitShortCircuitAssignment(pn)) { + return false; + } break; case ParseNodeKind::Conditional: @@ -12049,25 +9807,52 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage:: return false; break; - case ParseNodeKind::Dot: - if (pn->as().isSuper()) { - if (!emitSuperPropOp(pn, JSOP_GETPROP_SUPER)) + case ParseNodeKind::Dot: { + PropertyAccess* prop = &pn->as(); + bool isSuper = prop->isSuper(); + PropOpEmitter poe(this, + PropOpEmitter::Kind::Get, + isSuper + ? PropOpEmitter::ObjKind::Super + : PropOpEmitter::ObjKind::Other); + if (!poe.prepareForObj()) { + return false; + } + if (isSuper) { + UnaryNode* base = &prop->expression().as(); + if (!emitGetThisForSuperBase(base)) { // THIS return false; + } } else { - if (!emitPropOp(pn, JSOP_GETPROP)) + if (!emitPropLHS(prop)) { // OBJ return false; + } + } + if (!poe.emitGet(prop->key().pn_atom)) { // PROP + return false; } break; - - case ParseNodeKind::Elem: - if (pn->as().isSuper()) { - if (!emitSuperElemOp(pn, JSOP_GETELEM_SUPER)) - return false; - } else { - if (!emitElemOp(pn, JSOP_GETELEM)) - return false; + } + case ParseNodeKind::Elem: { + PropertyByValue* elem = &pn->as(); + bool isSuper = elem->isSuper(); + ElemOpEmitter eoe(this, + ElemOpEmitter::Kind::Get, + isSuper + ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other); + if (!emitElemObjAndKey(elem, isSuper, eoe)) { // [Super] + // // THIS KEY + // // [Other] + // // OBJ KEY + return false; + } + if (!eoe.emitGet()) { // ELEM + return false; } + break; + } case ParseNodeKind::New: case ParseNodeKind::TaggedTemplate: @@ -12157,7 +9942,7 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage:: case ParseNodeKind::TemplateString: case ParseNodeKind::String: - if (!emitAtomOp(pn, JSOP_STRING)) + if (!emitAtomOp(pn->pn_atom, JSOP_STRING)) return false; break; @@ -12480,16 +10265,6 @@ CGObjectList::add(ObjectBox* objbox) return length++; } -unsigned -CGObjectList::indexOf(JSObject* obj) -{ - MOZ_ASSERT(length > 0); - unsigned index = length - 1; - for (ObjectBox* box = lastbox; box->object != obj; box = box->emitLink) - index--; - return index; -} - void CGObjectList::finish(ObjectArray* array) { @@ -12507,16 +10282,6 @@ CGObjectList::finish(ObjectArray* array) MOZ_ASSERT(cursor == array->vector); } -ObjectBox* -CGObjectList::find(uint32_t index) -{ - MOZ_ASSERT(index < length); - ObjectBox* box = lastbox; - for (unsigned n = length - 1; n > index; n--) - box = box->emitLink; - return box; -} - void CGScopeList::finish(ScopeArray* array) { diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 29fd3c06c544..f028285c1225 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -18,8 +18,10 @@ #include "ds/InlineTable.h" #include "frontend/EitherParser.h" +#include "frontend/JumpList.h" #include "frontend/SharedContext.h" #include "frontend/SourceNotes.h" +#include "frontend/ValueUsage.h" #include "vm/Interpreter.h" namespace js { @@ -44,9 +46,7 @@ struct CGObjectList { CGObjectList() : length(0), lastbox(nullptr) {} unsigned add(ObjectBox* objbox); - unsigned indexOf(JSObject* obj); void finish(ObjectArray* array); - ObjectBox* find(uint32_t index); }; struct MOZ_STACK_CLASS CGScopeList { @@ -113,73 +113,17 @@ static constexpr size_t MaxSrcNotesLength = INT32_MAX; typedef Vector BytecodeVector; typedef Vector SrcNotesVector; -// Linked list of jump instructions that need to be patched. The linked list is -// stored in the bytes of the incomplete bytecode that will be patched, so no -// extra memory is needed, and patching the instructions destroys the list. -// -// Example: -// -// JumpList brList; -// if (!emitJump(JSOP_IFEQ, &brList)) -// return false; -// ... -// JumpTarget label; -// if (!emitJumpTarget(&label)) -// return false; -// ... -// if (!emitJump(JSOP_GOTO, &brList)) -// return false; -// ... -// patchJumpsToTarget(brList, label); -// -// +-> -1 -// | -// | -// ifeq .. <+ + +-+ ifeq .. -// .. | | .. -// label: | +-> label: -// jumptarget | | jumptarget -// .. | | .. -// goto .. <+ + +-+ goto .. <+ -// | | -// | | -// + + -// brList brList -// -// | ^ -// +------- patchJumpsToTarget -------+ -// - -// Offset of a jump target instruction, used for patching jump instructions. -struct JumpTarget { - ptrdiff_t offset; -}; - -struct JumpList { - // -1 is used to mark the end of jump lists. - JumpList() : offset(-1) {} - ptrdiff_t offset; - - // Add a jump instruction to the list. - void push(jsbytecode* code, ptrdiff_t jumpOffset); - - // Patch all jump instructions in this list to jump to `target`. This - // clobbers the list. - void patchAll(jsbytecode* code, JumpTarget target); -}; - -enum class ValueUsage { - WantValue, - IgnoreValue -}; +class CallOrNewEmitter; +class ElemOpEmitter; +class EmitterScope; +class NestableControl; +class OptionalEmitter; +class PropertyEmitter; +class PropOpEmitter; +class TDZCheckCache; struct MOZ_STACK_CLASS BytecodeEmitter { - class TDZCheckCache; - class NestableControl; - class EmitterScope; - class OptionalEmitter; - SharedContext* const sc; /* context shared between parsing and bytecode generation */ JSContext* const cx; @@ -334,9 +278,6 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool init(); - template bool */> - NestableControl* findInnermostNestableControl(Predicate predicate) const; - template T* findInnermostNestableControl() const; @@ -379,8 +320,10 @@ struct MOZ_STACK_CLASS BytecodeEmitter } uint32_t index = atomIndices->count(); - if (!atomIndices->add(p, atom, index)) + if (!atomIndices->add(p, atom, index)) { + ReportOutOfMemory(cx); return false; + } *indexp = index; return true; @@ -514,13 +457,20 @@ struct MOZ_STACK_CLASS BytecodeEmitter // Emit three bytecodes, an opcode with two bytes of immediate operands. MOZ_MUST_USE bool emit3(JSOp op, jsbytecode op1, jsbytecode op2); - // Helper to emit JSOP_DUPAT. The argument is the value's depth on the - // JS stack, as measured from the top. - MOZ_MUST_USE bool emitDupAt(unsigned slotFromTop); + // Helper to duplicate one or more stack values. |slotFromTop| is the value's + // depth on the JS stack, as measured from the top. |count| is the number of + // values to duplicate, in their original order. + MOZ_MUST_USE bool emitDupAt(unsigned slotFromTop, unsigned count = 1); // Helper to emit JSOP_POP or JSOP_POPN. MOZ_MUST_USE bool emitPopN(unsigned n); + // Helper to emit JSOP_SWAP or JSOP_PICK. + MOZ_MUST_USE bool emitPickN(uint8_t n); + + // Helper to emit JSOP_SWAP or JSOP_UNPICK. + MOZ_MUST_USE bool emitUnpickN(uint8_t n); + // Helper to emit JSOP_CHECKISOBJ. MOZ_MUST_USE bool emitCheckIsObj(CheckIsObjectKind kind); @@ -544,6 +494,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitThisLiteral(ParseNode* pn); MOZ_MUST_USE bool emitGetFunctionThis(ParseNode* pn); + MOZ_MUST_USE bool emitGetFunctionThis(const mozilla::Maybe& offset); MOZ_MUST_USE bool emitGetThisForSuperBase(ParseNode* pn); MOZ_MUST_USE bool emitSetThis(ParseNode* pn); MOZ_MUST_USE bool emitCheckDerivedClassConstructorReturn(); @@ -557,6 +508,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter void patchJumpsToTarget(JumpList jump, JumpTarget target); MOZ_MUST_USE bool emitJumpTargetAndPatch(JumpList jump); + MOZ_MUST_USE bool emitCall(JSOp op, uint16_t argc, + const mozilla::Maybe& sourceCoordOffset); MOZ_MUST_USE bool emitCall(JSOp op, uint16_t argc, ParseNode* pn = nullptr); MOZ_MUST_USE bool emitCallIncDec(ParseNode* incDec); @@ -570,7 +523,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitIndexOp(JSOp op, uint32_t index); MOZ_MUST_USE bool emitAtomOp(JSAtom* atom, JSOp op); - MOZ_MUST_USE bool emitAtomOp(ParseNode* pn, JSOp op); + MOZ_MUST_USE bool emitAtomOp(uint32_t atomIndex, JSOp op); MOZ_MUST_USE bool emitArrayLiteral(ParseNode* pn); MOZ_MUST_USE bool emitArray(ParseNode* pn, uint32_t count); @@ -589,7 +542,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitHoistedFunctionsInList(ParseNode* pn); - MOZ_MUST_USE bool emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp, + MOZ_MUST_USE bool emitPropertyList(ParseNode* pn, PropertyEmitter& pe, PropListType type); // To catch accidental misuse, emitUint16Operand/emit3 assert that they are @@ -602,42 +555,11 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitArgOp(JSOp op, uint16_t slot); MOZ_MUST_USE bool emitEnvCoordOp(JSOp op, EnvironmentCoordinate ec); - MOZ_MUST_USE bool emitGetNameAtLocation(JSAtom* name, const NameLocation& loc, - bool callContext = false); - MOZ_MUST_USE bool emitGetNameAtLocationForCompoundAssignment(JSAtom* name, - const NameLocation& loc); - MOZ_MUST_USE bool emitGetName(JSAtom* name, bool callContext = false) { - return emitGetNameAtLocation(name, lookupName(name), callContext); - } - MOZ_MUST_USE bool emitGetName(ParseNode* pn, bool callContext = false); - - template - MOZ_MUST_USE bool emitSetOrInitializeNameAtLocation(HandleAtom name, const NameLocation& loc, - RHSEmitter emitRhs, bool initialize); - template - MOZ_MUST_USE bool emitSetOrInitializeName(HandleAtom name, RHSEmitter emitRhs, - bool initialize) - { - return emitSetOrInitializeNameAtLocation(name, lookupName(name), emitRhs, initialize); - } - template - MOZ_MUST_USE bool emitSetName(ParseNode* pn, RHSEmitter emitRhs) { - RootedAtom name(cx, pn->name()); - return emitSetName(name, emitRhs); - } - template - MOZ_MUST_USE bool emitSetName(HandleAtom name, RHSEmitter emitRhs) { - return emitSetOrInitializeName(name, emitRhs, false); - } - template - MOZ_MUST_USE bool emitInitializeName(ParseNode* pn, RHSEmitter emitRhs) { - RootedAtom name(cx, pn->name()); - return emitInitializeName(name, emitRhs); - } - template - MOZ_MUST_USE bool emitInitializeName(HandleAtom name, RHSEmitter emitRhs) { - return emitSetOrInitializeName(name, emitRhs, true); + MOZ_MUST_USE bool emitGetNameAtLocation(JSAtom* name, const NameLocation& loc); + MOZ_MUST_USE bool emitGetName(JSAtom* name) { + return emitGetNameAtLocation(name, lookupName(name)); } + MOZ_MUST_USE bool emitGetName(ParseNode* pn); MOZ_MUST_USE bool emitTDZCheckIfNeeded(JSAtom* name, const NameLocation& loc); @@ -646,6 +568,9 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitDeclarationList(ParseNode* decls); MOZ_MUST_USE bool emitSingleDeclaration(ParseNode* decls, ParseNode* decl, ParseNode* initializer); + MOZ_MUST_USE bool emitAssignmentRhs(ParseNode* rhs, + HandleAtom anonFunctionName); + MOZ_MUST_USE bool emitAssignmentRhs(uint8_t offset); MOZ_MUST_USE bool emitNewInit(JSProtoKey key); MOZ_MUST_USE bool emitSingletonInitialiser(ParseNode* pn); @@ -670,7 +595,6 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitAwaitInScope(EmitterScope& currentScope); MOZ_MUST_USE bool emitPropLHS(ParseNode* pn); - MOZ_MUST_USE bool emitPropOp(ParseNode* pn, JSOp op); MOZ_MUST_USE bool emitPropIncDec(ParseNode* pn); MOZ_MUST_USE bool emitAsyncWrapperLambda(unsigned index, bool isArrow); @@ -682,9 +606,10 @@ struct MOZ_STACK_CLASS BytecodeEmitter // Emit bytecode to put operands for a JSOP_GETELEM/CALLELEM/SETELEM/DELELEM // opcode onto the stack in the right order. In the case of SETELEM, the // value to be assigned must already be pushed. - enum class EmitElemOption { Get, Set, Call, IncDec, CompoundAssign, Ref }; + enum class EmitElemOption { Get, Call, IncDec, CompoundAssign, Ref }; MOZ_MUST_USE bool emitElemOperands(ParseNode* pn, EmitElemOption opts); + MOZ_MUST_USE bool emitElemObjAndKey(PropertyByValue* elem, bool isSuper, ElemOpEmitter& eoe); MOZ_MUST_USE bool emitElemOpBase(JSOp op); MOZ_MUST_USE bool emitElemOp(ParseNode* pn, JSOp op); MOZ_MUST_USE bool emitElemIncDec(ParseNode* pn); @@ -693,7 +618,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitIf(ParseNode* pn); MOZ_MUST_USE bool emitWith(ParseNode* pn); - MOZ_NEVER_INLINE MOZ_MUST_USE bool emitLabeledStatement(const LabeledStatement* pn); + MOZ_NEVER_INLINE MOZ_MUST_USE bool emitLabeledStatement( + const LabeledStatement* labeledStmt); MOZ_NEVER_INLINE MOZ_MUST_USE bool emitLexicalScope(ParseNode* pn); MOZ_MUST_USE bool emitLexicalScopeBody(ParseNode* body, EmitLineNumberNote emitLineNote = EMIT_LINENOTE); @@ -739,12 +665,6 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlavor flav); MOZ_MUST_USE bool emitDestructuringOpsObject(ParseNode* pattern, DestructuringFlavor flav); - typedef bool - (*DestructuringDeclEmitter)(BytecodeEmitter* bce, ParseNode* pn); - - template - MOZ_MUST_USE bool emitDestructuringDeclsWithEmitter(ParseNode* pattern, NameEmitter emitName); - enum class CopyOption { Filtered, Unfiltered }; @@ -786,15 +706,20 @@ struct MOZ_STACK_CLASS BytecodeEmitter // is called at compile time. MOZ_MUST_USE bool emitDefault(ParseNode* defaultExpr, ParseNode* pattern); - MOZ_MUST_USE bool setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name, - FunctionPrefixKind prefixKind); + MOZ_MUST_USE bool emitAnonymousFunctionWithName(ParseNode* node, + HandleAtom name); + + MOZ_MUST_USE bool emitAnonymousFunctionWithComputedName( + ParseNode* node, FunctionPrefixKind prefixKind); + MOZ_MUST_USE bool setFunName(JSFunction* fun, JSAtom* name); MOZ_MUST_USE bool emitInitializer(ParseNode* initializer, ParseNode* pattern); MOZ_MUST_USE bool emitInitializerInBranch(ParseNode* initializer, ParseNode* pattern); MOZ_MUST_USE bool emitCallSiteObject(ParseNode* pn); MOZ_MUST_USE bool emitTemplateString(ParseNode* pn); - MOZ_MUST_USE bool emitAssignment(ParseNode* lhs, ParseNodeKind pnk, ParseNode* rhs); + MOZ_MUST_USE bool emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs); + MOZ_MUST_USE bool emitShortCircuitAssignment(ParseNode* pn); MOZ_MUST_USE bool emitReturn(ParseNode* pn); MOZ_MUST_USE bool emitExpressionStatement(ParseNode* pn); @@ -809,16 +734,16 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitOptionalChain(ParseNode* pn, ValueUsage valueUsage); MOZ_MUST_USE bool emitCalleeAndThisForOptionalChain(ParseNode* optionalChain, ParseNode* callNode, - bool isCall, bool isNew); + CallOrNewEmitter& cone); MOZ_MUST_USE bool emitDeleteOptionalChain(ParseNode* pn); // Optional methods which emit a shortCircuit jump. They need to be called by // a method which emits an Optional Jump Target, see below. MOZ_MUST_USE bool emitOptionalDotExpression(PropertyAccessBase* prop, - bool isCall, bool isSuper, + PropOpEmitter& poe, bool isSuper, OptionalEmitter& oe); MOZ_MUST_USE bool emitOptionalElemExpression(PropertyByValueBase* elem, - bool isCall, bool isSuper, + ElemOpEmitter& eoe, bool isSuper, OptionalEmitter& oe); MOZ_MUST_USE bool emitOptionalCall(BinaryNode* callNode, OptionalEmitter& oe, ValueUsage valueUsage); @@ -847,13 +772,15 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE ParseNode* getCoordNode(ParseNode* pn, ParseNode* calleeNode, ParseNode* argsList); - MOZ_MUST_USE bool emitArguments(ParseNode* pn, bool callop, bool spread); + MOZ_MUST_USE bool emitArguments(ListNode* argsList, bool isCall, bool isSpread, + CallOrNewEmitter& cone); MOZ_MUST_USE bool emitCallOrNew(ParseNode* pn, ValueUsage valueUsage = ValueUsage::WantValue); MOZ_MUST_USE bool emitSelfHostedCallFunction(ParseNode* pn); MOZ_MUST_USE bool emitSelfHostedResumeGenerator(ParseNode* pn); MOZ_MUST_USE bool emitSelfHostedForceInterpreter(ParseNode* pn); MOZ_MUST_USE bool emitSelfHostedAllowContentIter(ParseNode* pn); MOZ_MUST_USE bool emitSelfHostedDefineDataProperty(ParseNode* pn); + MOZ_MUST_USE bool emitSelfHostedGetPropertySuper(ParseNode* pn); MOZ_MUST_USE bool emitSelfHostedHasOwn(ParseNode* pn); MOZ_MUST_USE bool emitComprehensionFor(ParseNode* compFor); @@ -878,7 +805,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitFunctionFormalParameters(ParseNode* pn); MOZ_MUST_USE bool emitInitializeFunctionSpecialNames(); MOZ_MUST_USE bool emitFunctionBody(ParseNode* pn); - MOZ_MUST_USE bool emitLexicalInitialization(ParseNode* pn); + MOZ_MUST_USE bool emitLexicalInitialization(ParseNode* name); + MOZ_MUST_USE bool emitLexicalInitialization(JSAtom* name); // Emit bytecode for the spread operator. // @@ -890,18 +818,32 @@ struct MOZ_STACK_CLASS BytecodeEmitter // iteration count). The stack after iteration will look like |ARRAY INDEX|. MOZ_MUST_USE bool emitSpread(bool allowSelfHosted = false); - MOZ_MUST_USE bool emitClass(ParseNode* pn); + enum class ClassNameKind { + // The class name is defined through its BindingIdentifier, if present. + BindingName, + + // The class is anonymous and has a statically inferred name. + InferredName, + + // The class is anonymous and has a dynamically computed name. + ComputedName + }; + + MOZ_MUST_USE bool emitClass( + ParseNode* pn, ClassNameKind nameKind = ClassNameKind::BindingName, + HandleAtom nameForAnonymousClass = nullptr); MOZ_MUST_USE bool emitSuperPropLHS(ParseNode* superBase, bool isCall = false); - MOZ_MUST_USE bool emitSuperPropOp(ParseNode* pn, JSOp op, bool isCall = false); + MOZ_MUST_USE bool emitSuperGetProp(ParseNode* pn, bool isCall = false); MOZ_MUST_USE bool emitSuperElemOperands(ParseNode* pn, EmitElemOption opts = EmitElemOption::Get); - MOZ_MUST_USE bool emitSuperElemOp(ParseNode* pn, JSOp op, bool isCall = false); + MOZ_MUST_USE bool emitSuperGetElem(ParseNode* pn, bool isCall = false); MOZ_MUST_USE bool emitCalleeAndThis(ParseNode* callee, ParseNode* call, - bool isCall, bool isNew); + CallOrNewEmitter& cone); - MOZ_MUST_USE bool emitOptionalCalleeAndThis(ParseNode* callee, ParseNode* call, - bool isCall, bool isNew, + MOZ_MUST_USE bool emitOptionalCalleeAndThis(ParseNode* callee, + ParseNode* call, + CallOrNewEmitter& cone, OptionalEmitter& oe); MOZ_MUST_USE bool emitPipeline(ParseNode* pn); diff --git a/js/src/frontend/CallOrNewEmitter.cpp b/js/src/frontend/CallOrNewEmitter.cpp new file mode 100644 index 000000000000..0b45f355f41a --- /dev/null +++ b/js/src/frontend/CallOrNewEmitter.cpp @@ -0,0 +1,319 @@ +/* -*- 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/CallOrNewEmitter.h" + +#include "frontend/BytecodeEmitter.h" +#include "frontend/NameOpEmitter.h" +#include "frontend/SharedContext.h" +#include "vm/Opcodes.h" +#include "vm/String.h" + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; + +AutoEmittingRunOnceLambda::AutoEmittingRunOnceLambda(BytecodeEmitter* bce) + : bce_(bce) +{ + MOZ_ASSERT(!bce_->emittingRunOnceLambda); + bce_->emittingRunOnceLambda = true; +} + +AutoEmittingRunOnceLambda::~AutoEmittingRunOnceLambda() +{ + bce_->emittingRunOnceLambda = false; +} + +CallOrNewEmitter::CallOrNewEmitter(BytecodeEmitter* bce, JSOp op, + ArgumentsKind argumentsKind, + ValueUsage valueUsage) + : bce_(bce), + op_(op), + argumentsKind_(argumentsKind) +{ + if (op_ == JSOP_CALL && valueUsage == ValueUsage::IgnoreValue) { + op_ = JSOP_CALL_IGNORES_RV; + } + + MOZ_ASSERT(isCall() || isNew() || isSuperCall()); +} + +bool +CallOrNewEmitter::emitNameCallee(JSAtom* name) +{ + MOZ_ASSERT(state_ == State::Start); + + NameOpEmitter noe(bce_, name, + isCall() + ? NameOpEmitter::Kind::Call + : NameOpEmitter::Kind::Get); + if (!noe.emitGet()) { // CALLEE THIS + return false; + } + + state_ = State::NameCallee; + return true; +} + +MOZ_MUST_USE PropOpEmitter& +CallOrNewEmitter::prepareForPropCallee(bool isSuperProp) +{ + MOZ_ASSERT(state_ == State::Start); + + poe_.emplace(bce_, + isCall() + ? PropOpEmitter::Kind::Call + : PropOpEmitter::Kind::Get, + isSuperProp + ? PropOpEmitter::ObjKind::Super + : PropOpEmitter::ObjKind::Other); + + state_ = State::PropCallee; + return *poe_; +} + +MOZ_MUST_USE ElemOpEmitter& +CallOrNewEmitter::prepareForElemCallee(bool isSuperElem) +{ + MOZ_ASSERT(state_ == State::Start); + + eoe_.emplace(bce_, + isCall() + ? ElemOpEmitter::Kind::Call + : ElemOpEmitter::Kind::Get, + isSuperElem + ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other); + + state_ = State::ElemCallee; + return *eoe_; +} + +bool +CallOrNewEmitter::prepareForFunctionCallee() +{ + MOZ_ASSERT(state_ == State::Start); + + // Top level lambdas which are immediately invoked should be treated as + // only running once. Every time they execute we will create new types and + // scripts for their contents, to increase the quality of type information + // within them and enable more backend optimizations. Note that this does + // not depend on the lambda being invoked at most once (it may be named or + // be accessed via foo.caller indirection), as multiple executions will + // just cause the inner scripts to be repeatedly cloned. + MOZ_ASSERT(!bce_->emittingRunOnceLambda); + if (bce_->checkRunOnceContext()) { + autoEmittingRunOnceLambda_.emplace(bce_); + } + + state_ = State::FunctionCallee; + return true; +} + +bool +CallOrNewEmitter::emitSuperCallee() +{ + MOZ_ASSERT(state_ == State::Start); + + if (!bce_->emit1(JSOP_SUPERFUN)) { // CALLEE + return false; + } + if (!bce_->emit1(JSOP_IS_CONSTRUCTING)) { // CALLEE THIS + return false; + } + + state_ = State::SuperCallee; + return true; +} + +bool +CallOrNewEmitter::prepareForOtherCallee() +{ + MOZ_ASSERT(state_ == State::Start); + + state_ = State::OtherCallee; + return true; +} + +bool +CallOrNewEmitter::emitThis() +{ + MOZ_ASSERT(state_ == State::NameCallee || + state_ == State::PropCallee || + state_ == State::ElemCallee || + state_ == State::FunctionCallee || + state_ == State::SuperCallee || + state_ == State::OtherCallee); + + bool needsThis = false; + switch (state_) { + case State::NameCallee: + if (!isCall()) { + needsThis = true; + } + break; + case State::PropCallee: + poe_.reset(); + if (!isCall()) { + needsThis = true; + } + break; + case State::ElemCallee: + eoe_.reset(); + if (!isCall()) { + needsThis = true; + } + break; + case State::FunctionCallee: + autoEmittingRunOnceLambda_.reset(); + needsThis = true; + break; + case State::SuperCallee: + break; + case State::OtherCallee: + needsThis = true; + break; + default:; + } + if (needsThis) { + if (isNew() || isSuperCall()) { + if (!bce_->emit1(JSOP_IS_CONSTRUCTING)) { // CALLEE THIS + return false; + } + } else { + if (!bce_->emit1(JSOP_UNDEFINED)) { // CALLEE THIS + return false; + } + } + } + + state_ = State::This; + return true; +} + +// Used by BytecodeEmitter::emitPipeline to reuse CallOrNewEmitter instance +// across multiple chained calls. +void +CallOrNewEmitter::reset() +{ + MOZ_ASSERT(state_ == State::End); + state_ = State::Start; +} + +bool +CallOrNewEmitter::prepareForNonSpreadArguments() +{ + MOZ_ASSERT(state_ == State::This); + MOZ_ASSERT(!isSpread()); + + state_ = State::Arguments; + return true; +} + +// See the usage in the comment at the top of the class. +bool +CallOrNewEmitter::wantSpreadOperand() +{ + MOZ_ASSERT(state_ == State::This); + MOZ_ASSERT(isSpread()); + + state_ = State::WantSpreadOperand; + return isSingleSpreadRest(); +} + +bool +CallOrNewEmitter::emitSpreadArgumentsTest() +{ + // Caller should check wantSpreadOperand before this. + MOZ_ASSERT(state_ == State::WantSpreadOperand); + MOZ_ASSERT(isSpread()); + + if (isSingleSpreadRest()) { + // Emit a preparation code to optimize the spread call with a rest + // parameter: + // + // function f(...args) { + // g(...args); + // } + // + // If the spread operand is a rest parameter and it's optimizable + // array, skip spread operation and pass it directly to spread call + // operation. See the comment in OptimizeSpreadCall in + // Interpreter.cpp for the optimizable conditons. + + ifNotOptimizable_.emplace(bce_); + // // CALLEE THIS ARG0 + if (!bce_->emit1(JSOP_OPTIMIZE_SPREADCALL)) { // CALLEE THIS ARG0 OPTIMIZED + return false; + } + if (!bce_->emit1(JSOP_NOT)) { // CALLEE THIS ARG0 !OPTIMIZED + return false; + } + if (!ifNotOptimizable_->emitThen()) { // CALLEE THIS ARG0 + return false; + } + if (!bce_->emit1(JSOP_POP)) { // CALLEE THIS + return false; + } + } + + state_ = State::Arguments; + return true; +} + +bool +CallOrNewEmitter::emitEnd(uint32_t argc, const Maybe& beginPos) +{ + MOZ_ASSERT(state_ == State::Arguments); + + if (isSingleSpreadRest()) { + if (!ifNotOptimizable_->emitEnd()) { // CALLEE THIS ARR + return false; + } + + ifNotOptimizable_.reset(); + } + if (isNew() || isSuperCall()) { + if (isSuperCall()) { + if (!bce_->emit1(JSOP_NEWTARGET)) { // CALLEE THIS ARG.. NEW.TARGET + return false; + } + } else { + // Repush the callee as new.target + uint32_t effectiveArgc = isSpread() ? 1 : argc; + if (!bce_->emitDupAt(effectiveArgc + 1)) { + return false; // CALLEE THIS ARR CALLEE + } + } + } + if (!isSpread()) { + if (!bce_->emitCall(op_, argc, beginPos)) { // RVAL + return false; + } + } else { + if (beginPos) { + if (!bce_->updateSourceCoordNotes(*beginPos)) { + return false; + } + } + if (!bce_->emit1(op_)) { // RVAL + return false; + } + } + bce_->checkTypeSet(op_); + + if (isEval() && beginPos) { + uint32_t lineNum = bce_->parser.tokenStream().srcCoords.lineNum(*beginPos); + if (!bce_->emitUint32Operand(JSOP_LINENO, lineNum)) { + return false; + } + } + + state_ = State::End; + return true; +} diff --git a/js/src/frontend/CallOrNewEmitter.h b/js/src/frontend/CallOrNewEmitter.h new file mode 100644 index 000000000000..089fe6bca782 --- /dev/null +++ b/js/src/frontend/CallOrNewEmitter.h @@ -0,0 +1,338 @@ +/* -*- 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_CallOrNewEmitter_h +#define frontend_CallOrNewEmitter_h + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" + +#include + +#include "frontend/ElemOpEmitter.h" +#include "frontend/IfEmitter.h" +#include "frontend/PropOpEmitter.h" +#include "frontend/ValueUsage.h" +#include "js/TypeDecls.h" +#include "vm/Opcodes.h" +#include "jsopcode.h" + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +class MOZ_RAII AutoEmittingRunOnceLambda +{ + BytecodeEmitter* bce_; + + public: + explicit AutoEmittingRunOnceLambda(BytecodeEmitter* bce); + ~AutoEmittingRunOnceLambda(); +}; + +// Class for emitting bytecode for call or new expression. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `print(arg);` +// CallOrNewEmitter cone(this, JSOP_CALL, +// CallOrNewEmitter::ArgumentsKind::Other, +// ValueUsage::WantValue); +// cone.emitNameCallee(); +// emit(print); +// cone.emitThis(); +// cone.prepareForNonSpreadArguments(); +// emit(arg); +// cone.emitEnd(1, Some(offset_of_callee)); +// +// `callee.prop(arg1, arg2);` +// CallOrNewEmitter cone(this, JSOP_CALL, +// CallOrNewEmitter::ArgumentsKind::Other, +// ValueUsage::WantValue); +// PropOpEmitter& poe = cone.prepareForPropCallee(false); +// ... emit `callee.prop` with `poe` here... +// cone.emitThis(); +// cone.prepareForNonSpreadArguments(); +// emit(arg1); +// emit(arg2); +// cone.emitEnd(2, Some(offset_of_callee)); +// +// `callee[key](arg);` +// CallOrNewEmitter cone(this, JSOP_CALL, +// CallOrNewEmitter::ArgumentsKind::Other, +// ValueUsage::WantValue); +// ElemOpEmitter& eoe = cone.prepareForElemCallee(false); +// ... emit `callee[key]` with `eoe` here... +// cone.emitThis(); +// cone.prepareForNonSpreadArguments(); +// emit(arg); +// cone.emitEnd(1, Some(offset_of_callee)); +// +// `(function() { ... })(arg);` +// CallOrNewEmitter cone(this, JSOP_CALL, +// CallOrNewEmitter::ArgumentsKind::Other, +// ValueUsage::WantValue); +// cone.prepareForFunctionCallee(); +// emit(function); +// cone.emitThis(); +// cone.prepareForNonSpreadArguments(); +// emit(arg); +// cone.emitEnd(1, Some(offset_of_callee)); +// +// `super(arg);` +// CallOrNewEmitter cone(this, JSOP_CALL, +// CallOrNewEmitter::ArgumentsKind::Other, +// ValueUsage::WantValue); +// cone.emitSuperCallee(); +// cone.emitThis(); +// cone.prepareForNonSpreadArguments(); +// emit(arg); +// cone.emitEnd(1, Some(offset_of_callee)); +// +// `(some_other_expression)(arg);` +// CallOrNewEmitter cone(this, JSOP_CALL, +// CallOrNewEmitter::ArgumentsKind::Other, +// ValueUsage::WantValue); +// cone.prepareForOtherCallee(); +// emit(some_other_expression); +// cone.emitThis(); +// cone.prepareForNonSpreadArguments(); +// emit(arg); +// cone.emitEnd(1, Some(offset_of_callee)); +// +// `print(...arg);` +// CallOrNewEmitter cone(this, JSOP_SPREADCALL, +// CallOrNewEmitter::ArgumentsKind::Other, +// ValueUsage::WantValue); +// cone.emitNameCallee(); +// emit(print); +// cone.emitThis(); +// if (cone.wantSpreadOperand()) +// emit(arg) +// cone.emitSpreadArgumentsTest(); +// emit([...arg]); +// cone.emitEnd(1, Some(offset_of_callee)); +// +// `print(...rest);` +// where `rest` is rest parameter +// CallOrNewEmitter cone(this, JSOP_SPREADCALL, +// CallOrNewEmitter::ArgumentsKind::SingleSpreadRest, +// ValueUsage::WantValue); +// cone.emitNameCallee(); +// emit(print); +// cone.emitThis(); +// if (cone.wantSpreadOperand()) +// emit(arg) +// cone.emitSpreadArgumentsTest(); +// emit([...arg]); +// cone.emitEnd(1, Some(offset_of_callee)); +// +// `new f(arg);` +// CallOrNewEmitter cone(this, JSOP_NEW, +// CallOrNewEmitter::ArgumentsKind::Other, +// ValueUsage::WantValue); +// cone.emitNameCallee(); +// emit(f); +// cone.emitThis(); +// cone.prepareForNonSpreadArguments(); +// emit(arg); +// cone.emitEnd(1, Some(offset_of_callee)); +// +class MOZ_STACK_CLASS CallOrNewEmitter +{ + public: + enum class ArgumentsKind { + Other, + + // Specify this for the following case: + // + // function f(...rest) { + // g(...rest); + // } + // + // This enables optimization to avoid allocating an intermediate array + // for spread operation. + // + // wantSpreadOperand() returns true when this is specified. + SingleSpreadRest + }; + + private: + BytecodeEmitter* bce_; + + // The opcode for the call or new. + JSOp op_; + + // Whether the call is a spread call with single rest parameter or not. + // See the comment in emitSpreadArgumentsTest for more details. + ArgumentsKind argumentsKind_; + + // The branch for spread call optimization. + mozilla::Maybe ifNotOptimizable_; + + mozilla::Maybe autoEmittingRunOnceLambda_; + + mozilla::Maybe poe_; + mozilla::Maybe eoe_; + + // The state of this emitter. + // + // +-------+ emitNameCallee +------------+ + // | Start |-+------------------------->| NameCallee |------+ + // +-------+ | +------------+ | + // | | + // | prepareForPropCallee +------------+ v + // +------------------------->| PropCallee |----->+ + // | +------------+ | + // | | + // | prepareForElemCallee +------------+ v + // +------------------------->| ElemCallee |----->+ + // | +------------+ | + // | | + // | prepareForFunctionCallee +----------------+ v + // +------------------------->| FunctionCallee |->+ + // | +----------------+ | + // | | + // | emitSuperCallee +-------------+ v + // +------------------------->| SuperCallee |---->+ + // | +-------------+ | + // | | + // | prepareForOtherCallee +-------------+ v + // +------------------------->| OtherCallee |---->+ + // +-------------+ | + // | + // +--------------------------------------------------------+ + // | + // | emitThis +------+ + // +--------->| This |-+ + // +------+ | + // | + // +-------------------+ + // | + // | [!isSpread] + // | prepareForNonSpreadArguments +-----------+ emitEnd +-----+ + // +------------------------------->+->| Arguments |-------->| End | + // | ^ +-----------+ +-----+ + // | | + // | +----------------------------------+ + // | | + // | [isSpread] | + // | wantSpreadOperand +-------------------+ emitSpreadArgumentsTest | + // +-------------------->| WantSpreadOperand |-------------------------+ + // +-------------------+ + enum class State { + // The initial state. + Start, + + // After calling emitNameCallee. + NameCallee, + + // After calling prepareForPropCallee. + PropCallee, + + // After calling prepareForElemCallee. + ElemCallee, + + // After calling prepareForFunctionCallee. + FunctionCallee, + + // After calling emitSuperCallee. + SuperCallee, + + // After calling prepareForOtherCallee. + OtherCallee, + + // After calling emitThis. + This, + + // After calling wantSpreadOperand. + WantSpreadOperand, + + // After calling prepareForNonSpreadArguments. + Arguments, + + // After calling emitEnd. + End + }; + State state_ = State::Start; + + public: + CallOrNewEmitter(BytecodeEmitter* bce, JSOp op, + ArgumentsKind argumentsKind, + ValueUsage valueUsage); + + private: + MOZ_MUST_USE bool isCall() const { + return op_ == JSOP_CALL || op_ == JSOP_CALL_IGNORES_RV || + op_ == JSOP_SPREADCALL || + isEval() || isFunApply() || isFunCall(); + } + + MOZ_MUST_USE bool isNew() const { + return op_ == JSOP_NEW || op_ == JSOP_SPREADNEW; + } + + MOZ_MUST_USE bool isSuperCall() const { + return op_ == JSOP_SUPERCALL || op_ == JSOP_SPREADSUPERCALL; + } + + MOZ_MUST_USE bool isEval() const { + return op_ == JSOP_EVAL || op_ == JSOP_STRICTEVAL || + op_ == JSOP_SPREADEVAL || op_ == JSOP_STRICTSPREADEVAL; + } + + MOZ_MUST_USE bool isFunApply() const { + return op_ == JSOP_FUNAPPLY; + } + + MOZ_MUST_USE bool isFunCall() const { + return op_ == JSOP_FUNCALL; + } + + MOZ_MUST_USE bool isSpread() const { + return JOF_OPTYPE(op_) == JOF_BYTE; + } + + MOZ_MUST_USE bool isSingleSpreadRest() const { + return argumentsKind_ == ArgumentsKind::SingleSpreadRest; + } + + public: + MOZ_MUST_USE bool emitNameCallee(JSAtom* name); + MOZ_MUST_USE PropOpEmitter& prepareForPropCallee(bool isSuperProp); + MOZ_MUST_USE ElemOpEmitter& prepareForElemCallee(bool isSuperElem); + MOZ_MUST_USE bool prepareForFunctionCallee(); + MOZ_MUST_USE bool emitSuperCallee(); + MOZ_MUST_USE bool prepareForOtherCallee(); + + MOZ_MUST_USE bool emitThis(); + + // Used by BytecodeEmitter::emitPipeline to reuse CallOrNewEmitter instance + // across multiple chained calls. + void reset(); + + MOZ_MUST_USE bool prepareForNonSpreadArguments(); + + // See the usage in the comment at the top of the class. + MOZ_MUST_USE bool wantSpreadOperand(); + MOZ_MUST_USE bool emitSpreadArgumentsTest(); + + // Parameters are the offset in the source code for each character below: + // + // callee(arg); + // ^ + // | + // beginPos + // + // Can be Nothing() if not available. + MOZ_MUST_USE bool emitEnd(uint32_t argc, const mozilla::Maybe& beginPos); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_CallOrNewEmitter_h */ diff --git a/js/src/frontend/ElemOpEmitter.cpp b/js/src/frontend/ElemOpEmitter.cpp new file mode 100644 index 000000000000..441ff5e2a4d1 --- /dev/null +++ b/js/src/frontend/ElemOpEmitter.cpp @@ -0,0 +1,280 @@ +/* -*- 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/ElemOpEmitter.h" + +#include "frontend/BytecodeEmitter.h" +#include "frontend/SharedContext.h" +#include "vm/Opcodes.h" + +using namespace js; +using namespace js::frontend; + +ElemOpEmitter::ElemOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind) + : bce_(bce), + kind_(kind), + objKind_(objKind) +{} + +bool +ElemOpEmitter::prepareForObj() +{ + MOZ_ASSERT(state_ == State::Start); + +#ifdef DEBUG + state_ = State::Obj; +#endif + return true; +} + +bool +ElemOpEmitter::prepareForKey() +{ + MOZ_ASSERT(state_ == State::Obj); + + if (!isSuper() && isIncDec()) { + if (!bce_->emit1(JSOP_CHECKOBJCOERCIBLE)) { // OBJ + return false; + } + } + if (isCall()) { + if (!bce_->emit1(JSOP_DUP)) { // [Super] + // // THIS THIS + // // [Other] + // // OBJ OBJ + return false; + } + } + +#ifdef DEBUG + state_ = State::Key; +#endif + return true; +} + +bool +ElemOpEmitter::emitGet() +{ + MOZ_ASSERT(state_ == State::Key); + + if (isIncDec() || isCompoundAssignment()) { + if (!bce_->emit1(JSOP_TOID)) { // [Super] + // // THIS KEY + // // [Other] + // // OBJ KEY + return false; + } + } + if (isSuper()) { + if (!bce_->emit1(JSOP_SUPERBASE)) { // THIS? THIS KEY SUPERBASE + return false; + } + } + if (isIncDec() || isCompoundAssignment()) { + if (isSuper()) { + if (!bce_->emitDupAt(2, 3)) { // THIS KEY SUPERBASE THIS KEY SUPERBASE + return false; + } + } else { + if (!bce_->emit1(JSOP_DUP2)) { // OBJ KEY OBJ KEY + return false; + } + } + } + + JSOp op; + if (isSuper()) { + op = JSOP_GETELEM_SUPER; + } else if (isCall()) { + op = JSOP_CALLELEM; + } else { + op = JSOP_GETELEM; + } + if (!bce_->emitElemOpBase(op)) { // [Get] + // // ELEM + // // [Call] + // // THIS ELEM + // // [Inc/Dec/Assignment, + // // Super] + // // THIS KEY SUPERBASE ELEM + // // [Inc/Dec/Assignment, + // // Other] + // // OBJ KEY ELEM + return false; + } + if (isCall()) { + if (!bce_->emit1(JSOP_SWAP)) { // ELEM THIS + return false; + } + } + +#ifdef DEBUG + state_ = State::Get; +#endif + return true; +} + +bool +ElemOpEmitter::prepareForRhs() +{ + MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment()); + MOZ_ASSERT_IF(isSimpleAssignment(), state_ == State::Key); + MOZ_ASSERT_IF(isCompoundAssignment(), state_ == State::Get); + + if (isSimpleAssignment()) { + // For CompoundAssignment, SUPERBASE is already emitted by emitGet. + if (isSuper()) { + if (!bce_->emit1(JSOP_SUPERBASE)) { // THIS KEY SUPERBASE + return false; + } + } + } + +#ifdef DEBUG + state_ = State::Rhs; +#endif + return true; +} + +bool +ElemOpEmitter::skipObjAndKeyAndRhs() +{ + MOZ_ASSERT(state_ == State::Start); + MOZ_ASSERT(isSimpleAssignment()); + +#ifdef DEBUG + state_ = State::Rhs; +#endif + return true; +} + +bool +ElemOpEmitter::emitDelete() +{ + MOZ_ASSERT(state_ == State::Key); + MOZ_ASSERT(isDelete()); + + if (isSuper()) { + if (!bce_->emit1(JSOP_TOID)) { // THIS KEY + return false; + } + if (!bce_->emit1(JSOP_SUPERBASE)) { // THIS KEY SUPERBASE + return false; + } + + // Unconditionally throw when attempting to delete a super-reference. + if (!bce_->emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER)) { + return false; // THIS KEY SUPERBASE + } + + // Another wrinkle: Balance the stack from the emitter's point of view. + // Execution will not reach here, as the last bytecode threw. + if (!bce_->emitPopN(2)) { // THIS + return false; + } + } else { + JSOp op = bce_->sc->strict() ? JSOP_STRICTDELELEM : JSOP_DELELEM; + if (!bce_->emitElemOpBase(op)){ // SUCCEEDED + return false; + } + } + +#ifdef DEBUG + state_ = State::Delete; +#endif + return true; +} + +bool +ElemOpEmitter::emitAssignment() +{ + MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment()); + MOZ_ASSERT(state_ == State::Rhs); + + JSOp setOp = isSuper() + ? bce_->sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER + : bce_->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM; + if (!bce_->emitElemOpBase(setOp)) { // ELEM + return false; + } + +#ifdef DEBUG + state_ = State::Assignment; +#endif + return true; +} + +bool +ElemOpEmitter::emitIncDec() +{ + MOZ_ASSERT(state_ == State::Key); + MOZ_ASSERT(isIncDec()); + + if (!emitGet()) { // ... ELEM + return false; + } + + MOZ_ASSERT(state_ == State::Get); + + JSOp binOp = isInc() ? JSOP_ADD : JSOP_SUB; + if (!bce_->emit1(JSOP_POS)) { // ... N + return false; + } + if (isPostIncDec()) { + if (!bce_->emit1(JSOP_DUP)) { // ... N? N + return false; + } + } + if (!bce_->emit1(JSOP_ONE)) { // ... N? N 1 + return false; + } + if (!bce_->emit1(binOp)) { // ... N? N+1 + return false; + } + if (isPostIncDec()) { + if (isSuper()) { // THIS KEY OBJ N N+1 + if (!bce_->emit2(JSOP_PICK, 4)) { // KEY SUPERBASE N N+1 THIS + return false; + } + if (!bce_->emit2(JSOP_PICK, 4)) { // SUPERBASE N N+1 THIS KEY + return false; + } + if (!bce_->emit2(JSOP_PICK, 4)) { // N N+1 THIS KEY SUPERBASE + return false; + } + if (!bce_->emit2(JSOP_PICK, 3)) { // N THIS KEY SUPERBASE N+1 + return false; + } + } else { // OBJ KEY N N+1 + if (!bce_->emit2(JSOP_PICK, 3)) { // KEY N N+1 OBJ + return false; + } + if (!bce_->emit2(JSOP_PICK, 3)) { // N N+1 OBJ KEY + return false; + } + if (!bce_->emit2(JSOP_PICK, 2)) { // N OBJ KEY N+1 + return false; + } + } + } + + JSOp setOp = isSuper() + ? (bce_->sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER) + : (bce_->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM); + if (!bce_->emitElemOpBase(setOp)) { // N? N+1 + return false; + } + if (isPostIncDec()) { + if (!bce_->emit1(JSOP_POP)) { // N + return false; + } + } + +#ifdef DEBUG + state_ = State::IncDec; +#endif + return true; +} diff --git a/js/src/frontend/ElemOpEmitter.h b/js/src/frontend/ElemOpEmitter.h new file mode 100644 index 000000000000..48b3eaa43cef --- /dev/null +++ b/js/src/frontend/ElemOpEmitter.h @@ -0,0 +1,278 @@ +/* -*- 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_ElemOpEmitter_h +#define frontend_ElemOpEmitter_h + +#include "mozilla/Attributes.h" + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting bytecode for element operation. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `obj[key];` +// ElemOpEmitter eoe(this, +// ElemOpEmitter::Kind::Get, +// ElemOpEmitter::ObjKind::Other); +// eoe.prepareForObj(); +// emit(obj); +// eoe.prepareForKey(); +// emit(key); +// eoe.emitGet(); +// +// `super[key];` +// ElemOpEmitter eoe(this, +// ElemOpEmitter::Kind::Get, +// ElemOpEmitter::ObjKind::Super); +// eoe.prepareForObj(); +// emit(this_for_super); +// eoe.prepareForKey(); +// emit(key); +// eoe.emitGet(); +// +// `obj[key]();` +// ElemOpEmitter eoe(this, +// ElemOpEmitter::Kind::Call, +// ElemOpEmitter::ObjKind::Other); +// eoe.prepareForObj(); +// emit(obj); +// eoe.prepareForKey(); +// emit(key); +// eoe.emitGet(); +// emit_call_here(); +// +// `new obj[key]();` +// ElemOpEmitter eoe(this, +// ElemOpEmitter::Kind::Call, +// ElemOpEmitter::ObjKind::Other); +// eoe.prepareForObj(); +// emit(obj); +// eoe.prepareForKey(); +// emit(key); +// eoe.emitGet(); +// emit_call_here(); +// +// `delete obj[key];` +// ElemOpEmitter eoe(this, +// ElemOpEmitter::Kind::Delete, +// ElemOpEmitter::ObjKind::Other); +// eoe.prepareForObj(); +// emit(obj); +// eoe.prepareForKey(); +// emit(key); +// eoe.emitDelete(); +// +// `delete super[key];` +// ElemOpEmitter eoe(this, +// ElemOpEmitter::Kind::Delete, +// ElemOpEmitter::ObjKind::Super); +// eoe.prepareForObj(); +// emit(this_for_super); +// eoe.prepareForKey(); +// emit(key); +// eoe.emitDelete(); +// +// `obj[key]++;` +// ElemOpEmitter eoe(this, +// ElemOpEmitter::Kind::PostIncrement, +// ElemOpEmitter::ObjKind::Other); +// eoe.prepareForObj(); +// emit(obj); +// eoe.prepareForKey(); +// emit(key); +// eoe.emitIncDec(); +// +// `obj[key] = value;` +// ElemOpEmitter eoe(this, +// ElemOpEmitter::Kind::SimpleAssignment, +// ElemOpEmitter::ObjKind::Other); +// eoe.prepareForObj(); +// emit(obj); +// eoe.prepareForKey(); +// emit(key); +// eoe.prepareForRhs(); +// emit(value); +// eoe.emitAssignment(); +// +// `obj[key] += value;` +// ElemOpEmitter eoe(this, +// ElemOpEmitter::Kind::CompoundAssignment, +// ElemOpEmitter::ObjKind::Other); +// eoe.prepareForObj(); +// emit(obj); +// eoe.prepareForKey(); +// emit(key); +// eoe.emitGet(); +// eoe.prepareForRhs(); +// emit(value); +// emit_add_op_here(); +// eoe.emitAssignment(); +// +class MOZ_STACK_CLASS ElemOpEmitter +{ + public: + enum class Kind { + Get, + Call, + Set, + Delete, + PostIncrement, + PreIncrement, + PostDecrement, + PreDecrement, + SimpleAssignment, + CompoundAssignment + }; + enum class ObjKind { + Super, + Other + }; + + private: + BytecodeEmitter* bce_; + + Kind kind_; + ObjKind objKind_; + +#ifdef DEBUG + // The state of this emitter. + // + // skipObjAndKeyAndRhs + // +------------------------------------------------+ + // | | + // +-------+ | prepareForObj +-----+ prepareForKey +-----+ | + // | Start |-+-------------->| Obj |-------------->| Key |-+ | + // +-------+ +-----+ +-----+ | | + // | | + // +-------------------------------------------------------+ | + // | | + // | | + // | | + // | [Get] | + // | [Call] | + // | emitGet +-----+ | + // +---------->| Get | | + // | +-----+ | + // | | + // | [Delete] | + // | emitDelete +--------+ | + // +------------->| Delete | | + // | +--------+ | + // | | + // | [PostIncrement] | + // | [PreIncrement] | + // | [PostDecrement] | + // | [PreDecrement] | + // | emitIncDec +--------+ | + // +------------->| IncDec | | + // | +--------+ | + // | +-------------------+ + // | [SimpleAssignment] | + // | prepareForRhs v +-----+ + // +--------------------->+-------------->+->| Rhs |-+ + // | ^ +-----+ | + // | | | + // | | +-------------+ + // | [CompoundAssignment] | | + // | emitGet +-----+ | | emitAssignment +------------+ + // +---------->| Get |----+ +--------------->| Assignment | + // +-----+ +------------+ + enum class State { + // The initial state. + Start, + + // After calling prepareForObj. + Obj, + + // After calling emitKey. + Key, + + // After calling emitGet. + Get, + + // After calling emitDelete. + Delete, + + // After calling emitIncDec. + IncDec, + + // After calling prepareForRhs or skipObjAndKeyAndRhs. + Rhs, + + // After calling emitAssignment. + Assignment, + }; + State state_ = State::Start; +#endif + + public: + ElemOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind); + + private: + MOZ_MUST_USE bool isCall() const { + return kind_ == Kind::Call; + } + + MOZ_MUST_USE bool isSimpleAssignment() const { + return kind_ == Kind::SimpleAssignment; + } + + MOZ_MUST_USE bool isDelete() const { + return kind_ == Kind::Delete; + } + + MOZ_MUST_USE bool isCompoundAssignment() const { + return kind_ == Kind::CompoundAssignment; + } + + MOZ_MUST_USE bool isIncDec() const { + return isPostIncDec() || isPreIncDec(); + } + + MOZ_MUST_USE bool isPostIncDec() const { + return kind_ == Kind::PostIncrement || + kind_ == Kind::PostDecrement; + } + + MOZ_MUST_USE bool isPreIncDec() const { + return kind_ == Kind::PreIncrement || + kind_ == Kind::PreDecrement; + } + + MOZ_MUST_USE bool isInc() const { + return kind_ == Kind::PostIncrement || + kind_ == Kind::PreIncrement; + } + + MOZ_MUST_USE bool isSuper() const { + return objKind_ == ObjKind::Super; + } + + public: + MOZ_MUST_USE bool prepareForObj(); + MOZ_MUST_USE bool prepareForKey(); + + MOZ_MUST_USE bool emitGet(); + + MOZ_MUST_USE bool prepareForRhs(); + MOZ_MUST_USE bool skipObjAndKeyAndRhs(); + + MOZ_MUST_USE bool emitDelete(); + + MOZ_MUST_USE bool emitAssignment(); + + MOZ_MUST_USE bool emitIncDec(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_ElemOpEmitter_h */ diff --git a/js/src/frontend/EmitterScope.cpp b/js/src/frontend/EmitterScope.cpp new file mode 100644 index 000000000000..df9ae154ad6d --- /dev/null +++ b/js/src/frontend/EmitterScope.cpp @@ -0,0 +1,1090 @@ +/* -*- 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/EmitterScope.h" + +#include "frontend/BytecodeEmitter.h" +#include "frontend/TDZCheckCache.h" + +using namespace js; +using namespace js::frontend; + +using mozilla::DebugOnly; +using mozilla::Maybe; +using mozilla::Nothing; +using mozilla::Some; + +EmitterScope::EmitterScope(BytecodeEmitter* bce) + : Nestable(&bce->innermostEmitterScope_), + nameCache_(bce->cx->frontendCollectionPool()), + hasEnvironment_(false), + environmentChainLength_(0), + nextFrameSlot_(0), + scopeIndex_(ScopeNote::NoScopeIndex), + noteIndex_(ScopeNote::NoScopeNoteIndex) +{} + +static inline void +MarkAllBindingsClosedOver(LexicalScope::Data& data) +{ + BindingName* names = data.names; + for (uint32_t i = 0; i < data.length; i++) + names[i] = BindingName(names[i].name(), true); +} + +bool +EmitterScope::ensureCache(BytecodeEmitter* bce) +{ + return nameCache_.acquire(bce->cx); +} + +template +bool +EmitterScope::checkSlotLimits(BytecodeEmitter* bce, const BindingIter& bi) +{ + if (bi.nextFrameSlot() >= LOCALNO_LIMIT || + bi.nextEnvironmentSlot() >= ENVCOORD_SLOT_LIMIT) + { + bce->reportError(nullptr, JSMSG_TOO_MANY_LOCALS); + return false; + } + return true; +} + +bool +EmitterScope::checkEnvironmentChainLength(BytecodeEmitter* bce) +{ + uint32_t hops; + if (EmitterScope* emitterScope = enclosing(&bce)) + hops = emitterScope->environmentChainLength_; + else + hops = bce->sc->compilationEnclosingScope()->environmentChainLength(); + + if (hops >= ENVCOORD_HOPS_LIMIT - 1) { + bce->reportError(nullptr, JSMSG_TOO_DEEP, js_function_str); + return false; + } + + environmentChainLength_ = mozilla::AssertedCast(hops + 1); + return true; +} + +void +EmitterScope::updateFrameFixedSlots(BytecodeEmitter* bce, const BindingIter& bi) +{ + nextFrameSlot_ = bi.nextFrameSlot(); + if (nextFrameSlot_ > bce->maxFixedSlots) + bce->maxFixedSlots = nextFrameSlot_; + MOZ_ASSERT_IF(bce->sc->isFunctionBox() && + (bce->sc->asFunctionBox()->isStarGenerator() || + bce->sc->asFunctionBox()->isLegacyGenerator() || + bce->sc->asFunctionBox()->isAsync()), + bce->maxFixedSlots == 0); +} + +bool +EmitterScope::putNameInCache(BytecodeEmitter* bce, JSAtom* name, NameLocation loc) +{ + NameLocationMap& cache = *nameCache_; + NameLocationMap::AddPtr p = cache.lookupForAdd(name); + MOZ_ASSERT(!p); + if (!cache.add(p, name, loc)) { + ReportOutOfMemory(bce->cx); + return false; + } + return true; +} + +Maybe +EmitterScope::lookupInCache(BytecodeEmitter* bce, JSAtom* name) +{ + if (NameLocationMap::Ptr p = nameCache_->lookup(name)) + return Some(p->value().wrapped); + if (fallbackFreeNameLocation_ && nameCanBeFree(bce, name)) + return fallbackFreeNameLocation_; + return Nothing(); +} + +EmitterScope* +EmitterScope::enclosing(BytecodeEmitter** bce) const +{ + // There is an enclosing scope with access to the same frame. + if (EmitterScope* inFrame = enclosingInFrame()) + return inFrame; + + // We are currently compiling the enclosing script, look in the + // enclosing BCE. + if ((*bce)->parent) { + *bce = (*bce)->parent; + return (*bce)->innermostEmitterScopeNoCheck(); + } + + return nullptr; +} + +Scope* +EmitterScope::enclosingScope(BytecodeEmitter* bce) const +{ + if (EmitterScope* es = enclosing(&bce)) + return es->scope(bce); + + // The enclosing script is already compiled or the current script is the + // global script. + return bce->sc->compilationEnclosingScope(); +} + +/* static */ bool +EmitterScope::nameCanBeFree(BytecodeEmitter* bce, JSAtom* name) +{ + // '.generator' cannot be accessed by name. + return name != bce->cx->names().dotGenerator; +} + +#ifdef DEBUG +static bool +NameIsOnEnvironment(Scope* scope, JSAtom* name) +{ + for (BindingIter bi(scope); bi; bi++) { + // If found, the name must already be on the environment or an import, + // or else there is a bug in the closed-over name analysis in the + // Parser. + if (bi.name() == name) { + BindingLocation::Kind kind = bi.location().kind(); + + if (bi.hasArgumentSlot()) { + JSScript* script = scope->as().script(); + if (!script->strict() && !script->functionHasParameterExprs()) { + // Check for duplicate positional formal parameters. + for (BindingIter bi2(bi); bi2 && bi2.hasArgumentSlot(); bi2++) { + if (bi2.name() == name) + kind = bi2.location().kind(); + } + } + } + + return kind == BindingLocation::Kind::Global || + kind == BindingLocation::Kind::Environment || + kind == BindingLocation::Kind::Import; + } + } + + // If not found, assume it's on the global or dynamically accessed. + return true; +} +#endif + +/* static */ NameLocation +EmitterScope::searchInEnclosingScope(JSAtom* name, Scope* scope, uint8_t hops) +{ + for (ScopeIter si(scope); si; si++) { + MOZ_ASSERT(NameIsOnEnvironment(si.scope(), name)); + + bool hasEnv = si.hasSyntacticEnvironment(); + + switch (si.kind()) { + case ScopeKind::Function: + if (hasEnv) { + JSScript* script = si.scope()->as().script(); + if (script->funHasExtensibleScope()) + return NameLocation::Dynamic(); + + for (BindingIter bi(si.scope()); bi; bi++) { + if (bi.name() != name) + continue; + + BindingLocation bindLoc = bi.location(); + if (bi.hasArgumentSlot() && + !script->strict() && + !script->functionHasParameterExprs()) + { + // Check for duplicate positional formal parameters. + for (BindingIter bi2(bi); bi2 && bi2.hasArgumentSlot(); bi2++) { + if (bi2.name() == name) + bindLoc = bi2.location(); + } + } + + MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment); + return NameLocation::EnvironmentCoordinate(bi.kind(), hops, bindLoc.slot()); + } + } + break; + + case ScopeKind::FunctionBodyVar: + case ScopeKind::ParameterExpressionVar: + case ScopeKind::Lexical: + case ScopeKind::NamedLambda: + case ScopeKind::StrictNamedLambda: + case ScopeKind::SimpleCatch: + case ScopeKind::Catch: + if (hasEnv) { + for (BindingIter bi(si.scope()); bi; bi++) { + if (bi.name() != name) + continue; + + // The name must already have been marked as closed + // over. If this assertion is hit, there is a bug in the + // name analysis. + BindingLocation bindLoc = bi.location(); + MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment); + return NameLocation::EnvironmentCoordinate(bi.kind(), hops, bindLoc.slot()); + } + } + break; + + case ScopeKind::Module: + if (hasEnv) { + for (BindingIter bi(si.scope()); bi; bi++) { + if (bi.name() != name) + continue; + + BindingLocation bindLoc = bi.location(); + + // Imports are on the environment but are indirect + // bindings and must be accessed dynamically instead of + // using an EnvironmentCoordinate. + if (bindLoc.kind() == BindingLocation::Kind::Import) { + MOZ_ASSERT(si.kind() == ScopeKind::Module); + return NameLocation::Import(); + } + + MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment); + return NameLocation::EnvironmentCoordinate(bi.kind(), hops, bindLoc.slot()); + } + } + break; + + case ScopeKind::Eval: + case ScopeKind::StrictEval: + // As an optimization, if the eval doesn't have its own var + // environment and its immediate enclosing scope is a global + // scope, all accesses are global. + if (!hasEnv && si.scope()->enclosing()->is()) + return NameLocation::Global(BindingKind::Var); + return NameLocation::Dynamic(); + + case ScopeKind::Global: + return NameLocation::Global(BindingKind::Var); + + case ScopeKind::With: + case ScopeKind::NonSyntactic: + return NameLocation::Dynamic(); + + case ScopeKind::WasmInstance: + case ScopeKind::WasmFunction: + MOZ_CRASH("No direct eval inside wasm functions"); + } + + if (hasEnv) { + MOZ_ASSERT(hops < ENVCOORD_HOPS_LIMIT - 1); + hops++; + } + } + + MOZ_CRASH("Malformed scope chain"); +} + +NameLocation +EmitterScope::searchAndCache(BytecodeEmitter* bce, JSAtom* name) +{ + Maybe loc; + uint8_t hops = hasEnvironment() ? 1 : 0; + DebugOnly inCurrentScript = enclosingInFrame(); + + // Start searching in the current compilation. + for (EmitterScope* es = enclosing(&bce); es; es = es->enclosing(&bce)) { + loc = es->lookupInCache(bce, name); + if (loc) { + if (loc->kind() == NameLocation::Kind::EnvironmentCoordinate) + *loc = loc->addHops(hops); + break; + } + + if (es->hasEnvironment()) + hops++; + +#ifdef DEBUG + if (!es->enclosingInFrame()) + inCurrentScript = false; +#endif + } + + // If the name is not found in the current compilation, walk the Scope + // chain encompassing the compilation. + if (!loc) { + inCurrentScript = false; + loc = Some(searchInEnclosingScope(name, bce->sc->compilationEnclosingScope(), hops)); + } + + // Each script has its own frame. A free name that is accessed + // from an inner script must not be a frame slot access. If this + // assertion is hit, it is a bug in the free name analysis in the + // parser. + MOZ_ASSERT_IF(!inCurrentScript, loc->kind() != NameLocation::Kind::FrameSlot); + + // It is always correct to not cache the location. Ignore OOMs to make + // lookups infallible. + if (!putNameInCache(bce, name, *loc)) + bce->cx->recoverFromOutOfMemory(); + + return *loc; +} + +template +bool +EmitterScope::internScope(BytecodeEmitter* bce, ScopeCreator createScope) +{ + RootedScope enclosing(bce->cx, enclosingScope(bce)); + Scope* scope = createScope(bce->cx, enclosing); + if (!scope) + return false; + hasEnvironment_ = scope->hasEnvironment(); + scopeIndex_ = bce->scopeList.length(); + return bce->scopeList.append(scope); +} + +template +bool +EmitterScope::internBodyScope(BytecodeEmitter* bce, ScopeCreator createScope) +{ + MOZ_ASSERT(bce->bodyScopeIndex == UINT32_MAX, "There can be only one body scope"); + bce->bodyScopeIndex = bce->scopeList.length(); + return internScope(bce, createScope); +} + +bool +EmitterScope::appendScopeNote(BytecodeEmitter* bce) +{ + MOZ_ASSERT(ScopeKindIsInBody(scope(bce)->kind()) && enclosingInFrame(), + "Scope notes are not needed for body-level scopes."); + noteIndex_ = bce->scopeNoteList.length(); + return bce->scopeNoteList.append(index(), bce->offset(), bce->inPrologue(), + enclosingInFrame() ? enclosingInFrame()->noteIndex() + : ScopeNote::NoScopeNoteIndex); +} + +bool +EmitterScope::deadZoneFrameSlotRange(BytecodeEmitter* bce, uint32_t slotStart, uint32_t slotEnd) +{ + // Lexical bindings throw ReferenceErrors if they are used before + // initialization. See ES6 8.1.1.1.6. + // + // For completeness, lexical bindings are initialized in ES6 by calling + // InitializeBinding, after which touching the binding will no longer + // throw reference errors. See 13.1.11, 9.2.13, 13.6.3.4, 13.6.4.6, + // 13.6.4.8, 13.14.5, 15.1.8, and 15.2.0.15. + if (slotStart != slotEnd) { + if (!bce->emit1(JSOP_UNINITIALIZED)) + return false; + for (uint32_t slot = slotStart; slot < slotEnd; slot++) { + if (!bce->emitLocalOp(JSOP_INITLEXICAL, slot)) + return false; + } + if (!bce->emit1(JSOP_POP)) + return false; + } + + return true; +} + +void +EmitterScope::dump(BytecodeEmitter* bce) +{ + fprintf(stdout, "EmitterScope [%s] %p\n", ScopeKindString(scope(bce)->kind()), this); + + for (NameLocationMap::Range r = nameCache_->all(); !r.empty(); r.popFront()) { + const NameLocation& l = r.front().value(); + + JSAutoByteString bytes; + if (!AtomToPrintableString(bce->cx, r.front().key(), &bytes)) + return; + if (l.kind() != NameLocation::Kind::Dynamic) + fprintf(stdout, " %s %s ", BindingKindString(l.bindingKind()), bytes.ptr()); + else + fprintf(stdout, " %s ", bytes.ptr()); + + switch (l.kind()) { + case NameLocation::Kind::Dynamic: + fprintf(stdout, "dynamic\n"); + break; + case NameLocation::Kind::Global: + fprintf(stdout, "global\n"); + break; + case NameLocation::Kind::Intrinsic: + fprintf(stdout, "intrinsic\n"); + break; + case NameLocation::Kind::NamedLambdaCallee: + fprintf(stdout, "named lambda callee\n"); + break; + case NameLocation::Kind::Import: + fprintf(stdout, "import\n"); + break; + case NameLocation::Kind::ArgumentSlot: + fprintf(stdout, "arg slot=%u\n", l.argumentSlot()); + break; + case NameLocation::Kind::FrameSlot: + fprintf(stdout, "frame slot=%u\n", l.frameSlot()); + break; + case NameLocation::Kind::EnvironmentCoordinate: + fprintf(stdout, "environment hops=%u slot=%u\n", + l.environmentCoordinate().hops(), l.environmentCoordinate().slot()); + break; + case NameLocation::Kind::DynamicAnnexBVar: + fprintf(stdout, "dynamic annex b var\n"); + break; + } + } + + fprintf(stdout, "\n"); +} + +bool +EmitterScope::enterLexical(BytecodeEmitter* bce, ScopeKind kind, + Handle bindings) +{ + MOZ_ASSERT(kind != ScopeKind::NamedLambda && kind != ScopeKind::StrictNamedLambda); + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + + if (!ensureCache(bce)) + return false; + + // Marks all names as closed over if the context requires it. This + // cannot be done in the Parser as we may not know if the context requires + // all bindings to be closed over until after parsing is finished. For + // example, legacy generators require all bindings to be closed over but + // it is unknown if a function is a legacy generator until the first + // 'yield' expression is parsed. + // + // This is not a problem with other scopes, as all other scopes with + // bindings are body-level. At the time of their creation, whether or not + // the context requires all bindings to be closed over is already known. + if (bce->sc->allBindingsClosedOver()) + MarkAllBindingsClosedOver(*bindings); + + // Resolve bindings. + TDZCheckCache* tdzCache = bce->innermostTDZCheckCache; + uint32_t firstFrameSlot = frameSlotStart(); + BindingIter bi(*bindings, firstFrameSlot, /* isNamedLambda = */ false); + for (; bi; bi++) { + if (!checkSlotLimits(bce, bi)) + return false; + + NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location()); + if (!putNameInCache(bce, bi.name(), loc)) + return false; + + if (!tdzCache->noteTDZCheck(bce, bi.name(), CheckTDZ)) + return false; + } + + updateFrameFixedSlots(bce, bi); + + // Create and intern the VM scope. + auto createScope = [kind, bindings, firstFrameSlot](JSContext* cx, + HandleScope enclosing) + { + return LexicalScope::create(cx, kind, bindings, firstFrameSlot, enclosing); + }; + if (!internScope(bce, createScope)) + return false; + + if (ScopeKindIsInBody(kind) && hasEnvironment()) { + // After interning the VM scope we can get the scope index. + if (!bce->emitInternedScopeOp(index(), JSOP_PUSHLEXICALENV)) + return false; + } + + // Lexical scopes need notes to be mapped from a pc. + if (!appendScopeNote(bce)) + return false; + + // Put frame slots in TDZ. Environment slots are poisoned during + // environment creation. + // + // This must be done after appendScopeNote to be considered in the extent + // of the scope. + if (!deadZoneFrameSlotRange(bce, firstFrameSlot, frameSlotEnd())) + return false; + + return checkEnvironmentChainLength(bce); +} + +bool +EmitterScope::enterNamedLambda(BytecodeEmitter* bce, FunctionBox* funbox) +{ + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + MOZ_ASSERT(funbox->namedLambdaBindings()); + + if (!ensureCache(bce)) + return false; + + // See comment in enterLexical about allBindingsClosedOver. + if (funbox->allBindingsClosedOver()) + MarkAllBindingsClosedOver(*funbox->namedLambdaBindings()); + + BindingIter bi(*funbox->namedLambdaBindings(), LOCALNO_LIMIT, /* isNamedLambda = */ true); + MOZ_ASSERT(bi.kind() == BindingKind::NamedLambdaCallee); + + // The lambda name, if not closed over, is accessed via JSOP_CALLEE and + // not a frame slot. Do not update frame slot information. + NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location()); + if (!putNameInCache(bce, bi.name(), loc)) + return false; + + bi++; + MOZ_ASSERT(!bi, "There should be exactly one binding in a NamedLambda scope"); + + auto createScope = [funbox](JSContext* cx, HandleScope enclosing) { + ScopeKind scopeKind = + funbox->strict() ? ScopeKind::StrictNamedLambda : ScopeKind::NamedLambda; + return LexicalScope::create(cx, scopeKind, funbox->namedLambdaBindings(), + LOCALNO_LIMIT, enclosing); + }; + if (!internScope(bce, createScope)) + return false; + + return checkEnvironmentChainLength(bce); +} + +bool +EmitterScope::enterComprehensionFor(BytecodeEmitter* bce, + Handle bindings) +{ + if (!enterLexical(bce, ScopeKind::Lexical, bindings)) + return false; + + // For comprehensions, initialize all lexical names up front to undefined + // because they're now a dead feature and don't interact properly with + // TDZ. + if (!bce->emit1(JSOP_UNDEFINED)) + return false; + + RootedAtom name(bce->cx); + for (BindingIter bi(*bindings, frameSlotStart(), /* isNamedLambda = */ false); bi; bi++) { + name = bi.name(); + NameOpEmitter noe(bce, name, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) + return false; + if (!noe.emitAssignment()) + return false; + } + + if (!bce->emit1(JSOP_POP)) + return false; + + return true; +} + +bool +EmitterScope::enterFunction(BytecodeEmitter* bce, FunctionBox* funbox) +{ + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + + // If there are parameter expressions, there is an extra var scope. + if (!funbox->hasExtraBodyVarScope()) + bce->setVarEmitterScope(this); + + if (!ensureCache(bce)) + return false; + + // Resolve body-level bindings, if there are any. + auto bindings = funbox->functionScopeBindings(); + Maybe lastLexicalSlot; + if (bindings) { + NameLocationMap& cache = *nameCache_; + + BindingIter bi(*bindings, funbox->hasParameterExprs); + for (; bi; bi++) { + if (!checkSlotLimits(bce, bi)) + return false; + + NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location()); + NameLocationMap::AddPtr p = cache.lookupForAdd(bi.name()); + + // The only duplicate bindings that occur are simple formal + // parameters, in which case the last position counts, so update the + // location. + if (p) { + MOZ_ASSERT(bi.kind() == BindingKind::FormalParameter); + MOZ_ASSERT(!funbox->hasDestructuringArgs); + MOZ_ASSERT(!funbox->hasRest()); + p->value() = loc; + continue; + } + + if (!cache.add(p, bi.name(), loc)) { + ReportOutOfMemory(bce->cx); + return false; + } + } + + updateFrameFixedSlots(bce, bi); + } else { + nextFrameSlot_ = 0; + } + + // If the function's scope may be extended at runtime due to sloppy direct + // eval and there is no extra var scope, any names beyond the function + // scope must be accessed dynamically as we don't know if the name will + // become a 'var' binding due to direct eval. + if (!funbox->hasParameterExprs && funbox->hasExtensibleScope()) + fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); + + // In case of parameter expressions, the parameters are lexical + // bindings and have TDZ. + if (funbox->hasParameterExprs && nextFrameSlot_) { + uint32_t paramFrameSlotEnd = 0; + for (BindingIter bi(*bindings, true); bi; bi++) { + if (!BindingKindIsLexical(bi.kind())) + break; + + NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location()); + if (loc.kind() == NameLocation::Kind::FrameSlot) { + MOZ_ASSERT(paramFrameSlotEnd <= loc.frameSlot()); + paramFrameSlotEnd = loc.frameSlot() + 1; + } + } + + if (!deadZoneFrameSlotRange(bce, 0, paramFrameSlotEnd)) + return false; + } + + // Create and intern the VM scope. + auto createScope = [funbox](JSContext* cx, HandleScope enclosing) { + RootedFunction fun(cx, funbox->function()); + return FunctionScope::create(cx, funbox->functionScopeBindings(), + funbox->hasParameterExprs, + funbox->needsCallObjectRegardlessOfBindings(), + fun, enclosing); + }; + if (!internBodyScope(bce, createScope)) + return false; + + return checkEnvironmentChainLength(bce); +} + +bool +EmitterScope::enterFunctionExtraBodyVar(BytecodeEmitter* bce, FunctionBox* funbox) +{ + MOZ_ASSERT(funbox->hasParameterExprs); + MOZ_ASSERT(funbox->extraVarScopeBindings() || + funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings()); + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + + // The extra var scope is never popped once it's entered. It replaces the + // function scope as the var emitter scope. + bce->setVarEmitterScope(this); + + if (!ensureCache(bce)) + return false; + + // Resolve body-level bindings, if there are any. + uint32_t firstFrameSlot = frameSlotStart(); + if (auto bindings = funbox->extraVarScopeBindings()) { + BindingIter bi(*bindings, firstFrameSlot); + for (; bi; bi++) { + if (!checkSlotLimits(bce, bi)) + return false; + + NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location()); + if (!putNameInCache(bce, bi.name(), loc)) + return false; + } + + updateFrameFixedSlots(bce, bi); + } else { + nextFrameSlot_ = firstFrameSlot; + } + + // If the extra var scope may be extended at runtime due to sloppy + // direct eval, any names beyond the var scope must be accessed + // dynamically as we don't know if the name will become a 'var' binding + // due to direct eval. + if (funbox->hasExtensibleScope()) + fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); + + // Create and intern the VM scope. + auto createScope = [funbox, firstFrameSlot](JSContext* cx, HandleScope enclosing) { + return VarScope::create(cx, ScopeKind::FunctionBodyVar, + funbox->extraVarScopeBindings(), firstFrameSlot, + funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings(), + enclosing); + }; + if (!internScope(bce, createScope)) + return false; + + if (hasEnvironment()) { + if (!bce->emitInternedScopeOp(index(), JSOP_PUSHVARENV)) + return false; + } + + // The extra var scope needs a note to be mapped from a pc. + if (!appendScopeNote(bce)) + return false; + + return checkEnvironmentChainLength(bce); +} + +bool +EmitterScope::enterParameterExpressionVar(BytecodeEmitter* bce) +{ + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + + if (!ensureCache(bce)) + return false; + + // Parameter expressions var scopes have no pre-set bindings and are + // always extensible, as they are needed for eval. + fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); + + // Create and intern the VM scope. + uint32_t firstFrameSlot = frameSlotStart(); + auto createScope = [firstFrameSlot](JSContext* cx, HandleScope enclosing) { + return VarScope::create(cx, ScopeKind::ParameterExpressionVar, + /* data = */ nullptr, firstFrameSlot, + /* needsEnvironment = */ true, enclosing); + }; + if (!internScope(bce, createScope)) + return false; + + MOZ_ASSERT(hasEnvironment()); + if (!bce->emitInternedScopeOp(index(), JSOP_PUSHVARENV)) + return false; + + // The extra var scope needs a note to be mapped from a pc. + if (!appendScopeNote(bce)) + return false; + + return checkEnvironmentChainLength(bce); +} + +class DynamicBindingIter : public BindingIter +{ + public: + explicit DynamicBindingIter(GlobalSharedContext* sc) + : BindingIter(*sc->bindings) + { } + + explicit DynamicBindingIter(EvalSharedContext* sc) + : BindingIter(*sc->bindings, /* strict = */ false) + { + MOZ_ASSERT(!sc->strict()); + } + + JSOp bindingOp() const { + switch (kind()) { + case BindingKind::Var: + return JSOP_DEFVAR; + case BindingKind::Let: + return JSOP_DEFLET; + case BindingKind::Const: + return JSOP_DEFCONST; + default: + MOZ_CRASH("Bad BindingKind"); + } + } +}; + +bool +EmitterScope::enterGlobal(BytecodeEmitter* bce, GlobalSharedContext* globalsc) +{ + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + + bce->setVarEmitterScope(this); + + if (!ensureCache(bce)) + return false; + + if (bce->emitterMode == BytecodeEmitter::SelfHosting) { + // In self-hosting, it is incorrect to consult the global scope because + // self-hosted scripts are cloned into their target compartments before + // they are run. Instead of Global, Intrinsic is used for all names. + // + // Intrinsic lookups are redirected to the special intrinsics holder + // in the global object, into which any missing values are cloned + // lazily upon first access. + fallbackFreeNameLocation_ = Some(NameLocation::Intrinsic()); + + auto createScope = [](JSContext* cx, HandleScope enclosing) { + MOZ_ASSERT(!enclosing); + return &cx->global()->emptyGlobalScope(); + }; + return internBodyScope(bce, createScope); + } + + // Resolve binding names and emit DEF{VAR,LET,CONST} prologue ops. + if (globalsc->bindings) { + for (DynamicBindingIter bi(globalsc); bi; bi++) { + NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location()); + JSAtom* name = bi.name(); + if (!putNameInCache(bce, name, loc)) + return false; + + // Define the name in the prologue. Do not emit DEFVAR for + // functions that we'll emit DEFFUN for. + if (bi.isTopLevelFunction()) + continue; + + if (!bce->emitAtomOp(name, bi.bindingOp())) + return false; + } + } + + // Note that to save space, we don't add free names to the cache for + // global scopes. They are assumed to be global vars in the syntactic + // global scope, dynamic accesses under non-syntactic global scope. + if (globalsc->scopeKind() == ScopeKind::Global) + fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var)); + else + fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); + + auto createScope = [globalsc](JSContext* cx, HandleScope enclosing) { + MOZ_ASSERT(!enclosing); + return GlobalScope::create(cx, globalsc->scopeKind(), globalsc->bindings); + }; + return internBodyScope(bce, createScope); +} + +bool +EmitterScope::enterEval(BytecodeEmitter* bce, EvalSharedContext* evalsc) +{ + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + + bce->setVarEmitterScope(this); + + if (!ensureCache(bce)) + return false; + + // For simplicity, treat all free name lookups in eval scripts as dynamic. + fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); + + // Create the `var` scope. Note that there is also a lexical scope, created + // separately in emitScript(). + auto createScope = [evalsc](JSContext* cx, HandleScope enclosing) { + ScopeKind scopeKind = evalsc->strict() ? ScopeKind::StrictEval : ScopeKind::Eval; + return EvalScope::create(cx, scopeKind, evalsc->bindings, enclosing); + }; + if (!internBodyScope(bce, createScope)) + return false; + + if (hasEnvironment()) { + if (!bce->emitInternedScopeOp(index(), JSOP_PUSHVARENV)) + return false; + } else { + // Resolve binding names and emit DEFVAR prologue ops if we don't have + // an environment (i.e., a sloppy eval not in a parameter expression). + // Eval scripts always have their own lexical scope, but non-strict + // scopes may introduce 'var' bindings to the nearest var scope. + // + // TODO: We may optimize strict eval bindings in the future to be on + // the frame. For now, handle everything dynamically. + if (!hasEnvironment() && evalsc->bindings) { + for (DynamicBindingIter bi(evalsc); bi; bi++) { + MOZ_ASSERT(bi.bindingOp() == JSOP_DEFVAR); + + if (bi.isTopLevelFunction()) + continue; + + if (!bce->emitAtomOp(bi.name(), JSOP_DEFVAR)) + return false; + } + } + + // As an optimization, if the eval does not have its own var + // environment and is directly enclosed in a global scope, then all + // free name lookups are global. + if (scope(bce)->enclosing()->is()) + fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var)); + } + + return true; +} + +bool +EmitterScope::enterModule(BytecodeEmitter* bce, ModuleSharedContext* modulesc) +{ + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + + bce->setVarEmitterScope(this); + + if (!ensureCache(bce)) + return false; + + // Resolve body-level bindings, if there are any. + TDZCheckCache* tdzCache = bce->innermostTDZCheckCache; + Maybe firstLexicalFrameSlot; + if (ModuleScope::Data* bindings = modulesc->bindings) { + BindingIter bi(*bindings); + for (; bi; bi++) { + if (!checkSlotLimits(bce, bi)) + return false; + + NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location()); + if (!putNameInCache(bce, bi.name(), loc)) + return false; + + if (BindingKindIsLexical(bi.kind())) { + if (loc.kind() == NameLocation::Kind::FrameSlot && !firstLexicalFrameSlot) + firstLexicalFrameSlot = Some(loc.frameSlot()); + + if (!tdzCache->noteTDZCheck(bce, bi.name(), CheckTDZ)) + return false; + } + } + + updateFrameFixedSlots(bce, bi); + } else { + nextFrameSlot_ = 0; + } + + // Modules are toplevel, so any free names are global. + fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var)); + + // Put lexical frame slots in TDZ. Environment slots are poisoned during + // environment creation. + if (firstLexicalFrameSlot) { + if (!deadZoneFrameSlotRange(bce, *firstLexicalFrameSlot, frameSlotEnd())) + return false; + } + + // Create and intern the VM scope. + auto createScope = [modulesc](JSContext* cx, HandleScope enclosing) { + return ModuleScope::create(cx, modulesc->bindings, modulesc->module(), enclosing); + }; + if (!internBodyScope(bce, createScope)) + return false; + + return checkEnvironmentChainLength(bce); +} + +bool +EmitterScope::enterWith(BytecodeEmitter* bce) +{ + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + + if (!ensureCache(bce)) + return false; + + // 'with' make all accesses dynamic and unanalyzable. + fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); + + auto createScope = [](JSContext* cx, HandleScope enclosing) { + return WithScope::create(cx, enclosing); + }; + if (!internScope(bce, createScope)) + return false; + + if (!bce->emitInternedScopeOp(index(), JSOP_ENTERWITH)) + return false; + + if (!appendScopeNote(bce)) + return false; + + return checkEnvironmentChainLength(bce); +} + +bool +EmitterScope::deadZoneFrameSlots(BytecodeEmitter* bce) +{ + return deadZoneFrameSlotRange(bce, frameSlotStart(), frameSlotEnd()); +} + +bool +EmitterScope::leave(BytecodeEmitter* bce, bool nonLocal) +{ + // If we aren't leaving the scope due to a non-local jump (e.g., break), + // we must be the innermost scope. + MOZ_ASSERT_IF(!nonLocal, this == bce->innermostEmitterScopeNoCheck()); + + ScopeKind kind = scope(bce)->kind(); + switch (kind) { + case ScopeKind::Lexical: + case ScopeKind::SimpleCatch: + case ScopeKind::Catch: + if (!bce->emit1(hasEnvironment() ? JSOP_POPLEXICALENV : JSOP_DEBUGLEAVELEXICALENV)) + return false; + break; + + case ScopeKind::With: + if (!bce->emit1(JSOP_LEAVEWITH)) + return false; + break; + + case ScopeKind::ParameterExpressionVar: + MOZ_ASSERT(hasEnvironment()); + if (!bce->emit1(JSOP_POPVARENV)) + return false; + break; + + case ScopeKind::Function: + case ScopeKind::FunctionBodyVar: + case ScopeKind::NamedLambda: + case ScopeKind::StrictNamedLambda: + case ScopeKind::Eval: + case ScopeKind::StrictEval: + case ScopeKind::Global: + case ScopeKind::NonSyntactic: + case ScopeKind::Module: + break; + + case ScopeKind::WasmInstance: + case ScopeKind::WasmFunction: + MOZ_CRASH("No wasm function scopes in JS"); + } + + // Finish up the scope if we are leaving it in LIFO fashion. + if (!nonLocal) { + // Popping scopes due to non-local jumps generate additional scope + // notes. See NonLocalExitControl::prepareForNonLocalJump. + if (ScopeKindIsInBody(kind)) { + // The extra function var scope is never popped once it's pushed, + // so its scope note extends until the end of any possible code. + uint32_t offset = kind == ScopeKind::FunctionBodyVar ? UINT32_MAX : bce->offset(); + bce->scopeNoteList.recordEnd(noteIndex_, offset, bce->inPrologue()); + } + } + + return true; +} + +Scope* +EmitterScope::scope(const BytecodeEmitter* bce) const +{ + return bce->scopeList.vector[index()]; +} + +NameLocation +EmitterScope::lookup(BytecodeEmitter* bce, JSAtom* name) +{ + if (Maybe loc = lookupInCache(bce, name)) + return *loc; + return searchAndCache(bce, name); +} + +Maybe +EmitterScope::locationBoundInScope(JSAtom* name, EmitterScope* target) +{ + // The target scope must be an intra-frame enclosing scope of this + // one. Count the number of extra hops to reach it. + uint8_t extraHops = 0; + for (EmitterScope* es = this; es != target; es = es->enclosingInFrame()) { + if (es->hasEnvironment()) + extraHops++; + } + + // Caches are prepopulated with bound names. So if the name is bound in a + // particular scope, it must already be in the cache. Furthermore, don't + // consult the fallback location as we only care about binding names. + Maybe loc; + if (NameLocationMap::Ptr p = target->nameCache_->lookup(name)) { + NameLocation l = p->value().wrapped; + if (l.kind() == NameLocation::Kind::EnvironmentCoordinate) + loc = Some(l.addHops(extraHops)); + else + loc = Some(l); + } + return loc; +} diff --git a/js/src/frontend/EmitterScope.h b/js/src/frontend/EmitterScope.h new file mode 100644 index 000000000000..f5adbfb9a5ff --- /dev/null +++ b/js/src/frontend/EmitterScope.h @@ -0,0 +1,152 @@ +/* -*- 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 + +#include "ds/Nestable.h" +#include "frontend/NameAnalysisTypes.h" +#include "frontend/NameCollections.h" +#include "frontend/ParseContext.h" +#include "frontend/SharedContext.h" +#include "js/TypeDecls.h" +#include "vm/Scope.h" + +namespace js { +namespace frontend { + +// A scope that introduces bindings. +class EmitterScope : public Nestable +{ + // 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 nameCache_; + + // If this scope's cache does not include free names, such as the + // global scope, the NameLocation to return. + mozilla::Maybe 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 + 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 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 + MOZ_MUST_USE bool internScope(BytecodeEmitter* bce, ScopeCreator createScope); + template + 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 bindings); + MOZ_MUST_USE bool enterNamedLambda(BytecodeEmitter* bce, FunctionBox* funbox); + MOZ_MUST_USE bool enterComprehensionFor(BytecodeEmitter* bce, + Handle 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_; + } + + EmitterScope* enclosingInFrame() const { + return Nestable::enclosing(); + } + + NameLocation lookup(BytecodeEmitter* bce, JSAtom* name); + + mozilla::Maybe locationBoundInScope(JSAtom* name, EmitterScope* target); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_EmitterScope_h */ diff --git a/js/src/frontend/ExpressionStatementEmitter.cpp b/js/src/frontend/ExpressionStatementEmitter.cpp new file mode 100644 index 000000000000..36fd3d158d0a --- /dev/null +++ b/js/src/frontend/ExpressionStatementEmitter.cpp @@ -0,0 +1,56 @@ +/* -*- 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/ExpressionStatementEmitter.h" + +#include "frontend/BytecodeEmitter.h" +#include "vm/Opcodes.h" + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; + +ExpressionStatementEmitter::ExpressionStatementEmitter(BytecodeEmitter* bce, + ValueUsage valueUsage) + : bce_(bce), + valueUsage_(valueUsage) +{} + +bool +ExpressionStatementEmitter::prepareForExpr(const Maybe& beginPos) +{ + MOZ_ASSERT(state_ == State::Start); + + if (beginPos) { + if (!bce_->updateSourceCoordNotes(*beginPos)) { + return false; + } + } + +#ifdef DEBUG + depth_ = bce_->stackDepth; + state_ = State::Expr; +#endif + return true; +} + +bool +ExpressionStatementEmitter::emitEnd() +{ + MOZ_ASSERT(state_ == State::Expr); + MOZ_ASSERT(bce_->stackDepth == depth_ + 1); + + JSOp op = valueUsage_ == ValueUsage::WantValue ? JSOP_SETRVAL : JSOP_POP; + if (!bce_->emit1(op)) { + return false; + } + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} diff --git a/js/src/frontend/ExpressionStatementEmitter.h b/js/src/frontend/ExpressionStatementEmitter.h new file mode 100644 index 000000000000..d19df1a3f867 --- /dev/null +++ b/js/src/frontend/ExpressionStatementEmitter.h @@ -0,0 +1,86 @@ +/* -*- 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_ExpressionStatementEmitter_h +#define frontend_ExpressionStatementEmitter_h + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" + +#include + +#include "frontend/ValueUsage.h" + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting bytecode for expression statement. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `expr;` +// // IgnoreValue if this is in normal script. +// // WantValue if this is in eval script. +// ValueUsage valueUsage = ...; +// +// ExpressionStatementEmitter ese(this, valueUsage); +// ese.prepareForExpr(Some(offset_of_expr)); +// emit(expr); +// ese.emitEnd(); +// +class MOZ_STACK_CLASS ExpressionStatementEmitter +{ + BytecodeEmitter* bce_; + +#ifdef DEBUG + // The stack depth before emitting expression. + int32_t depth_; +#endif + + // The usage of the value of the expression. + ValueUsage valueUsage_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ prepareForExpr +------+ emitEnd +-----+ + // | Start |--------------->| Expr |-------->| End | + // +-------+ +------+ +-----+ + enum class State + { + // The initial state. + Start, + + // After calling prepareForExpr. + Expr, + + // After calling emitEnd. + End + }; + State state_ = State::Start; +#endif + + public: + ExpressionStatementEmitter(BytecodeEmitter* bce, ValueUsage valueUsage); + + // Parameters are the offset in the source code for each character below: + // + // expr; + // ^ + // | + // beginPos + // + // Can be Nothing() if not available. + MOZ_MUST_USE bool prepareForExpr(const mozilla::Maybe& beginPos); + MOZ_MUST_USE bool emitEnd(); +}; + +} // namespace frontend +} // namespace js + +#endif /* frontend_ExpressionStatementEmitter_h */ diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index ed47d19e9727..bc8eeb10fb3b 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -356,6 +356,9 @@ ContainsHoistedDeclaration(JSContext* cx, ParseNode* node, bool* result) case ParseNodeKind::Assign: case ParseNodeKind::AddAssign: case ParseNodeKind::SubAssign: + case ParseNodeKind::CoalesceAssignExpr: + case ParseNodeKind::OrAssignExpr: + case ParseNodeKind::AndAssignExpr: case ParseNodeKind::BitOrAssign: case ParseNodeKind::BitXorAssign: case ParseNodeKind::BitAndAssign: @@ -1834,6 +1837,9 @@ Fold(JSContext* cx, ParseNode** pnp, Parser& parser, case ParseNodeKind::Assign: case ParseNodeKind::AddAssign: case ParseNodeKind::SubAssign: + case ParseNodeKind::CoalesceAssignExpr: + case ParseNodeKind::OrAssignExpr: + case ParseNodeKind::AndAssignExpr: case ParseNodeKind::BitOrAssign: case ParseNodeKind::BitAndAssign: case ParseNodeKind::BitXorAssign: diff --git a/js/src/frontend/ForOfLoopControl.cpp b/js/src/frontend/ForOfLoopControl.cpp new file mode 100644 index 000000000000..1fb468f33619 --- /dev/null +++ b/js/src/frontend/ForOfLoopControl.cpp @@ -0,0 +1,159 @@ +/* -*- 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::Kind::TryCatch, TryEmitter::ControlKind::NonSyntactic); + + 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 ... 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 value 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 + 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 + // an undefined to balance the stack. + if (!bce->emit1(JSOP_UNDEFINED)) // UNDEF UNDEF + return false; + } else { + if (!bce->emit1(JSOP_POP)) // + return false; + } + + return true; +} diff --git a/js/src/frontend/ForOfLoopControl.h b/js/src/frontend/ForOfLoopControl.h new file mode 100644 index 000000000000..4ac9d4b2f813 --- /dev/null +++ b/js/src/frontend/ForOfLoopControl.h @@ -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 + +#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 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() const +{ + return kind_ == StatementKind::ForOfLoop; +} + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_ForOfLoopControl_h */ diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index 6bda2ad9c523..a00aa711cf08 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -362,18 +362,31 @@ class FullParseHandler return true; } - MOZ_MUST_USE bool addPropertyDefinition(ParseNode* literal, ParseNode* key, ParseNode* val) { - MOZ_ASSERT(literal->isKind(ParseNodeKind::Object)); - MOZ_ASSERT(literal->isArity(PN_LIST)); + ParseNode* newPropertyDefinition(ParseNode* key, ParseNode* val) { MOZ_ASSERT(key->isKind(ParseNodeKind::Number) || key->isKind(ParseNodeKind::ObjectPropertyName) || key->isKind(ParseNodeKind::String) || key->isKind(ParseNodeKind::ComputedName)); + checkAndSetIsDirectRHSAnonFunction(val); + return newBinary(ParseNodeKind::Colon, key, val, JSOP_INITPROP); + } + + void addPropertyDefinition(ParseNode* literal, ParseNode* propdef) { + MOZ_ASSERT(literal->isKind(ParseNodeKind::Object)); + MOZ_ASSERT(literal->isArity(PN_LIST)); + MOZ_ASSERT(propdef->isKind(ParseNodeKind::Colon)); - ParseNode* propdef = newBinary(ParseNodeKind::Colon, key, val, JSOP_INITPROP); + if (!propdef->pn_right->isConstant()) + setListFlag(literal, PNX_NONCONST); + + literal->append(propdef); + } + + MOZ_MUST_USE bool addPropertyDefinition(ParseNode* literal, ParseNode* key, ParseNode* val) { + ParseNode* propdef = newPropertyDefinition(key, val); if (!propdef) return false; - literal->append(propdef); + addPropertyDefinition(literal, propdef); return true; } @@ -414,6 +427,8 @@ class FullParseHandler key->isKind(ParseNodeKind::String) || key->isKind(ParseNodeKind::ComputedName)); + checkAndSetIsDirectRHSAnonFunction(fn); + ParseNode* propdef = newBinary(ParseNodeKind::Colon, key, fn, AccessorTypeToJSOp(atype)); if (!propdef) return false; @@ -432,6 +447,8 @@ class FullParseHandler key->isKind(ParseNodeKind::String) || key->isKind(ParseNodeKind::ComputedName)); + checkAndSetIsDirectRHSAnonFunction(fn); + ParseNode* classMethod = new_(key, fn, AccessorTypeToJSOp(atype), isStatic); if (!classMethod) return false; @@ -569,6 +586,13 @@ class FullParseHandler ParseNode* newExportDefaultDeclaration(ParseNode* kid, ParseNode* maybeBinding, const TokenPos& pos) { + if (maybeBinding) { + MOZ_ASSERT(maybeBinding->isKind(ParseNodeKind::Name)); + MOZ_ASSERT(!maybeBinding->isInParens()); + + checkAndSetIsDirectRHSAnonFunction(kid); + } + return new_(ParseNodeKind::ExportDefault, JSOP_NOP, pos, kid, maybeBinding); } @@ -727,11 +751,13 @@ class FullParseHandler inline MOZ_MUST_USE bool setLastFunctionFormalParameterDefault(ParseNode* funcpn, ParseNode* pn); + private: void checkAndSetIsDirectRHSAnonFunction(ParseNode* pn) { if (IsAnonymousFunctionDefinition(pn)) pn->setDirectRHSAnonFunction(true); } + public: ParseNode* newFunctionStatement(const TokenPos& pos) { return new_(ParseNodeKind::Function, JSOP_NOP, pos); } @@ -782,6 +808,14 @@ class FullParseHandler } ParseNode* newAssignment(ParseNodeKind kind, ParseNode* lhs, ParseNode* rhs) { + if ((kind == ParseNodeKind::Assign || + kind == ParseNodeKind::CoalesceAssignExpr || + kind == ParseNodeKind::OrAssignExpr || + kind == ParseNodeKind::AndAssignExpr) && + lhs->isKind(ParseNodeKind::Name) && !lhs->isInParens()) { + checkAndSetIsDirectRHSAnonFunction(rhs); + } + return newBinary(kind, lhs, rhs); } @@ -921,10 +955,6 @@ class FullParseHandler pn->pn_prologue = true; } - bool isConstant(ParseNode* pn) { - return pn->isConstant(); - } - bool isName(ParseNode* node) { return node->isKind(ParseNodeKind::Name); } @@ -996,12 +1026,10 @@ FullParseHandler::setLastFunctionFormalParameterDefault(ParseNode* funcpn, MOZ_ASSERT(funcpn->isArity(PN_CODE)); ParseNode* arg = funcpn->pn_body->last(); - ParseNode* pn = newBinary(ParseNodeKind::Assign, arg, defaultValue); + ParseNode* pn = newAssignment(ParseNodeKind::Assign, arg, defaultValue); if (!pn) return false; - checkAndSetIsDirectRHSAnonFunction(defaultValue); - funcpn->pn_body->pn_pos.end = pn->pn_pos.end; ParseNode* pnchild = funcpn->pn_body->pn_head; ParseNode* pnlast = funcpn->pn_body->last(); @@ -1023,6 +1051,11 @@ FullParseHandler::setLastFunctionFormalParameterDefault(ParseNode* funcpn, inline bool FullParseHandler::finishInitializerAssignment(ParseNode* pn, ParseNode* init) { + MOZ_ASSERT(pn->isKind(ParseNodeKind::Name)); + MOZ_ASSERT(!pn->isInParens()); + + checkAndSetIsDirectRHSAnonFunction(init); + pn->pn_expr = init; pn->setOp(JSOP_SETNAME); diff --git a/js/src/frontend/IfEmitter.cpp b/js/src/frontend/IfEmitter.cpp new file mode 100644 index 000000000000..b85cb8e77a84 --- /dev/null +++ b/js/src/frontend/IfEmitter.cpp @@ -0,0 +1,219 @@ +/* -*- 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/IfEmitter.h" + +#include "frontend/BytecodeEmitter.h" +#include "frontend/SourceNotes.h" +#include "vm/Opcodes.h" + +using namespace js; +using namespace js::frontend; + +IfEmitter::IfEmitter(BytecodeEmitter* bce, Kind kind) + : bce_(bce), + thenDepth_(0), + kind_(kind) +#ifdef DEBUG + , pushed_(0), + calculatedPushed_(false), + state_(State::Start) +#endif +{} + +IfEmitter::IfEmitter(BytecodeEmitter* bce) + : IfEmitter(bce, Kind::MayContainLexicalAccessInBranch) +{} + +bool +IfEmitter::emitIfInternal(SrcNoteType type) +{ + MOZ_ASSERT_IF(state_ == State::ElseIf, tdzCache_.isSome()); + MOZ_ASSERT_IF(state_ != State::ElseIf, tdzCache_.isNothing()); + + // The end of TDZCheckCache for cond for else-if. + if (kind_ == Kind::MayContainLexicalAccessInBranch) + tdzCache_.reset(); + + // Emit an annotated branch-if-false around the then part. + if (!bce_->newSrcNote(type)) + return false; + if (!bce_->emitJump(JSOP_IFEQ, &jumpAroundThen_)) + return false; + + // To restore stack depth in else part, save depth of the then part. +#ifdef DEBUG + // If DEBUG, this is also necessary to calculate |pushed_|. + thenDepth_ = bce_->stackDepth; +#else + if (type == SRC_COND || type == SRC_IF_ELSE) + thenDepth_ = bce_->stackDepth; +#endif + + // Enclose then-branch with TDZCheckCache. + if (kind_ == Kind::MayContainLexicalAccessInBranch) + tdzCache_.emplace(bce_); + + return true; +} + +void +IfEmitter::calculateOrCheckPushed() +{ +#ifdef DEBUG + if (!calculatedPushed_) { + pushed_ = bce_->stackDepth - thenDepth_; + calculatedPushed_ = true; + } else { + MOZ_ASSERT(pushed_ == bce_->stackDepth - thenDepth_); + } +#endif +} + +bool +IfEmitter::emitThen() +{ + MOZ_ASSERT(state_ == State::Start || state_ == State::ElseIf); + if (!emitIfInternal(SRC_IF)) + return false; + +#ifdef DEBUG + state_ = State::Then; +#endif + return true; +} + +bool +IfEmitter::emitCond() +{ + MOZ_ASSERT(state_ == State::Start); + if (!emitIfInternal(SRC_COND)) + return false; + +#ifdef DEBUG + state_ = State::Cond; +#endif + return true; +} + +bool +IfEmitter::emitThenElse() +{ + MOZ_ASSERT(state_ == State::Start || state_ == State::ElseIf); + if (!emitIfInternal(SRC_IF_ELSE)) + return false; + +#ifdef DEBUG + state_ = State::ThenElse; +#endif + return true; +} + +bool +IfEmitter::emitElseInternal() +{ + calculateOrCheckPushed(); + + // The end of TDZCheckCache for then-clause. + if (kind_ == Kind::MayContainLexicalAccessInBranch) { + MOZ_ASSERT(tdzCache_.isSome()); + tdzCache_.reset(); + } + + // Emit a jump from the end of our then part around the else part. The + // patchJumpsToTarget call at the bottom of this function will fix up + // the offset with jumpsAroundElse value. + if (!bce_->emitJump(JSOP_GOTO, &jumpsAroundElse_)) + return false; + + // Ensure the branch-if-false comes here, then emit the else. + if (!bce_->emitJumpTargetAndPatch(jumpAroundThen_)) + return false; + + // Clear jumpAroundThen_ offset, to tell emitEnd there was an else part. + jumpAroundThen_ = JumpList(); + + // Restore stack depth of the then part. + bce_->stackDepth = thenDepth_; +#ifdef DEBUG + state_ = State::Else; +#endif + return true; +} + +bool +IfEmitter::emitElse() +{ + MOZ_ASSERT(state_ == State::ThenElse || state_ == State::Cond); + + if (!emitElseInternal()) + return false; + + // Enclose else-branch with TDZCheckCache. + if (kind_ == Kind::MayContainLexicalAccessInBranch) + tdzCache_.emplace(bce_); + +#ifdef DEBUG + state_ = State::Else; +#endif + return true; +} + +bool +IfEmitter::emitElseIf() +{ + MOZ_ASSERT(state_ == State::ThenElse); + + if (!emitElseInternal()) + return false; + + // Enclose cond for else-if with TDZCheckCache. + if (kind_ == Kind::MayContainLexicalAccessInBranch) + tdzCache_.emplace(bce_); + +#ifdef DEBUG + state_ = State::ElseIf; +#endif + return true; +} + +bool +IfEmitter::emitEnd() +{ + MOZ_ASSERT(state_ == State::Then || state_ == State::Else); + // If there was an else part for the last branch, jumpAroundThen_ is + // already fixed up when emitting the else part. + MOZ_ASSERT_IF(state_ == State::Then, jumpAroundThen_.offset != -1); + MOZ_ASSERT_IF(state_ == State::Else, jumpAroundThen_.offset == -1); + + // The end of TDZCheckCache for then or else-clause. + if (kind_ == Kind::MayContainLexicalAccessInBranch) { + MOZ_ASSERT(tdzCache_.isSome()); + tdzCache_.reset(); + } + + calculateOrCheckPushed(); + + if (jumpAroundThen_.offset != -1) { + // No else part for the last branch, fixup the branch-if-false to + // come here. + if (!bce_->emitJumpTargetAndPatch(jumpAroundThen_)) + return false; + } + + // Patch all the jumps around else parts. + if (!bce_->emitJumpTargetAndPatch(jumpsAroundElse_)) + return false; + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} + +InternalIfEmitter::InternalIfEmitter(BytecodeEmitter* bce) + : IfEmitter(bce, Kind::NoLexicalAccessInBranch) +{} diff --git a/js/src/frontend/IfEmitter.h b/js/src/frontend/IfEmitter.h new file mode 100644 index 000000000000..b12d6be1e729 --- /dev/null +++ b/js/src/frontend/IfEmitter.h @@ -0,0 +1,217 @@ +/* -*- 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_IfEmitter_h +#define frontend_IfEmitter_h + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" + +#include + +#include "frontend/JumpList.h" +#include "frontend/SourceNotes.h" +#include "frontend/TDZCheckCache.h" + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting bytecode for blocks like if-then-else. +// +// This class can be used to emit single if-then-else block, or cascading +// else-if blocks. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `if (cond) then_block` +// IfEmitter ifThen(this); +// emit(cond); +// ifThen.emitThen(); +// emit(then_block); +// ifThen.emitEnd(); +// +// `if (cond) then_block else else_block` +// IfEmitter ifThenElse(this); +// emit(cond); +// ifThenElse.emitThenElse(); +// emit(then_block); +// ifThenElse.emitElse(); +// emit(else_block); +// ifThenElse.emitEnd(); +// +// `if (c1) b1 else if (c2) b2 else if (c3) b3 else b4` +// IfEmitter ifThenElse(this); +// emit(c1); +// ifThenElse.emitThenElse(); +// emit(b1); +// ifThenElse.emitElseIf(); +// emit(c2); +// ifThenElse.emitThenElse(); +// emit(b2); +// ifThenElse.emitElseIf(); +// emit(c3); +// ifThenElse.emitThenElse(); +// emit(b3); +// ifThenElse.emitElse(); +// emit(b4); +// ifThenElse.emitEnd(); +// +// `cond ? then_expr : else_expr` +// IfEmitter condElse(this); +// emit(cond); +// condElse.emitCond(); +// emit(then_block); +// condElse.emitElse(); +// emit(else_block); +// condElse.emitEnd(); +// +class MOZ_STACK_CLASS IfEmitter +{ + public: + // Whether the then-clause, the else-clause, or else-if condition may + // contain declaration or access to lexical variables, which means they + // should have their own TDZCheckCache. Basically TDZCheckCache should be + // created for each basic block, which then-clause, else-clause, and + // else-if condition are, but for internally used branches which are + // known not to touch lexical variables we can skip creating TDZCheckCache + // for them. + // + // See the comment for TDZCheckCache class for more details. + enum class Kind { + // For syntactic branches (if, if-else, and conditional expression), + // which basically may contain declaration or accesses to lexical + // variables inside then-clause, else-clause, and else-if condition. + MayContainLexicalAccessInBranch, + + // For internally used branches which don't touch lexical variables + // inside then-clause, else-clause, nor else-if condition. + NoLexicalAccessInBranch + }; + + private: + BytecodeEmitter* bce_; + + // Jump around the then clause, to the beginning of the else clause. + JumpList jumpAroundThen_; + + // Jump around the else clause, to the end of the entire branch. + JumpList jumpsAroundElse_; + + // The stack depth before emitting the then block. + // Used for restoring stack depth before emitting the else block. + // Also used for assertion to make sure then and else blocks pushed the + // same number of values. + int32_t thenDepth_; + + Kind kind_; + mozilla::Maybe tdzCache_; + +#ifdef DEBUG + // The number of values pushed in the then and else blocks. + int32_t pushed_; + bool calculatedPushed_; + + // The state of this emitter. + // + // +-------+ emitCond +------+ emitElse +------+ emitEnd +-----+ + // | Start |-+--------->| Cond |--------->| Else |------>+------->| End | + // +-------+ | +------+ +------+ ^ +-----+ + // | | + // v emitThen +------+ | + // +->+--------->| Then |------------------------>+ + // ^ | +------+ ^ + // | | | + // | | +---+ + // | | | + // | | emitThenElse +----------+ emitElse +------+ | + // | +------------->| ThenElse |-+--------->| Else |-+ + // | +----------+ | +------+ + // | | + // | | emitElseIf +--------+ + // | +----------->| ElseIf |-+ + // | +--------+ | + // | | + // +------------------------------------------------------+ + enum class State { + // The initial state. + Start, + + // After calling emitThen. + Then, + + // After calling emitCond. + Cond, + + // After calling emitThenElse. + ThenElse, + + // After calling emitElse. + Else, + + // After calling emitElseIf. + ElseIf, + + // After calling emitEnd. + End + }; + State state_; +#endif + + protected: + // For InternalIfEmitter. + IfEmitter(BytecodeEmitter* bce, Kind kind); + + public: + explicit IfEmitter(BytecodeEmitter* bce); + + MOZ_MUST_USE bool emitThen(); + MOZ_MUST_USE bool emitCond(); + MOZ_MUST_USE bool emitThenElse(); + + MOZ_MUST_USE bool emitElse(); + MOZ_MUST_USE bool emitElseIf(); + + MOZ_MUST_USE bool emitEnd(); + +#ifdef DEBUG + // Returns the number of values pushed onto the value stack inside + // `then_block` and `else_block`. + // Can be used in assertion after emitting if-then-else. + int32_t pushed() const { + return pushed_; + } + + // Returns the number of values popped onto the value stack inside + // `then_block` and `else_block`. + // Can be used in assertion after emitting if-then-else. + int32_t popped() const { + return -pushed_; + } +#endif + + private: + MOZ_MUST_USE bool emitIfInternal(SrcNoteType type); + void calculateOrCheckPushed(); + MOZ_MUST_USE bool emitElseInternal(); +}; + +// Class for emitting bytecode for blocks like if-then-else which doesn't touch +// lexical variables. +// +// See the comments above NoLexicalAccessInBranch for more details when to use +// this instead of IfEmitter. +class MOZ_STACK_CLASS InternalIfEmitter : public IfEmitter +{ + public: + explicit InternalIfEmitter(BytecodeEmitter* bce); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_IfEmitter_h */ diff --git a/js/src/frontend/JumpList.cpp b/js/src/frontend/JumpList.cpp new file mode 100644 index 000000000000..20155af537c4 --- /dev/null +++ b/js/src/frontend/JumpList.cpp @@ -0,0 +1,33 @@ +/* -*- 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/JumpList.h" + +#include "jsopcode.h" + +using namespace js; +using namespace js::frontend; + +void +JumpList::push(jsbytecode* code, ptrdiff_t jumpOffset) +{ + SET_JUMP_OFFSET(&code[jumpOffset], offset - jumpOffset); + offset = jumpOffset; +} + +void +JumpList::patchAll(jsbytecode* code, JumpTarget target) +{ + ptrdiff_t delta; + for (ptrdiff_t jumpOffset = offset; jumpOffset != -1; jumpOffset += delta) { + jsbytecode* pc = &code[jumpOffset]; + MOZ_ASSERT(IsJumpOpcode(JSOp(*pc)) || JSOp(*pc) == JSOP_LABEL); + delta = GET_JUMP_OFFSET(pc); + MOZ_ASSERT(delta < 0); + ptrdiff_t span = target.offset - jumpOffset; + SET_JUMP_OFFSET(pc, span); + } +} diff --git a/js/src/frontend/JumpList.h b/js/src/frontend/JumpList.h new file mode 100644 index 000000000000..7be913ae3545 --- /dev/null +++ b/js/src/frontend/JumpList.h @@ -0,0 +1,76 @@ +/* -*- 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_JumpList_h +#define frontend_JumpList_h + +#include + +#include "js/TypeDecls.h" +#include "jsbytecode.h" + +namespace js { +namespace frontend { + +// Linked list of jump instructions that need to be patched. The linked list is +// stored in the bytes of the incomplete bytecode that will be patched, so no +// extra memory is needed, and patching the instructions destroys the list. +// +// Example: +// +// JumpList brList; +// if (!emitJump(JSOP_IFEQ, &brList)) +// return false; +// ... +// JumpTarget label; +// if (!emitJumpTarget(&label)) +// return false; +// ... +// if (!emitJump(JSOP_GOTO, &brList)) +// return false; +// ... +// patchJumpsToTarget(brList, label); +// +// +-> -1 +// | +// | +// ifeq .. <+ + +-+ ifeq .. +// .. | | .. +// label: | +-> label: +// jumptarget | | jumptarget +// .. | | .. +// goto .. <+ + +-+ goto .. <+ +// | | +// | | +// + + +// brList brList +// +// | ^ +// +------- patchJumpsToTarget -------+ +// + +// Offset of a jump target instruction, used for patching jump instructions. +struct JumpTarget { + ptrdiff_t offset; +}; + +struct JumpList { + JumpList() {} + // -1 is used to mark the end of jump lists. + ptrdiff_t offset = -1; + + // Add a jump instruction to the list. + void push(jsbytecode* code, ptrdiff_t jumpOffset); + + // Patch all jump instructions in this list to jump to `target`. This + // clobbers the list. + void patchAll(jsbytecode* code, JumpTarget target); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_JumpList_h */ diff --git a/js/src/frontend/LabelEmitter.cpp b/js/src/frontend/LabelEmitter.cpp new file mode 100644 index 000000000000..d39b99633603 --- /dev/null +++ b/js/src/frontend/LabelEmitter.cpp @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "frontend/LabelEmitter.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter +#include "vm/Opcodes.h" // JSOP_* + +using namespace js; +using namespace js::frontend; + +bool LabelEmitter::emitLabel(JSAtom* name) { + MOZ_ASSERT(state_ == State::Start); + + // Emit a JSOP_LABEL instruction. The operand is the offset to the + // statement following the labeled statement. + uint32_t index; + if (!bce_->makeAtomIndex(name, &index)) { + return false; + } + if (!bce_->emitJump(JSOP_LABEL, &top_)) { + return false; + } + + controlInfo_.emplace(bce_, name, bce_->offset()); + +#ifdef DEBUG + state_ = State::Label; +#endif + return true; +} + +bool LabelEmitter::emitEnd() { + MOZ_ASSERT(state_ == State::Label); + + // Patch the JSOP_LABEL offset. + JumpTarget brk{bce_->lastNonJumpTargetOffset()}; + bce_->patchJumpsToTarget(top_, brk); + + // Patch the break/continue to this label. + if (!controlInfo_->patchBreaks(bce_)) { + return false; + } + + controlInfo_.reset(); + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} diff --git a/js/src/frontend/LabelEmitter.h b/js/src/frontend/LabelEmitter.h new file mode 100644 index 000000000000..d644d6b05e39 --- /dev/null +++ b/js/src/frontend/LabelEmitter.h @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef frontend_LabelEmitter_h +#define frontend_LabelEmitter_h + +#include "mozilla/Attributes.h" // MOZ_MUST_USE, MOZ_STACK_CLASS +#include "mozilla/Maybe.h" // Maybe + +#include "frontend/BytecodeControlStructures.h" // LabelControl +#include "frontend/JumpList.h" // JumpList + +class JSAtom; + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting labeled statement. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `label: expr;` +// LabelEmitter le(this); +// le.emitLabel(name_of_label); +// emit(expr); +// le.emitEnd(); +// +class MOZ_STACK_CLASS LabelEmitter { + BytecodeEmitter* bce_; + + // The offset of the JSOP_LABEL. + JumpList top_; + + mozilla::Maybe controlInfo_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ emitLabel +-------+ emitEnd +-----+ + // | Start |---------->| Label |-------->| End | + // +-------+ +-------+ +-----+ + enum class State { + // The initial state. + Start, + + // After calling emitLabel. + Label, + + // After calling emitEnd. + End + }; + State state_ = State::Start; +#endif + + public: + explicit LabelEmitter(BytecodeEmitter* bce) : bce_(bce) {} + + MOZ_MUST_USE bool emitLabel(JSAtom* name); + MOZ_MUST_USE bool emitEnd(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_LabelEmitter_h */ diff --git a/js/src/frontend/NameFunctions.cpp b/js/src/frontend/NameFunctions.cpp index ed33fe17203c..5aaf74671278 100644 --- a/js/src/frontend/NameFunctions.cpp +++ b/js/src/frontend/NameFunctions.cpp @@ -167,7 +167,7 @@ class NameResolver case ParseNodeKind::Colon: case ParseNodeKind::Shorthand: /* - * Record the ParseNodeKind::Colon/SHORTHAND but skip the + * Record the ParseNodeKind::Colon/Shorthand but skip the * ParseNodeKind::Object so we're not flagged as a * contributor. */ @@ -280,7 +280,11 @@ class NameResolver retAtom.set(buf.finishAtom()); if (!retAtom) return false; - fun->setGuessedAtom(retAtom); + + // Skip assigning the guessed name if the function has a (dynamically) + // computed inferred name. + if (!pn->isDirectRHSAnonFunction()) + fun->setGuessedAtom(retAtom); return true; } @@ -358,9 +362,8 @@ class NameResolver */ bool resolve(ParseNode* const cur, HandleAtom prefixArg = nullptr) { RootedAtom prefix(cx, prefixArg); - if (cur == nullptr) - return true; + MOZ_ASSERT(cur != nullptr); MOZ_ASSERT(cur->isArity(PN_CODE) == (cur->isKind(ParseNodeKind::Function) || cur->isKind(ParseNodeKind::Module))); if (cur->isKind(ParseNodeKind::Function)) { @@ -556,9 +559,9 @@ class NameResolver case ParseNodeKind::ExportFrom: case ParseNodeKind::ExportDefault: MOZ_ASSERT(cur->isArity(PN_BINARY)); - // The left halves of these nodes don't contain any unconstrained - // expressions, but it's very hard to assert this to safely rely on - // it. So recur anyway. + // The left halves of Import and ExportFrom don't contain any + // unconstrained expressions, but it's very hard to assert this to + // safely rely on it. So recur anyway. if (!resolve(cur->pn_left, prefix)) return false; MOZ_ASSERT_IF(!cur->isKind(ParseNodeKind::ExportDefault), @@ -585,12 +588,9 @@ class NameResolver case ParseNodeKind::ForIn: case ParseNodeKind::ForOf: MOZ_ASSERT(cur->isArity(PN_TERNARY)); - if (ParseNode* decl = cur->pn_kid1) { - if (!resolve(decl, prefix)) - return false; - } - if (!resolve(cur->pn_kid2, prefix)) + if (!resolve(cur->pn_kid1, prefix)) return false; + MOZ_ASSERT(!cur->pn_kid2); if (!resolve(cur->pn_kid3, prefix)) return false; break; @@ -850,8 +850,10 @@ class NameResolver case ParseNodeKind::Name: MOZ_ASSERT(cur->isArity(PN_NAME)); - if (!resolve(cur->expr(), prefix)) - return false; + if (ParseNode* init = cur->expr()) { + if (!resolve(init, prefix)) + return false; + } break; case ParseNodeKind::LexicalScope: @@ -863,8 +865,10 @@ class NameResolver case ParseNodeKind::Function: case ParseNodeKind::Module: MOZ_ASSERT(cur->isArity(PN_CODE)); - if (!resolve(cur->pn_body, prefix)) - return false; + if (ParseNode* body = cur->pn_body) { + if (!resolve(body, prefix)) + return false; + } break; // Kinds that should be handled by parent node resolution. diff --git a/js/src/frontend/NameOpEmitter.cpp b/js/src/frontend/NameOpEmitter.cpp new file mode 100644 index 000000000000..c88b78a4bb24 --- /dev/null +++ b/js/src/frontend/NameOpEmitter.cpp @@ -0,0 +1,381 @@ +/* -*- 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/NameOpEmitter.h" + +#include "frontend/BytecodeEmitter.h" +#include "frontend/SharedContext.h" +#include "frontend/TDZCheckCache.h" +#include "vm/Opcodes.h" +#include "vm/Scope.h" +#include "vm/String.h" + +using namespace js; +using namespace js::frontend; + +NameOpEmitter::NameOpEmitter(BytecodeEmitter* bce, JSAtom* name, Kind kind) + : bce_(bce), + kind_(kind), + name_(bce_->cx, name), + loc_(bce_->lookupName(name_)) +{} + +NameOpEmitter::NameOpEmitter(BytecodeEmitter* bce, JSAtom* name, const NameLocation& loc, + Kind kind) + : bce_(bce), + kind_(kind), + name_(bce_->cx, name), + loc_(loc) +{} + +bool +NameOpEmitter::emitGet() +{ + MOZ_ASSERT(state_ == State::Start); + + switch (loc_.kind()) { + case NameLocation::Kind::Dynamic: + if (!bce_->emitAtomOp(name_, JSOP_GETNAME)) { // VAL + return false; + } + break; + case NameLocation::Kind::Global: + if (!bce_->emitAtomOp(name_, JSOP_GETGNAME)) {// VAL + return false; + } + break; + case NameLocation::Kind::Intrinsic: + if (!bce_->emitAtomOp(name_, JSOP_GETINTRINSIC)) { + return false; // VAL + } + break; + case NameLocation::Kind::NamedLambdaCallee: + if (!bce_->emit1(JSOP_CALLEE)) { // VAL + return false; + } + break; + case NameLocation::Kind::Import: + if (!bce_->emitAtomOp(name_, JSOP_GETIMPORT)) { + return false; // VAL + } + break; + case NameLocation::Kind::ArgumentSlot: + if (!bce_->emitArgOp(JSOP_GETARG, loc_.argumentSlot())) { + return false; // VAL + } + break; + case NameLocation::Kind::FrameSlot: + if (loc_.isLexical()) { + if (!bce_->emitTDZCheckIfNeeded(name_, loc_)) { + return false; + } + } + if (!bce_->emitLocalOp(JSOP_GETLOCAL, loc_.frameSlot())) { + return false; // VAL + } + break; + case NameLocation::Kind::EnvironmentCoordinate: + if (loc_.isLexical()) { + if (!bce_->emitTDZCheckIfNeeded(name_, loc_)) { + return false; + } + } + if (!bce_->emitEnvCoordOp(JSOP_GETALIASEDVAR, loc_.environmentCoordinate())) { + return false; // VAL + } + break; + case NameLocation::Kind::DynamicAnnexBVar: + MOZ_CRASH("Synthesized vars for Annex B.3.3 should only be used in initialization"); + } + + if (isCall()) { + switch (loc_.kind()) { + case NameLocation::Kind::Dynamic: { + JSOp thisOp = bce_->needsImplicitThis() ? JSOP_IMPLICITTHIS : JSOP_GIMPLICITTHIS; + if (!bce_->emitAtomOp(name_, thisOp)) { // CALLEE THIS + return false; + } + break; + } + case NameLocation::Kind::Global: + if (!bce_->emitAtomOp(name_, JSOP_GIMPLICITTHIS)) { + return false; // CALLEE THIS + } + break; + case NameLocation::Kind::Intrinsic: + case NameLocation::Kind::NamedLambdaCallee: + case NameLocation::Kind::Import: + case NameLocation::Kind::ArgumentSlot: + case NameLocation::Kind::FrameSlot: + case NameLocation::Kind::EnvironmentCoordinate: + if (!bce_->emit1(JSOP_UNDEFINED)) { // CALLEE UNDEF + return false; + } + break; + case NameLocation::Kind::DynamicAnnexBVar: + MOZ_CRASH("Synthesized vars for Annex B.3.3 should only be used in initialization"); + } + } + +#ifdef DEBUG + state_ = State::Get; +#endif + return true; +} + +bool +NameOpEmitter::prepareForRhs() +{ + MOZ_ASSERT(state_ == State::Start); + + switch (loc_.kind()) { + case NameLocation::Kind::Dynamic: + case NameLocation::Kind::Import: + case NameLocation::Kind::DynamicAnnexBVar: + if (!bce_->makeAtomIndex(name_, &atomIndex_)) { + return false; + } + if (loc_.kind() == NameLocation::Kind::DynamicAnnexBVar) { + // Annex B vars always go on the nearest variable environment, + // even if lexical environments in between contain same-named + // bindings. + if (!bce_->emit1(JSOP_BINDVAR)) { // ENV + return false; + } + } else { + if (!bce_->emitIndexOp(JSOP_BINDNAME, atomIndex_)) { + return false; // ENV + } + } + emittedBindOp_ = true; + break; + case NameLocation::Kind::Global: + if (!bce_->makeAtomIndex(name_, &atomIndex_)) { + return false; + } + if (loc_.isLexical() && isInitialize()) { + // INITGLEXICAL always gets the global lexical scope. It doesn't + // need a BINDGNAME. + MOZ_ASSERT(bce_->innermostScope()->is()); + } else { + if (!bce_->emitIndexOp(JSOP_BINDGNAME, atomIndex_)) { + return false; // ENV + } + emittedBindOp_ = true; + } + break; + case NameLocation::Kind::Intrinsic: + break; + case NameLocation::Kind::NamedLambdaCallee: + break; + case NameLocation::Kind::ArgumentSlot: { + // If we assign to a positional formal parameter and the arguments + // object is unmapped (strict mode or function with + // default/rest/destructing args), parameters do not alias + // arguments[i], and to make the arguments object reflect initial + // parameter values prior to any mutation we create it eagerly + // whenever parameters are (or might, in the case of calls to eval) + // assigned. + FunctionBox* funbox = bce_->sc->asFunctionBox(); + if (funbox->argumentsHasLocalBinding() && !funbox->hasMappedArgsObj()) { + funbox->setDefinitelyNeedsArgsObj(); + } + break; + } + case NameLocation::Kind::FrameSlot: + break; + case NameLocation::Kind::EnvironmentCoordinate: + break; + } + + // For compound assignments, first get the LHS value, then emit + // the RHS and the op. + if (isCompoundAssignment() || isIncDec()) { + if (loc_.kind() == NameLocation::Kind::Dynamic) { + // For dynamic accesses we need to emit GETBOUNDNAME instead of + // GETNAME for correctness: looking up @@unscopables on the + // environment chain (due to 'with' environments) must only happen + // once. + // + // GETBOUNDNAME uses the environment already pushed on the stack + // from the earlier BINDNAME. + if (!bce_->emit1(JSOP_DUP)) { // ENV ENV + return false; + } + if (!bce_->emitAtomOp(name_, JSOP_GETBOUNDNAME)) { + return false; // ENV V + } + } else { + if (!emitGet()) { // ENV? V + return false; + } + } + } + +#ifdef DEBUG + state_ = State::Rhs; +#endif + return true; +} + +bool +NameOpEmitter::emitAssignment() +{ + MOZ_ASSERT(state_ == State::Rhs); + + switch (loc_.kind()) { + case NameLocation::Kind::Dynamic: + case NameLocation::Kind::Import: + case NameLocation::Kind::DynamicAnnexBVar: + if (!bce_->emitIndexOp(bce_->strictifySetNameOp(JSOP_SETNAME), atomIndex_)) { + return false; + } + break; + case NameLocation::Kind::Global: { + JSOp op; + if (emittedBindOp_) { + op = bce_->strictifySetNameOp(JSOP_SETGNAME); + } else { + op = JSOP_INITGLEXICAL; + } + if (!bce_->emitIndexOp(op, atomIndex_)) { + return false; + } + break; + } + case NameLocation::Kind::Intrinsic: + if (!bce_->emitAtomOp(name_, JSOP_SETINTRINSIC)) { + return false; + } + break; + case NameLocation::Kind::NamedLambdaCallee: + // Assigning to the named lambda is a no-op in sloppy mode but + // throws in strict mode. + if (bce_->sc->strict()) { + if (!bce_->emit1(JSOP_THROWSETCALLEE)) { + return false; + } + } + break; + case NameLocation::Kind::ArgumentSlot: + if (!bce_->emitArgOp(JSOP_SETARG, loc_.argumentSlot())) { + return false; + } + break; + case NameLocation::Kind::FrameSlot: { + JSOp op = JSOP_SETLOCAL; + if (loc_.isLexical()) { + if (isInitialize()) { + op = JSOP_INITLEXICAL; + } else { + if (loc_.isConst()) { + op = JSOP_THROWSETCONST; + } + + if (!bce_->emitTDZCheckIfNeeded(name_, loc_)) { + return false; + } + } + } + if (!bce_->emitLocalOp(op, loc_.frameSlot())) { + return false; + } + if (op == JSOP_INITLEXICAL) { + if (!bce_->innermostTDZCheckCache->noteTDZCheck(bce_, name_, DontCheckTDZ)) { + return false; + } + } + break; + } + case NameLocation::Kind::EnvironmentCoordinate: { + JSOp op = JSOP_SETALIASEDVAR; + if (loc_.isLexical()) { + if (isInitialize()) { + op = JSOP_INITALIASEDLEXICAL; + } else { + if (loc_.isConst()) { + op = JSOP_THROWSETALIASEDCONST; + } + + if (!bce_->emitTDZCheckIfNeeded(name_, loc_)) { + return false; + } + } + } + if (loc_.bindingKind() == BindingKind::NamedLambdaCallee) { + // Assigning to the named lambda is a no-op in sloppy mode and throws + // in strict mode. + op = JSOP_THROWSETALIASEDCONST; + if (bce_->sc->strict()) { + if (!bce_->emitEnvCoordOp(op, loc_.environmentCoordinate())) { + return false; + } + } + } else { + if (!bce_->emitEnvCoordOp(op, loc_.environmentCoordinate())) { + return false; + } + } + if (op == JSOP_INITALIASEDLEXICAL) { + if (!bce_->innermostTDZCheckCache->noteTDZCheck(bce_, name_, DontCheckTDZ)) { + return false; + } + } + break; + } + } + +#ifdef DEBUG + state_ = State::Assignment; +#endif + return true; +} + +bool +NameOpEmitter::emitIncDec() +{ + MOZ_ASSERT(state_ == State::Start); + + JSOp binOp = isInc() ? JSOP_ADD : JSOP_SUB; + if (!prepareForRhs()) { // ENV? V + return false; + } + if (!bce_->emit1(JSOP_POS)) { // ENV? N + return false; + } + if (isPostIncDec()) { + if (!bce_->emit1(JSOP_DUP)) { // ENV? N? N + return false; + } + } + if (!bce_->emit1(JSOP_ONE)) { // ENV? N? N 1 + return false; + } + if (!bce_->emit1(binOp)) { // ENV? N? N+1 + return false; + } + if (isPostIncDec() && emittedBindOp()) { + if (!bce_->emit2(JSOP_PICK, 2)) { // N? N+1 ENV? + return false; + } + if (!bce_->emit1(JSOP_SWAP)) { // N? ENV? N+1 + return false; + } + } + if (!emitAssignment()) { // N? N+1 + return false; + } + if (isPostIncDec()) { + if (!bce_->emit1(JSOP_POP)) { // N + return false; + } + } + +#ifdef DEBUG + state_ = State::IncDec; +#endif + return true; +} diff --git a/js/src/frontend/NameOpEmitter.h b/js/src/frontend/NameOpEmitter.h new file mode 100644 index 000000000000..aab5cf9fb792 --- /dev/null +++ b/js/src/frontend/NameOpEmitter.h @@ -0,0 +1,195 @@ +/* -*- 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_NameOpEmitter_h +#define frontend_NameOpEmitter_h + +#include "mozilla/Attributes.h" + +#include + +#include "frontend/NameAnalysisTypes.h" +#include "js/TypeDecls.h" + +class JSAtom; + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting bytecode for name operation. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `name;` +// NameOpEmitter noe(this, atom_of_name +// ElemOpEmitter::Kind::Get); +// noe.emitGet(); +// +// `name();` +// this is handled in CallOrNewEmitter +// +// `name++;` +// NameOpEmitter noe(this, atom_of_name +// ElemOpEmitter::Kind::PostIncrement); +// noe.emitIncDec(); +// +// `name = 10;` +// NameOpEmitter noe(this, atom_of_name +// ElemOpEmitter::Kind::SimpleAssignment); +// noe.prepareForRhs(); +// emit(10); +// noe.emitAssignment(); +// +// `name += 10;` +// NameOpEmitter noe(this, atom_of_name +// ElemOpEmitter::Kind::CompoundAssignment); +// noe.prepareForRhs(); +// emit(10); +// emit_add_op_here(); +// noe.emitAssignment(); +// +// `name = 10;` part of `let name = 10;` +// NameOpEmitter noe(this, atom_of_name +// ElemOpEmitter::Kind::Initialize); +// noe.prepareForRhs(); +// emit(10); +// noe.emitAssignment(); +// +class MOZ_STACK_CLASS NameOpEmitter +{ + public: + enum class Kind { + Get, + Call, + PostIncrement, + PreIncrement, + PostDecrement, + PreDecrement, + SimpleAssignment, + CompoundAssignment, + Initialize + }; + + private: + BytecodeEmitter* bce_; + + Kind kind_; + + bool emittedBindOp_ = false; + + RootedAtom name_; + + uint32_t atomIndex_; + + NameLocation loc_; + +#ifdef DEBUG + // The state of this emitter. + // + // [Get] + // [Call] + // +-------+ emitGet +-----+ + // | Start |-+-+--------->| Get | + // +-------+ | +-----+ + // | + // | [PostIncrement] + // | [PreIncrement] + // | [PostDecrement] + // | [PreDecrement] + // | emitIncDec +--------+ + // +------------->| IncDec | + // | +--------+ + // | + // | [SimpleAssignment] + // | prepareForRhs +-----+ + // +--------------------->+-------------->| Rhs |-+ + // | ^ +-----+ | + // | | | + // | | +------------------+ + // | [CompoundAssignment] | | + // | emitGet +-----+ | | emitAssignment +------------+ + // +---------->| Get |----+ + -------------->| Assignment | + // +-----+ +------------+ + enum class State { + // The initial state. + Start, + + // After calling emitGet. + Get, + + // After calling emitIncDec. + IncDec, + + // After calling prepareForRhs. + Rhs, + + // After calling emitAssignment. + Assignment, + }; + State state_ = State::Start; +#endif + + public: + NameOpEmitter(BytecodeEmitter* bce, JSAtom* name, Kind kind); + NameOpEmitter(BytecodeEmitter* bce, JSAtom* name, const NameLocation& loc, Kind kind); + + private: + MOZ_MUST_USE bool isCall() const { + return kind_ == Kind::Call; + } + + MOZ_MUST_USE bool isSimpleAssignment() const { + return kind_ == Kind::SimpleAssignment; + } + + MOZ_MUST_USE bool isCompoundAssignment() const { + return kind_ == Kind::CompoundAssignment; + } + + MOZ_MUST_USE bool isIncDec() const { + return isPostIncDec() || isPreIncDec(); + } + + MOZ_MUST_USE bool isPostIncDec() const { + return kind_ == Kind::PostIncrement || + kind_ == Kind::PostDecrement; + } + + MOZ_MUST_USE bool isPreIncDec() const { + return kind_ == Kind::PreIncrement || + kind_ == Kind::PreDecrement; + } + + MOZ_MUST_USE bool isInc() const { + return kind_ == Kind::PostIncrement || + kind_ == Kind::PreIncrement; + } + + MOZ_MUST_USE bool isInitialize() const { + return kind_ == Kind::Initialize; + } + + public: + MOZ_MUST_USE bool emittedBindOp() const { + return emittedBindOp_; + } + + MOZ_MUST_USE const NameLocation& loc() const { + return loc_; + } + + MOZ_MUST_USE bool emitGet(); + MOZ_MUST_USE bool prepareForRhs(); + MOZ_MUST_USE bool emitAssignment(); + MOZ_MUST_USE bool emitIncDec(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_NameOpEmitter_h */ diff --git a/js/src/frontend/ObjectEmitter.cpp b/js/src/frontend/ObjectEmitter.cpp new file mode 100644 index 000000000000..1e88591bf55e --- /dev/null +++ b/js/src/frontend/ObjectEmitter.cpp @@ -0,0 +1,829 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "frontend/ObjectEmitter.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter +#include "frontend/IfEmitter.h" // IfEmitter +#include "frontend/SharedContext.h" // SharedContext +#include "frontend/SourceNotes.h" // SRC_* +#include "gc/Heap.h" // AllocKind +#include "js/Id.h" // jsid +#include "js/Value.h" // UndefinedHandleValue +#include "jsopcode.h" // IsHiddenInitOp +#include "jscntxt.h" // JSContext +#include "vm/NativeObject.h" // NativeDefineProperty +#include "vm/ObjectGroup.h" // TenuredObject +#include "vm/Opcodes.h" // JSOP_* +#include "vm/Runtime.h" // JSAtomState (cx->names()) + +#include "jsgc.h" // GetGCObjectKind +#include "jsatominlines.h" // AtomToId +#include "jsobjinlines.h" // NewBuiltinClassInstance + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; + +PropertyEmitter::PropertyEmitter(BytecodeEmitter* bce) + : bce_(bce), obj_(bce->cx) {} + +bool PropertyEmitter::prepareForProtoValue(const Maybe& keyPos) { + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] CTOR? OBJ CTOR? + + if (keyPos) { + if (!bce_->updateSourceCoordNotes(*keyPos)) { + return false; + } + } + +#ifdef DEBUG + propertyState_ = PropertyState::ProtoValue; +#endif + return true; +} + +bool PropertyEmitter::emitMutateProto() { + MOZ_ASSERT(propertyState_ == PropertyState::ProtoValue); + + // [stack] OBJ PROTO + + if (!bce_->emit1(JSOP_MUTATEPROTO)) { + // [stack] OBJ + return false; + } + + obj_ = nullptr; +#ifdef DEBUG + propertyState_ = PropertyState::Init; +#endif + return true; +} + +bool PropertyEmitter::prepareForSpreadOperand( + const Maybe& spreadPos) { + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] OBJ + + if (spreadPos) { + if (!bce_->updateSourceCoordNotes(*spreadPos)) { + return false; + } + } + if (!bce_->emit1(JSOP_DUP)) { + // [stack] OBJ OBJ + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::SpreadOperand; +#endif + return true; +} + +bool PropertyEmitter::emitSpread() { + MOZ_ASSERT(propertyState_ == PropertyState::SpreadOperand); + + // [stack] OBJ OBJ VAL + + if (!bce_->emitCopyDataProperties(BytecodeEmitter::CopyOption::Unfiltered)) { + // [stack] OBJ + return false; + } + + obj_ = nullptr; +#ifdef DEBUG + propertyState_ = PropertyState::Init; +#endif + return true; +} + +MOZ_ALWAYS_INLINE bool PropertyEmitter::prepareForProp( + const Maybe& keyPos, bool isStatic, bool isIndexOrComputed) { + isStatic_ = isStatic; + isIndexOrComputed_ = isIndexOrComputed; + + // [stack] CTOR? OBJ + + if (keyPos) { + if (!bce_->updateSourceCoordNotes(*keyPos)) { + return false; + } + } + + if (isStatic_) { + if (!bce_->emit1(JSOP_DUP2)) { + // [stack] CTOR HOMEOBJ CTOR HOMEOBJ + return false; + } + if (!bce_->emit1(JSOP_POP)) { + // [stack] CTOR HOMEOBJ CTOR + return false; + } + } + + return true; +} + +bool PropertyEmitter::prepareForPropValue(const Maybe& keyPos, + Kind kind /* = Kind::Prototype */) { + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] CTOR? OBJ + + if (!prepareForProp(keyPos, + /* isStatic_ = */ kind == Kind::Static, + /* isIndexOrComputed = */ false)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::PropValue; +#endif + return true; +} + +bool PropertyEmitter::prepareForIndexPropKey( + const Maybe& keyPos, Kind kind /* = Kind::Prototype */) { + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] CTOR? OBJ + + obj_ = nullptr; + + if (!prepareForProp(keyPos, + /* isStatic_ = */ kind == Kind::Static, + /* isIndexOrComputed = */ true)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::IndexKey; +#endif + return true; +} + +bool PropertyEmitter::prepareForIndexPropValue() { + MOZ_ASSERT(propertyState_ == PropertyState::IndexKey); + + // [stack] CTOR? OBJ CTOR? KEY + +#ifdef DEBUG + propertyState_ = PropertyState::IndexValue; +#endif + return true; +} + +bool PropertyEmitter::prepareForComputedPropKey( + const Maybe& keyPos, Kind kind /* = Kind::Prototype */) { + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] CTOR? OBJ + + obj_ = nullptr; + + if (!prepareForProp(keyPos, + /* isStatic_ = */ kind == Kind::Static, + /* isIndexOrComputed = */ true)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::ComputedKey; +#endif + return true; +} + +bool PropertyEmitter::prepareForComputedPropValue() { + MOZ_ASSERT(propertyState_ == PropertyState::ComputedKey); + + // [stack] CTOR? OBJ CTOR? KEY + + if (!bce_->emit1(JSOP_TOID)) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::ComputedValue; +#endif + return true; +} + +bool PropertyEmitter::emitInitHomeObject( + FunctionAsyncKind kind /* = FunctionAsyncKind::SyncFunction */) { + MOZ_ASSERT(propertyState_ == PropertyState::PropValue || + propertyState_ == PropertyState::IndexValue || + propertyState_ == PropertyState::ComputedValue); + + // [stack] CTOR? HOMEOBJ CTOR? KEY? FUN + + bool isAsync = kind == FunctionAsyncKind::AsyncFunction; + if (isAsync) { + // [stack] CTOR? HOMEOBJ CTOR? KEY? UNWRAPPED WRAPPED + if (!bce_->emit1(JSOP_SWAP)) { + // [stack] CTOR? HOMEOBJ CTOR? KEY? WRAPPED UNWRAPPED + return false; + } + } + + // There are the following values on the stack conditionally, between + // HOMEOBJ and FUN: + // * the 2nd CTOR if isStatic_ + // * KEY if isIndexOrComputed_ + // * WRAPPED if isAsync + // + // JSOP_INITHOMEOBJECT uses one of the following: + // * HOMEOBJ if !isStatic_ + // (`super.foo` points the super prototype property) + // * the 2nd CTOR if isStatic_ + // (`super.foo` points the super constructor property) + if (!bce_->emitDupAt(1 + isIndexOrComputed_ + isAsync)) { + // [stack] # non-static method + // [stack] CTOR? HOMEOBJ CTOR KEY? WRAPPED? FUN CTOR + // [stack] # static method + // [stack] CTOR? HOMEOBJ KEY? WRAPPED? FUN HOMEOBJ + return false; + } + if (!bce_->emit1(JSOP_INITHOMEOBJECT)) { + // [stack] CTOR? HOMEOBJ CTOR? KEY? WRAPPED? FUN + return false; + } + if (isAsync) { + if (!bce_->emit1(JSOP_POP)) { + // [stack] CTOR? HOMEOBJ CTOR? KEY? WRAPPED + return false; + } + } + +#ifdef DEBUG + if (propertyState_ == PropertyState::PropValue) { + propertyState_ = PropertyState::InitHomeObj; + } else if (propertyState_ == PropertyState::IndexValue) { + propertyState_ = PropertyState::InitHomeObjForIndex; + } else { + propertyState_ = PropertyState::InitHomeObjForComputed; + } +#endif + return true; +} + +bool PropertyEmitter::emitInitProp(JS::Handle key) { + return emitInit(isClass_ ? JSOP_INITHIDDENPROP : JSOP_INITPROP, key); +} + +bool PropertyEmitter::emitInitGetter(JS::Handle key) { + obj_ = nullptr; + return emitInit(isClass_ ? JSOP_INITHIDDENPROP_GETTER : JSOP_INITPROP_GETTER, + key); +} + +bool PropertyEmitter::emitInitSetter(JS::Handle key) { + obj_ = nullptr; + return emitInit(isClass_ ? JSOP_INITHIDDENPROP_SETTER : JSOP_INITPROP_SETTER, + key); +} + +bool PropertyEmitter::emitInitIndexProp() { + return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM + : JSOP_INITELEM); +} + +bool PropertyEmitter::emitInitIndexGetter() { + obj_ = nullptr; + return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM_GETTER + : JSOP_INITELEM_GETTER); +} + +bool PropertyEmitter::emitInitIndexSetter() { + obj_ = nullptr; + return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM_SETTER + : JSOP_INITELEM_SETTER); +} + +bool PropertyEmitter::emitInitComputedProp() { + return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM + : JSOP_INITELEM); +} + +bool PropertyEmitter::emitInitComputedGetter() { + obj_ = nullptr; + return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM_GETTER + : JSOP_INITELEM_GETTER); +} + +bool PropertyEmitter::emitInitComputedSetter() { + obj_ = nullptr; + return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM_SETTER + : JSOP_INITELEM_SETTER); +} + +bool PropertyEmitter::emitInit(JSOp op, JS::Handle key) { + MOZ_ASSERT(propertyState_ == PropertyState::PropValue || + propertyState_ == PropertyState::InitHomeObj); + + MOZ_ASSERT(op == JSOP_INITPROP || op == JSOP_INITHIDDENPROP || + op == JSOP_INITPROP_GETTER || op == JSOP_INITHIDDENPROP_GETTER || + op == JSOP_INITPROP_SETTER || op == JSOP_INITHIDDENPROP_SETTER); + + // [stack] CTOR? OBJ CTOR? VAL + + uint32_t index; + if (!bce_->makeAtomIndex(key, &index)) { + return false; + } + + if (obj_) { + MOZ_ASSERT(!IsHiddenInitOp(op)); + MOZ_ASSERT(!obj_->inDictionaryMode()); + JS::Rooted id(bce_->cx, AtomToId(key)); + if (!NativeDefineProperty(bce_->cx, obj_, id, UndefinedHandleValue, + nullptr, nullptr, JSPROP_ENUMERATE)) { + return false; + } + if (obj_->inDictionaryMode()) { + obj_ = nullptr; + } + } + + if (!bce_->emitIndex32(op, index)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + + if (!emitPopClassConstructor()) { + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::Init; +#endif + return true; +} + +bool PropertyEmitter::emitInitIndexOrComputed(JSOp op) { + MOZ_ASSERT(propertyState_ == PropertyState::IndexValue || + propertyState_ == PropertyState::InitHomeObjForIndex || + propertyState_ == PropertyState::ComputedValue || + propertyState_ == PropertyState::InitHomeObjForComputed); + + MOZ_ASSERT(op == JSOP_INITELEM || op == JSOP_INITHIDDENELEM || + op == JSOP_INITELEM_GETTER || op == JSOP_INITHIDDENELEM_GETTER || + op == JSOP_INITELEM_SETTER || op == JSOP_INITHIDDENELEM_SETTER); + + // [stack] CTOR? OBJ CTOR? KEY VAL + + if (!bce_->emit1(op)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + + if (!emitPopClassConstructor()) { + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::Init; +#endif + return true; +} + +bool PropertyEmitter::emitPopClassConstructor() { + if (isStatic_) { + // [stack] CTOR HOMEOBJ CTOR + + if (!bce_->emit1(JSOP_POP)) { + // [stack] CTOR HOMEOBJ + return false; + } + } + + return true; +} + +ObjectEmitter::ObjectEmitter(BytecodeEmitter* bce) : PropertyEmitter(bce) {} + +bool ObjectEmitter::emitObject(size_t propertyCount) { + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(objectState_ == ObjectState::Start); + + // [stack] + + // Emit code for {p:a, '%q':b, 2:c} that is equivalent to constructing + // a new object and defining (in source order) each property on the object + // (or mutating the object's [[Prototype]], in the case of __proto__). + top_ = bce_->offset(); + if (!bce_->emitNewInit(JSProto_Object)) { + // [stack] OBJ + return false; + } + + // Try to construct the shape of the object as we go, so we can emit a + // JSOP_NEWOBJECT with the final shape instead. + // In the case of computed property names and indices, we cannot fix the + // shape at bytecode compile time. When the shape cannot be determined, + // |obj| is nulled out. + + // No need to do any guessing for the object kind, since we know the upper + // bound of how many properties we plan to have. + gc::AllocKind kind = gc::GetGCObjectKind(propertyCount); + obj_ = NewBuiltinClassInstance(bce_->cx, kind, TenuredObject); + if (!obj_) { + return false; + } + +#ifdef DEBUG + objectState_ = ObjectState::Object; +#endif + return true; +} + +bool ObjectEmitter::emitEnd() { + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + MOZ_ASSERT(objectState_ == ObjectState::Object); + + // [stack] OBJ + + if (obj_) { + // The object survived and has a predictable shape: update the original + // bytecode. + if (!bce_->replaceNewInitWithNewObject(obj_, top_)) { + // [stack] OBJ + return false; + } + } + +#ifdef DEBUG + objectState_ = ObjectState::End; +#endif + return true; +} + +AutoSaveLocalStrictMode::AutoSaveLocalStrictMode(SharedContext* sc) : sc_(sc) { + savedStrictness_ = sc_->setLocalStrictMode(true); +} + +AutoSaveLocalStrictMode::~AutoSaveLocalStrictMode() { + if (sc_) { + restore(); + } +} + +void AutoSaveLocalStrictMode::restore() { + MOZ_ALWAYS_TRUE(sc_->setLocalStrictMode(savedStrictness_)); + sc_ = nullptr; +} + +ClassEmitter::ClassEmitter(BytecodeEmitter* bce) + : PropertyEmitter(bce), + strictMode_(bce->sc), + name_(bce->cx), + nameForAnonymousClass_(bce->cx) { + isClass_ = true; +} + +bool ClassEmitter::emitScopeForNamedClass( + JS::Handle scopeBindings) { + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Start); + + tdzCacheForInnerName_.emplace(bce_); + innerNameScope_.emplace(bce_); + if (!innerNameScope_->enterLexical(bce_, ScopeKind::Lexical, scopeBindings)) { + return false; + } + +#ifdef DEBUG + classState_ = ClassState::Scope; +#endif + return true; +} + +bool ClassEmitter::emitClass(JS::Handle name, + JS::Handle nameForAnonymousClass, + bool hasNameOnStack) { + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Start || + classState_ == ClassState::Scope); + MOZ_ASSERT_IF(nameForAnonymousClass || hasNameOnStack, !name); + MOZ_ASSERT(!(nameForAnonymousClass && hasNameOnStack)); + + // [stack] + + name_ = name; + nameForAnonymousClass_ = nameForAnonymousClass; + hasNameOnStack_ = hasNameOnStack; + isDerived_ = false; + + if (!bce_->emitNewInit(JSProto_Object)) { + // [stack] HOMEOBJ + return false; + } + +#ifdef DEBUG + classState_ = ClassState::Class; +#endif + return true; +} + +bool ClassEmitter::emitDerivedClass(JS::Handle name, + JS::Handle nameForAnonymousClass, + bool hasNameOnStack) { + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Start || + classState_ == ClassState::Scope); + MOZ_ASSERT_IF(nameForAnonymousClass || hasNameOnStack, !name); + MOZ_ASSERT(!nameForAnonymousClass || !hasNameOnStack); + + // [stack] + + name_ = name; + nameForAnonymousClass_ = nameForAnonymousClass; + hasNameOnStack_ = hasNameOnStack; + isDerived_ = true; + + InternalIfEmitter ifThenElse(bce_); + + // Heritage must be null or a non-generator constructor + if (!bce_->emit1(JSOP_CHECKCLASSHERITAGE)) { + // [stack] HERITAGE + return false; + } + + // [IF] (heritage !== null) + if (!bce_->emit1(JSOP_DUP)) { + // [stack] HERITAGE HERITAGE + return false; + } + if (!bce_->emit1(JSOP_NULL)) { + // [stack] HERITAGE HERITAGE NULL + return false; + } + if (!bce_->emit1(JSOP_STRICTNE)) { + // [stack] HERITAGE NE + return false; + } + + // [THEN] funProto = heritage, objProto = heritage.prototype + if (!ifThenElse.emitThenElse()) { + return false; + } + if (!bce_->emit1(JSOP_DUP)) { + // [stack] HERITAGE HERITAGE + return false; + } + if (!bce_->emitAtomOp(bce_->cx->names().prototype, JSOP_GETPROP)) { + // [stack] HERITAGE PROTO + return false; + } + + // [ELSE] funProto = %FunctionPrototype%, objProto = null + if (!ifThenElse.emitElse()) { + return false; + } + if (!bce_->emit1(JSOP_POP)) { + // [stack] + return false; + } + if (!bce_->emit2(JSOP_BUILTINPROTO, JSProto_Function)) { + // [stack] PROTO + return false; + } + if (!bce_->emit1(JSOP_NULL)) { + // [stack] PROTO NULL + return false; + } + + // [ENDIF] + if (!ifThenElse.emitEnd()) { + return false; + } + + if (!bce_->emit1(JSOP_OBJWITHPROTO)) { + // [stack] HERITAGE HOMEOBJ + return false; + } + if (!bce_->emit1(JSOP_SWAP)) { + // [stack] HOMEOBJ HERITAGE + return false; + } + +#ifdef DEBUG + classState_ = ClassState::Class; +#endif + return true; +} + +bool ClassEmitter::emitInitConstructor(bool needsHomeObject) { + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Class); + + // [stack] HOMEOBJ CTOR + + if (needsHomeObject) { + if (!bce_->emitDupAt(1)) { + // [stack] HOMEOBJ CTOR HOMEOBJ + return false; + } + if (!bce_->emit1(JSOP_INITHOMEOBJECT)) { + // [stack] HOMEOBJ CTOR + return false; + } + } + + if (!initProtoAndCtor()) { + // [stack] CTOR HOMEOBJ + return false; + } + +#ifdef DEBUG + classState_ = ClassState::InitConstructor; +#endif + return true; +} + +bool ClassEmitter::emitInitDefaultConstructor(const Maybe& classStart, + const Maybe& classEnd) { + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Class); + + if (classStart && classEnd) { + // In the case of default class constructors, emit the start and end + // offsets in the source buffer as source notes so that when we + // actually make the constructor during execution, we can give it the + // correct toString output. + if (!bce_->newSrcNote3(SRC_CLASS_SPAN, ptrdiff_t(*classStart), + ptrdiff_t(*classEnd))) { + return false; + } + } + + RootedAtom className(bce_->cx, name_); + if (!className) { + if (nameForAnonymousClass_) { + className = nameForAnonymousClass_; + } else { + className = bce_->cx->names().empty; + } + } + + if (isDerived_) { + // [stack] HERITAGE PROTO + if (!bce_->emitAtomOp(className, JSOP_DERIVEDCONSTRUCTOR)) { + // [stack] HOMEOBJ CTOR + return false; + } + } else { + // [stack] HOMEOBJ + if (!bce_->emitAtomOp(className, JSOP_CLASSCONSTRUCTOR)) { + // [stack] HOMEOBJ CTOR + return false; + } + } + + // The empty string is used as a placeholder, so if the inferred name for this + // anonymous class expression is also the empty string, we need to set it + // explicitly here. + if (nameForAnonymousClass_ == bce_->cx->names().empty) { + if (!emitSetEmptyClassConstructorNameForDefaultCtor()) { + return false; + } + } + + if (!initProtoAndCtor()) { + // [stack] CTOR HOMEOBJ + return false; + } + +#ifdef DEBUG + classState_ = ClassState::InitConstructor; +#endif + return true; +} + +bool ClassEmitter::emitSetEmptyClassConstructorNameForDefaultCtor() { + uint32_t nameIndex; + if (!bce_->makeAtomIndex(bce_->cx->names().empty, &nameIndex)) { + return false; + } + if (!bce_->emitIndexOp(JSOP_STRING, nameIndex)) { + // [stack] CTOR NAME + return false; + } + if (!bce_->emit2(JSOP_SETFUNNAME, uint8_t(FunctionPrefixKind::None))) { + // [stack] CTOR + return false; + } + return true; +} + +bool ClassEmitter::initProtoAndCtor() { + // [stack] NAME? HOMEOBJ CTOR + + if (hasNameOnStack_) { + if (!bce_->emitDupAt(2)) { + // [stack] NAME HOMEOBJ CTOR NAME + return false; + } + if (!bce_->emit2(JSOP_SETFUNNAME, uint8_t(FunctionPrefixKind::None))) { + // [stack] NAME HOMEOBJ CTOR + return false; + } + } + + if (!bce_->emit1(JSOP_SWAP)) { + // [stack] NAME? CTOR HOMEOBJ + return false; + } + if (!bce_->emit1(JSOP_DUP2)) { + // [stack] NAME? CTOR HOMEOBJ CTOR HOMEOBJ + return false; + } + if (!bce_->emitAtomOp(bce_->cx->names().prototype, JSOP_INITLOCKEDPROP)) { + // [stack] NAME? CTOR HOMEOBJ CTOR + return false; + } + if (!bce_->emitAtomOp(bce_->cx->names().constructor, JSOP_INITHIDDENPROP)) { + // [stack] NAME? CTOR HOMEOBJ + return false; + } + + return true; +} + +bool ClassEmitter::emitEnd(Kind kind) { + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + MOZ_ASSERT(classState_ == ClassState::InitConstructor); + + // [stack] CTOR HOMEOBJ + + if (!bce_->emit1(JSOP_POP)) { + // [stack] CTOR + return false; + } + + if (name_) { + MOZ_ASSERT(tdzCacheForInnerName_.isSome()); + MOZ_ASSERT(innerNameScope_.isSome()); + + if (!bce_->emitLexicalInitialization(name_)) { + // [stack] CTOR + return false; + } + + if (!innerNameScope_->leave(bce_)) { + return false; + } + innerNameScope_.reset(); + + if (kind == Kind::Declaration) { + if (!bce_->emitLexicalInitialization(name_)) { + // [stack] CTOR + return false; + } + // Only class statements make outer bindings, and they do not leave + // themselves on the stack. + if (!bce_->emit1(JSOP_POP)) { + // [stack] + return false; + } + } + + tdzCacheForInnerName_.reset(); + } else { + // [stack] CTOR + + MOZ_ASSERT(tdzCacheForInnerName_.isNothing()); + } + + // [stack] # class declaration + // [stack] + // [stack] # class expression + // [stack] CTOR + + strictMode_.restore(); + +#ifdef DEBUG + classState_ = ClassState::End; +#endif + return true; +} diff --git a/js/src/frontend/ObjectEmitter.h b/js/src/frontend/ObjectEmitter.h new file mode 100644 index 000000000000..25890f3889dd --- /dev/null +++ b/js/src/frontend/ObjectEmitter.h @@ -0,0 +1,712 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef frontend_ObjectEmitter_h +#define frontend_ObjectEmitter_h + +#include "mozilla/Attributes.h" // MOZ_MUST_USE, MOZ_STACK_CLASS, MOZ_ALWAYS_INLINE, MOZ_RAII +#include "mozilla/Maybe.h" // Maybe + +#include // size_t, ptrdiff_t +#include // uint32_t + +#include "frontend/EmitterScope.h" // EmitterScope +#include "frontend/TDZCheckCache.h" // TDZCheckCache +#include "js/RootingAPI.h" // JS::Handle, JS::Rooted +#include "jsopcode.h" // JSOp +#include "jsatom.h" // JSAtom +#include "jsfun.h" // JSFunction, FunctionPrefixKind +#include "jsscript.h" // FunctionAsyncKind +#include "vm/NativeObject.h" // PlainObject +#include "vm/Scope.h" // LexicalScope + +namespace js { + +namespace frontend { + +struct BytecodeEmitter; +class SharedContext; + +// Class for emitting bytecode for object and class properties. +// See ObjectEmitter and ClassEmitter for usage. +class MOZ_STACK_CLASS PropertyEmitter { + public: + enum class Kind { + // Prototype property. + Prototype, + + // Class static property. + Static + }; + + protected: + BytecodeEmitter* bce_; + + // True if the object is class. + // Set by ClassEmitter. + bool isClass_ = false; + + // True if the property is class static method. + bool isStatic_ = false; + + // True if the property has computed or index key. + bool isIndexOrComputed_ = false; + + // An object which keeps the shape of this object literal. + // This fields is reset to nullptr whenever the object literal turns out to + // have at least one numeric, computed, spread or __proto__ property, or + // the object becomes dictionary mode. + // This field is used only in ObjectEmitter. + JS::Rooted obj_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ + // | Start |-+ + // +-------+ | + // | + // +---------+ + // | + // | +------------------------------------------------------------+ + // | | | + // | | [normal property/method/accessor] | + // | v prepareForPropValue +-----------+ +------+ | + // +->+----------------------->| PropValue |-+ +->| Init |-+ + // | +-----------+ | | +------+ + // | | | + // | +----------------------------------+ +-----------+ + // | | | + // | +-+---------------------------------------+ | + // | | | | + // | | [method with super] | | + // | | emitInitHomeObject +-------------+ v | + // | +--------------------->| InitHomeObj |->+ | + // | +-------------+ | | + // | | | + // | +-------------------------------------- + | + // | | | + // | | emitInitProp | + // | | emitInitGetter | + // | | emitInitSetter | + // | +------------------------------------------------------>+ + // | ^ + // | [index property/method/accessor] | + // | prepareForIndexPropKey +----------+ | + // +-------------------------->| IndexKey |-+ | + // | +----------+ | | + // | | | + // | +-------------------------------------+ | + // | | | + // | | prepareForIndexPropValue +------------+ | + // | +------------------------->| IndexValue |-+ | + // | +------------+ | | + // | | | + // | +---------------------------------------+ | + // | | | + // | +-+--------------------------------------------------+ | + // | | | | + // | | [method with super] | | + // | | emitInitHomeObject +---------------------+ v | + // | +--------------------->| InitHomeObjForIndex |---->+ | + // | +---------------------+ | | + // | | | + // | +--------------------------------------------------+ | + // | | | + // | | emitInitIndexProp | + // | | emitInitIndexGetter | + // | | emitInitIndexSetter | + // | +---------------------------------------------------->+ + // | | + // | [computed property/method/accessor] | + // | prepareForComputedPropKey +-------------+ | + // +----------------------------->| ComputedKey |-+ | + // | +-------------+ | | + // | | | + // | +-------------------------------------------+ | + // | | | + // | | prepareForComputedPropValue +---------------+ | + // | +---------------------------->| ComputedValue |-+ | + // | +---------------+ | | + // | | | + // | +---------------------------------------------+ | + // | | | + // | +-+--------------------------------------------------+ | + // | | | | + // | | [method with super] | | + // | | emitInitHomeObject +------------------------+ v | + // | +--------------------->| InitHomeObjForComputed |->+ | + // | +------------------------+ | | + // | | | + // | +--------------------------------------------------+ | + // | | | + // | | emitInitComputedProp | + // | | emitInitComputedGetter | + // | | emitInitComputedSetter | + // | +---------------------------------------------------->+ + // | ^ + // | | + // | [__proto__] | + // | prepareForProtoValue +------------+ emitMutateProto | + // +------------------------>| ProtoValue |-------------------->+ + // | +------------+ ^ + // | | + // | [...prop] | + // | prepareForSpreadOperand +---------------+ emitSpread | + // +-------------------------->| SpreadOperand |----------------+ + // +---------------+ + enum class PropertyState { + // The initial state. + Start, + + // After calling prepareForPropValue. + PropValue, + + // After calling emitInitHomeObject, from PropValue. + InitHomeObj, + + // After calling prepareForIndexPropKey. + IndexKey, + + // prepareForIndexPropValue. + IndexValue, + + // After calling emitInitHomeObject, from IndexValue. + InitHomeObjForIndex, + + // After calling prepareForComputedPropKey. + ComputedKey, + + // prepareForComputedPropValue. + ComputedValue, + + // After calling emitInitHomeObject, from ComputedValue. + InitHomeObjForComputed, + + // After calling prepareForProtoValue. + ProtoValue, + + // After calling prepareForSpreadOperand. + SpreadOperand, + + // After calling one of emitInitProp, emitInitGetter, emitInitSetter, + // emitInitIndexOrComputedProp, emitInitIndexOrComputedGetter, + // emitInitIndexOrComputedSetter, emitMutateProto, or emitSpread. + Init, + }; + PropertyState propertyState_ = PropertyState::Start; +#endif + + public: + explicit PropertyEmitter(BytecodeEmitter* bce); + + // Parameters are the offset in the source code for each character below: + // + // { __proto__: protoValue } + // ^ + // | + // keyPos + MOZ_MUST_USE bool prepareForProtoValue( + const mozilla::Maybe& keyPos); + MOZ_MUST_USE bool emitMutateProto(); + + // { ...obj } + // ^ + // | + // spreadPos + MOZ_MUST_USE bool prepareForSpreadOperand( + const mozilla::Maybe& spreadPos); + MOZ_MUST_USE bool emitSpread(); + + // { key: value } + // ^ + // | + // keyPos + MOZ_MUST_USE bool prepareForPropValue(const mozilla::Maybe& keyPos, + Kind kind = Kind::Prototype); + + // { 1: value } + // ^ + // | + // keyPos + MOZ_MUST_USE bool prepareForIndexPropKey( + const mozilla::Maybe& keyPos, Kind kind = Kind::Prototype); + MOZ_MUST_USE bool prepareForIndexPropValue(); + + // { [ key ]: value } + // ^ + // | + // keyPos + MOZ_MUST_USE bool prepareForComputedPropKey( + const mozilla::Maybe& keyPos, Kind kind = Kind::Prototype); + MOZ_MUST_USE bool prepareForComputedPropValue(); + + MOZ_MUST_USE bool emitInitHomeObject( + FunctionAsyncKind kind = FunctionAsyncKind::SyncFunction); + + // @param key + // Property key + MOZ_MUST_USE bool emitInitProp(JS::Handle key); + MOZ_MUST_USE bool emitInitGetter(JS::Handle key); + MOZ_MUST_USE bool emitInitSetter(JS::Handle key); + + MOZ_MUST_USE bool emitInitIndexProp(); + MOZ_MUST_USE bool emitInitIndexGetter(); + MOZ_MUST_USE bool emitInitIndexSetter(); + + MOZ_MUST_USE bool emitInitComputedProp(); + MOZ_MUST_USE bool emitInitComputedGetter(); + MOZ_MUST_USE bool emitInitComputedSetter(); + + private: + MOZ_MUST_USE MOZ_ALWAYS_INLINE bool prepareForProp( + const mozilla::Maybe& keyPos, bool isStatic, bool isComputed); + + // @param op + // Opcode for initializing property + // @param key + // Atom of the property if the property key is not computed + MOZ_MUST_USE bool emitInit(JSOp op, JS::Handle key); + MOZ_MUST_USE bool emitInitIndexOrComputed(JSOp op); + + MOZ_MUST_USE bool emitPopClassConstructor(); +}; + +// Class for emitting bytecode for object literal. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `{}` +// ObjectEmitter oe(this); +// oe.emitObject(0); +// oe.emitEnd(); +// +// `{ prop: 10 }` +// ObjectEmitter oe(this); +// oe.emitObject(1); +// +// oe.prepareForPropValue(Some(offset_of_prop)); +// emit(10); +// oe.emitInitProp(atom_of_prop); +// +// oe.emitEnd(); +// +// `{ prop: function() {} }`, when property value is anonymous function +// ObjectEmitter oe(this); +// oe.emitObject(1); +// +// oe.prepareForPropValue(Some(offset_of_prop)); +// emit(function); +// oe.emitInitProp(atom_of_prop); +// +// oe.emitEnd(); +// +// `{ get prop() { ... }, set prop(v) { ... } }` +// ObjectEmitter oe(this); +// oe.emitObject(2); +// +// oe.prepareForPropValue(Some(offset_of_prop)); +// emit(function_for_getter); +// oe.emitInitGetter(atom_of_prop); +// +// oe.prepareForPropValue(Some(offset_of_prop)); +// emit(function_for_setter); +// oe.emitInitSetter(atom_of_prop); +// +// oe.emitEnd(); +// +// `{ 1: 10, get 2() { ... }, set 3(v) { ... } }` +// ObjectEmitter oe(this); +// oe.emitObject(3); +// +// oe.prepareForIndexPropKey(Some(offset_of_prop)); +// emit(1); +// oe.prepareForIndexPropValue(); +// emit(10); +// oe.emitInitIndexedProp(); +// +// oe.prepareForIndexPropKey(Some(offset_of_opening_bracket)); +// emit(2); +// oe.prepareForIndexPropValue(); +// emit(function_for_getter); +// oe.emitInitIndexGetter(); +// +// oe.prepareForIndexPropKey(Some(offset_of_opening_bracket)); +// emit(3); +// oe.prepareForIndexPropValue(); +// emit(function_for_setter); +// oe.emitInitIndexSetter(); +// +// oe.emitEnd(); +// +// `{ [prop1]: 10, get [prop2]() { ... }, set [prop3](v) { ... } }` +// ObjectEmitter oe(this); +// oe.emitObject(3); +// +// oe.prepareForComputedPropKey(Some(offset_of_opening_bracket)); +// emit(prop1); +// oe.prepareForComputedPropValue(); +// emit(10); +// oe.emitInitComputedProp(); +// +// oe.prepareForComputedPropKey(Some(offset_of_opening_bracket)); +// emit(prop2); +// oe.prepareForComputedPropValue(); +// emit(function_for_getter); +// oe.emitInitComputedGetter(); +// +// oe.prepareForComputedPropKey(Some(offset_of_opening_bracket)); +// emit(prop3); +// oe.prepareForComputedPropValue(); +// emit(function_for_setter); +// oe.emitInitComputedSetter(); +// +// oe.emitEnd(); +// +// `{ __proto__: obj }` +// ObjectEmitter oe(this); +// oe.emitObject(1); +// oe.prepareForProtoValue(Some(offset_of___proto__)); +// emit(obj); +// oe.emitMutateProto(); +// oe.emitEnd(); +// +// `{ ...obj }` +// ObjectEmitter oe(this); +// oe.emitObject(1); +// oe.prepareForSpreadOperand(Some(offset_of_triple_dots)); +// emit(obj); +// oe.emitSpread(); +// oe.emitEnd(); +// +class MOZ_STACK_CLASS ObjectEmitter : public PropertyEmitter { + private: + // The offset of JSOP_NEWINIT, which is replced by JSOP_NEWOBJECT later + // when the object is known to have a fixed shape. + ptrdiff_t top_ = 0; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ emitObject +--------+ + // | Start |----------->| Object |-+ + // +-------+ +--------+ | + // | + // +-----------------------------+ + // | + // | (do PropertyEmitter operation) emitEnd +-----+ + // +-------------------------------+--------->| End | + // +-----+ + enum class ObjectState { + // The initial state. + Start, + + // After calling emitObject. + Object, + + // After calling emitEnd. + End, + }; + ObjectState objectState_ = ObjectState::Start; +#endif + + public: + explicit ObjectEmitter(BytecodeEmitter* bce); + + MOZ_MUST_USE bool emitObject(size_t propertyCount); + MOZ_MUST_USE bool emitEnd(); +}; + +// Save and restore the strictness. +// Used by class declaration/expression to temporarily enable strict mode. +class MOZ_RAII AutoSaveLocalStrictMode { + SharedContext* sc_; + bool savedStrictness_; + + public: + explicit AutoSaveLocalStrictMode(SharedContext* sc); + ~AutoSaveLocalStrictMode(); + + // Force restore the strictness now. + void restore(); +}; + +// Class for emitting bytecode for JS class. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `class {}` +// ClassEmitter ce(this); +// ce.emitClass(nullptr, nullptr, false); +// +// ce.emitInitDefaultConstructor(Some(offset_of_class), +// Some(offset_of_closing_bracket)); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class { constructor() { ... } }` +// ClassEmitter ce(this); +// ce.emitClass(nullptr, nullptr, false); +// +// emit(function_for_constructor); +// ce.emitInitConstructor(/* needsHomeObject = */ false); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class X { constructor() { ... } }` +// ClassEmitter ce(this); +// ce.emitScopeForNamedClass(scopeBindingForName); +// ce.emitClass(atom_of_X, nullptr, false); +// +// ce.emitInitDefaultConstructor(Some(offset_of_class), +// Some(offset_of_closing_bracket)); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class X { constructor() { ... } }` +// ClassEmitter ce(this); +// ce.emitScopeForNamedClass(scopeBindingForName); +// ce.emitClass(atom_of_X, nullptr, false); +// +// emit(function_for_constructor); +// ce.emitInitConstructor(/* needsHomeObject = */ false); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class X extends Y { constructor() { ... } }` +// ClassEmitter ce(this); +// ce.emitScopeForNamedClass(scopeBindingForName); +// +// emit(Y); +// ce.emitDerivedClass(atom_of_X, nullptr, false); +// +// emit(function_for_constructor); +// ce.emitInitConstructor(/* needsHomeObject = */ false); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class X extends Y { constructor() { ... super.f(); ... } }` +// ClassEmitter ce(this); +// ce.emitScopeForNamedClass(scopeBindingForName); +// +// emit(Y); +// ce.emitDerivedClass(atom_of_X, nullptr, false); +// +// emit(function_for_constructor); +// // pass true if constructor contains super.prop access +// ce.emitInitConstructor(/* needsHomeObject = */ true); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `m() {}` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_m)); +// emit(function_for_m); +// ce.emitInitProp(atom_of_m); +// +// `m() { super.f(); }` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_m)); +// emit(function_for_m); +// ce.emitInitHomeObject(); +// ce.emitInitProp(atom_of_m); +// +// `async m() { super.f(); }` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_m)); +// emit(function_for_m); +// ce.emitInitHomeObject(FunctionAsyncKind::Async); +// ce.emitInitProp(atom_of_m); +// +// `get p() { super.f(); }` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_p)); +// emit(function_for_p); +// ce.emitInitHomeObject(); +// ce.emitInitGetter(atom_of_m); +// +// `static m() {}` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_m), +// PropertyEmitter::Kind::Static); +// emit(function_for_m); +// ce.emitInitProp(atom_of_m); +// +// `static get [p]() { super.f(); }` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForComputedPropValue(Some(offset_of_m), +// PropertyEmitter::Kind::Static); +// emit(p); +// ce.prepareForComputedPropValue(); +// emit(function_for_m); +// ce.emitInitHomeObject(); +// ce.emitInitComputedGetter(); +// +class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter { + public: + enum class Kind { + // Class expression. + Expression, + + // Class declaration. + Declaration, + }; + + private: + // Pseudocode for class declarations: + // + // class extends BaseExpression { + // constructor() { ... } + // ... + // } + // + // + // if defined { + // let heritage = BaseExpression; + // + // if (heritage !== null) { + // funProto = heritage; + // objProto = heritage.prototype; + // } else { + // funProto = %FunctionPrototype%; + // objProto = null; + // } + // } else { + // objProto = %ObjectPrototype%; + // } + // + // let homeObject = ObjectCreate(objProto); + // + // if defined { + // if defined { + // cons = DefineMethod(, proto=homeObject, + // funProto=funProto); + // } else { + // cons = DefineMethod(, proto=homeObject); + // } + // } else { + // if defined { + // cons = DefaultDerivedConstructor(proto=homeObject, + // funProto=funProto); + // } else { + // cons = DefaultConstructor(proto=homeObject); + // } + // } + // + // cons.prototype = homeObject; + // homeObject.constructor = cons; + // + // EmitPropertyList(...) + + bool isDerived_ = false; + + mozilla::Maybe tdzCacheForInnerName_; + mozilla::Maybe innerNameScope_; + AutoSaveLocalStrictMode strictMode_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ + // | Start |-+------------------------------------>+-+ + // +-------+ | ^ | + // | [named class] | | + // | emitScopeForNamedClass +-------+ | | + // +-------------------------->| Scope |-+ | + // +-------+ | + // | + // +-----------------------------------------------+ + // | + // | emitClass +-------+ + // +-+----------------->+->| Class |-+ + // | ^ +-------+ | + // | emitDerivedClass | | + // +------------------+ | + // | + // +-------------------------------+ + // | + // | + // | emitInitConstructor +-----------------+ + // +-+--------------------------->+->| InitConstructor |-+ + // | ^ +-----------------+ | + // | emitInitDefaultConstructor | | + // +----------------------------+ | + // | + // +---------------------------------------------------+ + // | + // | (do PropertyEmitter operation) emitEnd +-----+ + // +-------------------------------+--------->| End | + // +-----+ + enum class ClassState { + // The initial state. + Start, + + // After calling emitScopeForNamedClass. + Scope, + + // After calling emitClass or emitDerivedClass. + Class, + + // After calling emitInitConstructor or emitInitDefaultConstructor. + InitConstructor, + + // After calling emitEnd. + End, + }; + ClassState classState_ = ClassState::Start; +#endif + + JS::Rooted name_; + JS::Rooted nameForAnonymousClass_; + bool hasNameOnStack_ = false; + + public: + explicit ClassEmitter(BytecodeEmitter* bce); + + MOZ_MUST_USE bool emitScopeForNamedClass( + JS::Handle scopeBindings); + + // @param name + // Name of the class (nullptr if this is anonymous class) + // @param nameForAnonymousClass + // Statically inferred name of the class (only for anonymous classes) + // @param hasNameOnStack + // If true the name is on the stack (only for anonymous classes) + MOZ_MUST_USE bool emitClass(JS::Handle name, + JS::Handle nameForAnonymousClass, + bool hasNameOnStack); + MOZ_MUST_USE bool emitDerivedClass(JS::Handle name, + JS::Handle nameForAnonymousClass, + bool hasNameOnStack); + + // @param needsHomeObject + // True if the constructor contains `super.foo` + MOZ_MUST_USE bool emitInitConstructor(bool needsHomeObject); + + // Parameters are the offset in the source code for each character below: + // + // class X { foo() {} } + // ^ ^ + // | | + // | classEnd + // | + // classStart + // + MOZ_MUST_USE bool emitInitDefaultConstructor( + const mozilla::Maybe& classStart, + const mozilla::Maybe& classEnd); + + MOZ_MUST_USE bool emitEnd(Kind kind); + + private: + MOZ_MUST_USE bool emitSetEmptyClassConstructorNameForDefaultCtor(); + MOZ_MUST_USE bool initProtoAndCtor(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_ObjectEmitter_h */ diff --git a/js/src/frontend/OptionalEmitter.cpp b/js/src/frontend/OptionalEmitter.cpp new file mode 100644 index 000000000000..33576fb62959 --- /dev/null +++ b/js/src/frontend/OptionalEmitter.cpp @@ -0,0 +1,140 @@ +/* -*- 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/OptionalEmitter.h" + +#include "frontend/BytecodeEmitter.h" +#include "frontend/IfEmitter.h" // IfEmitter, InternalIfEmitter, CondEmitter +#include "frontend/SharedContext.h" +#include "vm/Opcodes.h" +#include "vm/String.h" + +using namespace js; +using namespace js::frontend; + +OptionalEmitter::OptionalEmitter(BytecodeEmitter* bce, int32_t initialDepth, + JSOp op, Kind kind) + : bce_(bce), tdzCache_(bce), + breakInfo_(bce, StatementKind::Label), + initialDepth_(initialDepth), op_(op), kind_(kind) {} + +bool OptionalEmitter::emitOptionalJumpLabel() { + MOZ_ASSERT(state_ == State::Start); + if (!bce_->emitJump(JSOP_LABEL, &top_)) { + return false; + } +#ifdef DEBUG + state_ = State::Label; +#endif + return true; +} + +bool OptionalEmitter::emitJumpShortCircuit() { + MOZ_ASSERT(state_ == State::Label || state_ == State::ShortCircuit || + state_ == State::ShortCircuitForCall); + MOZ_ASSERT(initialDepth_ + 1 == bce_->stackDepth); + InternalIfEmitter ifEmitter(bce_); + if (!bce_->emitPushNotUndefinedOrNull()) + return false; + + if (!bce_->emit1(JSOP_NOT)) + return false; + + if (!ifEmitter.emitThen()) + return false; + + // Perform ShortCircuiting code and break + if (!bce_->emit1(JSOP_POP)) + return false; + + if (!bce_->emit1(op_)) + return false; + + if (kind_ == Kind::Reference) { + if (!bce_->emit1(op_)) + return false; + } + + if (!bce_->emitGoto(&breakInfo_, &breakInfo_.breaks, SRC_BREAK2LABEL)) + return false; + + if (!ifEmitter.emitEnd()) { + return false; + } +#ifdef DEBUG + state_ = State::ShortCircuit; +#endif + return true; +} + +bool OptionalEmitter::emitJumpShortCircuitForCall() { + MOZ_ASSERT(state_ == State::Label || state_ == State::ShortCircuit || + state_ == State::ShortCircuitForCall); + int32_t depth = bce_->stackDepth; + MOZ_ASSERT(initialDepth_ + 2 == depth); + if (!bce_->emit1(JSOP_SWAP)) + return false; + + InternalIfEmitter ifEmitter(bce_); + if (!bce_->emitPushNotUndefinedOrNull()) + return false; + + if (!bce_->emit1(JSOP_NOT)) + return false; + + if (!ifEmitter.emitThen()) + return false; + + // Perform ShortCircuiting code for Call and break + if (!bce_->emit1(JSOP_POP)) + return false; + + if (!bce_->emit1(JSOP_POP)) + return false; + + if (!bce_->emit1(op_)) + return false; + + if (kind_ == Kind::Reference) { + if (!bce_->emit1(op_)) + return false; + } + + if (!bce_->emitGoto(&breakInfo_, &breakInfo_.breaks, SRC_BREAK2LABEL)) + return false; + + if (!ifEmitter.emitEnd()) + return false; + + bce_->stackDepth = depth; + + if (!bce_->emit1(JSOP_SWAP)) + return false; +#ifdef DEBUG + state_ = State::ShortCircuitForCall; +#endif + return true; +} + +bool OptionalEmitter::emitOptionalJumpTarget() { + MOZ_ASSERT(state_ == State::ShortCircuit || + state_ == State::ShortCircuitForCall); + // Patch the JSOP_LABEL offset. + JumpTarget brk{ bce_->lastNonJumpTargetOffset() }; + bce_->patchJumpsToTarget(top_, brk); + + // Patch the emitGoto() offset. + if (!breakInfo_.patchBreaks(bce_)) + return false; + + // XXX: Commented out due to workaround for missing JSOP_GOTO functionality + /*// reset stack depth to the depth when we jumped + bce_->stackDepth = initialDepth_ + 1;*/ +#ifdef DEBUG + state_ = State::JumpEnd; +#endif + return true; +} diff --git a/js/src/frontend/OptionalEmitter.h b/js/src/frontend/OptionalEmitter.h new file mode 100644 index 000000000000..ca3320adcb12 --- /dev/null +++ b/js/src/frontend/OptionalEmitter.h @@ -0,0 +1,240 @@ +/* -*- 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_OptionalEmitter_h +#define frontend_OptionalEmitter_h + +#include "mozilla/Attributes.h" +#include "frontend/BytecodeControlStructures.h" // BreakableControl +#include "frontend/IfEmitter.h" // IfEmitter, InternalIfEmitter, CondEmitter +#include "frontend/TDZCheckCache.h" + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting bytecode for optional expressions. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `obj?.prop;` +// OptionalEmitter oe(this, JSOp::Undefined); +// PropOpEmitter poe(this, +// PropOpEmitter::Kind::Get, +// PropOpEmitter::ObjKind::Other); +// oe.emitOptionalJumpLabel(); +// poe.prepareForObj(); +// emit(obj); +// oe.emitJumpShortCircuit(); +// poe.emitGet(atom_of_prop); +// oe.emitOptionalJumpTarget(); +// +// `delete obj?.prop;` +// OptionalEmitter oe(this, JSOp:True); +// OptionalPropOpEmitter poe(this, +// PropOpEmitter::Kind::Delete, +// PropOpEmitter::ObjKind::Other); +// oe.emitOptionalJumpLabel(); +// poe.prepareForObj(); +// emit(obj); +// oe.emitJumpShortCircuit(); +// poe.emitDelete(atom_of_prop); +// oe.emitOptionalJumpTarget(); +// +// `obj?.[key];` +// OptionalEmitter oe(this, JSOp::Undefined); +// ElemOpEmitter eoe(this, +// ElemOpEmitter::Kind::Get, +// ElemOpEmitter::ObjKind::Other); +// oe.emitOptionalJumpLabel(); +// eoe.prepareForObj(); +// emit(obj); +// oe.emitJumpShortCircuit(); +// eoe.prepareForKey(); +// emit(key); +// eoe.emitGet(); +// oe.emitOptionalJumpTarget(); +// +// `delete obj?.[key];` +// OptionalEmitter oe(this, JSOp::True); +// ElemOpEmitter eoe(this, +// ElemOpEmitter::Kind::Delete, +// ElemOpEmitter::ObjKind::Other); +// oe.emitOptionalJumpLabel(); +// eoe.prepareForObj(); +// emit(obj); +// oe.emitJumpShortCircuit(); +// eoe.prepareForKey(); +// emit(key); +// eoe.emitDelete(); +// oe.emitOptionalJumpTarget(); +// +// `print?.(arg);` +// OptionalEmitter oe(this, JSOp::Undefined); +// CallOrNewEmitter cone(this, JSOp::Call, +// CallOrNewEmitter::ArgumentsKind::Other, +// ValueUsage::WantValue); +// oe.emitOptionalJumpLabel(); +// cone.emitNameCallee(print); +// cone.emitThis(); +// oe.emitShortCircuitForCall(); +// cone.prepareForNonSpreadArguments(); +// emit(arg); +// cone.emitEnd(1, Some(offset_of_callee)); +// oe.emitOptionalJumpTarget(); +// +// `callee.prop?.(arg1, arg2);` +// OptionalEmitter oe(this, JSOp::Undefined); +// CallOrNewEmitter cone(this, JSOp::Call, +// CallOrNewEmitter::ArgumentsKind::Other, +// ValueUsage::WantValue); +// oe.emitOptionalJumpLabel(); +// PropOpEmitter& poe = cone.prepareForPropCallee(false); +// ... emit `callee.prop` with `poe` here... +// cone.emitThis(); +// oe.emitShortCircuitForCall(); +// cone.prepareForNonSpreadArguments(); +// emit(arg1); +// emit(arg2); +// cone.emitEnd(2, Some(offset_of_callee)); +// oe.emitOptionalJumpTarget(); +// +// `callee[key]?.(arg);` +// OptionalEmitter oe(this, JSOp::Undefined); +// CallOrNewEmitter cone(this, JSOp::Call, +// CallOrNewEmitter::ArgumentsKind::Other, +// ValueUsage::WantValue); +// oe.emitOptionalJumpLabel(); +// ElemOpEmitter& eoe = cone.prepareForElemCallee(false); +// ... emit `callee[key]` with `eoe` here... +// cone.emitThis(); +// oe.emitShortCircuitForCall(); +// cone.prepareForNonSpreadArguments(); +// emit(arg); +// cone.emitEnd(1, Some(offset_of_callee)); +// oe.emitOptionalJumpTarget(); +// +// `(function() { ... })?.(arg);` +// OptionalEmitter oe(this, JSOp::Undefined); +// CallOrNewEmitter cone(this, JSOp::Call, +// CallOrNewEmitter::ArgumentsKind::Other, +// ValueUsage::WantValue); +// oe.emitOptionalJumpLabel(); +// cone.prepareForFunctionCallee(); +// emit(function); +// cone.emitThis(); +// oe.emitShortCircuitForCall(); +// cone.prepareForNonSpreadArguments(); +// emit(arg); +// cone.emitEnd(1, Some(offset_of_callee)); +// oe.emitOptionalJumpTarget(); +// +// `(a?b)();` +// OptionalEmitter oe(this, JSOp::Undefined, OptionalEmitter::Kind::Reference); +// CallOrNewEmitter cone(this, JSOP_CALL, +// CallOrNewEmitter::ArgumentsKind::Other, +// ValueUsage::WantValue); +// oe.emitOptionalJumpLabel(); +// cone.prepareForFunctionCallee(); +// emit(optionalChain); +// cone.emitThis(); +// oe.emitOptionalJumpTarget(); +// oe.emitShortCircuitForCall(); +// cone.prepareForNonSpreadArguments(); +// emit(arg); +// cone.emitEnd(1, Some(offset_of_callee)); +// oe.emitOptionalJumpTarget(); +// +class MOZ_RAII OptionalEmitter { + public: + enum class Kind { + // Requires two values on the stack + Reference, + // Requires one value on the stack + Other + }; + + private: + BytecodeEmitter* bce_; + + TDZCheckCache tdzCache_; + + // jumpTarget for the fake label over the optional chaining code + JumpList top_; + + // BreakableControl target for the break from inside the optional chaining code + BreakableControl breakInfo_; + + int32_t initialDepth_; + + // JSOp is the op code to be emitted, Kind is if we are dealing with a + // reference (in which case we need two elements on the stack) or other value + // (which needs one element on the stack) + JSOp op_; + Kind kind_; + + // The state of this emitter. + // + // +-------+ + // | Start | + // +-------+ + // | + // | emitOptionalJumpLabel + // v + // +-------+ emitJumpShortCircuit +--------------+ + // | Label |-+---------------------------->| ShortCircuit |-----------+ + // +-------+ | +--------------+ | + // +----->| | + // | | emitJumpShortCircuitForCall +---------------------+ v + // | +---------------------------->| ShortCircuitForCall |--->+ + // | +---------------------+ | + // | | + // ---------------------------------------------------------------+ + // | + // | + // +------------------------------------------------------------------+ + // | + // | emitOptionalJumpTarget +---------+ + // +----------------------->| JumpEnd | + // +---------+ + // +#ifdef DEBUG + enum class State { + // The initial state. + Start, + + // The fake jump label + Label, + + // for shortcircuiting in most cases. + ShortCircuit, + + // for shortcircuiting from references, which have two items on + // the stack. For example function calls. + ShortCircuitForCall, + + // internally used, end of the jump code + JumpEnd + }; + + State state_ = State::Start; +#endif + + public: + OptionalEmitter(BytecodeEmitter* bce, int32_t initialDepth, + JSOp op = JSOP_UNDEFINED, Kind kind = Kind::Other); + + MOZ_MUST_USE bool emitOptionalJumpLabel(); + MOZ_MUST_USE bool emitJumpShortCircuit(); + MOZ_MUST_USE bool emitJumpShortCircuitForCall(); + MOZ_MUST_USE bool emitOptionalJumpTarget(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_OptionalEmitter_h */ diff --git a/js/src/frontend/ParseNode-inl.h b/js/src/frontend/ParseNode-inl.h index 2402f3b0698c..eaed735a37ea 100644 --- a/js/src/frontend/ParseNode-inl.h +++ b/js/src/frontend/ParseNode-inl.h @@ -22,13 +22,6 @@ ParseNode::name() const return atom->asPropertyName(); } -inline JSAtom* -ParseNode::atom() const -{ - MOZ_ASSERT(isKind(ParseNodeKind::String)); - return pn_atom; -} - } /* namespace frontend */ } /* namespace js */ diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index db6eef677b95..5e4927febe59 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -172,6 +172,9 @@ class ObjectBox; F(Assign) \ F(AddAssign) \ F(SubAssign) \ + F(CoalesceAssignExpr) \ + F(OrAssignExpr) \ + F(AndAssignExpr) \ F(BitOrAssign) \ F(BitXorAssign) \ F(BitAndAssign) \ @@ -328,6 +331,9 @@ IsTypeofKind(ParseNodeKind kind) * Assign binary pn_left: lvalue, pn_right: rvalue * AddAssign, binary pn_left: lvalue, pn_right: rvalue * SubAssign, pn_op: JSOP_ADD for +=, etc. + * CoalesceAssignExpr, + * OrAssignExpr, + * AndAssignExpr, * BitOrAssign, * BitXorAssign, * BitAndAssign, @@ -638,7 +644,6 @@ class ParseNode FullParseHandler* handler, ParseContext* pc); inline PropertyName* name() const; - inline JSAtom* atom() const; ParseNode* expr() const { MOZ_ASSERT(pn_arity == PN_NAME || pn_arity == PN_CODE); @@ -978,6 +983,12 @@ struct CodeNode : public ParseNode MOZ_ASSERT(!pn_objbox); } + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::Function) || node.isKind(ParseNodeKind::Module); + MOZ_ASSERT_IF(match, node.isArity(PN_CODE)); + return match; + } + public: #ifdef DEBUG void dump(GenericPrinter& out, int indent); @@ -993,6 +1004,10 @@ struct NameNode : public ParseNode pn_expr = nullptr; } + static bool test(const ParseNode& node) { + return node.isArity(PN_NAME); + } + #ifdef DEBUG void dump(GenericPrinter& out, int indent); #endif @@ -1236,6 +1251,10 @@ class PropertyAccessBase : public BinaryNode return *pn_u.binary.left; } + ParseNode& key() const { + return *pn_u.binary.right; + } + static bool test(const ParseNode& node) { bool match = node.isKind(ParseNodeKind::Dot) || node.isKind(ParseNodeKind::OptionalDot); @@ -1300,6 +1319,10 @@ class PropertyByValueBase : public ParseNode return *pn_u.binary.left; } + ParseNode& key() const { + return *pn_u.binary.right; + } + static bool test(const ParseNode& node) { bool match = node.isKind(ParseNodeKind::Elem) || node.isKind(ParseNodeKind::OptionalElem); diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index f7d1a23674b8..a9be04baa23c 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -4445,8 +4445,6 @@ Parser::bindingInitializer(Node lhs, DeclarationKind kind, if (!rhs) return null(); - handler.checkAndSetIsDirectRHSAnonFunction(rhs); - Node assign = handler.newAssignment(ParseNodeKind::Assign, lhs, rhs); if (!assign) return null(); @@ -4826,8 +4824,6 @@ Parser::declarationPattern(DeclarationKind declKind, TokenK if (!init) return null(); - handler.checkAndSetIsDirectRHSAnonFunction(init); - return handler.newAssignment(ParseNodeKind::Assign, pattern, init); } @@ -4851,8 +4847,6 @@ Parser::initializerInNameDeclaration(Node binding, if (!initializer) return false; - handler.checkAndSetIsDirectRHSAnonFunction(initializer); - if (forHeadKind && initialDeclaration) { bool isForIn, isForOf; if (!matchInOrOf(&isForIn, &isForOf)) @@ -5771,8 +5765,6 @@ Parser::exportDefaultAssignExpr(uint32_t begin) if (!kid) return null(); - handler.checkAndSetIsDirectRHSAnonFunction(kid); - if (!matchOrInsertSemicolon()) return null(); @@ -7297,18 +7289,16 @@ Parser::classDefinition(YieldHandling yieldHandling, bool isStatic = false; if (tt == TokenKind::Static) { - if (!tokenStream.peekToken(&tt)) - return null(); - if (tt == TokenKind::RightCurly) { - tokenStream.consumeKnownToken(tt); - error(JSMSG_UNEXPECTED_TOKEN, "property name", TokenKindToDesc(tt)); + if (!tokenStream.peekToken(&tt)) { return null(); } - if (tt != TokenKind::LeftParen) + if (tt != TokenKind::LeftParen && tt != TokenKind::Assign && + tt != TokenKind::Semi && tt != TokenKind::RightCurly) { isStatic = true; - else + } else { anyChars.ungetToken(); + } } else { anyChars.ungetToken(); } @@ -7379,8 +7369,6 @@ Parser::classDefinition(YieldHandling yieldHandling, if (!fn) return null(); - handler.checkAndSetIsDirectRHSAnonFunction(fn); - AccessorType atype = ToAccessorType(propType); if (!handler.addClassMethodDefinition(classMethods, propName, fn, atype, isStatic)) return null(); @@ -8327,6 +8315,15 @@ Parser::assignExpr(InHandling inHandling, YieldHandling yie case TokenKind::Assign: kind = ParseNodeKind::Assign; break; case TokenKind::AddAssign: kind = ParseNodeKind::AddAssign; break; case TokenKind::SubAssign: kind = ParseNodeKind::SubAssign; break; + case TokenKind::CoalesceAssign: + kind = ParseNodeKind::CoalesceAssignExpr; + break; + case TokenKind::OrAssign: + kind = ParseNodeKind::OrAssignExpr; + break; + case TokenKind::AndAssign: + kind = ParseNodeKind::AndAssignExpr; + break; case TokenKind::BitOrAssign: kind = ParseNodeKind::BitOrAssign; break; case TokenKind::BitXorAssign: kind = ParseNodeKind::BitXorAssign; break; case TokenKind::BitAndAssign: kind = ParseNodeKind::BitAndAssign; break; @@ -8371,11 +8368,24 @@ Parser::assignExpr(InHandling inHandling, YieldHandling yie } else if (handler.isPropertyAccess(lhs)) { // Permitted: no additional testing/fixup needed. } else if (handler.isFunctionCall(lhs)) { - if (!strictModeErrorAt(exprPos.begin, JSMSG_BAD_LEFTSIDE_OF_ASS)) + // We don't have to worry about backward compatibility issues with the new + // compound assignment operators, so we always throw here. Also that way we + // don't have to worry if |f() &&= expr| should always throw an error or + // only if |f()| returns true. + if (kind == ParseNodeKind::CoalesceAssignExpr || + kind == ParseNodeKind::OrAssignExpr || + kind == ParseNodeKind::AndAssignExpr) { + errorAt(exprPos.begin, JSMSG_BAD_LEFTSIDE_OF_ASS); return null(); + } - if (possibleError) + if (!strictModeErrorAt(exprPos.begin, JSMSG_BAD_LEFTSIDE_OF_ASS)) { + return null(); + } + + if (possibleError) { possibleError->setPendingDestructuringErrorAt(exprPos, JSMSG_BAD_DESTRUCT_TARGET); + } } else { errorAt(exprPos.begin, JSMSG_BAD_LEFTSIDE_OF_ASS); return null(); @@ -8388,9 +8398,6 @@ Parser::assignExpr(InHandling inHandling, YieldHandling yie if (!rhs) return null(); - if (kind == ParseNodeKind::Assign) - handler.checkAndSetIsDirectRHSAnonFunction(rhs); - return handler.newAssignment(kind, lhs, rhs); } @@ -10159,17 +10166,12 @@ Parser::objectLiteral(YieldHandling yieldHandling, if (!propExpr) return null(); - handler.checkAndSetIsDirectRHSAnonFunction(propExpr); - if (!checkDestructuringAssignmentElement(propExpr, exprPos, &possibleErrorInner, possibleError)) { return null(); } - if (foldConstants && !FoldConstants(context, &propExpr, this)) - return null(); - if (propAtom == context->names().proto) { if (seenPrototypeMutation) { // Directly report the error when we're definitely not @@ -10186,18 +10188,25 @@ Parser::objectLiteral(YieldHandling yieldHandling, } seenPrototypeMutation = true; - // Note: this occurs *only* if we observe TokenKind::Colon! - // Only __proto__: v mutates [[Prototype]]. Getters, setters, - // method/generator definitions, computed property name - // versions of all of these, and shorthands do not. + if (foldConstants && !FoldConstants(context, &propExpr, this)) + return null(); + + // This occurs *only* if we observe PropertyType::Normal! + // Only |__proto__: v| mutates [[Prototype]]. Getters, + // setters, method/generator definitions, computed + // property name versions of all of these, and shorthands + // do not. if (!handler.addPrototypeMutation(literal, namePos.begin, propExpr)) return null(); } else { - if (!handler.isConstant(propExpr)) - handler.setListFlag(literal, PNX_NONCONST); + Node propDef = handler.newPropertyDefinition(propName, propExpr); + if (!propDef) + return null(); - if (!handler.addPropertyDefinition(literal, propName, propExpr)) + if (foldConstants && !FoldConstants(context, &propDef, this)) return null(); + + handler.addPropertyDefinition(literal, propDef); } } else if (propType == PropertyType::Shorthand) { /* @@ -10265,8 +10274,6 @@ Parser::objectLiteral(YieldHandling yieldHandling, if (!rhs) return null(); - handler.checkAndSetIsDirectRHSAnonFunction(rhs); - Node propExpr = handler.newAssignment(ParseNodeKind::Assign, lhs, rhs); if (!propExpr) return null(); @@ -10289,8 +10296,6 @@ Parser::objectLiteral(YieldHandling yieldHandling, if (!fn) return null(); - handler.checkAndSetIsDirectRHSAnonFunction(fn); - AccessorType atype = ToAccessorType(propType); if (!handler.addObjectMethodDefinition(literal, propName, fn, atype)) return null(); diff --git a/js/src/frontend/PropOpEmitter.cpp b/js/src/frontend/PropOpEmitter.cpp new file mode 100644 index 000000000000..fd00024e7cd0 --- /dev/null +++ b/js/src/frontend/PropOpEmitter.cpp @@ -0,0 +1,275 @@ +/* -*- 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/PropOpEmitter.h" + +#include "frontend/BytecodeEmitter.h" +#include "frontend/SharedContext.h" +#include "vm/Opcodes.h" +#include "vm/String.h" + +using namespace js; +using namespace js::frontend; + +PropOpEmitter::PropOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind) + : bce_(bce), + kind_(kind), + objKind_(objKind) +{} + +bool +PropOpEmitter::prepareAtomIndex(JSAtom* prop) +{ + if (!bce_->makeAtomIndex(prop, &propAtomIndex_)) { + return false; + } + isLength_ = prop == bce_->cx->names().length; + + return true; +} + +bool +PropOpEmitter::prepareForObj() +{ + MOZ_ASSERT(state_ == State::Start); + +#ifdef DEBUG + state_ = State::Obj; +#endif + return true; +} + +bool +PropOpEmitter::emitGet(JSAtom* prop) +{ + MOZ_ASSERT(state_ == State::Obj); + + if (!prepareAtomIndex(prop)) { + return false; + } + if (isCall()) { + if (!bce_->emit1(JSOP_DUP)) { // [Super] + // // THIS THIS + // // [Other] + // // OBJ OBJ + return false; + } + } + if (isSuper()) { + if (!bce_->emit1(JSOP_SUPERBASE)) { // THIS? THIS SUPERBASE + return false; + } + } + if (isIncDec() || isCompoundAssignment()) { + if (isSuper()) { + if (!bce_->emit1(JSOP_DUP2)) { // THIS SUPERBASE THIS SUPERBASE + return false; + } + } else { + if (!bce_->emit1(JSOP_DUP)) { // OBJ OBJ + return false; + } + } + } + + JSOp op; + if (isSuper()) { + op = JSOP_GETPROP_SUPER; + } else if (isCall()) { + op = JSOP_CALLPROP; + } else { + op = isLength_ ? JSOP_LENGTH : JSOP_GETPROP; + } + if (!bce_->emitAtomOp(propAtomIndex_, op)) { // [Get] + // // PROP + // // [Call] + // // THIS PROP + // // [Inc/Dec/Compound, + // // Super] + // // THIS SUPERBASE PROP + // // [Inc/Dec/Compound, + // // Other] + // // OBJ PROP + return false; + } + if (isCall()) { + if (!bce_->emit1(JSOP_SWAP)) { // PROP THIS + return false; + } + } + +#ifdef DEBUG + state_ = State::Get; +#endif + return true; +} + +bool +PropOpEmitter::prepareForRhs() +{ + MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment()); + MOZ_ASSERT_IF(isSimpleAssignment(), state_ == State::Obj); + MOZ_ASSERT_IF(isCompoundAssignment(), state_ == State::Get); + + if (isSimpleAssignment()) { + // For CompoundAssignment, SUPERBASE is already emitted by emitGet. + if (isSuper()) { + if (!bce_->emit1(JSOP_SUPERBASE)) { // THIS SUPERBASE + return false; + } + } + } + +#ifdef DEBUG + state_ = State::Rhs; +#endif + return true; +} + +bool +PropOpEmitter::skipObjAndRhs() +{ + MOZ_ASSERT(state_ == State::Start); + MOZ_ASSERT(isSimpleAssignment()); + +#ifdef DEBUG + state_ = State::Rhs; +#endif + return true; +} + +bool +PropOpEmitter::emitDelete(JSAtom* prop) +{ + MOZ_ASSERT_IF(!isSuper(), state_ == State::Obj); + MOZ_ASSERT_IF(isSuper(), state_ == State::Start); + MOZ_ASSERT(isDelete()); + + if (!prepareAtomIndex(prop)) { + return false; + } + if (isSuper()) { + if (!bce_->emit1(JSOP_SUPERBASE)) { // THIS SUPERBASE + return false; + } + + // Unconditionally throw when attempting to delete a super-reference. + if (!bce_->emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER)) { + return false; // THIS SUPERBASE + } + + // Another wrinkle: Balance the stack from the emitter's point of view. + // Execution will not reach here, as the last bytecode threw. + if (!bce_->emit1(JSOP_POP)) { // THIS + return false; + } + } else { + JSOp op = bce_->sc->strict() ? JSOP_STRICTDELPROP : JSOP_DELPROP; + if (!bce_->emitAtomOp(propAtomIndex_, op)) { // SUCCEEDED + return false; + } + } + +#ifdef DEBUG + state_ = State::Delete; +#endif + return true; +} + +bool +PropOpEmitter::emitAssignment(JSAtom* prop) +{ + MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment()); + MOZ_ASSERT(state_ == State::Rhs); + + if (isSimpleAssignment()) { + if (!prepareAtomIndex(prop)) { + return false; + } + } + + JSOp setOp = isSuper() + ? bce_->sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER + : bce_->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP; + if (!bce_->emitAtomOp(propAtomIndex_, setOp)) { // VAL + return false; + } + +#ifdef DEBUG + state_ = State::Assignment; +#endif + return true; +} + +bool +PropOpEmitter::emitIncDec(JSAtom* prop) +{ + MOZ_ASSERT(state_ == State::Obj); + MOZ_ASSERT(isIncDec()); + + if (!emitGet(prop)) { + return false; + } + + MOZ_ASSERT(state_ == State::Get); + + JSOp binOp = isInc() ? JSOP_ADD : JSOP_SUB; + + if (!bce_->emit1(JSOP_POS)) { // ... N + return false; + } + if (isPostIncDec()) { + if (!bce_->emit1(JSOP_DUP)) { // ... N N + return false; + } + } + if (!bce_->emit1(JSOP_ONE)) { // ... N? N 1 + return false; + } + if (!bce_->emit1(binOp)) { // ... N? N+1 + return false; + } + if (isPostIncDec()) { + if (isSuper()) { // THIS OBJ N N+1 + if (!bce_->emit2(JSOP_PICK, 3)) { // OBJ N N+1 THIS + return false; + } + if (!bce_->emit1(JSOP_SWAP)) { // OBJ N THIS N+1 + return false; + } + if (!bce_->emit2(JSOP_PICK, 3)) { // N THIS N+1 OBJ + return false; + } + if (!bce_->emit1(JSOP_SWAP)) { // N THIS OBJ N+1 + return false; + } + } else { // OBJ N N+1 + if (!bce_->emit2(JSOP_PICK, 2)) { // N N+1 OBJ + return false; + } + if (!bce_->emit1(JSOP_SWAP)) { // N OBJ N+1 + return false; + } + } + } + + JSOp setOp = isSuper() + ? bce_->sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER + : bce_->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP; + if (!bce_->emitAtomOp(propAtomIndex_, setOp)) { // N? N+1 + return false; + } + if (isPostIncDec()) { + if (!bce_->emit1(JSOP_POP)) { // N + return false; + } + } + +#ifdef DEBUG + state_ = State::IncDec; +#endif + return true; +} diff --git a/js/src/frontend/PropOpEmitter.h b/js/src/frontend/PropOpEmitter.h new file mode 100644 index 000000000000..2c5a26ec4513 --- /dev/null +++ b/js/src/frontend/PropOpEmitter.h @@ -0,0 +1,269 @@ +/* -*- 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_PropOpEmitter_h +#define frontend_PropOpEmitter_h + +#include "mozilla/Attributes.h" + +#include + +#include "js/TypeDecls.h" + +class JSAtom; + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting bytecode for property operation. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `obj.prop;` +// PropOpEmitter poe(this, +// PropOpEmitter::Kind::Get, +// PropOpEmitter::ObjKind::Other); +// poe.prepareForObj(); +// emit(obj); +// poe.emitGet(atom_of_prop); +// +// `super.prop;` +// PropOpEmitter poe(this, +// PropOpEmitter::Kind::Get, +// PropOpEmitter::ObjKind::Super); +// poe.prepareForObj(); +// emit(obj); +// poe.emitGet(atom_of_prop); +// +// `obj.prop();` +// PropOpEmitter poe(this, +// PropOpEmitter::Kind::Call, +// PropOpEmitter::ObjKind::Other); +// poe.prepareForObj(); +// emit(obj); +// poe.emitGet(atom_of_prop); +// emit_call_here(); +// +// `new obj.prop();` +// PropOpEmitter poe(this, +// PropOpEmitter::Kind::Call, +// PropOpEmitter::ObjKind::Other); +// poe.prepareForObj(); +// emit(obj); +// poe.emitGet(atom_of_prop); +// emit_call_here(); +// +// `delete obj.prop;` +// PropOpEmitter poe(this, +// PropOpEmitter::Kind::Delete, +// PropOpEmitter::ObjKind::Other); +// poe.prepareForObj(); +// emit(obj); +// poe.emitDelete(atom_of_prop); +// +// `delete super.prop;` +// PropOpEmitter poe(this, +// PropOpEmitter::Kind::Delete, +// PropOpEmitter::ObjKind::Other); +// poe.emitDelete(atom_of_prop); +// +// `obj.prop++;` +// PropOpEmitter poe(this, +// PropOpEmitter::Kind::PostIncrement, +// PropOpEmitter::ObjKind::Other); +// poe.prepareForObj(); +// emit(obj); +// poe.emitIncDec(atom_of_prop); +// +// `obj.prop = value;` +// PropOpEmitter poe(this, +// PropOpEmitter::Kind::SimpleAssignment, +// PropOpEmitter::ObjKind::Other); +// poe.prepareForObj(); +// emit(obj); +// poe.prepareForRhs(); +// emit(value); +// poe.emitAssignment(atom_of_prop); +// +// `obj.prop += value;` +// PropOpEmitter poe(this, +// PropOpEmitter::Kind::CompoundAssignment, +// PropOpEmitter::ObjKind::Other); +// poe.prepareForObj(); +// emit(obj); +// poe.emitGet(atom_of_prop); +// poe.prepareForRhs(); +// emit(value); +// emit_add_op_here(); +// poe.emitAssignment(nullptr); // nullptr for CompoundAssignment +// +class MOZ_STACK_CLASS PropOpEmitter +{ + public: + enum class Kind { + Get, + Call, + Set, + Delete, + PostIncrement, + PreIncrement, + PostDecrement, + PreDecrement, + SimpleAssignment, + CompoundAssignment + }; + enum class ObjKind { + Super, + Other + }; + + private: + BytecodeEmitter* bce_; + + Kind kind_; + ObjKind objKind_; + + // The index for the property name's atom. + uint32_t propAtomIndex_ = 0; + + // Whether the property name is `length` or not. + bool isLength_ = false; + +#ifdef DEBUG + // The state of this emitter. + // + // skipObjAndRhs + // +----------------------------+ + // | | + // +-------+ | prepareForObj +-----+ | + // | Start |-+-------------->| Obj |-+ | + // +-------+ +-----+ | | + // | | + // +---------------------------------+ | + // | | + // | | + // | [Get] | + // | [Call] | + // | emitGet +-----+ | + // +---------->| Get | | + // | +-----+ | + // | | + // | [Delete] | + // | emitDelete +--------+ | + // +------------->| Delete | | + // | +--------+ | + // | | + // | [PostIncrement] | + // | [PreIncrement] | + // | [PostDecrement] | + // | [PreDecrement] | + // | emitIncDec +--------+ | + // +------------->| IncDec | | + // | +--------+ | + // | | + // | [SimpleAssignment] | + // | prepareForRhs | +-----+ + // +--------------------->+-------------->+->| Rhs |-+ + // | ^ +-----+ | + // | | | + // | | +---------+ + // | [CompoundAssignment] | | + // | emitGet +-----+ | | emitAssignment +------------+ + // +---------->| Get |----+ + -------------->| Assignment | + // +-----+ +------------+ + enum class State { + // The initial state. + Start, + + // After calling prepareForObj. + Obj, + + // After calling emitGet. + Get, + + // After calling emitDelete. + Delete, + + // After calling emitIncDec. + IncDec, + + // After calling prepareForRhs or skipObjAndRhs. + Rhs, + + // After calling emitAssignment. + Assignment, + }; + State state_ = State::Start; +#endif + + public: + PropOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind); + + private: + MOZ_MUST_USE bool isCall() const { + return kind_ == Kind::Call; + } + + MOZ_MUST_USE bool isSuper() const { + return objKind_ == ObjKind::Super; + } + + MOZ_MUST_USE bool isSimpleAssignment() const { + return kind_ == Kind::SimpleAssignment; + } + + MOZ_MUST_USE bool isDelete() const { + return kind_ == Kind::Delete; + } + + MOZ_MUST_USE bool isCompoundAssignment() const { + return kind_ == Kind::CompoundAssignment; + } + + MOZ_MUST_USE bool isIncDec() const { + return isPostIncDec() || isPreIncDec(); + } + + MOZ_MUST_USE bool isPostIncDec() const { + return kind_ == Kind::PostIncrement || + kind_ == Kind::PostDecrement; + } + + MOZ_MUST_USE bool isPreIncDec() const { + return kind_ == Kind::PreIncrement || + kind_ == Kind::PreDecrement; + } + + MOZ_MUST_USE bool isInc() const { + return kind_ == Kind::PostIncrement || + kind_ == Kind::PreIncrement; + } + + MOZ_MUST_USE bool + prepareAtomIndex(JSAtom* prop); + + public: + MOZ_MUST_USE bool prepareForObj(); + + MOZ_MUST_USE bool emitGet(JSAtom* prop); + + MOZ_MUST_USE bool prepareForRhs(); + MOZ_MUST_USE bool skipObjAndRhs(); + + MOZ_MUST_USE bool emitDelete(JSAtom* prop); + + // `prop` can be nullptr for CompoundAssignment. + MOZ_MUST_USE bool emitAssignment(JSAtom* prop); + + MOZ_MUST_USE bool emitIncDec(JSAtom* prop); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_PropOpEmitter_h */ diff --git a/js/src/frontend/SharedContext.h b/js/src/frontend/SharedContext.h index ea98f06f1834..fbd6e88fb9c6 100644 --- a/js/src/frontend/SharedContext.h +++ b/js/src/frontend/SharedContext.h @@ -15,12 +15,14 @@ #include "builtin/ModuleObject.h" #include "ds/InlineTable.h" +#include "frontend/ParseNode.h" #include "frontend/TokenStream.h" #include "vm/EnvironmentObject.h" namespace js { namespace frontend { +class ParseContext; class ParseNode; enum class StatementKind : uint8_t @@ -314,10 +316,6 @@ class SharedContext bool needStrictChecks() const { return strict() || extraWarnings; } - - bool isDotVariable(JSAtom* atom) const { - return atom == context->names().dotGenerator || atom == context->names().dotThis; - } }; class MOZ_STACK_CLASS GlobalSharedContext : public SharedContext diff --git a/js/src/frontend/SourceNotes.h b/js/src/frontend/SourceNotes.h index 6ae184ae4bed..d09db7998521 100644 --- a/js/src/frontend/SourceNotes.h +++ b/js/src/frontend/SourceNotes.h @@ -37,8 +37,8 @@ namespace js { #define FOR_EACH_SRC_NOTE_TYPE(M) \ M(SRC_NULL, "null", 0) /* Terminates a note vector. */ \ M(SRC_IF, "if", 0) /* JSOP_IFEQ bytecode is from an if-then. */ \ - M(SRC_IF_ELSE, "if-else", 1) /* JSOP_IFEQ bytecode is from an if-then-else. */ \ - M(SRC_COND, "cond", 1) /* JSOP_IFEQ is from conditional ?: operator. */ \ + M(SRC_IF_ELSE, "if-else", 0) /* JSOP_IFEQ bytecode is from an if-then-else. */ \ + M(SRC_COND, "cond", 0) /* JSOP_IFEQ is from conditional ?: operator. */ \ M(SRC_FOR, "for", 3) /* JSOP_NOP or JSOP_POP in for(;;) loop head. */ \ M(SRC_WHILE, "while", 1) /* JSOP_GOTO to for or while loop condition from before \ loop, else JSOP_NOP at top of do-while loop. */ \ diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index 0f2fcd869b8e..f5ff80e4282e 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -266,6 +266,8 @@ class SyntaxParseHandler Node newSuperBase(Node thisName, const TokenPos& pos) { return NodeSuperBase; } MOZ_MUST_USE bool addPrototypeMutation(Node literal, uint32_t begin, Node expr) { return true; } + Node newPropertyDefinition(Node name, Node expr) { return NodeGeneric; } + void addPropertyDefinition(Node literal, Node propdef) {} MOZ_MUST_USE bool addPropertyDefinition(Node literal, Node name, Node expr) { return true; } MOZ_MUST_USE bool addShorthand(Node literal, Node name, Node expr) { return true; } MOZ_MUST_USE bool addSpreadProperty(Node literal, uint32_t begin, Node inner) { return true; } @@ -352,8 +354,6 @@ class SyntaxParseHandler MOZ_MUST_USE bool setLastFunctionFormalParameterDefault(Node funcpn, Node pn) { return true; } - void checkAndSetIsDirectRHSAnonFunction(Node pn) {} - Node newFunctionStatement(const TokenPos& pos) { return NodeFunctionDefinition; } Node newFunctionExpression(const TokenPos& pos) { return NodeFunctionDefinition; } Node newArrowFunction(const TokenPos& pos) { return NodeFunctionDefinition; } @@ -534,8 +534,6 @@ class SyntaxParseHandler } void setInDirectivePrologue(Node pn) {} - bool isConstant(Node pn) { return false; } - bool isName(Node node) { return node == NodeName || node == NodeArgumentsName || diff --git a/js/src/frontend/TDZCheckCache.cpp b/js/src/frontend/TDZCheckCache.cpp new file mode 100644 index 000000000000..2f9245e7d1e1 --- /dev/null +++ b/js/src/frontend/TDZCheckCache.cpp @@ -0,0 +1,76 @@ +/* -*- 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/TDZCheckCache.h" + +#include "frontend/BytecodeEmitter.h" + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; +using mozilla::Nothing; +using mozilla::Some; + +TDZCheckCache::TDZCheckCache(BytecodeEmitter* bce) + : Nestable(&bce->innermostTDZCheckCache), + cache_(bce->cx->frontendCollectionPool()) +{} + +bool +TDZCheckCache::ensureCache(BytecodeEmitter* bce) +{ + return cache_ || cache_.acquire(bce->cx); +} + +Maybe +TDZCheckCache::needsTDZCheck(BytecodeEmitter* bce, JSAtom* name) +{ + if (!ensureCache(bce)) + return Nothing(); + + CheckTDZMap::AddPtr p = cache_->lookupForAdd(name); + if (p) + return Some(p->value().wrapped); + + MaybeCheckTDZ rv = CheckTDZ; + for (TDZCheckCache* it = enclosing(); it; it = it->enclosing()) { + if (it->cache_) { + if (CheckTDZMap::Ptr p2 = it->cache_->lookup(name)) { + rv = p2->value(); + break; + } + } + } + + if (!cache_->add(p, name, rv)) { + ReportOutOfMemory(bce->cx); + return Nothing(); + } + + return Some(rv); +} + +bool +TDZCheckCache::noteTDZCheck(BytecodeEmitter* bce, JSAtom* name, + MaybeCheckTDZ check) +{ + if (!ensureCache(bce)) + return false; + + CheckTDZMap::AddPtr p = cache_->lookupForAdd(name); + if (p) { + MOZ_ASSERT(!check, "TDZ only needs to be checked once per binding per basic block."); + p->value() = check; + } else { + if (!cache_->add(p, name, check)) { + ReportOutOfMemory(bce->cx); + return false; + } + } + + return true; +} diff --git a/js/src/frontend/TDZCheckCache.h b/js/src/frontend/TDZCheckCache.h new file mode 100644 index 000000000000..29ccefda1485 --- /dev/null +++ b/js/src/frontend/TDZCheckCache.h @@ -0,0 +1,55 @@ +/* -*- 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_TDZCheckCache_h +#define frontend_TDZCheckCache_h + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" + +#include "ds/Nestable.h" +#include "frontend/NameCollections.h" +#include "js/TypeDecls.h" +#include "vm/Stack.h" + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// A cache that tracks Temporal Dead Zone (TDZ) checks, so that any use of a +// lexical variable that's dominated by an earlier use, or by evaluation of its +// declaration (which will initialize it, perhaps to |undefined|), doesn't have +// to redundantly check that the lexical variable has been initialized +// +// Each basic block should have a TDZCheckCache in scope. Some NestableControl +// subclasses contain a TDZCheckCache. +// +// When a scope containing lexical variables is entered, all such variables are +// marked as CheckTDZ. When a lexical variable is accessed, its entry is +// checked. If it's CheckTDZ, a JSOP_CHECKLEXICAL is emitted and then the +// entry is marked DontCheckTDZ. If it's DontCheckTDZ, no check is emitted +// because a prior check would have already failed. Finally, because +// evaluating a lexical variable declaration initializes it (after any +// initializer is evaluated), evaluating a lexical declaration marks its entry +// as DontCheckTDZ. +class TDZCheckCache : public Nestable +{ + PooledMapPtr cache_; + + MOZ_MUST_USE bool ensureCache(BytecodeEmitter* bce); + + public: + explicit TDZCheckCache(BytecodeEmitter* bce); + + mozilla::Maybe needsTDZCheck(BytecodeEmitter* bce, JSAtom* name); + MOZ_MUST_USE bool noteTDZCheck(BytecodeEmitter* bce, JSAtom* name, MaybeCheckTDZ check); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_TDZCheckCache_h */ diff --git a/js/src/frontend/TokenKind.h b/js/src/frontend/TokenKind.h index 20acd2ce542e..48d97a39ecad 100644 --- a/js/src/frontend/TokenKind.h +++ b/js/src/frontend/TokenKind.h @@ -223,6 +223,9 @@ range(AssignmentStart, Assign) \ macro(AddAssign, "'+='") \ macro(SubAssign, "'-='") \ + macro(CoalesceAssign, "'\?\?='") /* avoid trigraphs warning */ \ + macro(OrAssign, "'||='") \ + macro(AndAssign, "'&&='") \ macro(BitOrAssign, "'|='") \ macro(BitXorAssign, "'^='") \ macro(BitAndAssign, "'&='") \ diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp index e1464b732573..f6832c132323 100644 --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -1912,7 +1912,7 @@ TokenStreamSpecific::getTokenInternal(TokenKind* ttp, Mod case '|': if (matchChar('|')) - tp->type = TokenKind::Or; + tp->type = matchChar('=') ? TokenKind::OrAssign : TokenKind::Or; #ifdef ENABLE_PIPELINE_OPERATOR else if (matchChar('>')) tp->type = TokenKind::Pipeline; @@ -1927,7 +1927,7 @@ TokenStreamSpecific::getTokenInternal(TokenKind* ttp, Mod case '&': if (matchChar('&')) - tp->type = TokenKind::And; + tp->type = matchChar('=') ? TokenKind::AndAssign : TokenKind::And; else tp->type = matchChar('=') ? TokenKind::BitAndAssign : TokenKind::BitAnd; goto out; @@ -1946,8 +1946,11 @@ TokenStreamSpecific::getTokenInternal(TokenKind* ttp, Mod ungetCharIgnoreEOL(c); tp->type = TokenKind::OptionalChain; } + } else if (matchChar('?')) { + tp->type = matchChar('=') ? TokenKind::CoalesceAssign + : TokenKind::Coalesce; } else { - tp->type = matchChar('?') ? TokenKind::Coalesce : TokenKind::Hook; + tp->type = TokenKind::Hook; } goto out; diff --git a/js/src/frontend/TokenStream.h b/js/src/frontend/TokenStream.h index 95e6b633108d..a52cd9ab6cb6 100644 --- a/js/src/frontend/TokenStream.h +++ b/js/src/frontend/TokenStream.h @@ -477,10 +477,8 @@ class TokenStreamAnyChars return currentToken().type == type; } - bool getMutedErrors() const { return mutedErrors; } JSVersion versionNumber() const { return VersionNumber(options().version); } JSVersion versionWithFlags() const { return options().version; } - MOZ_MUST_USE bool checkOptions(); private: @@ -507,14 +505,6 @@ class TokenStreamAnyChars return false; } - PropertyName* nextName() const { - if (nextToken().type != TokenKind::Name) - return nextToken().name(); - - MOZ_ASSERT(TokenKindIsPossibleIdentifierName(nextToken().type)); - return reservedWordToPropertyName(nextToken().type); - } - bool isCurrentTokenAssignment() const { return TokenKindIsAssignment(currentToken().type); } @@ -695,10 +685,6 @@ class TokenStreamAnyChars SourceCoords srcCoords; - JSAtomState& names() const { - return cx->names(); - } - JSContext* context() const { return cx; } diff --git a/js/src/frontend/TryEmitter.cpp b/js/src/frontend/TryEmitter.cpp new file mode 100644 index 000000000000..57abd800d9d5 --- /dev/null +++ b/js/src/frontend/TryEmitter.cpp @@ -0,0 +1,301 @@ +/* -*- 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, ControlKind controlKind) + : bce_(bce), + kind_(kind), + controlKind_(controlKind), + depth_(0), + noteIndex_(0), + tryStart_(0), + emittedTry_(false) +#ifdef DEBUG + , state_(State::Start) +#endif +{ + if (controlKind_ == ControlKind::Syntactic) + 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_ == 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(); + +#ifdef DEBUG + state_ = State::Try; +#endif + return true; +} + +bool +TryEmitter::emitTryEnd() +{ + MOZ_ASSERT(state_ == 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 (!emittedTry_) { + MOZ_ASSERT(state_ == State::Try); + if (!emitTryEnd()) + return false; + } else { + MOZ_ASSERT(state_ == State::Catch); + if (!emitCatchEnd(true)) + return false; + } + + MOZ_ASSERT(bce_->stackDepth == depth_); + + if (controlKind_ == ControlKind::Syntactic) { + // 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; + } + + if (!bce_->emit1(JSOP_EXCEPTION)) { + return false; + } + +#ifdef DEBUG + state_ = State::Catch; +#endif + emittedTry_ = true; + return true; +} + +bool +TryEmitter::emitCatchEnd(bool hasNext) +{ + MOZ_ASSERT(state_ == State::Catch); + + if (!controlInfo_) + return true; + + // gosub , 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& 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 non-syntactic try blocks, like those emitted for + // yield* and IteratorClose inside for-of loops, we can emitFinally even + // without specifying up front, since the internal non-syntactic try + // blocks emit no GOSUBs. + if (!controlInfo_) { + if (kind_ == Kind::TryCatch) + kind_ = Kind::TryCatchFinally; + } else { + MOZ_ASSERT(hasFinally()); + } + + if (!hasCatch()) { + MOZ_ASSERT(state_ == State::Try); + if (!emitTryEnd()) + return false; + } else { + MOZ_ASSERT(state_ == 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 (controlKind_ == ControlKind::Syntactic) { + 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; + } + +#ifdef DEBUG + state_ = State::Finally; +#endif + return true; +} + +bool +TryEmitter::emitFinallyEnd() +{ + MOZ_ASSERT(state_ == State::Finally); + + if (controlKind_ == ControlKind::Syntactic) { + if (!bce_->emit1(JSOP_SETRVAL)) + return false; + } + + if (!bce_->emit1(JSOP_RETSUB)) + return false; + + bce_->hasTryFinally = true; + return true; +} + +bool +TryEmitter::emitEnd() +{ + if (!hasFinally()) { + MOZ_ASSERT(state_ == State::Catch); + if (!emitCatchEnd(false)) + return false; + } else { + MOZ_ASSERT(state_ == State::Finally); + 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; + } + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} diff --git a/js/src/frontend/TryEmitter.h b/js/src/frontend/TryEmitter.h new file mode 100644 index 000000000000..69b1d15a136a --- /dev/null +++ b/js/src/frontend/TryEmitter.h @@ -0,0 +1,232 @@ +/* -*- 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 +#include + +#include "frontend/BytecodeControlStructures.h" +#include "frontend/JumpList.h" +#include "frontend/TDZCheckCache.h" + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting bytecode for blocks like try-catch-finally. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `try { try_block } catch (ex) { catch_block }` +// TryEmitter tryCatch(this, TryEmitter::Kind::TryCatch, +// TryEmitter::ControlKind::Syntactic); +// tryCatch.emitTry(); +// emit(try_block); +// tryCatch.emitCatch(); +// emit(catch_block); // Pending exception is on stack +// tryCatch.emitEnd(); +// +// `try { try_block } finally { finally_block }` +// TryEmitter tryCatch(this, TryEmitter::Kind::TryFinally, +// TryEmitter::ControlKind::Syntactic); +// tryCatch.emitTry(); +// emit(try_block); +// // finally_pos: The "{" character's position in the source code text. +// tryCatch.emitFinally(Some(finally_pos)); +// emit(finally_block); +// tryCatch.emitEnd(); +// +// `try { try_block } catch (ex) {catch_block} finally { finally_block }` +// TryEmitter tryCatch(this, TryEmitter::Kind::TryCatchFinally, +// TryEmitter::ControlKind::Syntactic); +// tryCatch.emitTry(); +// emit(try_block); +// tryCatch.emitCatch(); +// emit(catch_block); +// tryCatch.emitFinally(Some(finally_pos)); +// emit(finally_block); +// tryCatch.emitEnd(); +// +class MOZ_STACK_CLASS TryEmitter +{ + public: + enum class Kind { + TryCatch, + TryCatchFinally, + TryFinally + }; + + // Syntactic try-catch-finally and internally used non-syntactic + // try-catch-finally behave differently for 2 points. + // + // The first one is whether TryFinallyControl is used or not. + // See the comment for `controlInfo_`. + // + // The second one is whether the catch and finally blocks handle the frame's + // return value. For syntactic try-catch-finally, the bytecode marked with + // "*" are emitted to clear return value with `undefined` before the catch + // block and the finally block, and also to save/restore the return value + // before/after the finally block. + // + // JSOP_TRY + // + // try_body... + // + // JSOP_GOSUB finally + // JSOP_JUMPTARGET + // JSOP_GOTO end: + // + // catch: + // JSOP_JUMPTARGET + // * JSOP_UNDEFINED + // * JSOP_SETRVAL + // + // catch_body... + // + // JSOP_GOSUB finally + // JSOP_JUMPTARGET + // JSOP_GOTO end + // + // finally: + // JSOP_JUMPTARGET + // * JSOP_GETRVAL + // * JSOP_UNDEFINED + // * JSOP_SETRVAL + // + // finally_body... + // + // * JSOP_SETRVAL + // JSOP_NOP + // + // end: + // JSOP_JUMPTARGET + // + // For syntactic try-catch-finally, Syntactic should be used. + // For non-syntactic try-catch-finally, NonSyntactic should be used. + enum class ControlKind { + Syntactic, + NonSyntactic + }; + + private: + BytecodeEmitter* bce_; + Kind kind_; + ControlKind controlKind_; + + // 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. + // + // For non-syntactic try-catch-finally, all that handling is skipped. + // The non-syntactic try-catch-finally 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 for non-syntactic + // try-catch-finally, even if the kind is TryCatch, because GOSUBs are not + // emitted. + mozilla::Maybe controlInfo_; + + // The stack depth before emitting JSOP_TRY. + int depth_; + + // The source note index for SRC_TRY. + unsigned noteIndex_; + + // The offset after JSOP_TRY. + ptrdiff_t tryStart_; + + // JSOP_JUMPTARGET after the entire try-catch-finally block. + JumpList catchAndFinallyJump_; + + // The offset of JSOP_GOTO at the end of the try block. + JumpTarget tryEnd_; + + // The offset of JSOP_JUMPTARGET at the beginning of the finally block. + JumpTarget finallyStart_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ emitTry +-----+ emitCatch +-------+ emitEnd +-----+ + // | Start |-------->| Try |-+-------+-->| Catch |-+->+--------->| End | + // +-------+ +-----+ | ^ +-------+ | ^ +-----+ + // | | | | + // | +----+-------------+ +----+ + // | | | + // | v emitFinally +---------+ | + // +->+------------>| Finally |--+ + // +---------+ + enum class State { + // The initial state. + Start, + + // After calling emitTry. + Try, + + // After calling emitCatch. + Catch, + + // After calling emitFinally. + Finally, + + // After calling emitEnd. + End + }; + State state_; +#endif + + // Waterfox: We haven't yet removed conditional catch clauses, so we still + // need some sort of miniature state tracking even outside of debug builds. + bool emittedTry_; + + bool hasCatch() const { + return kind_ == Kind::TryCatch || kind_ == Kind::TryCatchFinally; + } + bool hasFinally() const { + return kind_ == Kind::TryCatchFinally || kind_ == Kind::TryFinally; + } + + public: + TryEmitter(BytecodeEmitter* bce, Kind kind, ControlKind controlKind); + + // Emits JSOP_GOTO to the end of try-catch-finally. + // Used in `yield*`. + MOZ_MUST_USE bool emitJumpOverCatchAndFinally(); + + MOZ_MUST_USE bool emitTry(); + MOZ_MUST_USE bool emitCatch(); + + // If `finallyPos` is specified, it's an offset of the finally block's + // "{" character in the source code text, to improve line:column number in + // the error reporting. + // For non-syntactic try-catch-finally, `finallyPos` can be omitted. + MOZ_MUST_USE bool emitFinally(const mozilla::Maybe& 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 */ diff --git a/js/src/frontend/ValueUsage.h b/js/src/frontend/ValueUsage.h new file mode 100644 index 000000000000..9bb6458e2178 --- /dev/null +++ b/js/src/frontend/ValueUsage.h @@ -0,0 +1,28 @@ +/* -*- 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_ValueUsage_h +#define frontend_ValueUsage_h + +namespace js { +namespace frontend { + +// Used to control whether JSOP_CALL_IGNORES_RV is emitted for function calls. +enum class ValueUsage { + // Assume the value of the current expression may be used. This is always + // correct but prohibits JSOP_CALL_IGNORES_RV. + WantValue, + + // Pass this when emitting an expression if the expression's value is + // definitely unused by later instructions. You must make sure the next + // instruction is JSOP_POP, a jump to a JSOP_POP, or something similar. + IgnoreValue +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_ValueUsage_h */ diff --git a/js/src/frontend/moz.build b/js/src/frontend/moz.build index 993ed5961224..2eb236b40a5b 100644 --- a/js/src/frontend/moz.build +++ b/js/src/frontend/moz.build @@ -13,11 +13,26 @@ LOCAL_INCLUDES += ["!..", ".."] UNIFIED_SOURCES += [ 'BytecodeCompiler.cpp', + 'BytecodeControlStructures.cpp', 'BytecodeEmitter.cpp', + 'CallOrNewEmitter.cpp', + 'ElemOpEmitter.cpp', + 'EmitterScope.cpp', + 'ExpressionStatementEmitter.cpp', 'FoldConstants.cpp', + 'ForOfLoopControl.cpp', + 'IfEmitter.cpp', + 'JumpList.cpp', + 'LabelEmitter.cpp', 'NameFunctions.cpp', + 'NameOpEmitter.cpp', + 'ObjectEmitter.cpp', + 'OptionalEmitter.cpp', 'ParseNode.cpp', + 'PropOpEmitter.cpp', + 'TDZCheckCache.cpp', 'TokenStream.cpp', + 'TryEmitter.cpp', ] # frontend/Parser.cpp cannot be built in unified mode because of explicit diff --git a/js/src/jit-test/tests/auto-regress/bug1448582-1.js b/js/src/jit-test/tests/auto-regress/bug1448582-1.js new file mode 100644 index 000000000000..28f2e3ea9f78 --- /dev/null +++ b/js/src/jit-test/tests/auto-regress/bug1448582-1.js @@ -0,0 +1,21 @@ +// Overview: +// - The outer function is an IIFE which gets marked as a singleton. +// - The |o[index]| inner function is then also marked as a singleton. +// - The |o[index]| inner function has a dynamic name from a computed property name. +// - The |self| inner function uses |Function.prototype.caller| to reinvoke the outer function. +// +// When we reinvoke outer, we end up cloning a previously reused, i.e. non-cloned, +// function which triggered an assertion in js::SetFunctionNameIfNoOwnName(). + +(function(index) { + var o = { + [index]: function() {} + }; + + // Reinvoke the IIFE through |Function.prototype.caller|. + if (index === 0) { + (function self() { + self.caller(1); + })(); + } +})(0); diff --git a/js/src/jit-test/tests/auto-regress/bug1448582-2.js b/js/src/jit-test/tests/auto-regress/bug1448582-2.js new file mode 100644 index 000000000000..fdf59d281b0a --- /dev/null +++ b/js/src/jit-test/tests/auto-regress/bug1448582-2.js @@ -0,0 +1,25 @@ +// Overview: +// - The outer function is an IIFE which gets marked as a singleton. +// - The |o[index]| inner function is then also marked as a singleton. +// - The |o[index]| inner function has a dynamic name from a computed property name. +// - The |self| inner function uses |Function.prototype.caller| to reinvoke the outer function. +// +// When we reinvoke outer, we end up cloning a previously reused, i.e. non-cloned, +// function which triggered an assertion in js::SetFunctionNameIfNoOwnName(). + +(function(index) { + var o = { + [index]: function() {} + }; + + // Accessing |.name| sets the resolved-name flag, which triggered yet + // another assertion when compared to bug1448582-1.js + assertEq(o[index].name, String(index)); + + // Reinvoke the IIFE through |Function.prototype.caller|. + if (index === 0) { + (function self() { + self.caller(1); + })(); + } +})(0); diff --git a/js/src/jit-test/tests/auto-regress/bug1448582-3.js b/js/src/jit-test/tests/auto-regress/bug1448582-3.js new file mode 100644 index 000000000000..d6d491f4331b --- /dev/null +++ b/js/src/jit-test/tests/auto-regress/bug1448582-3.js @@ -0,0 +1,22 @@ +// Overview: +// - The outer function is an IIFE which gets marked as a singleton. +// - The |fn| inner function is then also marked as a singleton. +// - The |self| inner function uses |Function.prototype.caller| to reinvoke the outer function. +// +// When we reinvoke outer, we end up cloning a previously reused, i.e. non-cloned, +// function. + +(function(index) { + var fn = function() {}; + + // Accessing |.name| sets the resolved-name flag, which should not be + // copied over to the function clone. + assertEq(fn.name, "fn"); + + // Reinvoke the IIFE through |Function.prototype.caller|. + if (index === 0) { + (function self() { + self.caller(1); + })(); + } +})(0); diff --git a/js/src/jit-test/tests/auto-regress/bug1448582-4.js b/js/src/jit-test/tests/auto-regress/bug1448582-4.js new file mode 100644 index 000000000000..a806e4a2e8cd --- /dev/null +++ b/js/src/jit-test/tests/auto-regress/bug1448582-4.js @@ -0,0 +1,22 @@ +// Overview: +// - The outer function is an IIFE which gets marked as a singleton. +// - The |fn| inner function is then also marked as a singleton. +// - The |self| inner function uses |Function.prototype.caller| to reinvoke the outer function. +// +// When we reinvoke outer, we end up cloning a previously reused, i.e. non-cloned, +// function. + +(function(index) { + var fn = function(a) {}; + + // Accessing |.length| sets the resolved-length flag, which should not be + // copied over to the function clone. + assertEq(fn.length, 1); + + // Reinvoke the IIFE through |Function.prototype.caller|. + if (index === 0) { + (function self() { + self.caller(1); + })(); + } +})(0); diff --git a/js/src/jit-test/tests/auto-regress/bug1448582-5.js b/js/src/jit-test/tests/auto-regress/bug1448582-5.js new file mode 100644 index 000000000000..a877ee6c1e45 --- /dev/null +++ b/js/src/jit-test/tests/auto-regress/bug1448582-5.js @@ -0,0 +1,133 @@ +// Repeat 1448582-{1,3,4}.js for classes. + +(function(index) { + // Does not assert. + var c = class { constructor(){} }; + + if (index === 0) { + (function self() { + self.caller(1); + })(); + } +})(0); + +(function(index) { + var c = class { constructor(){} }; + + // Accessing |.name| sets the resolved-name flag, which should not be + // copied over to the function clone. + assertEq(c.name, "c"); + + if (index === 0) { + (function self() { + self.caller(1); + })(); + } +})(0); + +(function(index) { + var c = class { constructor(a){} }; + + // Accessing |.length| sets the resolved-length flag, which should not be + // copied over to the function clone. + assertEq(c.length, 1); + + if (index === 0) { + (function self() { + self.caller(1); + })(); + } +})(0); + + +// Repeat 1448582-{3,4}.js for generator functions. + +(function(index) { + function* f() {} + + // Accessing |.name| sets the resolved-name flag, which should not be + // copied over to the function clone. + assertEq(f.name, "f"); + + if (index === 0) { + (function self() { + self.caller(1); + })(); + } +})(0); + +(function(index) { + function* f(a) {} + + // Accessing |.length| sets the resolved-length flag, which should not be + // copied over to the function clone. + assertEq(f.length, 1); + + if (index === 0) { + (function self() { + self.caller(1); + })(); + } +})(0); + + +// Repeat 1448582-{3,4}.js for async functions. + +(function(index) { + async function f() {} + + // Accessing |.name| sets the resolved-name flag, which should not be + // copied over to the function clone. + assertEq(f.name, "f"); + + if (index === 0) { + (function self() { + self.caller(1); + })(); + } +})(0); + +(function(index) { + async function f(a) {} + + // Accessing |.length| sets the resolved-length flag, which should not be + // copied over to the function clone. + assertEq(f.length, 1); + + if (index === 0) { + (function self() { + self.caller(1); + })(); + } +})(0); + + +// Repeat 1448582-{3,4}.js for async generator functions. + +(function(index) { + async function* f() {} + + // Accessing |.name| sets the resolved-name flag, which should not be + // copied over to the function clone. + assertEq(f.name, "f"); + + if (index === 0) { + (function self() { + self.caller(1); + })(); + } +})(0); + +(function(index) { + async function* f(a) {} + + // Accessing |.length| sets the resolved-length flag, which should not be + // copied over to the function clone. + assertEq(f.length, 1); + + if (index === 0) { + (function self() { + self.caller(1); + })(); + } +})(0); diff --git a/js/src/jit-test/tests/auto-regress/bug1448582-6.js b/js/src/jit-test/tests/auto-regress/bug1448582-6.js new file mode 100644 index 000000000000..b864a1c13a37 --- /dev/null +++ b/js/src/jit-test/tests/auto-regress/bug1448582-6.js @@ -0,0 +1,26 @@ +// Overview: +// - The outer function is an IIFE which gets marked as a singleton. +// - The |o[index]| inner function is then also marked as a singleton. +// - The |o[index]| inner function has a dynamic name from a computed property name. +// - The |self| inner function uses |Function.prototype.caller| to reinvoke the outer function. + +(function(index) { + var o = { + [index]: class { + constructor() {} + + // The static method named "name" is added after assigning the + // inferred name. + static [(index === 0 ? "not-name" : "name")]() {} + } + } + + // The inferred name matches the current index. + assertEq(displayName(o[index]), String(index)); + + if (index === 0) { + (function self() { + self.caller(1); + })(); + } +})(0); diff --git a/js/src/jit-test/tests/auto-regress/class-method-async.js b/js/src/jit-test/tests/auto-regress/class-method-async.js new file mode 100644 index 000000000000..e8cce25874e5 --- /dev/null +++ b/js/src/jit-test/tests/auto-regress/class-method-async.js @@ -0,0 +1,5 @@ +class X { + async ["foo"]() { + return eval(); + } +} diff --git a/js/src/jit-test/tests/basic/bug1459258.js b/js/src/jit-test/tests/basic/bug1459258.js new file mode 100644 index 000000000000..48dc7b34859a --- /dev/null +++ b/js/src/jit-test/tests/basic/bug1459258.js @@ -0,0 +1,8 @@ +if (!('oomTest' in this)) + quit(); +oomTest(function() { + return [0, Math.PI, NaN, Infinity, true, false, Symbol(), Math.tan, + Reflect, Proxy, print, assertEq, Array, String, Boolean, Number, parseInt, + parseFloat, Math.sin, Math.cos, Math.abs, Math.pow, Math.sqrt, + Uint8Array, Int8Array, Int32Array, Int16Array, Uint16Array]; +}); diff --git a/js/src/jit-test/tests/basic/expression-autopsy.js b/js/src/jit-test/tests/basic/expression-autopsy.js index e942ff2deafb..e22c589fe2e3 100644 --- a/js/src/jit-test/tests/basic/expression-autopsy.js +++ b/js/src/jit-test/tests/basic/expression-autopsy.js @@ -73,7 +73,7 @@ check("o[268435455]"); check("o['1.1']"); check("o[4 + 'h']", "o['4h']"); check("ieval(undef)", "ieval(...)"); -check("ieval.call()", "ieval.call(...)"); +check("ieval.call()", "ieval.call()"); check("ieval(...[])", "ieval(...)"); check("ieval(...[undef])", "ieval(...)"); check("ieval(...[undef, undef])", "ieval(...)"); @@ -101,7 +101,7 @@ check("o[(- (o + 1))]"); check_one("6", (function () { 6() }), " is not a function"); check_one("4", (function() { (4||eval)(); }), " is not a function"); check_one("0", (function () { Array.prototype.reverse.call('123'); }), " is read-only"); -check_one("[...][Symbol.iterator](...).next(...).value", +check_one("[...][Symbol.iterator]().next().value", function () { ieval("{ let x; var [a, b, [c0, c1]] = [x, x, x]; }") }, " is undefined"); check_one("(void 1)", function() { (void 1)(); }, " is not a function"); check_one("(void o[1])", function() { var o = []; (void o[1])() }, " is not a function"); @@ -125,11 +125,11 @@ check_one("(delete obj[y])", function() { "use strict"; var obj = {}, y = {}; (delete obj[y])(); }, " is not a function"); -check_one("foo.apply(...)", +check_one("foo.apply()", function() { function foo() {} foo.apply()(); }, " is not a function"); -check_one("super(...)", +check_one("super()", function() { class X extends Object { constructor() { @@ -150,6 +150,81 @@ check_one("super(...)", }, " is not a function"); +check_one("super.a", + function() { + class X extends Object { + test() { + super.a(); + } + } + var x = new X(); + x.test(); + }, + " is not a function"); + +check_one("super[a]", + function() { + var a = "a"; + class X extends Object { + test() { + super[a](); + } + } + var x = new X(); + x.test(); + }, + " is not a function"); + +check_one("super.a()", + function() { + class Y { + a() { + return 5; + } + } + + class X extends Y { + test() { + super.a()(); + } + } + + var x = new X(); + x.test(); + }, + " is not a function"); + +check_one("super[a]()", + function() { + class Y { + a() { + return 5; + } + } + + var a = "a"; + class X extends Y { + test() { + super[a]()(); + } + } + + var x = new X(); + x.test(); + }, + " is not a function"); + +check_one("super[1]", + function() { + class X extends Object { + foo() { + return super[1](); + } + } + new X().foo(); + }, + " is not a function"); + check_one("eval(...)", function() { eval("")(); }, " is not a function"); @@ -163,13 +238,13 @@ check_one("eval(...)", function() { "use strict"; eval(...[""])(); }, " is not a function"); -check_one("(new foo(...))", +check_one("(new foo())", function() { function foo() {}; new foo()(); }, " is not a function"); check_one("(new foo(...))", function() { function foo() {}; new foo(...[])(); }, " is not a function"); -check_one("(new foo.x(...))", +check_one("(new foo.x())", function() { var foo = { x: function() {} }; new foo.x()(); }, " is not a function"); check_one("(new foo.x(...))", @@ -183,9 +258,9 @@ check_one("[...].foo", function() { [undefined, ...[]].foo(); }, " is not a function"); -check_one("[...][Symbol.iterator](...).next(...).value", +check_one("[...][Symbol.iterator]().next().value", function () { var [{x}] = [null, {}]; }, " is null"); -check_one("[...][Symbol.iterator](...).next(...).value", +check_one("[...][Symbol.iterator]().next().value", function () { var [{x}] = [void 0, {}]; }, " is undefined"); try { diff --git a/js/src/jit-test/tests/basic/functionnames.js b/js/src/jit-test/tests/basic/functionnames.js index 7fef872fcefe..6ab440fb410e 100644 --- a/js/src/jit-test/tests/basic/functionnames.js +++ b/js/src/jit-test/tests/basic/functionnames.js @@ -130,7 +130,7 @@ assertName(z[0], 'z<'); odeURIL:(function(){}) a = { 1: function () {} }; -assertName(a[1], 'a[1]'); +assertName(a[1], '1'); a = { "embedded spaces": function(){}, diff --git a/js/src/jit-test/tests/class/bug1709328.js b/js/src/jit-test/tests/class/bug1709328.js new file mode 100644 index 000000000000..9ad856a5e46d --- /dev/null +++ b/js/src/jit-test/tests/class/bug1709328.js @@ -0,0 +1,18 @@ +class A0 { constructor() { this.dummy = true; } } +class A1 { constructor() { this.dummy = true; } } +class A2 { constructor() { this.dummy = true; } } +class A3 { constructor() { this.dummy = true; } } +class A4 { constructor() { this.dummy = true; } } +class A5 { constructor() { this.dummy = true; } } +class A6 { constructor() { this.dummy = true; } } +class A7 { constructor() { this.dummy = true; } } +class A8 { constructor() { this.dummy = true; } } +class A9 { constructor() { this.dummy = true; } } + +var constructors = [A1, A2, A3, A4, A5, A6, A7, A8, A9]; +for (var i = 0; i < 1000; i++) { + for (var construct of constructors) { + var h = new construct(); + assertEq(Reflect.get(h, "nonexistent", "dummy"), undefined); + } +} diff --git a/js/src/jit-test/tests/fields/bug1552875.js b/js/src/jit-test/tests/fields/bug1552875.js new file mode 100644 index 000000000000..42b15ad9451e --- /dev/null +++ b/js/src/jit-test/tests/fields/bug1552875.js @@ -0,0 +1,15 @@ +// |jit-test| --enable-experimental-fields + +class C { + x = function(){}; + 0 = function(){}; + ["y" + 0] = function(){}; +} + +let c = new C(); +assertEq(c["x"].name, "x"); +assertEq(c[0].name, "0"); +assertEq(c["y0"].name, "y0"); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/jit-test/tests/fields/bug1571289.js b/js/src/jit-test/tests/fields/bug1571289.js new file mode 100644 index 000000000000..8ae8c38d365c --- /dev/null +++ b/js/src/jit-test/tests/fields/bug1571289.js @@ -0,0 +1,6 @@ +class C66 { + 0 = class { + static set name(x) {} + }; +} +new C66(); diff --git a/js/src/jit-test/tests/gc/bug-1449887.js b/js/src/jit-test/tests/gc/bug-1449887.js new file mode 100644 index 000000000000..60cc8f86c5a1 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1449887.js @@ -0,0 +1,4 @@ +if (!('oomTest' in this)) + quit(); + +oomTest(function() { x, 0, { z: function() {} } }); diff --git a/js/src/jit-test/tests/ion/bug1410683.js b/js/src/jit-test/tests/ion/bug1410683.js new file mode 100644 index 000000000000..669662d4f9cc --- /dev/null +++ b/js/src/jit-test/tests/ion/bug1410683.js @@ -0,0 +1,17 @@ +class C {}; +C.prototype.a = "a"; +C.prototype.q = "q"; +C.prototype.NaN = NaN; +class D extends C { + foo(p) { + return super[p]; + } +} +function f() { + var d = new D(); + for (let p in C.prototype) { + assertEq(p, String(d.foo(p))); + } +} +f(); +f(); diff --git a/js/src/jit-test/tests/ion/super-getelem-profiling.js b/js/src/jit-test/tests/ion/super-getelem-profiling.js new file mode 100644 index 000000000000..76aaf28707a2 --- /dev/null +++ b/js/src/jit-test/tests/ion/super-getelem-profiling.js @@ -0,0 +1,12 @@ +enableGeckoProfiling(); + +class base {} +class derived extends base { + testElem() { + super[ruin()]; + } +} +let instance = new derived(); +try { + instance.testElem(); +} catch { /* don't crash */ } diff --git a/js/src/jit-test/tests/ion/super-prop.js b/js/src/jit-test/tests/ion/super-prop.js new file mode 100644 index 000000000000..004284730f0d --- /dev/null +++ b/js/src/jit-test/tests/ion/super-prop.js @@ -0,0 +1,87 @@ +class Y { + a() { + assertEq(this.__proto__, X.prototype); + return 1; + } + b() { + assertEq(this.__proto__, X.prototype); + return 2; + } +} + +class X extends Y { + a() { throw "not invoked"; } + b() { + return super.a() + super.b(); + } + c(i) { + var a, b; + + if (i % 2) { + a = "a"; + b = "b" + } else { + a = "b"; + b = "a"; + } + + return super[a]() + super[b](); + } +} + +function simple() { + var x = new X(); + assertEq(x.b(), 3); + assertEq(x.c(), 3); +} + +class A { + b() { return 1;} +} +class B extends A { + a() { + assertEq(super.b(), 1); + } +} + +function nullHomeObjectSuperBase(i) { + var b = new B(); + if (i == 500) { + Object.setPrototypeOf(B.prototype, null); + // Don't crash + } + b.a(); +} + +class SArray extends Array { + constructor() { + super("a", "b"); + } + + a() { + assertEq(super.length, 0); + assertEq(this.length, 2); + + assertEq(this[0], "a"); + assertEq(this[1], "b"); + + assertEq(super[0], undefined); + assertEq(super[1], undefined); + } +} + +function array() { + var s = new SArray(); + s.a(); +} + +for (var i = 0; i < 1e4; i++) { + simple(); + array(); + + try { + nullHomeObjectSuperBase(i); + } catch (e) { + assertEq(i >= 500, true); + } +} diff --git a/js/src/jit/BaselineBailouts.cpp b/js/src/jit/BaselineBailouts.cpp index 4ae787f86793..2d83b1f16b97 100644 --- a/js/src/jit/BaselineBailouts.cpp +++ b/js/src/jit/BaselineBailouts.cpp @@ -1111,13 +1111,18 @@ InitFromBailout(JSContext* cx, HandleScript caller, jsbytecode* callerPC, // To enter a monitoring chain, we load the top stack value into R0 JitSpew(JitSpew_BaselineBailouts, " Popping top stack value into R0."); builder.popValueInto(PCMappingSlotInfo::SlotInR0); - - // Need to adjust the frameSize for the frame to match the values popped - // into registers. frameSize -= sizeof(Value); + + if (JSOp(*pc) == JSOP_GETELEM_SUPER) { + // Push a fake value so that the stack stays balanced. + if (!builder.writeValue(UndefinedValue(), "GETELEM_SUPER stack balance")) + return false; + frameSize += sizeof(Value); + } + + // Update the frame's frame size. blFrame->setFrameSize(frameSize); - JitSpew(JitSpew_BaselineBailouts, " Adjusted framesize -= %d: %d", - (int) sizeof(Value), (int) frameSize); + JitSpew(JitSpew_BaselineBailouts, " Adjusted framesize: %u", unsigned(frameSize)); // If resuming into a JSOP_CALL, baseline keeps the arguments on the // stack and pops them only after returning from the call IC. diff --git a/js/src/jit/BaselineCacheIRCompiler.cpp b/js/src/jit/BaselineCacheIRCompiler.cpp index dbae345975b7..0fe6c38398b2 100644 --- a/js/src/jit/BaselineCacheIRCompiler.cpp +++ b/js/src/jit/BaselineCacheIRCompiler.cpp @@ -1997,6 +1997,11 @@ BaselineCacheIRCompiler::init(CacheKind kind) allocator.initInputLocation(1, R1); break; case CacheKind::GetElemSuper: + MOZ_ASSERT(numInputs == 3); + allocator.initInputLocation(0, BaselineFrameSlot(0)); + allocator.initInputLocation(1, R1); + allocator.initInputLocation(2, R0); + break; case CacheKind::SetElem: MOZ_ASSERT(numInputs == 3); allocator.initInputLocation(0, R0); diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index 7dcc6a52f8cf..8e36d451136d 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -1796,7 +1796,7 @@ BaselineCompiler::emit_JSOP_LAMBDA_ARROW() typedef bool (*SetFunNameFn)(JSContext*, HandleFunction, HandleValue, FunctionPrefixKind); static const VMFunction SetFunNameInfo = - FunctionInfo(js::SetFunctionNameIfNoOwnName, "SetFunName"); + FunctionInfo(js::SetFunctionName, "SetFunName"); bool BaselineCompiler::emit_JSOP_SETFUNNAME() @@ -2325,21 +2325,21 @@ BaselineCompiler::emit_JSOP_GETELEM() bool BaselineCompiler::emit_JSOP_GETELEM_SUPER() { - // Index -> R1, Receiver -> R2, Object -> R0 - frame.popRegsAndSync(1); - masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R2); - masm.loadValue(frame.addressOfStackValue(frame.peek(-2)), R1); + // Store obj in the scratch slot. + storeValue(frame.peek(-1), frame.addressOfScratchValue(), R2); + frame.pop(); - // Keep receiver on stack. - frame.popn(2); - frame.push(R2); - frame.syncStack(0); + // Keep receiver and index in R0 and R1. + frame.popRegsAndSync(2); + + // Keep obj on the stack. + frame.pushScratchValue(); ICGetElem_Fallback::Compiler stubCompiler(cx, /* hasReceiver = */ true); if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) return false; - frame.pop(); + frame.pop(); // This value is also popped in InitFromBailout. frame.push(R0); return true; } @@ -2387,10 +2387,10 @@ BaselineCompiler::emit_JSOP_SETELEM_SUPER() { bool strict = IsCheckStrictOp(JSOp(*pc)); - // Incoming stack is |propval, receiver, obj, rval|. We need to shuffle + // Incoming stack is |receiver, propval, obj, rval|. We need to shuffle // stack to leave rval when operation is complete. - // Pop rval into R0, then load propval into R1 and replace with rval. + // Pop rval into R0, then load receiver into R1 and replace with rval. frame.popRegsAndSync(1); masm.loadValue(frame.addressOfStackValue(frame.peek(-3)), R1); masm.storeValue(R0, frame.addressOfStackValue(frame.peek(-3))); @@ -2398,10 +2398,10 @@ BaselineCompiler::emit_JSOP_SETELEM_SUPER() prepareVMCall(); pushArg(Imm32(strict)); - masm.loadValue(frame.addressOfStackValue(frame.peek(-2)), R2); - pushArg(R2); // receiver + pushArg(R1); // receiver pushArg(R0); // rval - pushArg(R1); // propval + masm.loadValue(frame.addressOfStackValue(frame.peek(-2)), R0); + pushArg(R0); // propval masm.unboxObject(frame.addressOfStackValue(frame.peek(-1)), R0.scratchReg()); pushArg(R0.scratchReg()); // obj @@ -5019,12 +5019,8 @@ BaselineCompiler::emit_JSOP_CHECKCLASSHERITAGE() bool BaselineCompiler::emit_JSOP_INITHOMEOBJECT() { - frame.syncStack(0); - - // Load HomeObject off stack - unsigned skipOver = GET_UINT8(pc); - MOZ_ASSERT(frame.stackDepth() >= skipOver + 2); - masm.loadValue(frame.addressOfStackValue(frame.peek(-2 - skipOver)), R0); + // Load HomeObject in R0. + frame.popRegsAndSync(1); // Load function off stack Register func = R2.scratchReg(); diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index f634408a5c90..39d3aea0b446 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -855,7 +855,7 @@ DoGetElemFallback(JSContext* cx, BaselineFrame* frame, ICGetElem_Fallback* stub_ static bool DoGetElemSuperFallback(JSContext* cx, BaselineFrame* frame, ICGetElem_Fallback* stub_, - HandleValue receiver, HandleValue lhs, HandleValue rhs, + HandleValue lhs, HandleValue rhs, HandleValue receiver, MutableHandleValue res) { // This fallback stub may trigger debug mode toggling. @@ -937,7 +937,7 @@ typedef bool (*DoGetElemSuperFallbackFn)(JSContext*, BaselineFrame*, ICGetElem_F MutableHandleValue); static const VMFunction DoGetElemSuperFallbackInfo = FunctionInfo(DoGetElemSuperFallback, "DoGetElemSuperFallback", - TailCall, PopValues(1)); + TailCall, PopValues(3)); bool ICGetElem_Fallback::Compiler::generateStubCode(MacroAssembler& masm) @@ -950,13 +950,18 @@ ICGetElem_Fallback::Compiler::generateStubCode(MacroAssembler& masm) // Super property getters use a |this| that differs from base object if (hasReceiver_) { + // State: receiver in R0, index in R1, obj on the stack + // Ensure stack is fully synced for the expression decompiler. + // We need: receiver, index, obj + masm.pushValue(R0); masm.pushValue(R1); + masm.pushValue(Address(masm.getStackPointer(), sizeof(Value) * 2)); - masm.pushValue(R1); // Index - masm.pushValue(R0); // Object - masm.loadValue(Address(masm.getStackPointer(), 3 * sizeof(Value)), R0); + // Push arguments. masm.pushValue(R0); // Receiver + masm.pushValue(R1); // Index + masm.pushValue(Address(masm.getStackPointer(), sizeof(Value) * 5)); // Obj masm.push(ICStubReg); pushStubPayload(masm, R0.scratchReg()); diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 257d5b2a25df..862b00e735f3 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -149,6 +149,11 @@ typedef bool (*IonSetPropertyICFn)(JSContext*, HandleScript, IonSetPropertyIC*, static const VMFunction IonSetPropertyICInfo = FunctionInfo(IonSetPropertyIC::update, "IonSetPropertyIC::update"); +typedef bool (*IonGetPropSuperICFn)(JSContext*, HandleScript, IonGetPropSuperIC*, HandleObject, HandleValue, + HandleValue, MutableHandleValue); +static const VMFunction IonGetPropSuperICInfo = + FunctionInfo(IonGetPropSuperIC::update, "IonGetPropSuperIC::update"); + typedef bool (*IonGetNameICFn)(JSContext*, HandleScript, IonGetNameIC*, HandleObject, MutableHandleValue); static const VMFunction IonGetNameICInfo = @@ -203,6 +208,26 @@ CodeGenerator::visitOutOfLineICFallback(OutOfLineICFallback* ool) masm.jump(ool->rejoin()); return; } + case CacheKind::GetPropSuper: + case CacheKind::GetElemSuper: { + IonGetPropSuperIC* getPropSuperIC = ic->asGetPropSuperIC(); + + saveLive(lir); + + pushArg(getPropSuperIC->id()); + pushArg(getPropSuperIC->receiver()); + pushArg(getPropSuperIC->object()); + icInfo_[cacheInfoIndex].icOffsetForPush = pushArgWithPatch(ImmWord(-1)); + pushArg(ImmGCPtr(gen->info().script())); + + callVM(IonGetPropSuperICInfo, lir); + + StoreValueTo(getPropSuperIC->output()).generate(this); + restoreLiveIgnore(lir, StoreValueTo(getPropSuperIC->output()).clobbered()); + + masm.jump(ool->rejoin()); + return; + } case CacheKind::SetProp: case CacheKind::SetElem: { IonSetPropertyIC* setPropIC = ic->asSetPropertyIC(); @@ -312,8 +337,6 @@ CodeGenerator::visitOutOfLineICFallback(OutOfLineICFallback* ool) case CacheKind::Call: case CacheKind::Compare: case CacheKind::TypeOf: - case CacheKind::GetPropSuper: - case CacheKind::GetElemSuper: MOZ_CRASH("Unsupported IC"); } MOZ_CRASH(); @@ -2867,7 +2890,7 @@ CodeGenerator::emitLambdaInit(Register output, Register envChain, typedef bool (*SetFunNameFn)(JSContext*, HandleFunction, HandleValue, FunctionPrefixKind); static const VMFunction SetFunNameInfo = - FunctionInfo(js::SetFunctionNameIfNoOwnName, "SetFunName"); + FunctionInfo(js::SetFunctionName, "SetFunName"); void CodeGenerator::visitSetFunName(LSetFunName* lir) @@ -3581,6 +3604,37 @@ CodeGenerator::visitFunctionEnvironment(LFunctionEnvironment* lir) masm.loadPtr(environment, ToRegister(lir->output())); } +void +CodeGenerator::visitHomeObject(LHomeObject* lir) +{ + Address homeObject(ToRegister(lir->function()), FunctionExtended::offsetOfMethodHomeObjectSlot()); +#ifdef DEBUG + Label isObject; + masm.branchTestObject(Assembler::Equal, homeObject, &isObject); + masm.assumeUnreachable("[[HomeObject]] must be Object"); + masm.bind(&isObject); +#endif + masm.unboxObject(homeObject, ToRegister(lir->output())); +} + +typedef JSObject* (*HomeObjectSuperBaseFn)(JSContext*, HandleObject); +static const VMFunction HomeObjectSuperBaseInfo = + FunctionInfo(HomeObjectSuperBase, "HomeObjectSuperBase"); + +void +CodeGenerator::visitHomeObjectSuperBase(LHomeObjectSuperBase* lir) +{ + Register homeObject = ToRegister(lir->homeObject()); + Register output = ToRegister(lir->output()); + + OutOfLineCode* ool = oolCallVM(HomeObjectSuperBaseInfo, lir, ArgList(homeObject), + StoreRegisterTo(output)); + + masm.loadObjProto(homeObject, output); + masm.branchPtr(Assembler::BelowOrEqual, output, ImmWord(1), ool->entry()); + masm.bind(ool->rejoin()); +} + typedef LexicalEnvironmentObject* (*NewLexicalEnvironmentObjectFn)(JSContext*, Handle, HandleObject, gc::InitialHeap); @@ -10260,6 +10314,28 @@ CodeGenerator::visitGetPropertyCacheT(LGetPropertyCacheT* ins) IonGetPropertyICFlags(ins->mir()), ins->mir()->profilerLeavePc()); } +void +CodeGenerator::visitGetPropSuperCacheV(LGetPropSuperCacheV* ins) +{ + LiveRegisterSet liveRegs = ins->safepoint()->liveRegs(); + Register obj = ToRegister(ins->obj()); + TypedOrValueRegister receiver = + toConstantOrRegister(ins, LGetPropSuperCacheV::Receiver, ins->mir()->receiver()->type()).reg(); + ConstantOrRegister id = toConstantOrRegister(ins, LGetPropSuperCacheV::Id, ins->mir()->idval()->type()); + TypedOrValueRegister output = TypedOrValueRegister(GetValueOutput(ins)); + + CacheKind kind = CacheKind::GetElemSuper; + if (id.constant() && id.value().isString()) { + JSString* idString = id.value().toString(); + uint32_t dummy; + if (idString->isAtom() && !idString->asAtom().isIndex(&dummy)) + kind = CacheKind::GetPropSuper; + } + + IonGetPropSuperIC cache(kind, liveRegs, obj, receiver, id, output); + addIC(ins, allocateIC(cache)); +} + void CodeGenerator::visitBindNameCache(LBindNameCache* ins) { diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index 53f89838070c..d55657b755fe 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -290,6 +290,8 @@ class CodeGenerator final : public CodeGeneratorSpecific void visitSinCos(LSinCos *lir); void visitStringSplit(LStringSplit* lir); void visitFunctionEnvironment(LFunctionEnvironment* lir); + void visitHomeObject(LHomeObject* lir); + void visitHomeObjectSuperBase(LHomeObjectSuperBase* lir); void visitNewLexicalEnvironmentObject(LNewLexicalEnvironmentObject* lir); void visitCopyLexicalEnvironmentObject(LCopyLexicalEnvironmentObject* lir); void visitCallGetProperty(LCallGetProperty* lir); @@ -427,6 +429,7 @@ class CodeGenerator final : public CodeGeneratorSpecific void visitGetPropertyCacheV(LGetPropertyCacheV* ins); void visitGetPropertyCacheT(LGetPropertyCacheT* ins); + void visitGetPropSuperCacheV(LGetPropSuperCacheV* ins); void visitBindNameCache(LBindNameCache* ins); void visitCallSetProperty(LInstruction* ins); void visitSetPropertyCache(LSetPropertyCache* ins); diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 88377e0566ee..38ca2d4a4bca 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -2198,6 +2198,18 @@ IonBuilder::inspectOpcode(JSOp op) return Ok(); } + case JSOP_SUPERBASE: + return jsop_superbase(); + + case JSOP_GETPROP_SUPER: + { + PropertyName* name = info().getAtom(pc)->asPropertyName(); + return jsop_getprop_super(name); + } + + case JSOP_GETELEM_SUPER: + return jsop_getelem_super(); + case JSOP_GETPROP: case JSOP_CALLPROP: { @@ -2393,10 +2405,7 @@ IonBuilder::inspectOpcode(JSOp op) case JSOP_CHECKTHISREINIT: // Super - case JSOP_SUPERBASE: case JSOP_SETPROP_SUPER: - case JSOP_GETPROP_SUPER: - case JSOP_GETELEM_SUPER: case JSOP_SETELEM_SUPER: case JSOP_STRICTSETPROP_SUPER: case JSOP_STRICTSETELEM_SUPER: @@ -9733,6 +9742,64 @@ IonBuilder::jsop_not() return Ok(); } +AbortReasonOr +IonBuilder::jsop_superbase() +{ + JSFunction* fun = info().funMaybeLazy(); + if (!fun || !fun->allowSuperProperty()) + return abort(AbortReason::Disable, "super only supported directly in methods"); + + auto* homeObject = MHomeObject::New(alloc(), getCallee()); + current->add(homeObject); + + auto* superBase = MHomeObjectSuperBase::New(alloc(), homeObject); + current->add(superBase); + current->push(superBase); + + MOZ_TRY(resumeAfter(superBase)); + return Ok(); +} + + +AbortReasonOr +IonBuilder::jsop_getprop_super(PropertyName* name) +{ + MDefinition* obj = current->pop(); + MDefinition* receiver = current->pop(); + + MConstant* id = constant(StringValue(name)); + auto* ins = MGetPropSuperCache::New(alloc(), obj, receiver, id); + current->add(ins); + current->push(ins); + + MOZ_TRY(resumeAfter(ins)); + + TemporaryTypeSet* types = bytecodeTypes(pc); + return pushTypeBarrier(ins, types, BarrierKind::TypeSet); +} + +AbortReasonOr +IonBuilder::jsop_getelem_super() +{ + MDefinition* obj = current->pop(); + MDefinition* id = current->pop(); + MDefinition* receiver = current->pop(); + +#if defined(JS_CODEGEN_X86) + if (instrumentedProfiling()) + return abort(AbortReason::Disable, "profiling functions with GETELEM_SUPER is disabled on x86"); +#endif + + auto* ins = MGetPropSuperCache::New(alloc(), obj, receiver, id); + current->add(ins); + current->push(ins); + + MOZ_TRY(resumeAfter(ins)); + + TemporaryTypeSet* types = bytecodeTypes(pc); + return pushTypeBarrier(ins, types, BarrierKind::TypeSet); +} + NativeObject* IonBuilder::commonPrototypeWithGetterSetter(TemporaryTypeSet* types, PropertyName* name, bool isGetter, JSFunction* getterOrSetter, diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 7bc906beb4b4..8f436f9284fe 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -537,6 +537,9 @@ class IonBuilder AbortReasonOr jsop_runonce(); AbortReasonOr jsop_rest(); AbortReasonOr jsop_not(); + AbortReasonOr jsop_superbase(); + AbortReasonOr jsop_getprop_super(PropertyName* name); + AbortReasonOr jsop_getelem_super(); AbortReasonOr jsop_getprop(PropertyName* name); AbortReasonOr jsop_setprop(PropertyName* name); AbortReasonOr jsop_delprop(PropertyName* name); diff --git a/js/src/jit/IonCacheIRCompiler.cpp b/js/src/jit/IonCacheIRCompiler.cpp index a2911802a5b8..6c49eb5ff1d2 100644 --- a/js/src/jit/IonCacheIRCompiler.cpp +++ b/js/src/jit/IonCacheIRCompiler.cpp @@ -399,6 +399,32 @@ IonCacheIRCompiler::init() allocator.initInputLocation(1, ic->id()); break; } + case CacheKind::GetPropSuper: + case CacheKind::GetElemSuper: { + IonGetPropSuperIC* ic = ic_->asGetPropSuperIC(); + TypedOrValueRegister output = ic->output(); + + available.add(output.valueReg()); + + liveRegs_.emplace(ic->liveRegs()); + outputUnchecked_.emplace(output); + + allowDoubleResult_.emplace(true); + + MOZ_ASSERT(numInputs == 2 || numInputs == 3); + + allocator.initInputLocation(0, ic->object(), JSVAL_TYPE_OBJECT); + + if (ic->kind() == CacheKind::GetPropSuper) { + MOZ_ASSERT(numInputs == 2); + allocator.initInputLocation(1, ic->receiver()); + } else { + MOZ_ASSERT(numInputs == 3); + allocator.initInputLocation(1, ic->id()); + allocator.initInputLocation(2, ic->receiver()); + } + break; + } case CacheKind::SetProp: case CacheKind::SetElem: { IonSetPropertyIC* ic = ic_->asSetPropertyIC(); @@ -494,8 +520,6 @@ IonCacheIRCompiler::init() case CacheKind::Call: case CacheKind::Compare: case CacheKind::TypeOf: - case CacheKind::GetPropSuper: - case CacheKind::GetElemSuper: MOZ_CRASH("Unsupported IC"); } diff --git a/js/src/jit/IonControlFlow.cpp b/js/src/jit/IonControlFlow.cpp index ba5ffd69697e..43c701cea8b1 100644 --- a/js/src/jit/IonControlFlow.cpp +++ b/js/src/jit/IonControlFlow.cpp @@ -1756,22 +1756,22 @@ ControlFlowGenerator::processIfStart(JSOp op) // The bytecode for if/ternary gets emitted either like this: // - // IFEQ X ; src note (IF_ELSE, COND) points to the GOTO + // IFEQ X ; src note (IF_ELSE, COND) // ... // GOTO Z - // X: ... ; else/else if + // X: JUMPTARGET ; else/else if // ... - // Z: ; join + // Z: JUMPTARGET ; join // // Or like this: // - // IFEQ X ; src note (IF) has no offset + // IFEQ X ; src note (IF) // ... - // Z: ... ; join + // X: JUMPTARGET ; join // // We want to parse the bytecode as if we were parsing the AST, so for the - // IF_ELSE/COND cases, we use the source note and follow the GOTO. For the - // IF case, the IFEQ offset is the join point. + // IF_ELSE/COND cases, we use the IFEQ/GOTO bytecode offsets to follow the + // branch. For the IF case, the IFEQ offset is the join point. switch (SN_TYPE(sn)) { case SRC_IF: if (!cfgStack_.append(CFGState::If(falseStart, test))) @@ -1783,9 +1783,9 @@ ControlFlowGenerator::processIfStart(JSOp op) { // Infer the join point from the JSOP_GOTO[X] sitting here, then // assert as we much we can that this is the right GOTO. - jsbytecode* trueEnd = pc + GetSrcNoteOffset(sn, 0); + MOZ_ASSERT(JSOp(*falseStart) == JSOP_JUMPTARGET); + jsbytecode* trueEnd = falseStart - JSOP_GOTO_LENGTH; MOZ_ASSERT(trueEnd > pc); - MOZ_ASSERT(trueEnd < falseStart); MOZ_ASSERT(JSOp(*trueEnd) == JSOP_GOTO); MOZ_ASSERT(!GetSrcNote(gsn, script, trueEnd)); diff --git a/js/src/jit/IonIC.cpp b/js/src/jit/IonIC.cpp index d55df876ef51..c599ad310908 100644 --- a/js/src/jit/IonIC.cpp +++ b/js/src/jit/IonIC.cpp @@ -40,6 +40,11 @@ IonIC::scratchRegisterForEntryJump() TypedOrValueRegister output = asGetPropertyIC()->output(); return output.hasValue() ? output.valueReg().scratchReg() : output.typedReg().gpr(); } + case CacheKind::GetPropSuper: + case CacheKind::GetElemSuper: { + TypedOrValueRegister output = asGetPropSuperIC()->output(); + return output.valueReg().scratchReg(); + } case CacheKind::SetProp: case CacheKind::SetElem: return asSetPropertyIC()->temp(); @@ -56,8 +61,6 @@ IonIC::scratchRegisterForEntryJump() case CacheKind::Call: case CacheKind::Compare: case CacheKind::TypeOf: - case CacheKind::GetPropSuper: - case CacheKind::GetElemSuper: MOZ_CRASH("Unsupported IC"); } @@ -115,7 +118,7 @@ IonIC::trace(JSTracer* trc) /* static */ bool IonGetPropertyIC::update(JSContext* cx, HandleScript outerScript, IonGetPropertyIC* ic, - HandleValue val, HandleValue idVal, MutableHandleValue res) + HandleValue val, HandleValue idVal, MutableHandleValue res) { // Override the return value if we are invalidated (bug 728188). IonScript* ionScript = outerScript->ionScript(); @@ -194,6 +197,43 @@ IonGetPropertyIC::update(JSContext* cx, HandleScript outerScript, IonGetProperty return true; } +/* static */ bool +IonGetPropSuperIC::update(JSContext* cx, HandleScript outerScript, IonGetPropSuperIC* ic, + HandleObject obj, HandleValue receiver, HandleValue idVal, MutableHandleValue res) +{ + // Override the return value if we are invalidated (bug 728188). + IonScript* ionScript = outerScript->ionScript(); + AutoDetectInvalidation adi(cx, res, ionScript); + + if (ic->state().maybeTransition()) + ic->discardStubs(cx->zone()); + + bool attached = false; + if (ic->state().canAttachStub()) { + RootedValue val(cx, ObjectValue(*obj)); + bool isTemporarilyUnoptimizable = false; + GetPropIRGenerator gen(cx, outerScript, ic->pc(), ic->kind(), ic->state().mode(), + &isTemporarilyUnoptimizable, val, idVal, receiver, + GetPropertyResultFlags::All); + if (gen.tryAttachStub()) + ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ionScript, &attached); + + if (!attached && !isTemporarilyUnoptimizable) + ic->state().trackNotAttached(); + } + + RootedId id(cx); + if (!ValueToId(cx, idVal, &id)) + return false; + + if (!GetProperty(cx, obj, receiver, id, res)) + return false; + + // Monitor changes to cache entry. + TypeScript::Monitor(cx, ic->script(), ic->pc(), res); + return true; +} + /* static */ bool IonSetPropertyIC::update(JSContext* cx, HandleScript outerScript, IonSetPropertyIC* ic, HandleObject obj, HandleValue idVal, HandleValue rhs) diff --git a/js/src/jit/IonIC.h b/js/src/jit/IonIC.h index f32d46e87cf0..d81c4b01518e 100644 --- a/js/src/jit/IonIC.h +++ b/js/src/jit/IonIC.h @@ -58,6 +58,7 @@ class IonICStub class IonGetPropertyIC; class IonSetPropertyIC; +class IonGetPropSuperIC; class IonGetNameIC; class IonBindNameIC; class IonGetIteratorIC; @@ -142,6 +143,10 @@ class IonIC MOZ_ASSERT(kind_ == CacheKind::SetProp || kind_ == CacheKind::SetElem); return (IonSetPropertyIC*)this; } + IonGetPropSuperIC* asGetPropSuperIC() { + MOZ_ASSERT(kind_ == CacheKind::GetPropSuper || kind_ == CacheKind::GetElemSuper); + return (IonGetPropSuperIC*)this; + } IonGetNameIC* asGetNameIC() { MOZ_ASSERT(kind_ == CacheKind::GetName); return (IonGetNameIC*)this; @@ -214,6 +219,37 @@ class IonGetPropertyIC : public IonIC HandleValue val, HandleValue idVal, MutableHandleValue res); }; +class IonGetPropSuperIC : public IonIC +{ + LiveRegisterSet liveRegs_; + + Register object_; + TypedOrValueRegister receiver_; + ConstantOrRegister id_; + TypedOrValueRegister output_; + + public: + IonGetPropSuperIC(CacheKind kind, LiveRegisterSet liveRegs, Register object, TypedOrValueRegister receiver, + const ConstantOrRegister& id, TypedOrValueRegister output) + : IonIC(kind), + liveRegs_(liveRegs), + object_(object), + receiver_(receiver), + id_(id), + output_(output) + { } + + Register object() const { return object_; } + TypedOrValueRegister receiver() const { return receiver_; } + ConstantOrRegister id() const { return id_; } + TypedOrValueRegister output() const { return output_; } + LiveRegisterSet liveRegs() const { return liveRegs_; } + + static MOZ_MUST_USE bool update(JSContext* cx, HandleScript outerScript, IonGetPropSuperIC* ic, + HandleObject obj, HandleValue receiver, HandleValue idVal, + MutableHandleValue res); +}; + class IonSetPropertyIC : public IonIC { LiveRegisterSet liveRegs_; diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index c9e1c516213d..e9d3e64236c4 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -2680,6 +2680,23 @@ LIRGenerator::visitFunctionEnvironment(MFunctionEnvironment* ins) define(new(alloc()) LFunctionEnvironment(useRegisterAtStart(ins->function())), ins); } +void +LIRGenerator::visitHomeObject(MHomeObject* ins) +{ + define(new(alloc()) LHomeObject(useRegisterAtStart(ins->function())), ins); +} + +void +LIRGenerator::visitHomeObjectSuperBase(MHomeObjectSuperBase* ins) +{ + MOZ_ASSERT(ins->homeObject()->type() == MIRType::Object); + MOZ_ASSERT(ins->type() == MIRType::Object); + + auto lir = new(alloc()) LHomeObjectSuperBase(useRegister(ins->homeObject())); + define(lir, ins); + assignSafepoint(lir, ins); +} + void LIRGenerator::visitInterruptCheck(MInterruptCheck* ins) { @@ -3758,6 +3775,24 @@ LIRGenerator::visitCallGetIntrinsicValue(MCallGetIntrinsicValue* ins) assignSafepoint(lir, ins); } +void +LIRGenerator::visitGetPropSuperCache(MGetPropSuperCache* ins) +{ + MDefinition* obj = ins->object(); + MDefinition* receiver = ins->receiver(); + MDefinition* id = ins->idval(); + + gen->setNeedsOverrecursedCheck(); + + bool useConstId = id->type() == MIRType::String || id->type() == MIRType::Symbol; + + auto* lir = new(alloc()) LGetPropSuperCacheV(useRegister(obj), + useBoxOrTyped(receiver), + useBoxOrTypedOrConstant(id, useConstId)); + defineBox(lir, ins); + assignSafepoint(lir, ins); +} + void LIRGenerator::visitGetPropertyCache(MGetPropertyCache* ins) { diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h index 0dd02041151c..a0cb40956cc3 100644 --- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -203,6 +203,8 @@ class LIRGenerator : public LIRGeneratorSpecific void visitLoadSlot(MLoadSlot* ins); void visitLoadFixedSlotAndUnbox(MLoadFixedSlotAndUnbox* ins); void visitFunctionEnvironment(MFunctionEnvironment* ins); + void visitHomeObject(MHomeObject* ins); + void visitHomeObjectSuperBase(MHomeObjectSuperBase* ins); void visitInterruptCheck(MInterruptCheck* ins); void visitWasmTrap(MWasmTrap* ins); void visitWasmReinterpret(MWasmReinterpret* ins); @@ -249,6 +251,7 @@ class LIRGenerator : public LIRGeneratorSpecific void visitClampToUint8(MClampToUint8* ins); void visitLoadFixedSlot(MLoadFixedSlot* ins); void visitStoreFixedSlot(MStoreFixedSlot* ins); + void visitGetPropSuperCache(MGetPropSuperCache* ins); void visitGetPropertyCache(MGetPropertyCache* ins); void visitGetPropertyPolymorphic(MGetPropertyPolymorphic* ins); void visitSetPropertyPolymorphic(MSetPropertyPolymorphic* ins); diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index b490d5582ff7..0f4c83237e36 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -1784,8 +1784,8 @@ class MWasmFloatConstant : public MNullaryInstruction // Generic constructor of SIMD valuesX4. class MSimdValueX4 : public MQuaternaryInstruction, - public Mix4Policy, SimdScalarPolicy<1>, - SimdScalarPolicy<2>, SimdScalarPolicy<3> >::Data + public MixPolicy, SimdScalarPolicy<1>, + SimdScalarPolicy<2>, SimdScalarPolicy<3> >::Data { protected: MSimdValueX4(MIRType type, MDefinition* x, MDefinition* y, MDefinition* z, MDefinition* w) @@ -3834,9 +3834,9 @@ class MSimdUnbox // as dead code. class MNewDerivedTypedObject : public MTernaryInstruction, - public Mix3Policy, - ObjectPolicy<1>, - IntPolicy<2> >::Data + public MixPolicy, + ObjectPolicy<1>, + IntPolicy<2> >::Data { private: TypedObjectPrediction prediction_; @@ -4092,7 +4092,7 @@ class MInitPropGetterSetter class MInitElem : public MTernaryInstruction, - public Mix3Policy, BoxPolicy<1>, BoxPolicy<2> >::Data + public MixPolicy, BoxPolicy<1>, BoxPolicy<2> >::Data { MInitElem(MDefinition* obj, MDefinition* id, MDefinition* value) : MTernaryInstruction(classOpcode, obj, id, value) @@ -4112,7 +4112,7 @@ class MInitElem class MInitElemGetterSetter : public MTernaryInstruction, - public Mix3Policy, BoxPolicy<1>, ObjectPolicy<2> >::Data + public MixPolicy, BoxPolicy<1>, ObjectPolicy<2> >::Data { MInitElemGetterSetter(MDefinition* obj, MDefinition* id, MDefinition* value) : MTernaryInstruction(classOpcode, obj, id, value) @@ -4323,7 +4323,7 @@ class MCallDOMNative : public MCall // fun.apply(self, arguments) class MApplyArgs : public MTernaryInstruction, - public Mix3Policy, IntPolicy<1>, BoxPolicy<2> >::Data + public MixPolicy, IntPolicy<1>, BoxPolicy<2> >::Data { protected: // Monomorphic cache of single target from TI, or nullptr. @@ -4360,7 +4360,7 @@ class MApplyArgs // fun.apply(fn, array) class MApplyArray : public MTernaryInstruction, - public Mix3Policy, ObjectPolicy<1>, BoxPolicy<2> >::Data + public MixPolicy, ObjectPolicy<1>, BoxPolicy<2> >::Data { protected: // Monomorphic cache of single target from TI, or nullptr. @@ -4542,9 +4542,9 @@ class MGetDynamicName class MCallDirectEval : public MTernaryInstruction, - public Mix3Policy, - StringPolicy<1>, - BoxPolicy<2> >::Data + public MixPolicy, + StringPolicy<1>, + BoxPolicy<2> >::Data { protected: MCallDirectEval(MDefinition* envChain, MDefinition* string, @@ -5080,7 +5080,7 @@ class MCreateThisWithTemplate // Given a prototype operand, construct |this| for JSOP_NEW. class MCreateThisWithProto : public MTernaryInstruction, - public Mix3Policy, ObjectPolicy<1>, ObjectPolicy<2> >::Data + public MixPolicy, ObjectPolicy<1>, ObjectPolicy<2> >::Data { MCreateThisWithProto(MDefinition* callee, MDefinition* newTarget, MDefinition* prototype) : MTernaryInstruction(classOpcode, callee, newTarget, prototype) @@ -8359,9 +8359,9 @@ class MRegExp : public MNullaryInstruction class MRegExpMatcher : public MTernaryInstruction, - public Mix3Policy, - StringPolicy<1>, - IntPolicy<2> >::Data + public MixPolicy, + StringPolicy<1>, + IntPolicy<2> >::Data { private: @@ -8391,9 +8391,9 @@ class MRegExpMatcher class MRegExpSearcher : public MTernaryInstruction, - public Mix3Policy, - StringPolicy<1>, - IntPolicy<2> >::Data + public MixPolicy, + StringPolicy<1>, + IntPolicy<2> >::Data { private: @@ -8422,9 +8422,9 @@ class MRegExpSearcher class MRegExpTester : public MTernaryInstruction, - public Mix3Policy, - StringPolicy<1>, - IntPolicy<2> >::Data + public MixPolicy, + StringPolicy<1>, + IntPolicy<2> >::Data { private: @@ -8518,7 +8518,7 @@ class MGetFirstDollarIndex class MStringReplace : public MTernaryInstruction, - public Mix3Policy, StringPolicy<1>, StringPolicy<2> >::Data + public MixPolicy, StringPolicy<1>, StringPolicy<2> >::Data { private: @@ -8574,7 +8574,7 @@ class MStringReplace class MSubstr : public MTernaryInstruction, - public Mix3Policy, IntPolicy<1>, IntPolicy<2>>::Data + public MixPolicy, IntPolicy<1>, IntPolicy<2>>::Data { private: @@ -8690,7 +8690,7 @@ class MLambda class MLambdaArrow : public MTernaryInstruction, - public Mix3Policy, BoxPolicy<1>, ObjectPolicy<2>>::Data + public MixPolicy, BoxPolicy<1>, ObjectPolicy<2>>::Data { const LambdaFunctionInfo info_; @@ -9954,7 +9954,7 @@ class MArrayPush // Array.prototype.slice on a dense array. class MArraySlice : public MTernaryInstruction, - public Mix3Policy, IntPolicy<1>, IntPolicy<2>>::Data + public MixPolicy, IntPolicy<1>, IntPolicy<2>>::Data { CompilerObject templateObj_; gc::InitialHeap initialHeap_; @@ -10851,6 +10851,40 @@ class MGetPropertyCache } }; +class MHomeObjectSuperBase + : public MUnaryInstruction, + public SingleObjectPolicy::Data +{ + explicit MHomeObjectSuperBase(MDefinition* homeObject) + : MUnaryInstruction(classOpcode, homeObject) + { + setResultType(MIRType::Object); + setGuard(); // May throw if [[Prototype]] is null + } + + public: + INSTRUCTION_HEADER(HomeObjectSuperBase) + TRIVIAL_NEW_WRAPPERS + NAMED_OPERANDS((0, homeObject)) +}; + +class MGetPropSuperCache + : public MTernaryInstruction, + public MixPolicy, BoxExceptPolicy<1, MIRType::Object>, CacheIdPolicy<2>>::Data +{ + MGetPropSuperCache(MDefinition* obj, MDefinition* receiver, MDefinition* id) + : MTernaryInstruction(classOpcode, obj, receiver, id) + { + setResultType(MIRType::Value); + setGuard(); + } + + public: + INSTRUCTION_HEADER(GetPropSuperCache) + TRIVIAL_NEW_WRAPPERS + NAMED_OPERANDS((0, object), (1, receiver), (2, idval)) +}; + struct PolymorphicEntry { // The group and/or shape to guard against. ReceiverGuard receiver; @@ -11578,6 +11612,28 @@ class MCopyLexicalEnvironmentObject } }; +class MHomeObject + : public MUnaryInstruction, + public SingleObjectPolicy::Data +{ + explicit MHomeObject(MDefinition* function) + : MUnaryInstruction(classOpcode, function) + { + setResultType(MIRType::Object); + setMovable(); + } + + public: + INSTRUCTION_HEADER(HomeObject) + TRIVIAL_NEW_WRAPPERS + NAMED_OPERANDS((0, function)) + + // A function's [[HomeObject]] is fixed. + AliasSet getAliasSet() const override { + return AliasSet::None(); + } +}; + // Store to vp[slot] (slots that are not inline in an object). class MStoreSlot : public MBinaryInstruction, @@ -11802,7 +11858,7 @@ class MCallSetProperty class MSetPropertyCache : public MTernaryInstruction, - public Mix3Policy, NoFloatPolicy<2>>::Data + public MixPolicy, NoFloatPolicy<2>>::Data { bool strict_ : 1; bool needsPostBarrier_ : 1; @@ -11924,7 +11980,7 @@ class MCallSetElement class MCallInitElementArray : public MTernaryInstruction, - public Mix3Policy, IntPolicy<1>, BoxPolicy<2> >::Data + public MixPolicy, IntPolicy<1>, BoxPolicy<2> >::Data { MCallInitElementArray(MDefinition* obj, MDefinition* index, MDefinition* val) : MTernaryInstruction(classOpcode, obj, index, val) @@ -13511,7 +13567,7 @@ class MGuardSharedTypedArray class MCompareExchangeTypedArrayElement : public MQuaternaryInstruction, - public Mix4Policy, IntPolicy<1>, TruncateToInt32Policy<2>, TruncateToInt32Policy<3>>::Data + public MixPolicy, IntPolicy<1>, TruncateToInt32Policy<2>, TruncateToInt32Policy<3>>::Data { Scalar::Type arrayType_; @@ -13546,7 +13602,7 @@ class MCompareExchangeTypedArrayElement class MAtomicExchangeTypedArrayElement : public MTernaryInstruction, - public Mix3Policy, IntPolicy<1>, TruncateToInt32Policy<2>>::Data + public MixPolicy, IntPolicy<1>, TruncateToInt32Policy<2>>::Data { Scalar::Type arrayType_; @@ -13578,7 +13634,7 @@ class MAtomicExchangeTypedArrayElement class MAtomicTypedArrayElementBinop : public MTernaryInstruction, - public Mix3Policy< ObjectPolicy<0>, IntPolicy<1>, TruncateToInt32Policy<2> >::Data + public MixPolicy< ObjectPolicy<0>, IntPolicy<1>, TruncateToInt32Policy<2> >::Data { private: AtomicOp op_; @@ -13718,7 +13774,7 @@ class MDebugCheckSelfHosted class MFinishBoundFunctionInit : public MTernaryInstruction, - public Mix3Policy, ObjectPolicy<1>, IntPolicy<2>>::Data + public MixPolicy, ObjectPolicy<1>, IntPolicy<2>>::Data { MFinishBoundFunctionInit(MDefinition* bound, MDefinition* target, MDefinition* argCount) : MTernaryInstruction(classOpcode, bound, target, argCount) diff --git a/js/src/jit/MOpcodes.h b/js/src/jit/MOpcodes.h index 1aecbe9e420c..633ce0a7ad5e 100644 --- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -179,11 +179,14 @@ namespace jit { _(FunctionEnvironment) \ _(NewLexicalEnvironmentObject) \ _(CopyLexicalEnvironmentObject) \ + _(HomeObject) \ + _(HomeObjectSuperBase) \ _(FilterTypeSet) \ _(TypeBarrier) \ _(MonitorTypes) \ _(PostWriteBarrier) \ _(PostWriteElementBarrier) \ + _(GetPropSuperCache) \ _(GetPropertyCache) \ _(GetPropertyPolymorphic) \ _(SetPropertyPolymorphic) \ diff --git a/js/src/jit/TypePolicy.cpp b/js/src/jit/TypePolicy.cpp index 8d4aca26291d..7c8a76e7b841 100644 --- a/js/src/jit/TypePolicy.cpp +++ b/js/src/jit/TypePolicy.cpp @@ -1228,23 +1228,24 @@ FilterTypeSetPolicy::adjustInputs(TempAllocator& alloc, MInstruction* ins) _(FloatingPointPolicy<0>) \ _(IntPolicy<0>) \ _(IntPolicy<1>) \ - _(Mix3Policy, StringPolicy<1>, BoxPolicy<2> >) \ - _(Mix3Policy, BoxPolicy<1>, BoxPolicy<2> >) \ - _(Mix3Policy, BoxPolicy<1>, ObjectPolicy<2> >) \ - _(Mix3Policy, IntPolicy<1>, BoxPolicy<2> >) \ - _(Mix3Policy, IntPolicy<1>, IntPolicy<2> >) \ - _(Mix3Policy, IntPolicy<1>, TruncateToInt32Policy<2> >) \ - _(Mix3Policy, ObjectPolicy<1>, BoxPolicy<2> >) \ - _(Mix3Policy, ObjectPolicy<1>, IntPolicy<2> >) \ - _(Mix3Policy, ObjectPolicy<1>, ObjectPolicy<2> >) \ - _(Mix3Policy, IntPolicy<1>, IntPolicy<2>>) \ - _(Mix3Policy, ObjectPolicy<1>, StringPolicy<2> >) \ - _(Mix3Policy, StringPolicy<1>, StringPolicy<2> >) \ - _(Mix3Policy, StringPolicy<1>, IntPolicy<2>>) \ - _(Mix4Policy, IntPolicy<1>, IntPolicy<2>, IntPolicy<3>>) \ - _(Mix4Policy, IntPolicy<1>, TruncateToInt32Policy<2>, TruncateToInt32Policy<3> >) \ - _(Mix3Policy, CacheIdPolicy<1>, NoFloatPolicy<2>>) \ - _(Mix4Policy, SimdScalarPolicy<1>, SimdScalarPolicy<2>, SimdScalarPolicy<3> >) \ + _(MixPolicy, StringPolicy<1>, BoxPolicy<2> >) \ + _(MixPolicy, BoxPolicy<1>, BoxPolicy<2> >) \ + _(MixPolicy, BoxPolicy<1>, ObjectPolicy<2> >) \ + _(MixPolicy, IntPolicy<1>, BoxPolicy<2> >) \ + _(MixPolicy, IntPolicy<1>, IntPolicy<2> >) \ + _(MixPolicy, IntPolicy<1>, TruncateToInt32Policy<2> >) \ + _(MixPolicy, ObjectPolicy<1>, BoxPolicy<2> >) \ + _(MixPolicy, ObjectPolicy<1>, IntPolicy<2> >) \ + _(MixPolicy, ObjectPolicy<1>, ObjectPolicy<2> >) \ + _(MixPolicy, IntPolicy<1>, IntPolicy<2>>) \ + _(MixPolicy, ObjectPolicy<1>, StringPolicy<2> >) \ + _(MixPolicy, StringPolicy<1>, StringPolicy<2> >) \ + _(MixPolicy, StringPolicy<1>, IntPolicy<2>>) \ + _(MixPolicy, IntPolicy<1>, IntPolicy<2>, IntPolicy<3>>) \ + _(MixPolicy, IntPolicy<1>, TruncateToInt32Policy<2>, TruncateToInt32Policy<3> >) \ + _(MixPolicy, CacheIdPolicy<1>, NoFloatPolicy<2>>) \ + _(MixPolicy, SimdScalarPolicy<1>, SimdScalarPolicy<2>, SimdScalarPolicy<3> >) \ + _(MixPolicy, BoxExceptPolicy<1, MIRType::Object>, CacheIdPolicy<2>>) \ _(MixPolicy, ObjectPolicy<1> >) \ _(MixPolicy, ConvertToStringPolicy<1> >) \ _(MixPolicy, ObjectPolicy<1> >) \ diff --git a/js/src/jit/TypePolicy.h b/js/src/jit/TypePolicy.h index 1c716022012d..8b4b6cafd0e2 100644 --- a/js/src/jit/TypePolicy.h +++ b/js/src/jit/TypePolicy.h @@ -7,6 +7,8 @@ #ifndef jit_TypePolicy_h #define jit_TypePolicy_h +#include "mozilla/TypeTraits.h" + #include "jit/IonTypes.h" #include "jit/JitAllocPolicy.h" @@ -410,46 +412,25 @@ class CacheIdPolicy final : public TypePolicy }; // Combine multiple policies. -template +template class MixPolicy final : public TypePolicy { - public: - EMPTY_DATA_; - static MOZ_MUST_USE bool staticAdjustInputs(TempAllocator& alloc, MInstruction* ins) { - return Lhs::staticAdjustInputs(alloc, ins) && Rhs::staticAdjustInputs(alloc, ins); + template + static bool staticAdjustInputsHelper(TempAllocator& alloc, MInstruction* ins) { + return P::staticAdjustInputs(alloc, ins); } - virtual MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* ins) override { - return staticAdjustInputs(alloc, ins); - } -}; -// Combine three policies. -template -class Mix3Policy final : public TypePolicy -{ - public: - EMPTY_DATA_; - static MOZ_MUST_USE bool staticAdjustInputs(TempAllocator& alloc, MInstruction* ins) { - return Policy1::staticAdjustInputs(alloc, ins) && - Policy2::staticAdjustInputs(alloc, ins) && - Policy3::staticAdjustInputs(alloc, ins); - } - virtual MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* ins) override { - return staticAdjustInputs(alloc, ins); + template + static typename mozilla::EnableIf<(sizeof...(Rest) > 0), bool>::Type + staticAdjustInputsHelper(TempAllocator& alloc, MInstruction* ins) { + return P::staticAdjustInputs(alloc, ins) && + MixPolicy::staticAdjustInputsHelper(alloc, ins); } -}; -// Combine four policies. (Missing variadic templates yet?) -template -class Mix4Policy : public TypePolicy -{ public: EMPTY_DATA_; static MOZ_MUST_USE bool staticAdjustInputs(TempAllocator& alloc, MInstruction* ins) { - return Policy1::staticAdjustInputs(alloc, ins) && - Policy2::staticAdjustInputs(alloc, ins) && - Policy3::staticAdjustInputs(alloc, ins) && - Policy4::staticAdjustInputs(alloc, ins); + return MixPolicy::staticAdjustInputsHelper(alloc, ins); } virtual MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* ins) override { return staticAdjustInputs(alloc, ins); diff --git a/js/src/jit/shared/LIR-shared.h b/js/src/jit/shared/LIR-shared.h index ea743495cb6a..fd7e13da85fb 100644 --- a/js/src/jit/shared/LIR-shared.h +++ b/js/src/jit/shared/LIR-shared.h @@ -6667,6 +6667,28 @@ class LCallGetIntrinsicValue : public LCallInstructionHelper } }; +class LGetPropSuperCacheV : public LInstructionHelper +{ + public: + LIR_HEADER(GetPropSuperCacheV) + + static const size_t Receiver = 1; + static const size_t Id = Receiver + BOX_PIECES; + + LGetPropSuperCacheV(const LAllocation& obj, const LBoxAllocation& receiver, + const LBoxAllocation& id) { + setOperand(0, obj); + setBoxOperand(Receiver, receiver); + setBoxOperand(Id, id); + } + const LAllocation* obj() { + return getOperand(0); + } + const MGetPropSuperCache* mir() const { + return mir_->toGetPropSuperCache(); + } +}; + // Patchable jump to stubs generated for a GetProperty cache, which loads a // boxed value. class LGetPropertyCacheV : public LInstructionHelper @@ -7092,6 +7114,34 @@ class LFunctionEnvironment : public LInstructionHelper<1, 1, 0> } }; +class LHomeObject : public LInstructionHelper<1, 1, 0> +{ + public: + LIR_HEADER(HomeObject) + + explicit LHomeObject(const LAllocation& function) { + setOperand(0, function); + } + const LAllocation* function() { + return getOperand(0); + } +}; + +class LHomeObjectSuperBase : public LInstructionHelper<1, 1, 0> +{ + public: + LIR_HEADER(HomeObjectSuperBase) + + explicit LHomeObjectSuperBase(const LAllocation& homeObject) + { + setOperand(0, homeObject); + } + + const LAllocation* homeObject() { + return getOperand(0); + } +}; + // Allocate a new LexicalEnvironmentObject. class LNewLexicalEnvironmentObject : public LCallInstructionHelper<1, 1, 0> { diff --git a/js/src/jit/shared/LOpcodes-shared.h b/js/src/jit/shared/LOpcodes-shared.h index 274ee68d4f72..6bb5bdb9fbe4 100644 --- a/js/src/jit/shared/LOpcodes-shared.h +++ b/js/src/jit/shared/LOpcodes-shared.h @@ -317,6 +317,9 @@ _(FunctionEnvironment) \ _(NewLexicalEnvironmentObject) \ _(CopyLexicalEnvironmentObject) \ + _(HomeObject) \ + _(HomeObjectSuperBase) \ + _(GetPropSuperCacheV) \ _(GetPropertyCacheV) \ _(GetPropertyCacheT) \ _(GetPropertyPolymorphicV) \ diff --git a/js/src/jsalloc.h b/js/src/jsalloc.h index d81ff5cfc051..3b74bdde0e45 100644 --- a/js/src/jsalloc.h +++ b/js/src/jsalloc.h @@ -17,7 +17,7 @@ #include "js/TypeDecls.h" #include "js/Utility.h" -extern JS_PUBLIC_API(void) JS_ReportOutOfMemory(JSContext* cx); +extern MOZ_COLD JS_PUBLIC_API(void) JS_ReportOutOfMemory(JSContext* cx); namespace js { @@ -47,7 +47,7 @@ class SystemAllocPolicy } }; -void ReportOutOfMemory(JSContext* cx); +MOZ_COLD JS_FRIEND_API(void) ReportOutOfMemory(JSContext* cx); /* * Allocation policy that calls the system memory functions and reports errors diff --git a/js/src/jsapi.h b/js/src/jsapi.h index ad158cfe7d16..7bcf28fd3cac 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -5480,7 +5480,7 @@ JS_ReportErrorFlagsAndNumberUC(JSContext* cx, unsigned flags, /** * Complain when out of memory. */ -extern JS_PUBLIC_API(void) +extern MOZ_COLD JS_PUBLIC_API(void) JS_ReportOutOfMemory(JSContext* cx); /** diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index 55fb0fae5887..d3e95dacba2a 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -298,7 +298,7 @@ PopulateReportBlame(JSContext* cx, JSErrorReport* report) * Furthermore, callers of ReportOutOfMemory (viz., malloc) assume a GC does * not occur, so GC must be avoided or suppressed. */ -void +JS_FRIEND_API(void) js::ReportOutOfMemory(JSContext* cx) { #ifdef JS_MORE_DETERMINISTIC diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index 39179a303415..7ae6696e0da1 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -591,7 +591,7 @@ js::XDRInterpretedFunction(XDRState* xdr, HandleScope enclosingScope, if (!fun->isInterpreted()) return xdr->fail(JS::TranscodeResult_Failure_NotInterpretedFun); - if (fun->explicitName() || fun->hasCompileTimeName() || fun->hasGuessedAtom()) + if (fun->explicitName() || fun->hasInferredName() || fun->hasGuessedAtom()) firstword |= HasAtom; if (fun->isStarGenerator() || fun->isAsync()) @@ -1395,12 +1395,8 @@ JSFunction::getUnresolvedName(JSContext* cx, HandleFunction fun, MutableHandleAt MOZ_ASSERT(!IsInternalFunctionObject(*fun)); MOZ_ASSERT(!fun->hasResolvedName()); - JSAtom* name = fun->explicitOrCompileTimeName(); + JSAtom* name = fun->explicitOrInferredName(); if (fun->isClassConstructor()) { - // It's impossible to have an empty named class expression. We use - // empty as a sentinel when creating default class constructors. - MOZ_ASSERT(name != cx->names().empty); - // Unnamed class expressions should not get a .name property at all. if (name) v.set(name); @@ -2163,7 +2159,17 @@ NewFunctionClone(JSContext* cx, HandleFunction fun, NewObjectKind newKind, return nullptr; RootedFunction clone(cx, &cloneobj->as()); - uint16_t flags = fun->flags() & ~JSFunction::EXTENDED; + // JSFunction::HAS_INFERRED_NAME can be set at compile-time and at + // runtime. In the latter case we should actually clear the flag before + // cloning the function, but since we can't differentiate between both + // cases here, we'll end up with a momentarily incorrect function name. + // This will be fixed up in SetFunctionName(), which should happen through + // JSOP_SETFUNNAME directly after JSOP_LAMBDA. + constexpr uint16_t NonCloneableFlags = JSFunction::EXTENDED | + JSFunction::RESOLVED_LENGTH | + JSFunction::RESOLVED_NAME; + + uint16_t flags = fun->flags() & ~NonCloneableFlags; if (allocKind == AllocKind::FUNCTION_EXTENDED) flags |= JSFunction::EXTENDED; @@ -2278,14 +2284,67 @@ js::CloneFunctionAndScript(JSContext* cx, HandleFunction fun, HandleObject enclo return clone; } +static JSAtom* +SymbolToFunctionName(JSContext* cx, JS::Symbol* symbol, FunctionPrefixKind prefixKind) +{ + // Step 4.a. + JSAtom* desc = symbol->description(); + + // Step 4.b, no prefix fastpath. + if (!desc && prefixKind == FunctionPrefixKind::None) + return cx->names().empty; + + // Step 5 (reordered). + StringBuffer sb(cx); + if (prefixKind == FunctionPrefixKind::Get) { + if (!sb.append("get ")) + return nullptr; + } else if (prefixKind == FunctionPrefixKind::Set) { + if (!sb.append("set ")) + return nullptr; + } + + // Step 4.b. + if (desc) { + // Step 4.c. + if (!sb.append('[') || !sb.append(desc) || !sb.append(']')) + return nullptr; + } + return sb.finishAtom(); +} + +static JSAtom* +NameToFunctionName(JSContext* cx, HandleValue name, FunctionPrefixKind prefixKind) +{ + MOZ_ASSERT(name.isString() || name.isNumber()); + + if (prefixKind == FunctionPrefixKind::None) + return ToAtom(cx, name); + + JSString* nameStr = ToString(cx, name); + if (!nameStr) + return nullptr; + + StringBuffer sb(cx); + if (prefixKind == FunctionPrefixKind::Get) { + if (!sb.append("get ")) + return nullptr; + } else { + if (!sb.append("set ")) + return nullptr; + } + if (!sb.append(nameStr)) + return nullptr; + return sb.finishAtom(); +} + /* * Return an atom for use as the name of a builtin method with the given * property id. * * Function names are always strings. If id is the well-known @@iterator * symbol, this returns "[Symbol.iterator]". If a prefix is supplied the final - * name is |prefix + " " + name|. A prefix cannot be supplied if id is a - * symbol value. + * name is |prefix + " " + name|. * * Implements steps 3-5 of 9.2.11 SetFunctionName in ES2016. */ @@ -2293,6 +2352,8 @@ JSAtom* js::IdToFunctionName(JSContext* cx, HandleId id, FunctionPrefixKind prefixKind /* = FunctionPrefixKind::None */) { + MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_SYMBOL(id) || JSID_IS_INT(id)); + // No prefix fastpath. if (JSID_IS_ATOM(id) && prefixKind == FunctionPrefixKind::None) return JSID_TO_ATOM(id); @@ -2300,96 +2361,43 @@ js::IdToFunctionName(JSContext* cx, HandleId id, // Step 3 (implicit). // Step 4. - if (JSID_IS_SYMBOL(id)) { - // Step 4.a. - RootedAtom desc(cx, JSID_TO_SYMBOL(id)->description()); - - // Step 4.b, no prefix fastpath. - if (!desc && prefixKind == FunctionPrefixKind::None) - return cx->names().empty; - - // Step 5 (reordered). - StringBuffer sb(cx); - if (prefixKind == FunctionPrefixKind::Get) { - if (!sb.append("get ")) - return nullptr; - } else if (prefixKind == FunctionPrefixKind::Set) { - if (!sb.append("set ")) - return nullptr; - } - - // Step 4.b. - if (desc) { - // Step 4.c. - if (!sb.append('[') || !sb.append(desc) || !sb.append(']')) - return nullptr; - } - return sb.finishAtom(); - } - - RootedValue idv(cx, IdToValue(id)); - RootedAtom name(cx, ToAtom(cx, idv)); - if (!name) - return nullptr; + if (JSID_IS_SYMBOL(id)) + return SymbolToFunctionName(cx, JSID_TO_SYMBOL(id), prefixKind); // Step 5. - return NameToFunctionName(cx, name, prefixKind); -} - -JSAtom* -js::NameToFunctionName(JSContext* cx, HandleAtom name, - FunctionPrefixKind prefixKind /* = FunctionPrefixKind::None */) -{ - if (prefixKind == FunctionPrefixKind::None) - return name; - - StringBuffer sb(cx); - if (prefixKind == FunctionPrefixKind::Get) { - if (!sb.append("get ")) - return nullptr; - } else { - if (!sb.append("set ")) - return nullptr; - } - if (!sb.append(name)) - return nullptr; - return sb.finishAtom(); + RootedValue idv(cx, IdToValue(id)); + return NameToFunctionName(cx, idv, prefixKind); } bool -js::SetFunctionNameIfNoOwnName(JSContext* cx, HandleFunction fun, HandleValue name, - FunctionPrefixKind prefixKind) +js::SetFunctionName(JSContext* cx, HandleFunction fun, HandleValue name, + FunctionPrefixKind prefixKind) { MOZ_ASSERT(name.isString() || name.isSymbol() || name.isNumber()); - if (fun->isClassConstructor()) { - // A class may have static 'name' method or accessor. - RootedId nameId(cx, NameToId(cx->names().name)); - bool result; - if (!HasOwnProperty(cx, fun, nameId, &result)) - return false; - - if (result) - return true; - } else { - // Anonymous function shouldn't have own 'name' property at this point. - MOZ_ASSERT(!fun->containsPure(cx->names().name)); + // `fun` is a newly created function, so normally it can't already have an + // inferred name. The rare exception is when `fun` was created by cloning + // a singleton function; see the comment in NewFunctionClone. In that case, + // the inferred name is bogus, so clear it out. + if (fun->hasInferredName()) { + MOZ_ASSERT(fun->isSingleton()); + fun->clearInferredName(); } - RootedId id(cx); - if (!ValueToId(cx, name, &id)) - return false; - - RootedAtom funNameAtom(cx, IdToFunctionName(cx, id, prefixKind)); - if (!funNameAtom) - return false; + // Anonymous functions should neither have an own 'name' property nor a + // resolved name at this point. + MOZ_ASSERT(!fun->containsPure(cx->names().name)); + MOZ_ASSERT(!fun->hasResolvedName()); - RootedValue funNameVal(cx, StringValue(funNameAtom)); - if (!NativeDefineProperty(cx, fun, cx->names().name, funNameVal, nullptr, nullptr, - JSPROP_READONLY)) - { + JSAtom* funName = name.isSymbol() + ? SymbolToFunctionName(cx, name.toSymbol(), prefixKind) + : NameToFunctionName(cx, name, prefixKind); + if (!funName) { return false; } + + fun->setInferredName(funName); + return true; } diff --git a/js/src/jsfun.h b/js/src/jsfun.h index d611c41c2941..ca4a7e3307e2 100644 --- a/js/src/jsfun.h +++ b/js/src/jsfun.h @@ -59,7 +59,8 @@ class JSFunction : public js::NativeObject EXTENDED = 0x0004, /* structure is FunctionExtended */ BOUND_FUN = 0x0008, /* function was created with Function.prototype.bind. */ HAS_GUESSED_ATOM = 0x0020, /* function had no explicit name, but a - name was guessed for it anyway */ + name was guessed for it anyway. See + atom_ for more info about this flag. */ HAS_BOUND_FUNCTION_NAME_PREFIX = 0x0020, /* bound functions reuse the HAS_GUESSED_ATOM flag to track if atom_ already contains the "bound " function name prefix */ @@ -68,9 +69,10 @@ class JSFunction : public js::NativeObject function-statement) */ SELF_HOSTED = 0x0080, /* function is self-hosted builtin and must not be decompilable nor constructible. */ - HAS_COMPILE_TIME_NAME = 0x0100, /* function had no explicit name, but a - name was set by SetFunctionName - at compile time */ + HAS_INFERRED_NAME = 0x0100, /* function had no explicit name, but a name was + set by SetFunctionName at compile time or + SetFunctionName at runtime. See atom_ for + more info about this flag. */ INTERPRETED_LAZY = 0x0200, /* function is interpreted but doesn't have a script yet */ RESOLVED_LENGTH = 0x0400, /* f.length has been resolved (see fun_resolve). */ RESOLVED_NAME = 0x0800, /* f.name has been resolved (see fun_resolve). */ @@ -103,8 +105,7 @@ class JSFunction : public js::NativeObject INTERPRETED_GENERATOR_OR_ASYNC = INTERPRETED, NO_XDR_FLAGS = RESOLVED_LENGTH | RESOLVED_NAME, - STABLE_ACROSS_CLONES = CONSTRUCTOR | LAMBDA | SELF_HOSTED | HAS_COMPILE_TIME_NAME | - FUNCTION_KIND_MASK + STABLE_ACROSS_CLONES = CONSTRUCTOR | LAMBDA | SELF_HOSTED | FUNCTION_KIND_MASK }; static_assert((INTERPRETED | INTERPRETED_LAZY) == js::JS_FUNCTION_INTERPRETED_BITS, @@ -134,7 +135,41 @@ class JSFunction : public js::NativeObject } i; void* nativeOrScript; } u; - js::GCPtrAtom atom_; /* name for diagnostics and decompiling */ + + // The |atom_| field can have different meanings depending on the function + // type and flags. It is used for diagnostics, decompiling, and + // + // 1. If the function is not a bound function: + // a. If HAS_GUESSED_ATOM is not set, to store the initial value of the + // "name" property of functions. But also see RESOLVED_NAME. + // b. If HAS_GUESSED_ATOM is set, |atom_| is only used for diagnostics, + // but must not be used for the "name" property. + // c. If HAS_INFERRED_NAME is set, the function wasn't given an explicit + // name in the source text, e.g. |function fn(){}|, but instead it + // was inferred based on how the function was defined in the source + // text. The exact name inference rules are defined in the ECMAScript + // specification. + // Name inference can happen at compile-time, for example in + // |var fn = function(){}|, or it can happen at runtime, for example + // in |var o = {[Symbol.iterator]: function(){}}|. When it happens at + // compile-time, the HAS_INFERRED_NAME is set directly in the + // bytecode emitter, when it happens at runtime, the flag is set when + // evaluating the JSOP_SETFUNNAME bytecode. + // d. HAS_GUESSED_ATOM and HAS_INFERRED_NAME cannot both be set. + // e. |atom_| can be null if neither an explicit, nor inferred, nor a + // guessed name was set. + // f. HAS_INFERRED_NAME can be set for cloned singleton function, even + // though the clone shouldn't receive an inferred name. See the + // comments in NewFunctionClone() and SetFunctionName() for details. + // + // 2. If the function is a bound function: + // a. To store the initial value of the "name" property. + // b. If HAS_BOUND_FUNCTION_NAME_PREFIX is not set, |atom_| doesn't + // contain the "bound " prefix which is prepended to the "name" + // property of bound functions per ECMAScript. + // c. Bound functions can never have an inferred or guessed name. + // d. |atom_| is never null for bound functions. + js::GCPtrAtom atom_; public: /* Call objects must be created for each invocation of this function. */ @@ -191,7 +226,7 @@ class JSFunction : public js::NativeObject /* Possible attributes of an interpreted function: */ bool isBoundFunction() const { return flags() & BOUND_FUN; } - bool hasCompileTimeName() const { return flags() & HAS_COMPILE_TIME_NAME; } + bool hasInferredName() const { return flags() & HAS_INFERRED_NAME; } bool hasGuessedAtom() const { static_assert(HAS_GUESSED_ATOM == HAS_BOUND_FUNCTION_NAME_PREFIX, "HAS_GUESSED_ATOM is unused for bound functions"); @@ -242,7 +277,7 @@ class JSFunction : public js::NativeObject } bool isNamedLambda() const { - return isLambda() && displayAtom() && !hasCompileTimeName() && !hasGuessedAtom(); + return isLambda() && displayAtom() && !hasInferredName() && !hasGuessedAtom(); } bool hasLexicalThis() const { @@ -329,9 +364,9 @@ class JSFunction : public js::NativeObject js::MutableHandleAtom v); JSAtom* explicitName() const { - return (hasCompileTimeName() || hasGuessedAtom()) ? nullptr : atom_.get(); + return (hasInferredName() || hasGuessedAtom()) ? nullptr : atom_.get(); } - JSAtom* explicitOrCompileTimeName() const { + JSAtom* explicitOrInferredName() const { return hasGuessedAtom() ? nullptr : atom_.get(); } @@ -349,16 +384,21 @@ class JSFunction : public js::NativeObject return atom_; } - void setCompileTimeName(JSAtom* atom) { + void setInferredName(JSAtom* atom) { MOZ_ASSERT(!atom_); MOZ_ASSERT(atom); MOZ_ASSERT(!hasGuessedAtom()); - MOZ_ASSERT(!isClassConstructor()); setAtom(atom); - flags_ |= HAS_COMPILE_TIME_NAME; + flags_ |= HAS_INFERRED_NAME; } - JSAtom* compileTimeName() const { - MOZ_ASSERT(hasCompileTimeName()); + void clearInferredName() { + MOZ_ASSERT(hasInferredName()); + MOZ_ASSERT(atom_); + setAtom(nullptr); + flags_ &= ~HAS_INFERRED_NAME; + } + JSAtom* inferredName() const { + MOZ_ASSERT(hasInferredName()); MOZ_ASSERT(atom_); return atom_; } @@ -366,7 +406,7 @@ class JSFunction : public js::NativeObject void setGuessedAtom(JSAtom* atom) { MOZ_ASSERT(!atom_); MOZ_ASSERT(atom); - MOZ_ASSERT(!hasCompileTimeName()); + MOZ_ASSERT(!hasInferredName()); MOZ_ASSERT(!hasGuessedAtom()); MOZ_ASSERT(!isBoundFunction()); setAtom(atom); @@ -734,13 +774,9 @@ extern JSAtom* IdToFunctionName(JSContext* cx, HandleId id, FunctionPrefixKind prefixKind = FunctionPrefixKind::None); -extern JSAtom* -NameToFunctionName(JSContext* cx, HandleAtom name, - FunctionPrefixKind prefixKind = FunctionPrefixKind::None); - extern bool -SetFunctionNameIfNoOwnName(JSContext* cx, HandleFunction fun, HandleValue name, - FunctionPrefixKind prefixKind); +SetFunctionName(JSContext* cx, HandleFunction fun, HandleValue name, + FunctionPrefixKind prefixKind); extern JSFunction* DefineFunction(JSContext* cx, HandleObject obj, HandleId id, JSNative native, diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp index 9ee5a6e28efb..730deb986aa0 100644 --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -716,9 +716,9 @@ BytecodeParser::simulateOp(JSOp op, uint32_t offset, OffsetAndDefIndex* offsetSt break; case JSOP_INITHOMEOBJECT: - // Keep the top 2 values. + // Pop the top value, keep the other value. MOZ_ASSERT(nuses == 2); - MOZ_ASSERT(ndefs == 2); + MOZ_ASSERT(ndefs == 1); break; case JSOP_SETGNAME: @@ -1810,7 +1810,7 @@ ExpressionDecompiler::decompilePC(jsbytecode* pc, uint8_t defIndex) case JSOP_GETELEM_SUPER: return write("super[") && - decompilePCForStackOperand(pc, -3) && + decompilePCForStackOperand(pc, -2) && write("]"); case JSOP_NULL: return write(js_null_str); @@ -1846,9 +1846,11 @@ ExpressionDecompiler::decompilePC(jsbytecode* pc, uint8_t defIndex) case JSOP_CALL_IGNORES_RV: case JSOP_CALLITER: case JSOP_FUNCALL: - case JSOP_FUNAPPLY: - return decompilePCForStackOperand(pc, -int32_t(GET_ARGC(pc) + 2)) && - write("(...)"); + case JSOP_FUNAPPLY: { + uint16_t argc = GET_ARGC(pc); + return decompilePCForStackOperand(pc, -int32_t(argc + 2)) && + write(argc ? "(...)" : "()"); + } case JSOP_SPREADCALL: return decompilePCForStackOperand(pc, -3) && write("(...)"); @@ -1893,6 +1895,10 @@ ExpressionDecompiler::decompilePC(jsbytecode* pc, uint8_t defIndex) write(")"); case JSOP_SUPERCALL: + if (GET_ARGC(pc) == 0) { + return write("super()"); + } + [[fallthrough]]; case JSOP_SPREADSUPERCALL: return write("super(...)"); case JSOP_SUPERFUN: @@ -1904,10 +1910,12 @@ ExpressionDecompiler::decompilePC(jsbytecode* pc, uint8_t defIndex) case JSOP_STRICTSPREADEVAL: return write("eval(...)"); - case JSOP_NEW: + case JSOP_NEW: { + uint16_t argc = GET_ARGC(pc); return write("(new ") && - decompilePCForStackOperand(pc, -int32_t(GET_ARGC(pc) + 3)) && - write("(...))"); + decompilePCForStackOperand(pc, -int32_t(argc + 3)) && + write(argc ? "(...))" : "())"); + } case JSOP_SPREADNEW: return write("(new ") && diff --git a/js/src/jsopcode.h b/js/src/jsopcode.h index d287202d519f..a5bc3ee92a33 100644 --- a/js/src/jsopcode.h +++ b/js/src/jsopcode.h @@ -12,6 +12,7 @@ */ #include "mozilla/Attributes.h" +#include "mozilla/EndianUtils.h" #include "jsbytecode.h" #include "jstypes.h" @@ -136,17 +137,26 @@ UINT16_LO(uint16_t i) static MOZ_ALWAYS_INLINE uint16_t GET_UINT16(const jsbytecode* pc) { - return uint16_t((pc[1] << 8) | pc[2]); +#if MOZ_LITTLE_ENDIAN + uint16_t result; + memcpy(&result, pc + 1, sizeof(result)); + return result; +#else + return uint16_t((pc[2] << 8) | pc[1]); +#endif } static MOZ_ALWAYS_INLINE void SET_UINT16(jsbytecode* pc, uint16_t i) { - pc[1] = UINT16_HI(i); - pc[2] = UINT16_LO(i); +#if MOZ_LITTLE_ENDIAN + memcpy(pc + 1, &i, sizeof(i)); +#else + pc[1] = UINT16_LO(i); + pc[2] = UINT16_HI(i); +#endif } -static const unsigned UINT16_LEN = 2; static const unsigned UINT16_LIMIT = 1 << 16; /* Helpers for accessing the offsets of jump opcodes. */ @@ -154,69 +164,32 @@ static const unsigned JUMP_OFFSET_LEN = 4; static const int32_t JUMP_OFFSET_MIN = INT32_MIN; static const int32_t JUMP_OFFSET_MAX = INT32_MAX; -static MOZ_ALWAYS_INLINE int32_t -GET_JUMP_OFFSET(jsbytecode* pc) -{ - return (pc[1] << 24) | (pc[2] << 16) | (pc[3] << 8) | pc[4]; -} - -static MOZ_ALWAYS_INLINE void -SET_JUMP_OFFSET(jsbytecode* pc, int32_t off) -{ - pc[1] = jsbytecode(off >> 24); - pc[2] = jsbytecode(off >> 16); - pc[3] = jsbytecode(off >> 8); - pc[4] = jsbytecode(off); -} - -static const unsigned UINT32_INDEX_LEN = 4; - static MOZ_ALWAYS_INLINE uint32_t -GET_UINT32_INDEX(const jsbytecode* pc) -{ - return (pc[1] << 24) | (pc[2] << 16) | (pc[3] << 8) | pc[4]; -} - -static MOZ_ALWAYS_INLINE void -SET_UINT32_INDEX(jsbytecode* pc, uint32_t index) -{ - pc[1] = jsbytecode(index >> 24); - pc[2] = jsbytecode(index >> 16); - pc[3] = jsbytecode(index >> 8); - pc[4] = jsbytecode(index); -} - -static inline jsbytecode -UINT24_HI(unsigned i) -{ - return jsbytecode(i >> 16); -} - -static inline jsbytecode -UINT24_MID(unsigned i) -{ - return jsbytecode(i >> 8); -} - -static inline jsbytecode -UINT24_LO(unsigned i) -{ - return jsbytecode(i); -} - -static MOZ_ALWAYS_INLINE unsigned GET_UINT24(const jsbytecode* pc) { - return unsigned((pc[1] << 16) | (pc[2] << 8) | pc[3]); +#if MOZ_LITTLE_ENDIAN + // Do a single 32-bit load (for opcode and operand), then shift off the + // opcode. + uint32_t result; + memcpy(&result, pc, 4); + return result >> 8; +#else + return unsigned((pc[3] << 16) | (pc[2] << 8) | pc[1]); +#endif } static MOZ_ALWAYS_INLINE void -SET_UINT24(jsbytecode* pc, unsigned i) +SET_UINT24(jsbytecode* pc, uint32_t i) { MOZ_ASSERT(i < (1 << 24)); - pc[1] = UINT24_HI(i); - pc[2] = UINT24_MID(i); - pc[3] = UINT24_LO(i); + +#if MOZ_LITTLE_ENDIAN + memcpy(pc + 1, &i, 3); +#else + pc[1] = jsbytecode(i); + pc[2] = jsbytecode(i >> 8); + pc[3] = jsbytecode(i >> 16); +#endif } static MOZ_ALWAYS_INLINE int8_t @@ -228,19 +201,29 @@ GET_INT8(const jsbytecode* pc) static MOZ_ALWAYS_INLINE uint32_t GET_UINT32(const jsbytecode* pc) { - return (uint32_t(pc[1]) << 24) | - (uint32_t(pc[2]) << 16) | - (uint32_t(pc[3]) << 8) | - uint32_t(pc[4]); +#if MOZ_LITTLE_ENDIAN + uint32_t result; + memcpy(&result, pc + 1, sizeof(result)); + return result; +#else + return (uint32_t(pc[4]) << 24) | + (uint32_t(pc[3]) << 16) | + (uint32_t(pc[2]) << 8) | + uint32_t(pc[1]); +#endif } static MOZ_ALWAYS_INLINE void SET_UINT32(jsbytecode* pc, uint32_t u) { - pc[1] = jsbytecode(u >> 24); - pc[2] = jsbytecode(u >> 16); - pc[3] = jsbytecode(u >> 8); - pc[4] = jsbytecode(u); +#if MOZ_LITTLE_ENDIAN + memcpy(pc + 1, &u, sizeof(u)); +#else + pc[1] = jsbytecode(u); + pc[2] = jsbytecode(u >> 8); + pc[3] = jsbytecode(u >> 16); + pc[4] = jsbytecode(u >> 24); +#endif } static MOZ_ALWAYS_INLINE int32_t @@ -255,6 +238,32 @@ SET_INT32(jsbytecode* pc, int32_t i) SET_UINT32(pc, static_cast(i)); } +static MOZ_ALWAYS_INLINE int32_t +GET_JUMP_OFFSET(jsbytecode* pc) +{ + return GET_INT32(pc); +} + +static MOZ_ALWAYS_INLINE void +SET_JUMP_OFFSET(jsbytecode* pc, int32_t off) +{ + SET_INT32(pc, off); +} + +static const unsigned UINT32_INDEX_LEN = 4; + +static MOZ_ALWAYS_INLINE uint32_t +GET_UINT32_INDEX(const jsbytecode* pc) +{ + return GET_UINT32(pc); +} + +static MOZ_ALWAYS_INLINE void +SET_UINT32_INDEX(jsbytecode* pc, uint32_t index) +{ + SET_UINT32(pc, index); +} + /* Index limit is determined by SN_4BYTE_OFFSET_FLAG, see frontend/BytecodeEmitter.h. */ static const unsigned INDEX_LIMIT_LOG2 = 31; static const uint32_t INDEX_LIMIT = uint32_t(1) << INDEX_LIMIT_LOG2; diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 3ac90fcfff51..13603d46448e 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -2391,6 +2391,8 @@ SrcNotes(JSContext* cx, HandleScript script, Sprinter* sp) switch (type) { case SRC_NULL: case SRC_IF: + case SRC_IF_ELSE: + case SRC_COND: case SRC_CONTINUE: case SRC_BREAK: case SRC_BREAK2LABEL: @@ -2425,18 +2427,12 @@ SrcNotes(JSContext* cx, HandleScript script, Sprinter* sp) } break; - case SRC_IF_ELSE: - if (!sp->jsprintf(" else %u", unsigned(GetSrcNoteOffset(sn, 0)))) - return false; - break; - case SRC_FOR_IN: case SRC_FOR_OF: if (!sp->jsprintf(" closingjump %u", unsigned(GetSrcNoteOffset(sn, 0)))) return false; break; - case SRC_COND: case SRC_WHILE: case SRC_NEXTCASE: if (!sp->jsprintf(" offset %u", unsigned(GetSrcNoteOffset(sn, 0)))) diff --git a/js/src/tests/ecma_6/Class/superPropDelete.js b/js/src/tests/ecma_6/Class/superPropDelete.js index 44bf0d3f6d9c..1da338558ba3 100644 --- a/js/src/tests/ecma_6/Class/superPropDelete.js +++ b/js/src/tests/ecma_6/Class/superPropDelete.js @@ -42,5 +42,23 @@ Object.setPrototypeOf(thing2, new Proxy({}, { })); assertThrowsInstanceOf(() => thing2.go(), ReferenceError); +class derivedTestDeleteProp extends base { + constructor() { + // The deletion error is a reference error, but by munging the prototype + // chain, we can force a type error from JSOP_SUPERBASE. + Object.setPrototypeOf(derivedTestDeleteProp.prototype, null); + + assertThrowsInstanceOf(() => delete super.prop, ReferenceError); + + super(); + + assertThrowsInstanceOf(() => delete super.prop, TypeError); + + return {}; + } +} + +new derivedTestDeleteProp(); + if (typeof reportCompare === 'function') reportCompare(0,0,"OK"); diff --git a/js/src/tests/js1_8/regress/regress-469625-03.js b/js/src/tests/js1_8/regress/regress-469625-03.js index c189a26dd7de..357a4ae17382 100644 --- a/js/src/tests/js1_8/regress/regress-469625-03.js +++ b/js/src/tests/js1_8/regress/regress-469625-03.js @@ -26,7 +26,7 @@ function test() var [a, b, [c0, c1]] = [x, x, x]; } - expect = `TypeError: [...][Symbol.iterator](...).next(...).value is null`; + expect = `TypeError: [...][Symbol.iterator]().next().value is null`; actual = 'No Error'; try { diff --git a/js/src/tests/non262/class/superElemDelete.js b/js/src/tests/non262/class/superElemDelete.js new file mode 100644 index 000000000000..6132cbafc2c1 --- /dev/null +++ b/js/src/tests/non262/class/superElemDelete.js @@ -0,0 +1,68 @@ +// Make sure we get the proper side effects. +// |delete super[expr]| applies ToPropertyKey on |expr| before throwing. + +class base { + constructor() { } +} + +class derived extends base { + constructor() { super(); } + testDeleteElem() { + let sideEffect = 0; + let key = { + toString() { + sideEffect++; + return ""; + } + }; + assertThrowsInstanceOf(() => delete super[key], ReferenceError); + assertEq(sideEffect, 1); + } + testDeleteElemPropValFirst() { + // The deletion error is a reference error, but by munging the prototype + // chain, we can force a type error from JSOP_SUPERBASE. + let key = { + toString() { + Object.setPrototypeOf(derived.prototype, null); + return ""; + } + }; + delete super[key]; + } +} + +class derivedTestDeleteElem extends base { + constructor() { + let sideEffect = 0; + let key = { + toString() { + sideEffect++; + return ""; + } + }; + + assertThrowsInstanceOf(() => delete super[key], ReferenceError); + assertEq(sideEffect, 0); + + super(); + + assertThrowsInstanceOf(() => delete super[key], ReferenceError); + assertEq(sideEffect, 1); + + Object.setPrototypeOf(derivedTestDeleteElem.prototype, null); + + assertThrowsInstanceOf(() => delete super[key], TypeError); + assertEq(sideEffect, 2); + + return {}; + } +} + +var d = new derived(); +d.testDeleteElem(); +assertThrowsInstanceOf(() => d.testDeleteElemPropValFirst(), TypeError); + +new derivedTestDeleteElem(); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/non262/expressions/short-circuit-compound-assignment-anon-fns.js b/js/src/tests/non262/expressions/short-circuit-compound-assignment-anon-fns.js new file mode 100644 index 000000000000..406f143eadbf --- /dev/null +++ b/js/src/tests/non262/expressions/short-circuit-compound-assignment-anon-fns.js @@ -0,0 +1,42 @@ +// NamedEvaluation applies to short-circuit assignment. + +{ + let a; + a ??= function(){}; + assertEq(a.name, "a"); +} + +{ + let a = false; + a ||= function(){}; + assertEq(a.name, "a"); +} + +{ + let a = true; + a &&= function(){}; + assertEq(a.name, "a"); +} + +// No name assignments for parenthesised left-hand sides. + +{ + let a; + (a) ??= function(){}; + assertEq(a.name, ""); +} + +{ + let a = false; + (a) ||= function(){}; + assertEq(a.name, ""); +} + +{ + let a = true; + (a) &&= function(){}; + assertEq(a.name, ""); +} + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/non262/expressions/short-circuit-compound-assignment-const.js b/js/src/tests/non262/expressions/short-circuit-compound-assignment-const.js new file mode 100644 index 000000000000..8499e2fb7454 --- /dev/null +++ b/js/src/tests/non262/expressions/short-circuit-compound-assignment-const.js @@ -0,0 +1,90 @@ +// Test assignment to const and function name bindings. The latter is kind of a +// const binding, but only throws in strict mode. + +function notEvaluated() { + throw new Error("should not be evaluated"); +} + +// AndAssignExpr with const lexical binding. +{ + const a = false; + a &&= notEvaluated(); + assertEq(a, false); + + const b = true; + assertThrowsInstanceOf(() => { b &&= 1; }, TypeError); + assertEq(b, true); +} + +// AndAssignExpr with function name binding. +{ + let f = function fn() { + fn &&= true; + assertEq(fn, f); + }; + f(); + + let g = function fn() { + "use strict"; + assertThrowsInstanceOf(() => { fn &&= 1; }, TypeError); + assertEq(fn, g); + }; + g(); +} + +// OrAssignExpr with const lexical binding. +{ + const a = true; + a ||= notEvaluated(); + assertEq(a, true); + + const b = false; + assertThrowsInstanceOf(() => { b ||= 0; }, TypeError); + assertEq(b, false); +} + +// OrAssignExpr with function name binding. +{ + let f = function fn() { + fn ||= notEvaluated(); + assertEq(fn, f); + }; + f(); + + let g = function fn() { + "use strict"; + fn ||= notEvaluated(); + assertEq(fn, g); + }; + g(); +} + +// CoalesceAssignExpr with const lexical binding. +{ + const a = true; + a ??= notEvaluated(); + assertEq(a, true); + + const b = null; + assertThrowsInstanceOf(() => { b ??= 0; }, TypeError); + assertEq(b, null); +} + +// CoalesceAssignExpr with function name binding. +{ + let f = function fn() { + fn ??= notEvaluated(); + assertEq(fn, f); + }; + f(); + + let g = function fn() { + "use strict"; + fn ??= notEvaluated(); + assertEq(fn, g); + }; + g(); +} + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/non262/expressions/short-circuit-compound-assignment-deleted-decl-binding.js b/js/src/tests/non262/expressions/short-circuit-compound-assignment-deleted-decl-binding.js new file mode 100644 index 000000000000..256278a93876 --- /dev/null +++ b/js/src/tests/non262/expressions/short-circuit-compound-assignment-deleted-decl-binding.js @@ -0,0 +1,65 @@ +// Test when a declarative binding is deleted. + +// ES2020, 8.1.1.1.5 SetMutableBinding ( N, V, S ) +// +// 1. ... +// 2. If envRec does not have a binding for N, then +// a. ... +// b. Perform envRec.CreateMutableBinding(N, true). +// c. Perform envRec.InitializeBinding(N, V). +// d. Return NormalCompletion(empty). +// 3. ... + +// AndAssignExpr +{ + let a = 0; + + let f = function() { + eval("var a = 1;"); + + a &&= (delete a, 2); + + assertEq(a, 2); + } + + f(); + + assertEq(a, 0); +} + +// OrAssignExpr +{ + let a = 1; + + let f = function() { + eval("var a = 0;"); + + a ||= (delete a, 2); + + assertEq(a, 2); + } + + f(); + + assertEq(a, 1); +} + +// CoalesceAssignExpr +{ + let a = undefined; + + let f = function() { + eval("var a = null;"); + + a ??= (delete a, 2); + + assertEq(a, 2); + } + + f(); + + assertEq(a, undefined); +} + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/non262/expressions/short-circuit-compound-assignment-property-key-evaluation.js b/js/src/tests/non262/expressions/short-circuit-compound-assignment-property-key-evaluation.js new file mode 100644 index 000000000000..2026aa25d194 --- /dev/null +++ b/js/src/tests/non262/expressions/short-circuit-compound-assignment-property-key-evaluation.js @@ -0,0 +1,68 @@ +// Test that property keys are only evaluated once. + +class PropertyKey { + constructor(key) { + this.key = key; + this.count = 0; + } + + toString() { + this.count++; + return this.key; + } + + valueOf() { + throw new Error("unexpected valueOf call"); + } +} + +// AndAssignExpr +{ + let obj = {p: true}; + let pk = new PropertyKey("p"); + + obj[pk] &&= false; + + assertEq(obj.p, false); + assertEq(pk.count, 1); + + obj[pk] &&= true; + + assertEq(obj.p, false); + assertEq(pk.count, 2); +} + +// OrAssignExpr +{ + let obj = {p: false}; + let pk = new PropertyKey("p"); + + obj[pk] ||= true; + + assertEq(obj.p, true); + assertEq(pk.count, 1); + + obj[pk] ||= false; + + assertEq(obj.p, true); + assertEq(pk.count, 2); +} + +// CoalesceAssignExpr +{ + let obj = {p: null}; + let pk = new PropertyKey("p"); + + obj[pk] ??= true; + + assertEq(obj.p, true); + assertEq(pk.count, 1); + + obj[pk] ??= false; + + assertEq(obj.p, true); + assertEq(pk.count, 2); +} + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/non262/expressions/short-circuit-compound-assignment-scope-lookup.js b/js/src/tests/non262/expressions/short-circuit-compound-assignment-scope-lookup.js new file mode 100644 index 000000000000..dc1d7a421d42 --- /dev/null +++ b/js/src/tests/non262/expressions/short-circuit-compound-assignment-scope-lookup.js @@ -0,0 +1,191 @@ +// Test scope lookups are executed in the correct order. + +function createScope() { + let log = []; + let environment = {}; + let scope = new Proxy(environment, new Proxy({ + has(target, property) { + log.push({target, property}); + return Reflect.has(target, property); + }, + get(target, property, receiver) { + log.push({target, property, receiver}); + return Reflect.get(target, property, receiver); + }, + set(target, property, value, receiver) { + log.push({target, property, value, receiver}); + return Reflect.set(target, property, value, receiver); + }, + getOwnPropertyDescriptor(target, property) { + log.push({target, property}); + return Reflect.getOwnPropertyDescriptor(target, property); + }, + defineProperty(target, property, descriptor) { + log.push({target, property, descriptor}); + return Reflect.defineProperty(target, property, descriptor); + }, + }, { + get(target, property, receiver) { + log.push(property); + return Reflect.get(target, property, receiver); + } + })); + + return {log, environment, scope}; +} + +// AndAssignExpr +{ + let {log, environment, scope} = createScope(); + + environment.a = true; + + with (scope) { + a &&= false; + } + assertEq(environment.a, false); + + with (scope) { + a &&= true; + } + assertEq(environment.a, false); + + assertDeepEq(log, [ + // Execution Contexts, 8.3.2 ResolveBinding ( name [ , env ] ) + // Lexical Environments, 8.1.2.1 GetIdentifierReference ( lex, name, strict ) + // Object Environment Records, 8.1.1.2.1 HasBinding ( N ) + "has", {target: environment, property: "a"}, + "get", {target: environment, property: Symbol.unscopables, receiver: scope}, + + // Reference Type, 6.2.4.8 GetValue ( V ) + // Object Environment Records, 8.1.1.2.6 GetBindingValue ( N, S ) + "has", {target: environment, property: "a"}, + "get", {target: environment, property: "a", receiver: scope}, + + // Reference Type, 6.2.4.9 PutValue ( V, W ) + // Object Environment Records, 8.1.1.2.5 SetMutableBinding ( N, V, S ) + "set", {target: environment, property: "a", value: false, receiver: scope}, + + // Ordinary Objects, 9.1.9 [[Set]] ( P, V, Receiver ) + // Ordinary Objects, 9.1.9.1 OrdinarySet ( O, P, V, Receiver ) + // Ordinary Objects, 9.1.9.2 OrdinarySetWithOwnDescriptor ( O, P, V, Receiver, ownDesc ) + "getOwnPropertyDescriptor", {target: environment, property: "a"}, + "defineProperty", {target: environment, property: "a", descriptor: {value: false}}, + + // Execution Contexts, 8.3.2 ResolveBinding ( name [ , env ] ) + // Lexical Environments, 8.1.2.1 GetIdentifierReference ( lex, name, strict ) + // Object Environment Records, 8.1.1.2.1 HasBinding ( N ) + "has", {target: environment, property: "a"}, + "get", {target: environment, property: Symbol.unscopables, receiver: scope}, + + // Reference Type, 6.2.4.8 GetValue ( V ) + // Object Environment Records, 8.1.1.2.6 GetBindingValue ( N, S ) + "has", {target: environment, property: "a"}, + "get", {target: environment, property: "a", receiver: scope}, + ]); +} + +// OrAssignExpr +{ + let {log, environment, scope} = createScope(); + + environment.a = false; + + with (scope) { + a ||= true; + } + assertEq(environment.a, true); + + with (scope) { + a ||= false; + } + assertEq(environment.a, true); + + assertDeepEq(log, [ + // Execution Contexts, 8.3.2 ResolveBinding ( name [ , env ] ) + // Lexical Environments, 8.1.2.1 GetIdentifierReference ( lex, name, strict ) + // Object Environment Records, 8.1.1.2.1 HasBinding ( N ) + "has", {target: environment, property: "a"}, + "get", {target: environment, property: Symbol.unscopables, receiver: scope}, + + // Reference Type, 6.2.4.8 GetValue ( V ) + // Object Environment Records, 8.1.1.2.6 GetBindingValue ( N, S ) + "has", {target: environment, property: "a"}, + "get", {target: environment, property: "a", receiver: scope}, + + // Reference Type, 6.2.4.9 PutValue ( V, W ) + // Object Environment Records, 8.1.1.2.5 SetMutableBinding ( N, V, S ) + "set", {target: environment, property: "a", value: true, receiver: scope}, + + // Ordinary Objects, 9.1.9 [[Set]] ( P, V, Receiver ) + // Ordinary Objects, 9.1.9.1 OrdinarySet ( O, P, V, Receiver ) + // Ordinary Objects, 9.1.9.2 OrdinarySetWithOwnDescriptor ( O, P, V, Receiver, ownDesc ) + "getOwnPropertyDescriptor", {target: environment, property: "a"}, + "defineProperty", {target: environment, property: "a", descriptor: {value: true}}, + + // Execution Contexts, 8.3.2 ResolveBinding ( name [ , env ] ) + // Lexical Environments, 8.1.2.1 GetIdentifierReference ( lex, name, strict ) + // Object Environment Records, 8.1.1.2.1 HasBinding ( N ) + "has", {target: environment, property: "a"}, + "get", {target: environment, property: Symbol.unscopables, receiver: scope}, + + // Reference Type, 6.2.4.8 GetValue ( V ) + // Object Environment Records, 8.1.1.2.6 GetBindingValue ( N, S ) + "has", {target: environment, property: "a"}, + "get", {target: environment, property: "a", receiver: scope}, + ]); +} + +// CoalesceAssignExpr +{ + let {log, environment, scope} = createScope(); + + environment.a = null; + + with (scope) { + a ??= true; + } + assertEq(environment.a, true); + + with (scope) { + a ??= false; + } + assertEq(environment.a, true); + + assertDeepEq(log, [ + // Execution Contexts, 8.3.2 ResolveBinding ( name [ , env ] ) + // Lexical Environments, 8.1.2.1 GetIdentifierReference ( lex, name, strict ) + // Object Environment Records, 8.1.1.2.1 HasBinding ( N ) + "has", {target: environment, property: "a"}, + "get", {target: environment, property: Symbol.unscopables, receiver: scope}, + + // Reference Type, 6.2.4.8 GetValue ( V ) + // Object Environment Records, 8.1.1.2.6 GetBindingValue ( N, S ) + "has", {target: environment, property: "a"}, + "get", {target: environment, property: "a", receiver: scope}, + + // Reference Type, 6.2.4.9 PutValue ( V, W ) + // Object Environment Records, 8.1.1.2.5 SetMutableBinding ( N, V, S ) + "set", {target: environment, property: "a", value: true, receiver: scope}, + + // Ordinary Objects, 9.1.9 [[Set]] ( P, V, Receiver ) + // Ordinary Objects, 9.1.9.1 OrdinarySet ( O, P, V, Receiver ) + // Ordinary Objects, 9.1.9.2 OrdinarySetWithOwnDescriptor ( O, P, V, Receiver, ownDesc ) + "getOwnPropertyDescriptor", {target: environment, property: "a"}, + "defineProperty", {target: environment, property: "a", descriptor: {value: true}}, + + // Execution Contexts, 8.3.2 ResolveBinding ( name [ , env ] ) + // Lexical Environments, 8.1.2.1 GetIdentifierReference ( lex, name, strict ) + // Object Environment Records, 8.1.1.2.1 HasBinding ( N ) + "has", {target: environment, property: "a"}, + "get", {target: environment, property: Symbol.unscopables, receiver: scope}, + + // Reference Type, 6.2.4.8 GetValue ( V ) + // Object Environment Records, 8.1.1.2.6 GetBindingValue ( N, S ) + "has", {target: environment, property: "a"}, + "get", {target: environment, property: "a", receiver: scope}, + ]); +} + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/non262/expressions/short-circuit-compound-assignment-tdz.js b/js/src/tests/non262/expressions/short-circuit-compound-assignment-tdz.js new file mode 100644 index 000000000000..b4fcbe8043e7 --- /dev/null +++ b/js/src/tests/non262/expressions/short-circuit-compound-assignment-tdz.js @@ -0,0 +1,56 @@ +// Test TDZ for short-circuit compound assignments. + +// TDZ for lexical |let| bindings. +{ + assertThrowsInstanceOf(() => { let a = (a &&= 0); }, ReferenceError); + assertThrowsInstanceOf(() => { let a = (a ||= 0); }, ReferenceError); + assertThrowsInstanceOf(() => { let a = (a ??= 0); }, ReferenceError); +} + +// TDZ for lexical |const| bindings. +{ + assertThrowsInstanceOf(() => { const a = (a &&= 0); }, ReferenceError); + assertThrowsInstanceOf(() => { const a = (a ||= 0); }, ReferenceError); + assertThrowsInstanceOf(() => { const a = (a ??= 0); }, ReferenceError); +} + +// TDZ for parameter expressions. +{ + assertThrowsInstanceOf((a = (b &&= 0), b) => {}, ReferenceError); + assertThrowsInstanceOf((a = (b ||= 0), b) => {}, ReferenceError); + assertThrowsInstanceOf((a = (b ??= 0), b) => {}, ReferenceError); +} + +// TDZ for |class| bindings. +{ + assertThrowsInstanceOf(() => { class a extends (a &&= 0) {} }, ReferenceError); + assertThrowsInstanceOf(() => { class a extends (a ||= 0) {} }, ReferenceError); + assertThrowsInstanceOf(() => { class a extends (a ??= 0) {} }, ReferenceError); +} + +// TDZ for lexical |let| bindings with conditional assignment. +{ + assertThrowsInstanceOf(() => { + const False = false; + False &&= b; + b = 2; + let b; + }, ReferenceError); + + assertThrowsInstanceOf(() => { + const True = true; + True ||= b; + b = 2; + let b; + }, ReferenceError); + + assertThrowsInstanceOf(() => { + const NonNull = {}; + NonNull ??= b; + b = 2; + let b; + }, ReferenceError); +} + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/non262/expressions/short-circuit-compound-assignment.js b/js/src/tests/non262/expressions/short-circuit-compound-assignment.js new file mode 100644 index 000000000000..c08c4ddecfb1 --- /dev/null +++ b/js/src/tests/non262/expressions/short-circuit-compound-assignment.js @@ -0,0 +1,265 @@ +const testCasesAnd = []; +const testCasesOr = []; +const testCasesCoalesce = []; + + +// Assignment to a global variable (JSOp::SetGName). +var globalVar; + +function testAnd_GlobalVar(init, value) { + globalVar = init; + return [(globalVar &&= value), globalVar]; +} +testCasesAnd.push(testAnd_GlobalVar); + +function testOr_GlobalVar(init, value) { + globalVar = init; + return [(globalVar ||= value), globalVar]; +} +testCasesOr.push(testOr_GlobalVar); + +function testCoalesce_GlobalVar(init, value) { + globalVar = init; + return [(globalVar ??= value), globalVar]; +} +testCasesCoalesce.push(testCoalesce_GlobalVar); + + +// Assignment to a local variable (JSOp::SetLocal). +function testAnd_LocalVar(init, value) { + let v = init; + return [(v &&= value), v]; +} +testCasesAnd.push(testAnd_LocalVar); + +function testOr_LocalVar(init, value) { + let v = init; + return [(v ||= value), v]; +} +testCasesOr.push(testOr_LocalVar); + +function testCoalesce_LocalVar(init, value) { + let v = init; + return [(v ??= value), v]; +} +testCasesCoalesce.push(testCoalesce_LocalVar); + + +// Assignment to a parameter (JSOp::SetArg). +function testAnd_Arg(init, value) { + function f(v) { + return [(v &&= value), v]; + } + return f(init); +} +testCasesAnd.push(testAnd_Arg); + +function testOr_Arg(init, value) { + function f(v) { + return [(v ||= value), v]; + } + return f(init); +} +testCasesOr.push(testOr_Arg); + +function testCoalesce_Arg(init, value) { + function f(v) { + return [(v ??= value), v]; + } + return f(init); +} +testCasesCoalesce.push(testCoalesce_Arg); + + +// Assignment to a closed over variable (JSOp::SetAliasedVar). +function testAnd_ClosedOver(init, value) { + let v = init; + function f() { + return (v &&= value); + } + return [f(), v]; +} +testCasesAnd.push(testAnd_ClosedOver); + +function testOr_ClosedOver(init, value) { + let v = init; + function f() { + return (v ||= value); + } + return [f(), v]; +} +testCasesOr.push(testOr_ClosedOver); + +function testCoalesce_ClosedOver(init, value) { + let v = init; + function f() { + return (v ??= value); + } + return [f(), v]; +} +testCasesCoalesce.push(testCoalesce_ClosedOver); + + +// Assignment to a dynamic name (JSOp::SetName). +function testAnd_DynamicName(init, value) { + eval("var v = init;"); + return [(v &&= value), v]; +} +testCasesAnd.push(testAnd_DynamicName); + +function testOr_DynamicName(init, value) { + eval("var v = init;"); + return [(v ||= value), v]; +} +testCasesOr.push(testOr_DynamicName); + +function testCoalesce_DynamicName(init, value) { + eval("var v = init;"); + return [(v ??= value), v]; +} +testCasesCoalesce.push(testCoalesce_DynamicName); + + +// Assignment to a property. +function testAnd_Property(init, value) { + let obj = {p: init}; + return [(obj.p &&= value), obj.p]; +} +testCasesAnd.push(testAnd_Property); + +function testOr_Property(init, value) { + let obj = {p: init}; + return [(obj.p ||= value), obj.p]; +} +testCasesOr.push(testOr_Property); + +function testCoalesce_Property(init, value) { + let obj = {p: init}; + return [(obj.p ??= value), obj.p]; +} +testCasesCoalesce.push(testCoalesce_Property); + + +// Assignment to a super property. +function testAnd_SuperProperty(init, value) { + let proto = {p: init}; + let obj = {__proto__: proto, m() { return (super.p &&= value); }}; + return [obj.m(), obj.p]; +} +testCasesAnd.push(testAnd_SuperProperty); + +function testOr_SuperProperty(init, value) { + let proto = {p: init}; + let obj = {__proto__: proto, m() { return (super.p ||= value); }}; + return [obj.m(), obj.p]; +} +testCasesOr.push(testOr_SuperProperty); + +function testCoalesce_SuperProperty(init, value) { + let proto = {p: init}; + let obj = {__proto__: proto, m() { return (super.p ??= value); }}; + return [obj.m(), obj.p]; +} +testCasesCoalesce.push(testCoalesce_SuperProperty); + + +// Assignment to an element. +function testAnd_Element(init, value) { + let p = 123; + let obj = {[p]: init}; + return [(obj[p] &&= value), obj[p]]; +} +testCasesAnd.push(testAnd_Element); + +function testOr_Element(init, value) { + let p = 123; + let obj = {[p]: init}; + return [(obj[p] ||= value), obj[p]]; +} +testCasesOr.push(testOr_Element); + +function testCoalesce_Element(init, value) { + let p = 123; + let obj = {[p]: init}; + return [(obj[p] ??= value), obj[p]]; +} +testCasesCoalesce.push(testCoalesce_Element); + + +// Assignment to a super element. +function testAnd_SuperElement(init, value) { + let p = 123; + let proto = {[p]: init}; + let obj = {__proto__: proto, m() { return (super[p] &&= value); }}; + return [obj.m(), obj[p]]; +} +testCasesAnd.push(testAnd_SuperElement); + +function testOr_SuperElement(init, value) { + let p = 123; + let proto = {[p]: init}; + let obj = {__proto__: proto, m() { return (super[p] ||= value); }}; + return [obj.m(), obj[p]]; +} +testCasesOr.push(testOr_SuperElement); + +function testCoalesce_SuperElement(init, value) { + let p = 123; + let proto = {[p]: init}; + let obj = {__proto__: proto, m() { return (super[p] ??= value); }}; + return [obj.m(), obj[p]]; +} +testCasesCoalesce.push(testCoalesce_SuperElement); + + +// Run the actual tests. + +function runTest(testCases, init, value, expected) { + for (let f of testCases) { + let [result, newValue] = f(init, value); + + assertEq(result, expected); + assertEq(newValue, expected); + } +} + +function testAnd(init, value) { + const expected = init ? value : init; + runTest(testCasesAnd, init, value, expected); +} + +function testOr(init, value) { + const expected = !init ? value : init; + runTest(testCasesOr, init, value, expected); +} + +function testCoalesce(init, value) { + const expected = init === undefined || init === null ? value : init; + runTest(testCasesCoalesce, init, value, expected); +} + + +// Repeat a number of times to ensure JITs can kick in, too. +for (let i = 0; i < 50; ++i) { + for (let thruthy of [true, 123, 123n, "hi", [], Symbol()]) { + testAnd(thruthy, "pass"); + testOr(thruthy, "fail"); + testCoalesce(thruthy, "fail"); + } + + for (let falsy of [false, 0, NaN, 0n, ""]) { + testAnd(falsy, "fail"); + testOr(falsy, "pass"); + testCoalesce(falsy, "fail"); + } + + for (let nullOrUndefined of [null, undefined]) { + testAnd(nullOrUndefined, "fail"); + testOr(nullOrUndefined, "pass"); + testCoalesce(nullOrUndefined, "pass"); + } +} + + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/non262/reflect-parse/expression-short-circuit-compound-assignment.js b/js/src/tests/non262/reflect-parse/expression-short-circuit-compound-assignment.js new file mode 100644 index 000000000000..538b9f31c240 --- /dev/null +++ b/js/src/tests/non262/reflect-parse/expression-short-circuit-compound-assignment.js @@ -0,0 +1,11 @@ +// |reftest| skip-if(!xulRuntime.shell||release_or_beta) + +function test() { + +assertExpr("(x ??= y)", aExpr("??=", ident("x"), ident("y"))); +assertExpr("(x ||= y)", aExpr("||=", ident("x"), ident("y"))); +assertExpr("(x &&= y)", aExpr("&&=", ident("x"), ident("y"))); + +} + +runtest(test); diff --git a/js/src/vm/AsyncFunction.cpp b/js/src/vm/AsyncFunction.cpp index 7328baf699d3..42b06f9addfe 100644 --- a/js/src/vm/AsyncFunction.cpp +++ b/js/src/vm/AsyncFunction.cpp @@ -139,8 +139,8 @@ js::WrapAsyncFunctionWithProto(JSContext* cx, HandleFunction unwrapped, HandleOb if (!wrapped) return nullptr; - if (unwrapped->hasCompileTimeName()) - wrapped->setCompileTimeName(unwrapped->compileTimeName()); + if (unwrapped->hasInferredName()) + wrapped->setInferredName(unwrapped->inferredName()); // Link them to each other to make GetWrappedAsyncFunction and // GetUnwrappedAsyncFunction work. diff --git a/js/src/vm/AsyncIteration.cpp b/js/src/vm/AsyncIteration.cpp index 1174fd2d2f39..879c3e0d1d2a 100644 --- a/js/src/vm/AsyncIteration.cpp +++ b/js/src/vm/AsyncIteration.cpp @@ -84,8 +84,8 @@ js::WrapAsyncGeneratorWithProto(JSContext* cx, HandleFunction unwrapped, HandleO if (!wrapped) return nullptr; - if (unwrapped->hasCompileTimeName()) - wrapped->setCompileTimeName(unwrapped->compileTimeName()); + if (unwrapped->hasInferredName()) + wrapped->setInferredName(unwrapped->inferredName()); // Link them to each other to make GetWrappedAsyncGenerator and // GetUnwrappedAsyncGenerator work. diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index 8fd07b7f4a4d..41ff4e4356db 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -162,6 +162,7 @@ macro(getOwnPropertyNames, getOwnPropertyNames, "getOwnPropertyNames") \ macro(getPrefix, getPrefix, "get ") \ macro(getPropertyDescriptor, getPropertyDescriptor, "getPropertyDescriptor") \ + macro(getPropertySuper, getPropertySuper, "getPropertySuper") \ macro(getPrototypeOf, getPrototypeOf, "getPrototypeOf") \ macro(global, global, "global") \ macro(globalThis, globalThis, "globalThis") \ diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 3254ae3278e3..33c7ad3b381e 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -2921,8 +2921,8 @@ END_CASE(JSOP_GETELEM) CASE(JSOP_GETELEM_SUPER) { - ReservedRooted rval(&rootValue0, REGS.sp[-3]); - ReservedRooted receiver(&rootValue1, REGS.sp[-2]); + ReservedRooted receiver(&rootValue1, REGS.sp[-3]); + ReservedRooted rval(&rootValue0, REGS.sp[-2]); ReservedRooted obj(&rootObject1, ®S.sp[-1].toObject()); MutableHandleValue res = REGS.stackHandleAt(-3); @@ -2964,8 +2964,8 @@ CASE(JSOP_STRICTSETELEM_SUPER) static_assert(JSOP_SETELEM_SUPER_LENGTH == JSOP_STRICTSETELEM_SUPER_LENGTH, "setelem-super and strictsetelem-super must be the same size"); - ReservedRooted index(&rootValue1, REGS.sp[-4]); - ReservedRooted receiver(&rootValue0, REGS.sp[-3]); + ReservedRooted receiver(&rootValue0, REGS.sp[-4]); + ReservedRooted index(&rootValue1, REGS.sp[-3]); ReservedRooted obj(&rootObject1, ®S.sp[-2].toObject()); HandleValue value = REGS.stackHandleAt(-1); @@ -3673,7 +3673,7 @@ CASE(JSOP_SETFUNNAME) FunctionPrefixKind prefixKind = FunctionPrefixKind(GET_UINT8(REGS.pc)); ReservedRooted name(&rootValue0, REGS.sp[-1]); ReservedRooted fun(&rootFunction0, ®S.sp[-2].toObject().as()); - if (!SetFunctionNameIfNoOwnName(cx, fun, name, prefixKind)) + if (!SetFunctionName(cx, fun, name, prefixKind)) goto error; REGS.sp--; @@ -4193,19 +4193,18 @@ END_CASE(JSOP_OBJWITHPROTO) CASE(JSOP_INITHOMEOBJECT) { - unsigned skipOver = GET_UINT8(REGS.pc); - MOZ_ASSERT(REGS.stackDepth() >= skipOver + 2); + MOZ_ASSERT(REGS.stackDepth() >= 2); /* Load the function to be initialized */ - ReservedRooted func(&rootFunction0, ®S.sp[-1].toObject().as()); + ReservedRooted func(&rootFunction0, ®S.sp[-2].toObject().as()); MOZ_ASSERT(func->allowSuperProperty()); /* Load the home object */ - ReservedRooted obj(&rootObject0); - obj = ®S.sp[int(-2 - skipOver)].toObject(); + ReservedRooted obj(&rootObject0, ®S.sp[-1].toObject()); MOZ_ASSERT(obj->is() || obj->is()); func->setExtendedSlot(FunctionExtended::METHOD_HOMEOBJECT_SLOT, ObjectValue(*obj)); + REGS.sp--; } END_CASE(JSOP_INITHOMEOBJECT) diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index dc55b41d14a5..5c1ab925b5b9 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -535,7 +535,7 @@ macro(JSOP_GETELEM, 55, "getelem", NULL, 1, 2, 1, JOF_BYTE |JOF_ELEM|JOF_TYPESET|JOF_LEFTASSOC) \ /* * Pops the top three values on the stack as 'val', 'propval' and 'obj', - * sets 'propval' property of 'obj' as 'val', pushes 'obj' onto the + * sets 'propval' property of 'obj' as 'val', pushes 'val' onto the * stack. * Category: Literals * Type: Object @@ -545,7 +545,7 @@ macro(JSOP_SETELEM, 56, "setelem", NULL, 1, 3, 1, JOF_BYTE |JOF_ELEM|JOF_PROPSET|JOF_DETECTING|JOF_CHECKSLOPPY) \ /* * Pops the top three values on the stack as 'val', 'propval' and 'obj', - * sets 'propval' property of 'obj' as 'val', pushes 'obj' onto the + * sets 'propval' property of 'obj' as 'val', pushes 'val' onto the * stack. Throws a TypeError if the set fails, per strict mode * semantics. * Category: Literals @@ -874,15 +874,15 @@ /* * Initialize the home object for functions with super bindings. * - * This opcode takes the function and the object to be the home object, does - * the set, and leaves both on the stack. + * This opcode takes the function and the object to be the home object, + * does the set, and leaves the function on the stack. + * * Category: Literals * Type: Object - * Operands: uint8_t n - * Stack: homeObject, [...n], fun => homeObject, [...n], fun - */\ - macro(JSOP_INITHOMEOBJECT, 92, "inithomeobject", NULL, 2, 2, 2, JOF_UINT8) \ - \ + * Operands: + * Stack: fun, homeObject => fun + */ \ + macro(JSOP_INITHOMEOBJECT, 92, "inithomeobject", NULL, 1, 2, 1, JOF_BYTE) \ /* * Initialize a named property in an object literal, like '{a: x}'. * @@ -1279,7 +1279,7 @@ * Category: Literals * Type: Object * Operands: - * Stack: propval, receiver, obj => obj[propval] + * Stack: receiver, propval, obj => obj[propval] */ \ macro(JSOP_GETELEM_SUPER, 125, "getelem-super", NULL, 1, 3, 1, JOF_BYTE|JOF_ELEM|JOF_TYPESET|JOF_LEFTASSOC) \ macro(JSOP_UNUSED126, 126, "unused126", NULL, 5, 0, 1, JOF_UINT32) \ @@ -1631,7 +1631,7 @@ * Category: Literals * Type: Object * Operands: - * Stack: propval, receiver, obj, val => val + * Stack: receiver, propval, obj, val => val */ \ macro(JSOP_SETELEM_SUPER, 158, "setelem-super", NULL, 1, 4, 1, JOF_BYTE |JOF_ELEM|JOF_PROPSET|JOF_DETECTING|JOF_CHECKSLOPPY) \ /* @@ -1640,7 +1640,7 @@ * Category: Literals * Type: Object * Operands: - * Stack: propval, receiver, obj, val => val + * Stack: receiver, propval, obj, val => val */ \ macro(JSOP_STRICTSETELEM_SUPER, 159, "strict-setelem-super", NULL, 1, 4, 1, JOF_BYTE |JOF_ELEM|JOF_PROPSET|JOF_DETECTING|JOF_CHECKSTRICT) \ \ diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index 724ce597549a..052bf7ab53ab 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -512,10 +512,10 @@ intrinsic_MakeDefaultConstructor(JSContext* cx, unsigned argc, Value* vp) // Because self-hosting code does not allow top-level lexicals, // class constructors are class expressions in top-level vars. - // Because of this, we give them a guessed atom. Since they + // Because of this, we give them an inferred atom. Since they // will always be cloned, and given an explicit atom, instead // overrule that. - ctor->clearGuessedAtom(); + ctor->clearInferredName(); args.rval().setUndefined(); return true;