From 2aa4a82499d4becd2284cdb482213d541b8804dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 16:29:10 +0200 Subject: Adding upstream version 86.0.1. Signed-off-by: Daniel Baumann --- js/src/frontend/AbstractScopePtr.cpp | 66 + js/src/frontend/AbstractScopePtr.h | 143 + js/src/frontend/AsyncEmitter.cpp | 198 + js/src/frontend/AsyncEmitter.h | 163 + js/src/frontend/BCEParserHandle.h | 30 + js/src/frontend/BytecodeCompilation.h | 130 + js/src/frontend/BytecodeCompiler.cpp | 1231 +++ js/src/frontend/BytecodeCompiler.h | 224 + js/src/frontend/BytecodeControlStructures.cpp | 111 + js/src/frontend/BytecodeControlStructures.h | 164 + js/src/frontend/BytecodeEmitter.cpp | 11322 ++++++++++++++++++++++ js/src/frontend/BytecodeEmitter.h | 920 ++ js/src/frontend/BytecodeOffset.h | 153 + js/src/frontend/BytecodeSection.cpp | 197 + js/src/frontend/BytecodeSection.h | 418 + js/src/frontend/CForEmitter.cpp | 179 + js/src/frontend/CForEmitter.h | 176 + js/src/frontend/CallOrNewEmitter.cpp | 277 + js/src/frontend/CallOrNewEmitter.h | 318 + js/src/frontend/CompilationInfo.h | 706 ++ js/src/frontend/DefaultEmitter.cpp | 76 + js/src/frontend/DefaultEmitter.h | 65 + js/src/frontend/DestructuringFlavor.h | 24 + js/src/frontend/DoWhileEmitter.cpp | 76 + js/src/frontend/DoWhileEmitter.h | 83 + js/src/frontend/EitherParser.h | 167 + js/src/frontend/ElemOpEmitter.cpp | 304 + js/src/frontend/ElemOpEmitter.h | 279 + js/src/frontend/EmitterScope.cpp | 1119 +++ js/src/frontend/EmitterScope.h | 171 + js/src/frontend/ErrorReporter.h | 352 + js/src/frontend/ExpressionStatementEmitter.cpp | 57 + js/src/frontend/ExpressionStatementEmitter.h | 84 + js/src/frontend/FoldConstants.cpp | 1571 +++ js/src/frontend/FoldConstants.h | 49 + js/src/frontend/ForInEmitter.cpp | 158 + js/src/frontend/ForInEmitter.h | 119 + js/src/frontend/ForOfEmitter.cpp | 220 + js/src/frontend/ForOfEmitter.h | 117 + js/src/frontend/ForOfLoopControl.cpp | 223 + js/src/frontend/ForOfLoopControl.h | 98 + js/src/frontend/Frontend2.cpp | 752 ++ js/src/frontend/Frontend2.h | 67 + js/src/frontend/FullParseHandler.h | 1136 +++ js/src/frontend/FunctionEmitter.cpp | 954 ++ js/src/frontend/FunctionEmitter.h | 437 + js/src/frontend/FunctionSyntaxKind.h | 37 + js/src/frontend/GenerateReservedWords.py | 229 + js/src/frontend/IfEmitter.cpp | 285 + js/src/frontend/IfEmitter.h | 312 + js/src/frontend/IteratorKind.h | 16 + js/src/frontend/JumpList.cpp | 48 + js/src/frontend/JumpList.h | 81 + js/src/frontend/LabelEmitter.cpp | 40 + js/src/frontend/LabelEmitter.h | 67 + js/src/frontend/LexicalScopeEmitter.cpp | 59 + js/src/frontend/LexicalScopeEmitter.h | 96 + js/src/frontend/ModuleSharedContext.h | 44 + js/src/frontend/NameAnalysisTypes.h | 377 + js/src/frontend/NameCollections.h | 378 + js/src/frontend/NameFunctions.cpp | 485 + js/src/frontend/NameFunctions.h | 26 + js/src/frontend/NameOpEmitter.cpp | 403 + js/src/frontend/NameOpEmitter.h | 181 + js/src/frontend/ObjLiteral.cpp | 246 + js/src/frontend/ObjLiteral.h | 562 ++ js/src/frontend/ObjectEmitter.cpp | 913 ++ js/src/frontend/ObjectEmitter.h | 815 ++ js/src/frontend/OptionalEmitter.cpp | 163 + js/src/frontend/OptionalEmitter.h | 219 + js/src/frontend/ParseContext-inl.h | 180 + js/src/frontend/ParseContext.cpp | 720 ++ js/src/frontend/ParseContext.h | 593 ++ js/src/frontend/ParseNode.cpp | 442 + js/src/frontend/ParseNode.h | 2336 +++++ js/src/frontend/ParseNodeVerify.cpp | 50 + js/src/frontend/ParseNodeVerify.h | 42 + js/src/frontend/ParseNodeVisitor.h | 134 + js/src/frontend/Parser.cpp | 11654 +++++++++++++++++++++++ js/src/frontend/Parser.h | 1878 ++++ js/src/frontend/ParserAtom.cpp | 864 ++ js/src/frontend/ParserAtom.h | 810 ++ js/src/frontend/PropOpEmitter.cpp | 243 + js/src/frontend/PropOpEmitter.h | 254 + js/src/frontend/ReservedWords.h | 81 + js/src/frontend/ScriptIndex.h | 22 + js/src/frontend/SharedContext-inl.h | 28 + js/src/frontend/SharedContext.cpp | 492 + js/src/frontend/SharedContext.h | 712 ++ js/src/frontend/SourceNotes.cpp | 13 + js/src/frontend/SourceNotes.h | 428 + js/src/frontend/Stencil.cpp | 2261 +++++ js/src/frontend/Stencil.h | 886 ++ js/src/frontend/StencilXdr.cpp | 661 ++ js/src/frontend/StencilXdr.h | 65 + js/src/frontend/SwitchEmitter.cpp | 410 + js/src/frontend/SwitchEmitter.h | 466 + js/src/frontend/SyntaxParseHandler.h | 733 ++ js/src/frontend/TDZCheckCache.cpp | 75 + js/src/frontend/TDZCheckCache.h | 58 + js/src/frontend/Token.h | 226 + js/src/frontend/TokenKind.h | 325 + js/src/frontend/TokenStream.cpp | 3850 ++++++++ js/src/frontend/TokenStream.h | 2976 ++++++ js/src/frontend/TryEmitter.cpp | 291 + js/src/frontend/TryEmitter.h | 218 + js/src/frontend/TypedIndex.h | 41 + js/src/frontend/UsedNameTracker.h | 244 + js/src/frontend/ValueUsage.h | 28 + js/src/frontend/WhileEmitter.cpp | 90 + js/src/frontend/WhileEmitter.h | 93 + js/src/frontend/align_stack_comment.py | 109 + js/src/frontend/moz.build | 83 + js/src/frontend/smoosh/Cargo.toml | 23 + js/src/frontend/smoosh/build.rs | 30 + js/src/frontend/smoosh/cbindgen.toml | 19 + js/src/frontend/smoosh/moz.build | 15 + js/src/frontend/smoosh/src/lib.rs | 769 ++ 118 files changed, 68987 insertions(+) create mode 100644 js/src/frontend/AbstractScopePtr.cpp create mode 100644 js/src/frontend/AbstractScopePtr.h create mode 100644 js/src/frontend/AsyncEmitter.cpp create mode 100644 js/src/frontend/AsyncEmitter.h create mode 100644 js/src/frontend/BCEParserHandle.h create mode 100644 js/src/frontend/BytecodeCompilation.h create mode 100644 js/src/frontend/BytecodeCompiler.cpp create mode 100644 js/src/frontend/BytecodeCompiler.h create mode 100644 js/src/frontend/BytecodeControlStructures.cpp create mode 100644 js/src/frontend/BytecodeControlStructures.h create mode 100644 js/src/frontend/BytecodeEmitter.cpp create mode 100644 js/src/frontend/BytecodeEmitter.h create mode 100644 js/src/frontend/BytecodeOffset.h create mode 100644 js/src/frontend/BytecodeSection.cpp create mode 100644 js/src/frontend/BytecodeSection.h create mode 100644 js/src/frontend/CForEmitter.cpp create mode 100644 js/src/frontend/CForEmitter.h create mode 100644 js/src/frontend/CallOrNewEmitter.cpp create mode 100644 js/src/frontend/CallOrNewEmitter.h create mode 100644 js/src/frontend/CompilationInfo.h create mode 100644 js/src/frontend/DefaultEmitter.cpp create mode 100644 js/src/frontend/DefaultEmitter.h create mode 100644 js/src/frontend/DestructuringFlavor.h create mode 100644 js/src/frontend/DoWhileEmitter.cpp create mode 100644 js/src/frontend/DoWhileEmitter.h create mode 100644 js/src/frontend/EitherParser.h create mode 100644 js/src/frontend/ElemOpEmitter.cpp create mode 100644 js/src/frontend/ElemOpEmitter.h create mode 100644 js/src/frontend/EmitterScope.cpp create mode 100644 js/src/frontend/EmitterScope.h create mode 100644 js/src/frontend/ErrorReporter.h create mode 100644 js/src/frontend/ExpressionStatementEmitter.cpp create mode 100644 js/src/frontend/ExpressionStatementEmitter.h create mode 100644 js/src/frontend/FoldConstants.cpp create mode 100644 js/src/frontend/FoldConstants.h create mode 100644 js/src/frontend/ForInEmitter.cpp create mode 100644 js/src/frontend/ForInEmitter.h create mode 100644 js/src/frontend/ForOfEmitter.cpp create mode 100644 js/src/frontend/ForOfEmitter.h create mode 100644 js/src/frontend/ForOfLoopControl.cpp create mode 100644 js/src/frontend/ForOfLoopControl.h create mode 100644 js/src/frontend/Frontend2.cpp create mode 100644 js/src/frontend/Frontend2.h create mode 100644 js/src/frontend/FullParseHandler.h create mode 100644 js/src/frontend/FunctionEmitter.cpp create mode 100644 js/src/frontend/FunctionEmitter.h create mode 100644 js/src/frontend/FunctionSyntaxKind.h create mode 100644 js/src/frontend/GenerateReservedWords.py create mode 100644 js/src/frontend/IfEmitter.cpp create mode 100644 js/src/frontend/IfEmitter.h create mode 100644 js/src/frontend/IteratorKind.h create mode 100644 js/src/frontend/JumpList.cpp create mode 100644 js/src/frontend/JumpList.h create mode 100644 js/src/frontend/LabelEmitter.cpp create mode 100644 js/src/frontend/LabelEmitter.h create mode 100644 js/src/frontend/LexicalScopeEmitter.cpp create mode 100644 js/src/frontend/LexicalScopeEmitter.h create mode 100644 js/src/frontend/ModuleSharedContext.h create mode 100644 js/src/frontend/NameAnalysisTypes.h create mode 100644 js/src/frontend/NameCollections.h create mode 100644 js/src/frontend/NameFunctions.cpp create mode 100644 js/src/frontend/NameFunctions.h create mode 100644 js/src/frontend/NameOpEmitter.cpp create mode 100644 js/src/frontend/NameOpEmitter.h create mode 100644 js/src/frontend/ObjLiteral.cpp create mode 100644 js/src/frontend/ObjLiteral.h create mode 100644 js/src/frontend/ObjectEmitter.cpp create mode 100644 js/src/frontend/ObjectEmitter.h create mode 100644 js/src/frontend/OptionalEmitter.cpp create mode 100644 js/src/frontend/OptionalEmitter.h create mode 100644 js/src/frontend/ParseContext-inl.h create mode 100644 js/src/frontend/ParseContext.cpp create mode 100644 js/src/frontend/ParseContext.h create mode 100644 js/src/frontend/ParseNode.cpp create mode 100644 js/src/frontend/ParseNode.h create mode 100644 js/src/frontend/ParseNodeVerify.cpp create mode 100644 js/src/frontend/ParseNodeVerify.h create mode 100644 js/src/frontend/ParseNodeVisitor.h create mode 100644 js/src/frontend/Parser.cpp create mode 100644 js/src/frontend/Parser.h create mode 100644 js/src/frontend/ParserAtom.cpp create mode 100644 js/src/frontend/ParserAtom.h create mode 100644 js/src/frontend/PropOpEmitter.cpp create mode 100644 js/src/frontend/PropOpEmitter.h create mode 100644 js/src/frontend/ReservedWords.h create mode 100644 js/src/frontend/ScriptIndex.h create mode 100644 js/src/frontend/SharedContext-inl.h create mode 100644 js/src/frontend/SharedContext.cpp create mode 100644 js/src/frontend/SharedContext.h create mode 100644 js/src/frontend/SourceNotes.cpp create mode 100644 js/src/frontend/SourceNotes.h create mode 100644 js/src/frontend/Stencil.cpp create mode 100644 js/src/frontend/Stencil.h create mode 100644 js/src/frontend/StencilXdr.cpp create mode 100644 js/src/frontend/StencilXdr.h create mode 100644 js/src/frontend/SwitchEmitter.cpp create mode 100644 js/src/frontend/SwitchEmitter.h create mode 100644 js/src/frontend/SyntaxParseHandler.h create mode 100644 js/src/frontend/TDZCheckCache.cpp create mode 100644 js/src/frontend/TDZCheckCache.h create mode 100644 js/src/frontend/Token.h create mode 100644 js/src/frontend/TokenKind.h create mode 100644 js/src/frontend/TokenStream.cpp create mode 100644 js/src/frontend/TokenStream.h create mode 100644 js/src/frontend/TryEmitter.cpp create mode 100644 js/src/frontend/TryEmitter.h create mode 100644 js/src/frontend/TypedIndex.h create mode 100644 js/src/frontend/UsedNameTracker.h create mode 100644 js/src/frontend/ValueUsage.h create mode 100644 js/src/frontend/WhileEmitter.cpp create mode 100644 js/src/frontend/WhileEmitter.h create mode 100755 js/src/frontend/align_stack_comment.py create mode 100644 js/src/frontend/moz.build create mode 100644 js/src/frontend/smoosh/Cargo.toml create mode 100644 js/src/frontend/smoosh/build.rs create mode 100644 js/src/frontend/smoosh/cbindgen.toml create mode 100644 js/src/frontend/smoosh/moz.build create mode 100644 js/src/frontend/smoosh/src/lib.rs (limited to 'js/src/frontend') diff --git a/js/src/frontend/AbstractScopePtr.cpp b/js/src/frontend/AbstractScopePtr.cpp new file mode 100644 index 0000000000..5d147bfb75 --- /dev/null +++ b/js/src/frontend/AbstractScopePtr.cpp @@ -0,0 +1,66 @@ +/* -*- 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/AbstractScopePtr.h" + +#include "mozilla/Maybe.h" + +#include "frontend/CompilationInfo.h" // CompilationState +#include "frontend/Stencil.h" +#include "js/GCPolicyAPI.h" +#include "js/GCVariant.h" + +using namespace js; +using namespace js::frontend; + +ScopeStencil& AbstractScopePtr::scopeData() const { + const Deferred& data = scope_.as(); + return data.compilationState.scopeData[data.index]; +} + +CompilationState& AbstractScopePtr::compilationState() const { + const Deferred& data = scope_.as(); + return data.compilationState; +} + +ScopeKind AbstractScopePtr::kind() const { + MOZ_ASSERT(!isNullptr()); + if (isScopeStencil()) { + return scopeData().kind(); + } + return scope()->kind(); +} + +AbstractScopePtr AbstractScopePtr::enclosing() const { + MOZ_ASSERT(!isNullptr()); + if (isScopeStencil()) { + return scopeData().enclosing(compilationState()); + } + return AbstractScopePtr(scope()->enclosing()); +} + +bool AbstractScopePtr::hasEnvironment() const { + MOZ_ASSERT(!isNullptr()); + if (isScopeStencil()) { + return scopeData().hasEnvironment(); + } + return scope()->hasEnvironment(); +} + +bool AbstractScopePtr::isArrow() const { + // nullptr will also fail the below assert, so effectively also checking + // !isNullptr() + MOZ_ASSERT(is()); + if (isScopeStencil()) { + return scopeData().isArrow(); + } + MOZ_ASSERT(scope()->as().canonicalFunction()); + return scope()->as().canonicalFunction()->isArrow(); +} + +void AbstractScopePtr::trace(JSTracer* trc) { + JS::GCPolicy::trace(trc, &scope_, "AbstractScopePtr"); +} diff --git a/js/src/frontend/AbstractScopePtr.h b/js/src/frontend/AbstractScopePtr.h new file mode 100644 index 0000000000..276470d5f3 --- /dev/null +++ b/js/src/frontend/AbstractScopePtr.h @@ -0,0 +1,143 @@ +/* -*- 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_AbstractScopePtr_h +#define frontend_AbstractScopePtr_h + +#include "mozilla/Maybe.h" +#include "mozilla/Variant.h" + +#include "frontend/TypedIndex.h" +#include "gc/Barrier.h" +#include "gc/Rooting.h" +#include "gc/Tracer.h" +#include "vm/Scope.h" +#include "vm/ScopeKind.h" // For ScopeKind + +namespace js { +class Scope; +class GlobalScope; +class EvalScope; +struct MemberInitializers; +class GCMarker; + +namespace frontend { +struct CompilationState; +struct CompilationGCOutput; +class ScopeStencil; +} // namespace frontend + +using ScopeIndex = frontend::TypedIndex; +using HeapPtrScope = HeapPtr; + +// An interface class to support Scope queries in the frontend without requiring +// a GC Allocated scope to necessarily exist. +// +// This abstracts Scope* and a ScopeStencil type used within the frontend before +// the Scope is allocated. +// +// Because a AbstractScopePtr may hold onto a Scope, it must be rooted if a GC +// may occur to ensure that the scope is traced. +class AbstractScopePtr { + public: + // Used to hold index and the compilationState together to avoid having a + // potentially nullable compilationState. + struct Deferred { + ScopeIndex index; + frontend::CompilationState& compilationState; + }; + + // To make writing code and managing invariants easier, we require that + // any nullptr scopes be stored on the HeapPtrScope arm of the variant. + using ScopeType = mozilla::Variant; + + private: + ScopeType scope_ = ScopeType(HeapPtrScope()); + + Scope* scope() const { return scope_.as(); } + + public: + friend class js::Scope; + + AbstractScopePtr() = default; + + explicit AbstractScopePtr(Scope* scope) : scope_(HeapPtrScope(scope)) {} + + AbstractScopePtr(frontend::CompilationState& compilationState, + ScopeIndex scope) + : scope_(Deferred{scope, compilationState}) {} + + bool isNullptr() const { + if (isScopeStencil()) { + return false; + } + return scope_.as() == nullptr; + } + + // Return true if this AbstractScopePtr represents a Scope, either existant + // or to be reified. This indicates that queries can be executed on this + // scope data. Returning false is the equivalent to a nullptr, and usually + // indicates the end of the scope chain. + explicit operator bool() const { return !isNullptr(); } + + bool isScopeStencil() const { return scope_.is(); } + + // Note: this handle is rooted in the CompilationState. + frontend::ScopeStencil& scopeData() const; + frontend::CompilationState& compilationState() const; + + // This allows us to check whether or not this provider wraps + // or otherwise would reify to a particular scope type. + template + bool is() const { + static_assert(std::is_base_of_v, + "Trying to ask about non-Scope type"); + if (isNullptr()) { + return false; + } + return kind() == T::classScopeKind_; + } + + ScopeKind kind() const; + AbstractScopePtr enclosing() const; + bool hasEnvironment() const; + // Valid iff is + bool isArrow() const; + + bool hasOnChain(ScopeKind kind) const { + for (AbstractScopePtr it = *this; it; it = it.enclosing()) { + if (it.kind() == kind) { + return true; + } + } + return false; + } + + void trace(JSTracer* trc); +}; + +// Specializations of AbstractScopePtr::is +template <> +inline bool AbstractScopePtr::is() const { + return !isNullptr() && + (kind() == ScopeKind::Global || kind() == ScopeKind::NonSyntactic); +} + +template <> +inline bool AbstractScopePtr::is() const { + return !isNullptr() && + (kind() == ScopeKind::Eval || kind() == ScopeKind::StrictEval); +} + +} // namespace js + +namespace JS { +template <> +struct GCPolicy + : JS::IgnoreGCPolicy {}; +} // namespace JS + +#endif // frontend_AbstractScopePtr_h diff --git a/js/src/frontend/AsyncEmitter.cpp b/js/src/frontend/AsyncEmitter.cpp new file mode 100644 index 0000000000..baa9d87f20 --- /dev/null +++ b/js/src/frontend/AsyncEmitter.cpp @@ -0,0 +1,198 @@ +/* -*- 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/AsyncEmitter.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter +#include "frontend/NameOpEmitter.h" // NameOpEmitter +#include "vm/AsyncFunctionResolveKind.h" // AsyncFunctionResolveKind +#include "vm/Opcodes.h" // JSOp + +using namespace js; +using namespace js::frontend; + +bool AsyncEmitter::prepareForParamsWithExpression() { + MOZ_ASSERT(state_ == State::Start); +#ifdef DEBUG + state_ = State::Parameters; +#endif + + rejectTryCatch_.emplace(bce_, TryEmitter::Kind::TryCatch, + TryEmitter::ControlKind::NonSyntactic); + return rejectTryCatch_->emitTry(); +} + +bool AsyncEmitter::prepareForParamsWithoutExpression() { + MOZ_ASSERT(state_ == State::Start); +#ifdef DEBUG + state_ = State::Parameters; +#endif + return true; +} + +bool AsyncEmitter::emitParamsEpilogue() { + MOZ_ASSERT(state_ == State::Parameters); + + if (rejectTryCatch_) { + // If we get here, we need to reset the TryEmitter. Parameters can't reuse + // the reject try-catch block from the function body, because the body + // may have pushed an additional var-environment. This messes up scope + // resolution for the |.generator| variable, because we'd need different + // hops to reach |.generator| depending on whether the error was thrown + // from the parameters or the function body. + if (!emitRejectCatch()) { + return false; + } + } + +#ifdef DEBUG + state_ = State::PostParams; +#endif + return true; +} + +bool AsyncEmitter::prepareForModule() { + // Unlike functions, modules do not have params that we need to worry about. + // Instead, this code is for setting up the required generator that will be + // used for top level await. Before we can start using top-level await in + // modules, we need to emit a + // |.generator| which we can use to pause and resume execution. + MOZ_ASSERT(state_ == State::Start); + MOZ_ASSERT( + bce_->lookupName(bce_->cx->parserNames().dotGenerator).hasKnownSlot()); + + NameOpEmitter noe(bce_, bce_->cx->parserNames().dotGenerator, + NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + // [stack] + return false; + } + if (!bce_->emit1(JSOp::Generator)) { + // [stack] GEN + return false; + } + if (!noe.emitAssignment()) { + // [stack] GEN + return false; + } + if (!bce_->emit1(JSOp::Pop)) { + // [stack] + return false; + } + +#ifdef DEBUG + state_ = State::ModulePrologue; +#endif + + return true; +} + +bool AsyncEmitter::prepareForBody() { + MOZ_ASSERT(state_ == State::PostParams || state_ == State::ModulePrologue); + + rejectTryCatch_.emplace(bce_, TryEmitter::Kind::TryCatch, + TryEmitter::ControlKind::NonSyntactic); +#ifdef DEBUG + state_ = State::Body; +#endif + return rejectTryCatch_->emitTry(); +} + +bool AsyncEmitter::emitEnd() { +#ifdef DEBUG + MOZ_ASSERT(state_ == State::Body); +#endif + + if (!emitFinalYield()) { + return false; + } + + if (!emitRejectCatch()) { + return false; + } + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} + +bool AsyncEmitter::emitFinalYield() { + if (!bce_->emit1(JSOp::Undefined)) { + // [stack] UNDEF + return false; + } + + if (!bce_->emitGetDotGeneratorInInnermostScope()) { + // [stack] UNDEF GEN + return false; + } + + if (!bce_->emit2(JSOp::AsyncResolve, + uint8_t(AsyncFunctionResolveKind::Fulfill))) { + // [stack] PROMISE + return false; + } + + if (!bce_->emit1(JSOp::SetRval)) { + // [stack] + return false; + } + + if (!bce_->emitGetDotGeneratorInInnermostScope()) { + // [stack] GEN + return false; + } + + if (!bce_->emitYieldOp(JSOp::FinalYieldRval)) { + // [stack] + return false; + } + + return true; +} + +bool AsyncEmitter::emitRejectCatch() { + if (!rejectTryCatch_->emitCatch()) { + // [stack] EXC + return false; + } + + if (!bce_->emitGetDotGeneratorInInnermostScope()) { + // [stack] EXC GEN + return false; + } + + if (!bce_->emit2(JSOp::AsyncResolve, + uint8_t(AsyncFunctionResolveKind::Reject))) { + // [stack] PROMISE + return false; + } + + if (!bce_->emit1(JSOp::SetRval)) { + // [stack] + return false; + } + + if (!bce_->emitGetDotGeneratorInInnermostScope()) { + // [stack] GEN + return false; + } + + if (!bce_->emitYieldOp(JSOp::FinalYieldRval)) { + // [stack] + return false; + } + + if (!rejectTryCatch_->emitEnd()) { + return false; + } + + rejectTryCatch_.reset(); + return true; +} diff --git a/js/src/frontend/AsyncEmitter.h b/js/src/frontend/AsyncEmitter.h new file mode 100644 index 0000000000..7ed945351c --- /dev/null +++ b/js/src/frontend/AsyncEmitter.h @@ -0,0 +1,163 @@ +/* -*- 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_AsyncEmitter_h +#define frontend_AsyncEmitter_h + +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS, MOZ_MUST_USE + +#include "frontend/TryEmitter.h" // TryEmitter + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting Bytecode associated with the AsyncFunctionGenerator. +// +// Usage: +// +// For an async function, the params have to be handled separately, +// because the body may have pushed an additional var environment, changing +// the number of hops required to reach the |.generator| variable. In order +// to handle this, we can't reuse the same TryCatch emitter. +// +// Simple case - For a function without expression parameters: +// `async function f() {}`, +// AsyncEmitter ae(this); +// +// ae.prepareForParamsWithoutExpression(); +// // Emit Params. +// ... +// ae.paramsEpilogue(); // We need to emit the epilogue before the extra +// VarScope emitExtraBodyVarScope(); +// +// // Emit new scope +// ae.prepareForBody(); +// +// // Emit body of the Function. +// +// ae.emitEnd(); +// +// Complex case - For a function with expression parameters: +// `async function f() {}`, +// AsyncEmitter ae(this); +// +// ae.prepareForParamsWithExpression(); +// +// // Emit Params. +// ... +// ae.paramsEpilogue(); // We need to emit the epilogue before the extra +// // VarScope +// emitExtraBodyVarScope(); +// +// // Emit new scope +// ae.prepareForBody(); +// +// // Emit body of the Function. +// ... +// ae.emitEnd(); +// +// +// Async Module case - For a module with `await` in the top level: +// AsyncEmitter ae(this); +// ae.prepareForModule(); // prepareForModule is used to setup the generator +// // for the async module. +// switchToMain(); +// ... +// +// // Emit new scope +// ae.prepareForBody(); +// +// // Emit body of the Script. +// +// ae.emitEnd(); +// + +class MOZ_STACK_CLASS AsyncEmitter { + private: + BytecodeEmitter* bce_; + + // try-catch block for async function parameter and body. + mozilla::Maybe rejectTryCatch_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ + // | Start |-+ + // +-------+ | + // | + // +----------+ + // | + // | [Parameters with Expression] + // | prepareForParamsWithExpression +------------+ + // +-------------------------------------| Parameters |-->+ + // | +------------+ | + // | | + // | [Parameters Without Expression] | + // | prepareForParamsWithoutExpression +------------+ | + // +-------------------------------------| Parameters |-->+ + // | +------------+ | + // | [Modules] | + // | prepareForModule +----------------+ | + // +-------------------->| ModulePrologue |--+ | + // +----------------+ | | + // | | + // | | + // +-----------------------------------------+ | + // | | + // | | + // V +------------+ paramsEpilogue | + // +<--------------------| PostParams |<------------------+ + // | +------------+ + // | + // | [Script body] + // | prepareForBody +---------+ + // +-------------------->| Body |--------+ + // +---------+ | + // +----------------------------------------+ + // | + // | emitEnd +-----+ + // +--------------------->| End | + // +-----+ + + enum class State { + // The initial state. + Start, + + Parameters, + + ModulePrologue, + + PostParams, + + Body, + + End, + }; + + State state_ = State::Start; +#endif + + MOZ_MUST_USE bool emitRejectCatch(); + MOZ_MUST_USE bool emitFinalYield(); + + public: + explicit AsyncEmitter(BytecodeEmitter* bce) : bce_(bce){}; + + MOZ_MUST_USE bool prepareForParamsWithoutExpression(); + MOZ_MUST_USE bool prepareForParamsWithExpression(); + MOZ_MUST_USE bool prepareForModule(); + MOZ_MUST_USE bool emitParamsEpilogue(); + MOZ_MUST_USE bool prepareForBody(); + MOZ_MUST_USE bool emitEnd(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_AsyncEmitter_h */ diff --git a/js/src/frontend/BCEParserHandle.h b/js/src/frontend/BCEParserHandle.h new file mode 100644 index 0000000000..4abb5a6f66 --- /dev/null +++ b/js/src/frontend/BCEParserHandle.h @@ -0,0 +1,30 @@ +/* -*- 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_BCEParserHandle_h +#define frontend_BCEParserHandle_h + +#include "frontend/ErrorReporter.h" +#include "frontend/FullParseHandler.h" +#include "frontend/Parser.h" +#include "frontend/ParserAtom.h" + +namespace js { +namespace frontend { + +struct BCEParserHandle { + virtual ErrorReporter& errorReporter() = 0; + virtual const ErrorReporter& errorReporter() const = 0; + + virtual const JS::ReadOnlyCompileOptions& options() const = 0; + + virtual FullParseHandler& astGenerator() = 0; +}; + +} // namespace frontend +} // namespace js + +#endif // frontend_BCEParserHandle_h diff --git a/js/src/frontend/BytecodeCompilation.h b/js/src/frontend/BytecodeCompilation.h new file mode 100644 index 0000000000..25737a559f --- /dev/null +++ b/js/src/frontend/BytecodeCompilation.h @@ -0,0 +1,130 @@ +/* -*- 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_BytecodeCompilation_h +#define frontend_BytecodeCompilation_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Attributes.h" // MOZ_MUST_USE, MOZ_STACK_CLASS +#include "mozilla/Maybe.h" // mozilla::Maybe, mozilla::Nothing +#include "mozilla/Utf8.h" // mozilla::Utf8Unit + +#include // size_t +#include // uint32_t + +#include "jstypes.h" // JS_PUBLIC_API + +#include "frontend/CompilationInfo.h" // CompilationStencil, CompilationStencilSet, CompilationGCOutput +#include "frontend/ParseContext.h" // js::frontend::UsedNameTracker +#include "frontend/SharedContext.h" // js::frontend::Directives, js::frontend::{,Eval,Global}SharedContext +#include "js/CompileOptions.h" // JS::ReadOnlyCompileOptions +#include "js/RootingAPI.h" // JS::{,Mutable}Handle, JS::Rooted +#include "js/SourceText.h" // JS::SourceText +#include "js/UniquePtr.h" // js::UniquePtr +#include "vm/JSScript.h" // js::{FunctionAsync,Generator}Kind, js::BaseScript, JSScript, js::ScriptSource, js::ScriptSourceObject +#include "vm/Scope.h" // js::ScopeKind + +class JS_PUBLIC_API JSFunction; +class JS_PUBLIC_API JSObject; + +class JSObject; + +namespace js { + +class Scope; + +namespace frontend { + +struct BytecodeEmitter; +class EitherParser; + +template +class SourceAwareCompiler; +template +class ScriptCompiler; +template +class ModuleCompiler; +template +class StandaloneFunctionCompiler; + +extern bool CompileGlobalScriptToStencil(JSContext* cx, + CompilationStencil& stencil, + JS::SourceText& srcBuf, + ScopeKind scopeKind); + +extern bool CompileGlobalScriptToStencil( + JSContext* cx, CompilationStencil& stencil, + JS::SourceText& srcBuf, ScopeKind scopeKind); + +extern UniquePtr CompileGlobalScriptToStencil( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, ScopeKind scopeKind); + +extern UniquePtr CompileGlobalScriptToStencil( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, ScopeKind scopeKind); + +// Perform some operation to reduce the time taken by instantiation. +// +// Part of InstantiateStencils can be done by calling PrepareForInstantiate. +// PrepareForInstantiate is GC-free operation that can be performed +// off-main-thread without parse global. +extern bool PrepareForInstantiate(JSContext* cx, CompilationStencil& stencil, + CompilationGCOutput& gcOutput); +extern bool PrepareForInstantiate( + JSContext* cx, CompilationStencilSet& stencilSet, + CompilationGCOutput& gcOutput, + CompilationGCOutput& gcOutputForDelazification); + +extern bool InstantiateStencils(JSContext* cx, CompilationStencil& stencil, + CompilationGCOutput& gcOutput); + +extern bool InstantiateStencils(JSContext* cx, + CompilationStencilSet& stencilSet, + CompilationGCOutput& gcOutput, + CompilationGCOutput& gcOutputForDelazification); + +extern JSScript* CompileGlobalScript(JSContext* cx, + const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, + ScopeKind scopeKind); + +extern JSScript* CompileGlobalScript(JSContext* cx, + const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, + ScopeKind scopeKind); + +extern JSScript* CompileEvalScript(JSContext* cx, + const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, + JS::Handle enclosingScope, + JS::Handle enclosingEnv); + +extern void FillCompileOptionsForLazyFunction(JS::CompileOptions& options, + Handle lazy); + +extern MOZ_MUST_USE bool CompileLazyFunctionToStencil( + JSContext* cx, CompilationStencil& stencil, JS::Handle lazy, + const char16_t* units, size_t length); + +extern MOZ_MUST_USE bool CompileLazyFunctionToStencil( + JSContext* cx, CompilationStencil& stencil, JS::Handle lazy, + const mozilla::Utf8Unit* units, size_t length); + +extern bool InstantiateStencilsForDelazify(JSContext* cx, + CompilationStencil& stencil); + +// Certain compile options will disable the syntax parser entirely. +inline bool CanLazilyParse(const JS::ReadOnlyCompileOptions& options) { + return !options.discardSource && !options.sourceIsLazy && + !options.forceFullParse(); +} + +} // namespace frontend + +} // namespace js + +#endif // frontend_BytecodeCompilation_h diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp new file mode 100644 index 0000000000..a5ac534445 --- /dev/null +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -0,0 +1,1231 @@ +/* -*- 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/BytecodeCompiler.h" + +#include "mozilla/Attributes.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Maybe.h" +#include "mozilla/Utf8.h" // mozilla::Utf8Unit + +#include "builtin/ModuleObject.h" +#include "frontend/BytecodeCompilation.h" +#include "frontend/BytecodeEmitter.h" +#include "frontend/EitherParser.h" +#include "frontend/ErrorReporter.h" +#include "frontend/FoldConstants.h" +#ifdef JS_ENABLE_SMOOSH +# include "frontend/Frontend2.h" // Smoosh +#endif +#include "frontend/ModuleSharedContext.h" +#include "frontend/Parser.h" +#include "js/SourceText.h" +#include "vm/FunctionFlags.h" // FunctionFlags +#include "vm/GeneratorAndAsyncKind.h" // js::GeneratorKind, js::FunctionAsyncKind +#include "vm/GlobalObject.h" +#include "vm/HelperThreadState.h" // ParseTask +#include "vm/JSContext.h" +#include "vm/JSScript.h" +#include "vm/ModuleBuilder.h" // js::ModuleBuilder +#include "vm/TraceLogging.h" +#include "wasm/AsmJS.h" + +#include "debugger/DebugAPI-inl.h" // DebugAPI +#include "vm/EnvironmentObject-inl.h" +#include "vm/GeckoProfiler-inl.h" +#include "vm/JSContext-inl.h" + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; +using mozilla::Nothing; +using mozilla::Utf8Unit; + +using JS::CompileOptions; +using JS::ReadOnlyCompileOptions; +using JS::SourceText; + +// RAII class to check the frontend reports an exception when it fails to +// compile a script. +class MOZ_RAII AutoAssertReportedException { +#ifdef DEBUG + JSContext* cx_; + bool check_; + + public: + explicit AutoAssertReportedException(JSContext* cx) : cx_(cx), check_(true) {} + void reset() { check_ = false; } + ~AutoAssertReportedException() { + if (!check_) { + return; + } + + if (!cx_->isHelperThreadContext()) { + MOZ_ASSERT(cx_->isExceptionPending()); + return; + } + + ParseTask* task = cx_->parseTask(); + MOZ_ASSERT(task->outOfMemory || task->overRecursed || + !task->errors.empty()); + } +#else + public: + explicit AutoAssertReportedException(JSContext*) {} + void reset() {} +#endif +}; + +static bool EmplaceEmitter(CompilationStencil& stencil, + CompilationState& compilationState, + Maybe& emitter, + const EitherParser& parser, SharedContext* sc); + +template +class MOZ_STACK_CLASS frontend::SourceAwareCompiler { + protected: + SourceText& sourceBuffer_; + + frontend::CompilationState compilationState_; + + Maybe> syntaxParser; + Maybe> parser; + + using TokenStreamPosition = frontend::TokenStreamPosition; + + protected: + explicit SourceAwareCompiler(JSContext* cx, LifoAllocScope& allocScope, + const JS::ReadOnlyCompileOptions& options, + CompilationStencil& stencil, + SourceText& sourceBuffer, + InheritThis inheritThis = InheritThis::No, + js::Scope* enclosingScope = nullptr, + JSObject* enclosingEnv = nullptr) + : sourceBuffer_(sourceBuffer), + compilationState_(cx, allocScope, options, stencil, inheritThis, + enclosingScope, enclosingEnv) { + MOZ_ASSERT(sourceBuffer_.get() != nullptr); + } + + // Call this before calling compile{Global,Eval}Script. + MOZ_MUST_USE bool createSourceAndParser(JSContext* cx, + CompilationStencil& stencil); + + void assertSourceAndParserCreated(CompilationInput& compilationInput) const { + MOZ_ASSERT(compilationInput.source() != nullptr); + MOZ_ASSERT(parser.isSome()); + } + + void assertSourceParserAndScriptCreated(CompilationInput& compilationInput) { + assertSourceAndParserCreated(compilationInput); + } + + MOZ_MUST_USE bool emplaceEmitter(CompilationStencil& stencil, + Maybe& emitter, + SharedContext* sharedContext) { + return EmplaceEmitter(stencil, compilationState_, emitter, + EitherParser(parser.ptr()), sharedContext); + } + + bool canHandleParseFailure(const Directives& newDirectives); + + void handleParseFailure(CompilationStencil& stencil, + const Directives& newDirectives, + TokenStreamPosition& startPosition, + CompilationStencil::RewindToken& startObj); + + public: + frontend::CompilationState& compilationState() { return compilationState_; }; +}; + +template +class MOZ_STACK_CLASS frontend::ScriptCompiler + : public SourceAwareCompiler { + using Base = SourceAwareCompiler; + + protected: + using Base::compilationState_; + using Base::parser; + using Base::sourceBuffer_; + + using Base::assertSourceParserAndScriptCreated; + using Base::canHandleParseFailure; + using Base::emplaceEmitter; + using Base::handleParseFailure; + + using typename Base::TokenStreamPosition; + + public: + explicit ScriptCompiler(JSContext* cx, LifoAllocScope& allocScope, + const JS::ReadOnlyCompileOptions& options, + CompilationStencil& stencil, + SourceText& sourceBuffer, + InheritThis inheritThis = InheritThis::No, + js::Scope* enclosingScope = nullptr, + JSObject* enclosingEnv = nullptr) + : Base(cx, allocScope, options, stencil, sourceBuffer, inheritThis, + enclosingScope, enclosingEnv) {} + + using Base::createSourceAndParser; + + bool compileScriptToStencil(JSContext* cx, CompilationStencil& stencil, + SharedContext* sc); +}; + +#ifdef JS_ENABLE_SMOOSH +bool TrySmoosh(JSContext* cx, CompilationStencil& stencil, + JS::SourceText& srcBuf, bool* fallback) { + if (!cx->options().trySmoosh()) { + *fallback = true; + return true; + } + + bool unimplemented = false; + JSRuntime* rt = cx->runtime(); + bool result = + Smoosh::compileGlobalScriptToStencil(cx, stencil, srcBuf, &unimplemented); + if (!unimplemented) { + *fallback = false; + + if (!stencil.input.assignSource(cx, srcBuf)) { + return false; + } + + if (cx->options().trackNotImplemented()) { + rt->parserWatcherFile.put("1"); + } + return result; + } + *fallback = true; + + if (cx->options().trackNotImplemented()) { + rt->parserWatcherFile.put("0"); + } + fprintf(stderr, "Falling back!\n"); + + return true; +} + +bool TrySmoosh(JSContext* cx, CompilationStencil& stencil, + JS::SourceText& srcBuf, bool* fallback) { + *fallback = true; + return true; +} +#endif // JS_ENABLE_SMOOSH + +template +static bool CompileGlobalScriptToStencilImpl(JSContext* cx, + CompilationStencil& stencil, + JS::SourceText& srcBuf, + ScopeKind scopeKind) { +#ifdef JS_ENABLE_SMOOSH + bool fallback = false; + if (!TrySmoosh(cx, stencil, srcBuf, &fallback)) { + return false; + } + if (!fallback) { + return true; + } +#endif // JS_ENABLE_SMOOSH + + AutoAssertReportedException assertException(cx); + + LifoAllocScope allocScope(&cx->tempLifoAlloc()); + frontend::ScriptCompiler compiler(cx, allocScope, stencil.input.options, + stencil, srcBuf); + + if (!compiler.createSourceAndParser(cx, stencil)) { + return false; + } + + SourceExtent extent = SourceExtent::makeGlobalExtent( + srcBuf.length(), stencil.input.options.lineno, + stencil.input.options.column); + frontend::GlobalSharedContext globalsc( + cx, scopeKind, stencil, compiler.compilationState().directives, extent); + + if (!compiler.compileScriptToStencil(cx, stencil, &globalsc)) { + return false; + } + + assertException.reset(); + return true; +} + +bool frontend::CompileGlobalScriptToStencil(JSContext* cx, + CompilationStencil& stencil, + JS::SourceText& srcBuf, + ScopeKind scopeKind) { + return CompileGlobalScriptToStencilImpl(cx, stencil, srcBuf, scopeKind); +} + +bool frontend::CompileGlobalScriptToStencil(JSContext* cx, + CompilationStencil& stencil, + JS::SourceText& srcBuf, + ScopeKind scopeKind) { + return CompileGlobalScriptToStencilImpl(cx, stencil, srcBuf, scopeKind); +} + +template +static UniquePtr CompileGlobalScriptToStencilImpl( + JSContext* cx, const ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, ScopeKind scopeKind) { + Rooted> stencil( + cx, js_new(cx, options)); + if (!stencil) { + ReportOutOfMemory(cx); + return nullptr; + } + + if (!stencil.get()->input.initForGlobal(cx)) { + return nullptr; + } + + if (!CompileGlobalScriptToStencil(cx, *stencil, srcBuf, scopeKind)) { + return nullptr; + } + + return std::move(stencil.get()); +} + +UniquePtr frontend::CompileGlobalScriptToStencil( + JSContext* cx, const ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, ScopeKind scopeKind) { + return CompileGlobalScriptToStencilImpl(cx, options, srcBuf, scopeKind); +} + +UniquePtr frontend::CompileGlobalScriptToStencil( + JSContext* cx, const ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, ScopeKind scopeKind) { + return CompileGlobalScriptToStencilImpl(cx, options, srcBuf, scopeKind); +} + +bool frontend::InstantiateStencils(JSContext* cx, CompilationStencil& stencil, + CompilationGCOutput& gcOutput) { + { + AutoGeckoProfilerEntry pseudoFrame(cx, "stencil instantiate", + JS::ProfilingCategoryPair::JS_Parsing); + + if (!CompilationStencil::instantiateStencils(cx, stencil, gcOutput)) { + return false; + } + } + + // Enqueue an off-thread source compression task after finishing parsing. + if (!cx->isHelperThreadContext()) { + if (!stencil.input.source()->tryCompressOffThread(cx)) { + return false; + } + + Rooted script(cx, gcOutput.script); + if (!stencil.input.options.hideScriptFromDebugger) { + DebugAPI::onNewScript(cx, script); + } + } + + return true; +} + +bool frontend::InstantiateStencils( + JSContext* cx, CompilationStencilSet& stencilSet, + CompilationGCOutput& gcOutput, + CompilationGCOutput& gcOutputForDelazification_) { + { + AutoGeckoProfilerEntry pseudoFrame(cx, "stencil instantiate", + JS::ProfilingCategoryPair::JS_Parsing); + + if (!stencilSet.instantiateStencils(cx, gcOutput, + gcOutputForDelazification_)) { + return false; + } + } + + // Enqueue an off-thread source compression task after finishing parsing. + if (!cx->isHelperThreadContext()) { + if (!stencilSet.input.source()->tryCompressOffThread(cx)) { + return false; + } + + Rooted script(cx, gcOutput.script); + if (!stencilSet.input.options.hideScriptFromDebugger) { + DebugAPI::onNewScript(cx, script); + } + } + + return true; +} + +bool frontend::PrepareForInstantiate(JSContext* cx, CompilationStencil& stencil, + CompilationGCOutput& gcOutput) { + AutoGeckoProfilerEntry pseudoFrame(cx, "stencil instantiate", + JS::ProfilingCategoryPair::JS_Parsing); + + return CompilationStencil::prepareForInstantiate(cx, stencil, gcOutput); +} + +bool frontend::PrepareForInstantiate( + JSContext* cx, CompilationStencilSet& stencilSet, + CompilationGCOutput& gcOutput, + CompilationGCOutput& gcOutputForDelazification_) { + AutoGeckoProfilerEntry pseudoFrame(cx, "stencil instantiate", + JS::ProfilingCategoryPair::JS_Parsing); + + return stencilSet.prepareForInstantiate(cx, gcOutput, + gcOutputForDelazification_); +} + +template +static JSScript* CompileGlobalScriptImpl( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, ScopeKind scopeKind) { + Rooted stencil(cx, CompilationStencil(cx, options)); + if (options.selfHostingMode) { + if (!stencil.get().input.initForSelfHostingGlobal(cx)) { + return nullptr; + } + } else { + if (!stencil.get().input.initForGlobal(cx)) { + return nullptr; + } + } + + if (!CompileGlobalScriptToStencil(cx, stencil.get(), srcBuf, scopeKind)) { + return nullptr; + } + + Rooted gcOutput(cx); + if (!InstantiateStencils(cx, stencil.get(), gcOutput.get())) { + return nullptr; + } + + return gcOutput.get().script; +} + +JSScript* frontend::CompileGlobalScript( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, ScopeKind scopeKind) { + return CompileGlobalScriptImpl(cx, options, srcBuf, scopeKind); +} + +JSScript* frontend::CompileGlobalScript( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, ScopeKind scopeKind) { + return CompileGlobalScriptImpl(cx, options, srcBuf, scopeKind); +} + +template +static JSScript* CompileEvalScriptImpl( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + SourceText& srcBuf, JS::Handle enclosingScope, + JS::Handle enclosingEnv) { + AutoAssertReportedException assertException(cx); + + Rooted stencil(cx, CompilationStencil(cx, options)); + if (!stencil.get().input.initForEval(cx, enclosingScope)) { + return nullptr; + } + + LifoAllocScope allocScope(&cx->tempLifoAlloc()); + + frontend::ScriptCompiler compiler( + cx, allocScope, stencil.get().input.options, stencil.get(), srcBuf, + InheritThis::Yes, enclosingScope, enclosingEnv); + if (!compiler.createSourceAndParser(cx, stencil.get())) { + return nullptr; + } + + uint32_t len = srcBuf.length(); + SourceExtent extent = + SourceExtent::makeGlobalExtent(len, stencil.get().input.options.lineno, + stencil.get().input.options.column); + frontend::EvalSharedContext evalsc(cx, stencil.get(), + compiler.compilationState(), extent); + if (!compiler.compileScriptToStencil(cx, stencil.get(), &evalsc)) { + return nullptr; + } + + Rooted gcOutput(cx); + if (!InstantiateStencils(cx, stencil.get(), gcOutput.get())) { + return nullptr; + } + + assertException.reset(); + return gcOutput.get().script; +} + +JSScript* frontend::CompileEvalScript(JSContext* cx, + const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, + JS::Handle enclosingScope, + JS::Handle enclosingEnv) { + return CompileEvalScriptImpl(cx, options, srcBuf, enclosingScope, + enclosingEnv); +} + +template +class MOZ_STACK_CLASS frontend::ModuleCompiler final + : public SourceAwareCompiler { + using Base = SourceAwareCompiler; + + using Base::assertSourceParserAndScriptCreated; + using Base::compilationState_; + using Base::createSourceAndParser; + using Base::emplaceEmitter; + using Base::parser; + + public: + explicit ModuleCompiler(JSContext* cx, LifoAllocScope& allocScope, + const JS::ReadOnlyCompileOptions& options, + CompilationStencil& stencil, + SourceText& sourceBuffer, + js::Scope* enclosingScope = nullptr, + JSObject* enclosingEnv = nullptr) + : Base(cx, allocScope, options, stencil, sourceBuffer, InheritThis::No, + enclosingScope, enclosingEnv) {} + + bool compile(JSContext* cx, CompilationStencil& stencil); +}; + +template +class MOZ_STACK_CLASS frontend::StandaloneFunctionCompiler final + : public SourceAwareCompiler { + using Base = SourceAwareCompiler; + + using Base::assertSourceAndParserCreated; + using Base::canHandleParseFailure; + using Base::compilationState_; + using Base::emplaceEmitter; + using Base::handleParseFailure; + using Base::parser; + using Base::sourceBuffer_; + + using typename Base::TokenStreamPosition; + + public: + explicit StandaloneFunctionCompiler(JSContext* cx, LifoAllocScope& allocScope, + const JS::ReadOnlyCompileOptions& options, + CompilationStencil& stencil, + SourceText& sourceBuffer, + InheritThis inheritThis = InheritThis::No, + js::Scope* enclosingScope = nullptr, + JSObject* enclosingEnv = nullptr) + : Base(cx, allocScope, options, stencil, sourceBuffer, inheritThis, + enclosingScope, enclosingEnv) {} + + using Base::createSourceAndParser; + + FunctionNode* parse(JSContext* cx, CompilationStencil& stencil, + FunctionSyntaxKind syntaxKind, + GeneratorKind generatorKind, FunctionAsyncKind asyncKind, + const Maybe& parameterListEnd); + + bool compile(JSContext* cx, CompilationStencil& stencil, + FunctionNode* parsedFunction, CompilationGCOutput& gcOutput); +}; + +AutoFrontendTraceLog::AutoFrontendTraceLog(JSContext* cx, + const TraceLoggerTextId id, + const ErrorReporter& errorReporter) +#ifdef JS_TRACE_LOGGING + : logger_(TraceLoggerForCurrentThread(cx)) { + if (!logger_) { + return; + } + + // If the tokenizer hasn't yet gotten any tokens, use the line and column + // numbers from CompileOptions. + uint32_t line, column; + if (errorReporter.hasTokenizationStarted()) { + line = errorReporter.options().lineno; + column = errorReporter.options().column; + } else { + errorReporter.currentLineAndColumn(&line, &column); + } + frontendEvent_.emplace(TraceLogger_Frontend, errorReporter.getFilename(), + line, column); + frontendLog_.emplace(logger_, *frontendEvent_); + typeLog_.emplace(logger_, id); +} +#else +{ +} +#endif + +AutoFrontendTraceLog::AutoFrontendTraceLog(JSContext* cx, + const TraceLoggerTextId id, + const ErrorReporter& errorReporter, + FunctionBox* funbox) +#ifdef JS_TRACE_LOGGING + : logger_(TraceLoggerForCurrentThread(cx)) { + if (!logger_) { + return; + } + + frontendEvent_.emplace(TraceLogger_Frontend, errorReporter.getFilename(), + funbox->extent().lineno, funbox->extent().column); + frontendLog_.emplace(logger_, *frontendEvent_); + typeLog_.emplace(logger_, id); +} +#else +{ +} +#endif + +AutoFrontendTraceLog::AutoFrontendTraceLog(JSContext* cx, + const TraceLoggerTextId id, + const ErrorReporter& errorReporter, + ParseNode* pn) +#ifdef JS_TRACE_LOGGING + : logger_(TraceLoggerForCurrentThread(cx)) { + if (!logger_) { + return; + } + + uint32_t line, column; + errorReporter.lineAndColumnAt(pn->pn_pos.begin, &line, &column); + frontendEvent_.emplace(TraceLogger_Frontend, errorReporter.getFilename(), + line, column); + frontendLog_.emplace(logger_, *frontendEvent_); + typeLog_.emplace(logger_, id); +} +#else +{ +} +#endif + +template +bool frontend::SourceAwareCompiler::createSourceAndParser( + JSContext* cx, CompilationStencil& stencil) { + if (!stencil.input.assignSource(cx, sourceBuffer_)) { + return false; + } + + if (CanLazilyParse(stencil.input.options)) { + syntaxParser.emplace(cx, stencil.input.options, sourceBuffer_.units(), + sourceBuffer_.length(), + /* foldConstants = */ false, stencil, + compilationState_, nullptr, nullptr); + if (!syntaxParser->checkOptions()) { + return false; + } + } + + parser.emplace(cx, stencil.input.options, sourceBuffer_.units(), + sourceBuffer_.length(), + /* foldConstants = */ true, stencil, compilationState_, + syntaxParser.ptrOr(nullptr), nullptr); + parser->ss = stencil.input.source(); + return parser->checkOptions(); +} + +static bool EmplaceEmitter(CompilationStencil& stencil, + CompilationState& compilationState, + Maybe& emitter, + const EitherParser& parser, SharedContext* sc) { + BytecodeEmitter::EmitterMode emitterMode = + sc->selfHosted() ? BytecodeEmitter::SelfHosting : BytecodeEmitter::Normal; + emitter.emplace(/* parent = */ nullptr, parser, sc, stencil, compilationState, + emitterMode); + return emitter->init(); +} + +template +bool frontend::SourceAwareCompiler::canHandleParseFailure( + const Directives& newDirectives) { + // Try to reparse if no parse errors were thrown and the directives changed. + // + // NOTE: + // Only the following two directive changes force us to reparse the script: + // - The "use asm" directive was encountered. + // - The "use strict" directive was encountered and duplicate parameter names + // are present. We reparse in this case to display the error at the correct + // source location. See |Parser::hasValidSimpleStrictParameterNames()|. + return !parser->anyChars.hadError() && + compilationState_.directives != newDirectives; +} + +template +void frontend::SourceAwareCompiler::handleParseFailure( + CompilationStencil& stencil, const Directives& newDirectives, + TokenStreamPosition& startPosition, + CompilationStencil::RewindToken& startObj) { + MOZ_ASSERT(canHandleParseFailure(newDirectives)); + + // Rewind to starting position to retry. + parser->tokenStream.rewind(startPosition); + stencil.rewind(compilationState_, startObj); + + // Assignment must be monotonic to prevent reparsing iloops + MOZ_ASSERT_IF(compilationState_.directives.strict(), newDirectives.strict()); + MOZ_ASSERT_IF(compilationState_.directives.asmJS(), newDirectives.asmJS()); + compilationState_.directives = newDirectives; +} + +template +bool frontend::ScriptCompiler::compileScriptToStencil( + JSContext* cx, CompilationStencil& stencil, SharedContext* sc) { + assertSourceParserAndScriptCreated(stencil.input); + + TokenStreamPosition startPosition(parser->tokenStream); + + // Emplace the topLevel stencil + MOZ_ASSERT(compilationState_.scriptData.length() == + CompilationStencil::TopLevelIndex); + if (!compilationState_.scriptData.emplaceBack()) { + ReportOutOfMemory(cx); + return false; + } + if (!compilationState_.scriptExtra.emplaceBack()) { + ReportOutOfMemory(cx); + return false; + } + + ParseNode* pn; + { + AutoGeckoProfilerEntry pseudoFrame(cx, "script parsing", + JS::ProfilingCategoryPair::JS_Parsing); + if (sc->isEvalContext()) { + pn = parser->evalBody(sc->asEvalContext()); + } else { + pn = parser->globalBody(sc->asGlobalContext()); + } + } + + if (!pn) { + // Global and eval scripts don't get reparsed after a new directive was + // encountered: + // - "use strict" doesn't require any special error reporting for scripts. + // - "use asm" directives don't have an effect in global/eval contexts. + MOZ_ASSERT(!canHandleParseFailure(compilationState_.directives)); + return false; + } + + { + // Successfully parsed. Emit the script. + AutoGeckoProfilerEntry pseudoFrame(cx, "script emit", + JS::ProfilingCategoryPair::JS_Parsing); + + Maybe emitter; + if (!emplaceEmitter(stencil, emitter, sc)) { + return false; + } + + if (!emitter->emitScript(pn)) { + return false; + } + + if (!compilationState_.finish(cx, stencil)) { + return false; + } + } + + MOZ_ASSERT_IF(!cx->isHelperThreadContext(), !cx->isExceptionPending()); + + return true; +} + +template +bool frontend::ModuleCompiler::compile(JSContext* cx, + CompilationStencil& stencil) { + if (!createSourceAndParser(cx, stencil)) { + return false; + } + + // Emplace the topLevel stencil + MOZ_ASSERT(compilationState_.scriptData.length() == + CompilationStencil::TopLevelIndex); + if (!compilationState_.scriptData.emplaceBack()) { + ReportOutOfMemory(cx); + return false; + } + if (!compilationState_.scriptExtra.emplaceBack()) { + ReportOutOfMemory(cx); + return false; + } + + ModuleBuilder builder(cx, parser.ptr()); + + uint32_t len = this->sourceBuffer_.length(); + SourceExtent extent = SourceExtent::makeGlobalExtent( + len, stencil.input.options.lineno, stencil.input.options.column); + ModuleSharedContext modulesc(cx, stencil, builder, extent); + + ParseNode* pn = parser->moduleBody(&modulesc); + if (!pn) { + return false; + } + + Maybe emitter; + if (!emplaceEmitter(stencil, emitter, &modulesc)) { + return false; + } + + if (!emitter->emitScript(pn->as().body())) { + return false; + } + + if (!compilationState_.finish(cx, stencil)) { + return false; + } + + StencilModuleMetadata& moduleMetadata = *stencil.moduleMetadata; + + builder.finishFunctionDecls(moduleMetadata); + + MOZ_ASSERT_IF(!cx->isHelperThreadContext(), !cx->isExceptionPending()); + return true; +} + +// Parse a standalone JS function, which might appear as the value of an +// event handler attribute in an HTML tag, or in a Function() +// constructor. +template +FunctionNode* frontend::StandaloneFunctionCompiler::parse( + JSContext* cx, CompilationStencil& stencil, FunctionSyntaxKind syntaxKind, + GeneratorKind generatorKind, FunctionAsyncKind asyncKind, + const Maybe& parameterListEnd) { + assertSourceAndParserCreated(stencil.input); + + TokenStreamPosition startPosition(parser->tokenStream); + CompilationStencil::RewindToken startObj = + stencil.getRewindToken(compilationState_); + + // Speculatively parse using the default directives implied by the context. + // If a directive is encountered (e.g., "use strict") that changes how the + // function should have been parsed, we backup and reparse with the new set + // of directives. + + FunctionNode* fn; + for (;;) { + Directives newDirectives = compilationState_.directives; + fn = parser->standaloneFunction(parameterListEnd, syntaxKind, generatorKind, + asyncKind, compilationState_.directives, + &newDirectives); + if (fn) { + break; + } + + // Maybe we encountered a new directive. See if we can try again. + if (!canHandleParseFailure(newDirectives)) { + return nullptr; + } + + handleParseFailure(stencil, newDirectives, startPosition, startObj); + } + + return fn; +} + +// Compile a standalone JS function. +template +bool frontend::StandaloneFunctionCompiler::compile( + JSContext* cx, CompilationStencil& stencil, FunctionNode* parsedFunction, + CompilationGCOutput& gcOutput) { + FunctionBox* funbox = parsedFunction->funbox(); + + if (funbox->isInterpreted()) { + Maybe emitter; + if (!emplaceEmitter(stencil, emitter, funbox)) { + return false; + } + + if (!emitter->emitFunctionScript(parsedFunction)) { + return false; + } + + // The parser extent has stripped off the leading `function...` but + // we want the SourceExtent used in the final standalone script to + // start from the beginning of the buffer, and use the provided + // line and column. + compilationState_.scriptExtra[CompilationStencil::TopLevelIndex].extent = + SourceExtent{/* sourceStart = */ 0, + sourceBuffer_.length(), + funbox->extent().toStringStart, + funbox->extent().toStringEnd, + stencil.input.options.lineno, + stencil.input.options.column}; + + if (!compilationState_.finish(cx, stencil)) { + return false; + } + } else { + if (!compilationState_.finish(cx, stencil)) { + return false; + } + + // The asm.js module was created by parser. Instantiation below will + // allocate the JSFunction that wraps it. + MOZ_ASSERT(funbox->isAsmJSModule()); + MOZ_ASSERT(stencil.asmJS.has(funbox->index())); + MOZ_ASSERT(compilationState_.scriptData[CompilationStencil::TopLevelIndex] + .functionFlags.isAsmJSNative()); + } + + if (!CompilationStencil::instantiateStencils(cx, stencil, gcOutput)) { + return false; + } + +#ifdef DEBUG + JSFunction* fun = gcOutput.functions[CompilationStencil::TopLevelIndex]; + MOZ_ASSERT(fun->hasBytecode() || IsAsmJSModule(fun)); +#endif + + // Enqueue an off-thread source compression task after finishing parsing. + if (!cx->isHelperThreadContext()) { + if (!stencil.input.source()->tryCompressOffThread(cx)) { + return false; + } + } + + return true; +} + +template +static bool ParseModuleToStencilImpl(JSContext* cx, CompilationStencil& stencil, + SourceText& srcBuf) { + MOZ_ASSERT(srcBuf.get()); + + AutoAssertReportedException assertException(cx); + + LifoAllocScope allocScope(&cx->tempLifoAlloc()); + ModuleCompiler compiler(cx, allocScope, stencil.input.options, stencil, + srcBuf); + if (!compiler.compile(cx, stencil)) { + return false; + } + + assertException.reset(); + return true; +} + +bool frontend::ParseModuleToStencil(JSContext* cx, CompilationStencil& stencil, + SourceText& srcBuf) { + return ParseModuleToStencilImpl(cx, stencil, srcBuf); +} + +bool frontend::ParseModuleToStencil(JSContext* cx, CompilationStencil& stencil, + SourceText& srcBuf) { + return ParseModuleToStencilImpl(cx, stencil, srcBuf); +} + +template +static UniquePtr ParseModuleToStencilImpl( + JSContext* cx, const ReadOnlyCompileOptions& options, + SourceText& srcBuf) { + Rooted> stencil( + cx, js_new(cx, options)); + if (!stencil) { + ReportOutOfMemory(cx); + return nullptr; + } + + if (!stencil.get()->input.initForModule(cx)) { + return nullptr; + } + + if (!ParseModuleToStencilImpl(cx, *stencil, srcBuf)) { + return nullptr; + } + + return std::move(stencil.get()); +} + +UniquePtr frontend::ParseModuleToStencil( + JSContext* cx, const ReadOnlyCompileOptions& options, + SourceText& srcBuf) { + return ParseModuleToStencilImpl(cx, options, srcBuf); +} + +UniquePtr frontend::ParseModuleToStencil( + JSContext* cx, const ReadOnlyCompileOptions& options, + SourceText& srcBuf) { + return ParseModuleToStencilImpl(cx, options, srcBuf); +} + +template +static ModuleObject* CompileModuleImpl( + JSContext* cx, const JS::ReadOnlyCompileOptions& optionsInput, + SourceText& srcBuf) { + AutoAssertReportedException assertException(cx); + + if (!GlobalObject::ensureModulePrototypesCreated(cx, cx->global())) { + return nullptr; + } + + CompileOptions options(cx, optionsInput); + options.setModule(); + + Rooted stencil(cx, CompilationStencil(cx, options)); + if (!stencil.get().input.initForModule(cx)) { + return nullptr; + } + + if (!ParseModuleToStencil(cx, stencil.get(), srcBuf)) { + return nullptr; + } + + Rooted gcOutput(cx); + if (!InstantiateStencils(cx, stencil.get(), gcOutput.get())) { + return nullptr; + } + + assertException.reset(); + return gcOutput.get().module; +} + +ModuleObject* frontend::CompileModule(JSContext* cx, + const JS::ReadOnlyCompileOptions& options, + SourceText& srcBuf) { + return CompileModuleImpl(cx, options, srcBuf); +} + +ModuleObject* frontend::CompileModule(JSContext* cx, + const JS::ReadOnlyCompileOptions& options, + SourceText& srcBuf) { + return CompileModuleImpl(cx, options, srcBuf); +} + +void frontend::FillCompileOptionsForLazyFunction(JS::CompileOptions& options, + JS::Handle lazy) { + options.setMutedErrors(lazy->mutedErrors()) + .setFileAndLine(lazy->filename(), lazy->lineno()) + .setColumn(lazy->column()) + .setScriptSourceOffset(lazy->sourceStart()) + .setNoScriptRval(false) + .setSelfHostingMode(false); +} + +template +static bool CompileLazyFunctionToStencilImpl(JSContext* cx, + CompilationStencil& stencil, + Handle lazy, + const Unit* units, size_t length) { + MOZ_ASSERT(cx->compartment() == lazy->compartment()); + + // We can only compile functions whose parents have previously been + // compiled, because compilation requires full information about the + // function's immediately enclosing scope. + MOZ_ASSERT(lazy->isReadyForDelazification()); + + AutoAssertReportedException assertException(cx); + + Rooted fun(cx, lazy->function()); + + InheritThis inheritThis = fun->isArrow() ? InheritThis::Yes : InheritThis::No; + + LifoAllocScope allocScope(&cx->tempLifoAlloc()); + frontend::CompilationState compilationState( + cx, allocScope, stencil.input.options, stencil, inheritThis, + fun->enclosingScope()); + + Parser parser( + cx, stencil.input.options, units, length, + /* foldConstants = */ true, stencil, compilationState, nullptr, lazy); + if (!parser.checkOptions()) { + return false; + } + + AutoGeckoProfilerEntry pseudoFrame(cx, "script delazify", + JS::ProfilingCategoryPair::JS_Parsing); + + FunctionNode* pn = + parser.standaloneLazyFunction(fun, lazy->toStringStart(), lazy->strict(), + lazy->generatorKind(), lazy->asyncKind()); + if (!pn) { + return false; + } + + BytecodeEmitter bce(/* parent = */ nullptr, &parser, pn->funbox(), stencil, + compilationState, BytecodeEmitter::LazyFunction); + if (!bce.init(pn->pn_pos)) { + return false; + } + + if (!bce.emitFunctionScript(pn)) { + return false; + } + + // NOTE: Only allow relazification if there was no lazy PrivateScriptData. + // This excludes non-leaf functions and all script class constructors. + bool hadLazyScriptData = lazy->hasPrivateScriptData(); + bool isRelazifiableAfterDelazify = lazy->isRelazifiableAfterDelazify(); + if (isRelazifiableAfterDelazify && !hadLazyScriptData) { + compilationState.scriptData[CompilationStencil::TopLevelIndex] + .setAllowRelazify(); + } + + if (!compilationState.finish(cx, stencil)) { + return false; + } + + // Record the FunctionKey in the BaseCompilationStencil since it does not + // contain any of the SourceExtents itself. + stencil.functionKey = BaseCompilationStencil::toFunctionKey(lazy->extent()); + + assertException.reset(); + return true; +} + +MOZ_MUST_USE bool frontend::CompileLazyFunctionToStencil( + JSContext* cx, CompilationStencil& stencil, JS::Handle lazy, + const char16_t* units, size_t length) { + return CompileLazyFunctionToStencilImpl(cx, stencil, lazy, units, length); +} + +MOZ_MUST_USE bool frontend::CompileLazyFunctionToStencil( + JSContext* cx, CompilationStencil& stencil, JS::Handle lazy, + const mozilla::Utf8Unit* units, size_t length) { + return CompileLazyFunctionToStencilImpl(cx, stencil, lazy, units, length); +} + +bool frontend::InstantiateStencilsForDelazify(JSContext* cx, + CompilationStencil& stencil) { + AutoAssertReportedException assertException(cx); + + mozilla::DebugOnly lazyFlags = + static_cast(stencil.input.lazy->immutableFlags()); + + Rooted gcOutput(cx); + if (!CompilationStencil::instantiateStencils(cx, stencil, gcOutput.get())) { + return false; + } + + MOZ_ASSERT(lazyFlags == gcOutput.get().script->immutableFlags()); + MOZ_ASSERT(gcOutput.get().script->outermostScope()->hasOnChain( + ScopeKind::NonSyntactic) == + gcOutput.get().script->immutableFlags().hasFlag( + JSScript::ImmutableFlags::HasNonSyntacticScope)); + + assertException.reset(); + return true; +} + +static JSFunction* CompileStandaloneFunction( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, const Maybe& parameterListEnd, + FunctionSyntaxKind syntaxKind, GeneratorKind generatorKind, + FunctionAsyncKind asyncKind, HandleScope enclosingScope = nullptr) { + AutoAssertReportedException assertException(cx); + + RootedScope scope(cx, enclosingScope); + if (!scope) { + scope = &cx->global()->emptyGlobalScope(); + } + + Rooted stencil(cx, CompilationStencil(cx, options)); + if (!stencil.get().input.initForStandaloneFunction(cx, scope)) { + return nullptr; + } + + LifoAllocScope allocScope(&cx->tempLifoAlloc()); + InheritThis inheritThis = (syntaxKind == FunctionSyntaxKind::Arrow) + ? InheritThis::Yes + : InheritThis::No; + StandaloneFunctionCompiler compiler( + cx, allocScope, stencil.get().input.options, stencil.get(), srcBuf, + inheritThis, enclosingScope); + if (!compiler.createSourceAndParser(cx, stencil.get())) { + return nullptr; + } + + FunctionNode* parsedFunction = + compiler.parse(cx, stencil.get(), syntaxKind, generatorKind, asyncKind, + parameterListEnd); + if (!parsedFunction) { + return nullptr; + } + + Rooted gcOutput(cx); + if (!compiler.compile(cx, stencil.get(), parsedFunction, gcOutput.get())) { + return nullptr; + } + + // Note: If AsmJS successfully compiles, the into.script will still be + // nullptr. In this case we have compiled to a native function instead of an + // interpreted script. + if (gcOutput.get().script) { + if (parameterListEnd) { + stencil.get().input.source()->setParameterListEnd(*parameterListEnd); + } + + MOZ_ASSERT(!cx->isHelperThreadContext()); + + Rooted script(cx, gcOutput.get().script); + if (!options.hideScriptFromDebugger) { + DebugAPI::onNewScript(cx, script); + } + } + + assertException.reset(); + return gcOutput.get().functions[CompilationStencil::TopLevelIndex]; +} + +JSFunction* frontend::CompileStandaloneFunction( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, const Maybe& parameterListEnd, + FunctionSyntaxKind syntaxKind, HandleScope enclosingScope /* = nullptr */) { + return CompileStandaloneFunction(cx, options, srcBuf, parameterListEnd, + syntaxKind, GeneratorKind::NotGenerator, + FunctionAsyncKind::SyncFunction, + enclosingScope); +} + +JSFunction* frontend::CompileStandaloneGenerator( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, const Maybe& parameterListEnd, + FunctionSyntaxKind syntaxKind) { + return CompileStandaloneFunction(cx, options, srcBuf, parameterListEnd, + syntaxKind, GeneratorKind::Generator, + FunctionAsyncKind::SyncFunction); +} + +JSFunction* frontend::CompileStandaloneAsyncFunction( + JSContext* cx, const ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, const Maybe& parameterListEnd, + FunctionSyntaxKind syntaxKind) { + return CompileStandaloneFunction(cx, options, srcBuf, parameterListEnd, + syntaxKind, GeneratorKind::NotGenerator, + FunctionAsyncKind::AsyncFunction); +} + +JSFunction* frontend::CompileStandaloneAsyncGenerator( + JSContext* cx, const ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, const Maybe& parameterListEnd, + FunctionSyntaxKind syntaxKind) { + return CompileStandaloneFunction(cx, options, srcBuf, parameterListEnd, + syntaxKind, GeneratorKind::Generator, + FunctionAsyncKind::AsyncFunction); +} + +bool frontend::CompilationInput::initScriptSource(JSContext* cx) { + ScriptSource* ss = cx->new_(); + if (!ss) { + return false; + } + setSource(ss); + + return ss->initFromOptions(cx, options); +} + +void CompilationInput::trace(JSTracer* trc) { + atomCache.trace(trc); + TraceNullableRoot(trc, &lazy, "compilation-input-lazy"); + source_.trace(trc); + TraceNullableRoot(trc, &enclosingScope, "compilation-input-enclosing-scope"); +} + +void CompilationAtomCache::trace(JSTracer* trc) { atoms_.trace(trc); } + +void CompilationStencil::trace(JSTracer* trc) { input.trace(trc); } + +void CompilationGCOutput::trace(JSTracer* trc) { + TraceNullableRoot(trc, &script, "compilation-gc-output-script"); + TraceNullableRoot(trc, &module, "compilation-gc-output-module"); + TraceNullableRoot(trc, &sourceObject, "compilation-gc-output-source"); + functions.trace(trc); + scopes.trace(trc); +} diff --git a/js/src/frontend/BytecodeCompiler.h b/js/src/frontend/BytecodeCompiler.h new file mode 100644 index 0000000000..52019b212c --- /dev/null +++ b/js/src/frontend/BytecodeCompiler.h @@ -0,0 +1,224 @@ +/* -*- 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_BytecodeCompiler_h +#define frontend_BytecodeCompiler_h + +#include "mozilla/Maybe.h" +#include "mozilla/Utf8.h" // mozilla::Utf8Unit + +#include "NamespaceImports.h" + +#include "frontend/FunctionSyntaxKind.h" +#include "js/CompileOptions.h" // JS::ReadOnlyCompileOptions +#include "js/SourceText.h" +#include "js/UniquePtr.h" // js::UniquePtr +#include "vm/Scope.h" +#include "vm/TraceLogging.h" + +/* + * Structure of all of the support classes. + * + * Parser: described in Parser.h. + * + * BytecodeCompiler.cpp: BytecodeCompiler.h *and* BytecodeCompilation.h. + * This is the "driver", the high-level operations like "compile this source to + * bytecode". It calls the parser, bytecode emitter, etc. + * + * ParseContext.h and SharedContext.h: Both have similar purposes. They're split + * because ParseContext contains information used only by the parser, and + * SharedContext contains information used by both the parser and + * BytecodeEmitter. + * + * SharedContext.h: class Directives: this contains boolean flags for tracking + * if we're in asm.js or "use strict" code. The "use strict" bit is stored in + * SharedContext, and additionally, the full Directives class is stored in + * ParseContext - if a direcive is encountered while parsing, this is updated, + * and checked in GeneralParser::functionDefinition, and if it changed, the + * whole function is re-parsed with the new flags. + * + * SharedContext.h: abstract class SharedContext: This class contains two + * different groups of flags: + * + * Parse context information. This is information conceptually "passed down" + * into parsing sub-nodes. This is like "are we parsing strict code?", and so + * the parser can make decisions of how to parse based off that. + * + * Gathered-while-parsing information. This is information conceptually + * "returned up" from parsing sub-nodes. This is like "did we see a use strict + * directive"? + * + * Additionally, subclasses (GlobalSharedContext, ModuleSharedContext, + * EvalSharedContext, and FunctionBox) contain binding information, scope + * information, and other such bits of data. + * + * ParseContext.h: class UsedNameTracker: Track which bindings are used in which + * scopes. This helps determine which bindings are closed-over, which affects + * how they're stored; and whether special bindings like `this` and `arguments` + * can be optimized away. + * + * ParseContext.h: class ParseContext: Extremely complex class that serves a lot + * of purposes, but it's a single class - essentially no derived classes - so + * it's a little easier to comprehend all at once. (SourceParseContext does + * derive from ParseContext, but they does nothing except adjust the + * constructor's arguments). + * Note it uses a thing called Nestable, which implements a stack of objects: + * you can push (and pop) instances to a stack (linked list) as you parse + * further into the parse tree. You may push to this stack via calling the + * constructor with a GeneralParser as an argument (usually `this`), which + * pushes itself onto `this->pc` (so it does get assigned/pushed, even though no + * assignment ever appears directly in the parser) + * + * ParseContext contains a pointer to a SharedContext. + * + * There's a decent chunk of flags/data collection in here too, some "pass-down" + * data and some "return-up" data. + * + * ParseContext also contains a significant number of *sub*-Nestables as fields + * of itself (nestables inside nestables). Note you also push/pop to these via + * passing `Parser->pc`, which the constructor of the sub-nestable knows which + * ParseContext field to push to. The sub-nestables are: + * + * ParseContext::Statement: stack of statements. + * `if (x) { while (true) { try { ..stack of [if, while, try].. } ... } }` + * + * ParseContext::LabelStatement: interspersed in Statement stack, for labeled + * statements, for e.g. `label: while (true) { break label; }` + * + * ParseContext::ClassStatement: interspersed in Statement stack, for classes + * the parser is currently inside of. + * + * ParseContext::Scope: Set of variables in each scope (stack of sets): + * `{ let a; let b; { let c; } }` + * (this gets complicated with `var`, etc., check the class for docs) + */ + +class JSLinearString; + +namespace js { + +class ModuleObject; +class ScriptSourceObject; + +namespace frontend { + +struct CompilationStencil; +struct CompilationGCOutput; +class ErrorReporter; +class FunctionBox; +class ParseNode; +class ParserAtom; + +// Compile a module of the given source using the given options. +ModuleObject* CompileModule(JSContext* cx, + const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf); +ModuleObject* CompileModule(JSContext* cx, + const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf); + +// Parse a module of the given source. This is an internal API; if you want to +// compile a module as a user, use CompileModule above. +bool ParseModuleToStencil(JSContext* cx, CompilationStencil& stencil, + JS::SourceText& srcBuf); +bool ParseModuleToStencil(JSContext* cx, CompilationStencil& stencil, + JS::SourceText& srcBuf); + +UniquePtr ParseModuleToStencil( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf); +UniquePtr ParseModuleToStencil( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf); + +// +// Compile a single function. The source in srcBuf must match the ECMA-262 +// FunctionExpression production. +// +// If nonzero, parameterListEnd is the offset within srcBuf where the parameter +// list is expected to end. During parsing, if we find that it ends anywhere +// else, it's a SyntaxError. This is used to implement the Function constructor; +// it's how we detect that these weird cases are SyntaxErrors: +// +// Function("/*", "*/x) {") +// Function("x){ if (3", "return x;}") +// +MOZ_MUST_USE JSFunction* CompileStandaloneFunction( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, + const mozilla::Maybe& parameterListEnd, + frontend::FunctionSyntaxKind syntaxKind, + HandleScope enclosingScope = nullptr); + +MOZ_MUST_USE JSFunction* CompileStandaloneGenerator( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, + const mozilla::Maybe& parameterListEnd, + frontend::FunctionSyntaxKind syntaxKind); + +MOZ_MUST_USE JSFunction* CompileStandaloneAsyncFunction( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, + const mozilla::Maybe& parameterListEnd, + frontend::FunctionSyntaxKind syntaxKind); + +MOZ_MUST_USE JSFunction* CompileStandaloneAsyncGenerator( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, + const mozilla::Maybe& parameterListEnd, + frontend::FunctionSyntaxKind syntaxKind); + +/* + * True if str consists of an IdentifierStart character, followed by one or + * more IdentifierPart characters, i.e. it matches the IdentifierName production + * in the language spec. + * + * This returns true even if str is a keyword like "if". + * + * Defined in TokenStream.cpp. + */ +bool IsIdentifier(JSLinearString* str); +bool IsIdentifier(const ParserAtom* atom); + +bool IsIdentifierNameOrPrivateName(JSLinearString* str); +bool IsIdentifierNameOrPrivateName(const ParserAtom* atom); + +/* + * As above, but taking chars + length. + */ +bool IsIdentifier(const Latin1Char* chars, size_t length); +bool IsIdentifier(const char16_t* chars, size_t length); + +bool IsIdentifierNameOrPrivateName(const Latin1Char* chars, size_t length); +bool IsIdentifierNameOrPrivateName(const char16_t* chars, size_t length); + +/* True if str is a keyword. Defined in TokenStream.cpp. */ +bool IsKeyword(const ParserAtom* atom); +bool IsKeyword(JSLinearString* str); + +class MOZ_STACK_CLASS AutoFrontendTraceLog { +#ifdef JS_TRACE_LOGGING + TraceLoggerThread* logger_; + mozilla::Maybe frontendEvent_; + mozilla::Maybe frontendLog_; + mozilla::Maybe typeLog_; +#endif + + public: + AutoFrontendTraceLog(JSContext* cx, const TraceLoggerTextId id, + const ErrorReporter& reporter); + + AutoFrontendTraceLog(JSContext* cx, const TraceLoggerTextId id, + const ErrorReporter& reporter, FunctionBox* funbox); + + AutoFrontendTraceLog(JSContext* cx, const TraceLoggerTextId id, + const ErrorReporter& reporter, ParseNode* pn); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_BytecodeCompiler_h */ diff --git a/js/src/frontend/BytecodeControlStructures.cpp b/js/src/frontend/BytecodeControlStructures.cpp new file mode 100644 index 0000000000..401e27d8b2 --- /dev/null +++ b/js/src/frontend/BytecodeControlStructures.cpp @@ -0,0 +1,111 @@ +/* -*- 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/BytecodeControlStructures.h" + +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter +#include "frontend/EmitterScope.h" // EmitterScope +#include "vm/Opcodes.h" // JSOp + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; + +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, const ParserAtom* label, + BytecodeOffset startOffset) + : BreakableControl(bce, StatementKind::Label), + label_(label), + startOffset_(startOffset) {} + +LoopControl::LoopControl(BytecodeEmitter* bce, StatementKind loopKind) + : BreakableControl(bce, loopKind), tdzCache_(bce) { + MOZ_ASSERT(is()); + + LoopControl* enclosingLoop = findNearest(enclosing()); + + stackDepth_ = bce->bytecodeSection().stackDepth(); + loopDepth_ = enclosingLoop ? enclosingLoop->loopDepth_ + 1 : 1; +} + +bool LoopControl::emitContinueTarget(BytecodeEmitter* bce) { + // Note: this is always called after emitting the loop body so we must have + // emitted all 'continues' by now. + return bce->emitJumpTargetAndPatch(continues); +} + +bool LoopControl::emitLoopHead(BytecodeEmitter* bce, + const Maybe& nextPos) { + // Insert a Nop if needed to ensure the script does not start with a + // JSOp::LoopHead. This avoids JIT issues with prologue code + try notes + // or OSR. See bug 1602390 and bug 1602681. + if (bce->bytecodeSection().offset().toUint32() == 0) { + if (!bce->emit1(JSOp::Nop)) { + return false; + } + } + + if (nextPos) { + if (!bce->updateSourceCoordNotes(*nextPos)) { + return false; + } + } + + MOZ_ASSERT(loopDepth_ > 0); + + head_ = {bce->bytecodeSection().offset()}; + + BytecodeOffset off; + if (!bce->emitJumpTargetOp(JSOp::LoopHead, &off)) { + return false; + } + SetLoopHeadDepthHint(bce->bytecodeSection().code(off), loopDepth_); + + return true; +} + +bool LoopControl::emitLoopEnd(BytecodeEmitter* bce, JSOp op, + TryNoteKind tryNoteKind) { + JumpList jump; + if (!bce->emitJumpNoFallthrough(op, &jump)) { + return false; + } + bce->patchJumpsToTarget(jump, head_); + + // Create a fallthrough for closing iterators, and as a target for break + // statements. + JumpTarget breakTarget; + if (!bce->emitJumpTarget(&breakTarget)) { + return false; + } + if (!patchBreaks(bce)) { + return false; + } + if (!bce->addTryNote(tryNoteKind, bce->bytecodeSection().stackDepth(), + headOffset(), breakTarget.offset)) { + return false; + } + 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 0000000000..a86163ac55 --- /dev/null +++ b/js/src/frontend/BytecodeControlStructures.h @@ -0,0 +1,164 @@ +/* -*- 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_BytecodeControlStructures_h +#define frontend_BytecodeControlStructures_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Attributes.h" // MOZ_MUST_USE +#include "mozilla/Maybe.h" // mozilla::Maybe + +#include // int32_t, uint32_t + +#include "ds/Nestable.h" // Nestable +#include "frontend/BytecodeSection.h" // BytecodeOffset +#include "frontend/JumpList.h" // JumpList, JumpTarget +#include "frontend/ParserAtom.h" // ParserAtom +#include "frontend/SharedContext.h" // StatementKind, StatementKindIsLoop, StatementKindIsUnlabeledBreakTarget +#include "frontend/TDZCheckCache.h" // TDZCheckCache +#include "vm/StencilEnums.h" // TryNoteKind + +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 { + const ParserAtom* label_; + + // The code offset when this was pushed. Used for effectfulness checking. + BytecodeOffset startOffset_; + + public: + LabelControl(BytecodeEmitter* bce, const ParserAtom* label, + BytecodeOffset startOffset); + + const ParserAtom* label() const { return label_; } + + BytecodeOffset 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_; + + // Here's the basic structure of a loop: + // + // head: + // JSOp::LoopHead + // {loop condition/body} + // + // continueTarget: + // {loop update if present} + // + // # Loop end, backward jump + // JSOp::Goto/JSOp::IfNe head + // + // breakTarget: + + // The bytecode offset of JSOp::LoopHead. + JumpTarget head_; + + // 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_; + + public: + // Offset of the last continue in the loop. + JumpList continues; + + LoopControl(BytecodeEmitter* bce, StatementKind loopKind); + + BytecodeOffset headOffset() const { return head_.offset; } + + MOZ_MUST_USE bool emitContinueTarget(BytecodeEmitter* bce); + + // `nextPos` is the offset in the source code for the character that + // corresponds to the next instruction after JSOp::LoopHead. + // Can be Nothing() if not available. + MOZ_MUST_USE bool emitLoopHead(BytecodeEmitter* bce, + const mozilla::Maybe& nextPos); + + MOZ_MUST_USE bool emitLoopEnd(BytecodeEmitter* bce, JSOp op, + TryNoteKind tryNoteKind); +}; +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; + + 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 new file mode 100644 index 0000000000..cc9d109407 --- /dev/null +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -0,0 +1,11322 @@ +/* -*- 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/. */ + +/* + * JS bytecode generation. + */ + +#include "frontend/BytecodeEmitter.h" + +#include "mozilla/Casting.h" // mozilla::AssertedCast +#include "mozilla/DebugOnly.h" // mozilla::DebugOnly +#include "mozilla/FloatingPoint.h" // mozilla::NumberEqualsInt32, mozilla::NumberIsInt32 +#include "mozilla/Maybe.h" // mozilla::{Maybe,Nothing,Some} +#include "mozilla/PodOperations.h" // mozilla::PodCopy +#include "mozilla/Sprintf.h" // SprintfLiteral +#include "mozilla/Unused.h" // mozilla::Unused +#include "mozilla/Variant.h" // mozilla::AsVariant + +#include +#include +#include + +#include "jstypes.h" // JS_BIT + +#include "ds/Nestable.h" // Nestable +#include "frontend/AbstractScopePtr.h" // ScopeIndex +#include "frontend/BytecodeControlStructures.h" // NestableControl, BreakableControl, LabelControl, LoopControl, TryFinallyControl +#include "frontend/CallOrNewEmitter.h" // CallOrNewEmitter +#include "frontend/CForEmitter.h" // CForEmitter +#include "frontend/DefaultEmitter.h" // DefaultEmitter +#include "frontend/DoWhileEmitter.h" // DoWhileEmitter +#include "frontend/ElemOpEmitter.h" // ElemOpEmitter +#include "frontend/EmitterScope.h" // EmitterScope +#include "frontend/ExpressionStatementEmitter.h" // ExpressionStatementEmitter +#include "frontend/ForInEmitter.h" // ForInEmitter +#include "frontend/ForOfEmitter.h" // ForOfEmitter +#include "frontend/ForOfLoopControl.h" // ForOfLoopControl +#include "frontend/FunctionEmitter.h" // FunctionEmitter, FunctionScriptEmitter, FunctionParamsEmitter +#include "frontend/IfEmitter.h" // IfEmitter, InternalIfEmitter, CondEmitter +#include "frontend/LabelEmitter.h" // LabelEmitter +#include "frontend/LexicalScopeEmitter.h" // LexicalScopeEmitter +#include "frontend/ModuleSharedContext.h" // ModuleSharedContext +#include "frontend/NameAnalysisTypes.h" // PrivateNameKind +#include "frontend/NameFunctions.h" // NameFunctions +#include "frontend/NameOpEmitter.h" // NameOpEmitter +#include "frontend/ObjectEmitter.h" // PropertyEmitter, ObjectEmitter, ClassEmitter +#include "frontend/OptionalEmitter.h" // OptionalEmitter +#include "frontend/ParseNode.h" // ParseNodeKind, ParseNode and subclasses +#include "frontend/Parser.h" // Parser +#include "frontend/ParserAtom.h" // ParserAtomsTable +#include "frontend/PropOpEmitter.h" // PropOpEmitter +#include "frontend/SourceNotes.h" // SrcNote, SrcNoteType, SrcNoteWriter +#include "frontend/SwitchEmitter.h" // SwitchEmitter +#include "frontend/TDZCheckCache.h" // TDZCheckCache +#include "frontend/TryEmitter.h" // TryEmitter +#include "frontend/WhileEmitter.h" // WhileEmitter +#include "js/CompileOptions.h" // TransitiveCompileOptions, CompileOptions +#include "js/friend/ErrorMessages.h" // JSMSG_* +#include "js/friend/StackLimits.h" // CheckRecursionLimit +#include "util/StringBuffer.h" // StringBuffer +#include "vm/AsyncFunctionResolveKind.h" // AsyncFunctionResolveKind +#include "vm/BytecodeUtil.h" // JOF_*, IsArgOp, IsLocalOp, SET_UINT24, SET_ICINDEX, BytecodeFallsThrough, BytecodeIsJumpTarget +#include "vm/FunctionPrefixKind.h" // FunctionPrefixKind +#include "vm/GeneratorObject.h" // AbstractGeneratorObject +#include "vm/JSAtom.h" // JSAtom, js_*_str +#include "vm/JSContext.h" // JSContext +#include "vm/JSFunction.h" // JSFunction, +#include "vm/JSScript.h" // JSScript, ScriptSourceObject, MemberInitializers, BaseScript +#include "vm/Opcodes.h" // JSOp, JSOpLength_* +#include "vm/SharedStencil.h" // ScopeNote +#include "vm/ThrowMsgKind.h" // ThrowMsgKind +#include "wasm/AsmJS.h" // IsAsmJSModule + +#include "vm/JSObject-inl.h" // JSObject + +using namespace js; +using namespace js::frontend; + +using mozilla::AssertedCast; +using mozilla::AsVariant; +using mozilla::DebugOnly; +using mozilla::Maybe; +using mozilla::Nothing; +using mozilla::NumberEqualsInt32; +using mozilla::NumberIsInt32; +using mozilla::PodCopy; +using mozilla::Some; +using mozilla::Unused; + +static bool ParseNodeRequiresSpecialLineNumberNotes(ParseNode* pn) { + // The few node types listed below are exceptions to the usual + // location-source-note-emitting code in BytecodeEmitter::emitTree(). + // Single-line `while` loops and C-style `for` loops require careful + // handling to avoid strange stepping behavior. + // Functions usually shouldn't have location information (bug 1431202). + + ParseNodeKind kind = pn->getKind(); + return kind == ParseNodeKind::WhileStmt || kind == ParseNodeKind::ForStmt || + kind == ParseNodeKind::Function; +} + +static bool NeedsFieldInitializer(ParseNode* member, bool isStatic) { + return member->is() && + member->as().isStatic() == isStatic; +} + +static bool NeedsMethodInitializer(ParseNode* member, bool isStatic) { + if (isStatic) { + return false; + } + return member->is() && + member->as().name().isKind(ParseNodeKind::PrivateName) && + !member->as().isStatic(); +} + +BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, SharedContext* sc, + CompilationStencil& stencil, + CompilationState& compilationState, + EmitterMode emitterMode) + : sc(sc), + cx(sc->cx_), + parent(parent), + bytecodeSection_(cx, sc->extent().lineno, sc->extent().column), + perScriptData_(cx, stencil, compilationState), + stencil(stencil), + compilationState(compilationState), + emitterMode(emitterMode) {} + +BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, + BCEParserHandle* handle, SharedContext* sc, + CompilationStencil& stencil, + CompilationState& compilationState, + EmitterMode emitterMode) + : BytecodeEmitter(parent, sc, stencil, compilationState, emitterMode) { + parser = handle; + instrumentationKinds = parser->options().instrumentationKinds; +} + +BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, + const EitherParser& parser, SharedContext* sc, + CompilationStencil& stencil, + CompilationState& compilationState, + EmitterMode emitterMode) + : BytecodeEmitter(parent, sc, stencil, compilationState, emitterMode) { + ep_.emplace(parser); + this->parser = ep_.ptr(); + instrumentationKinds = this->parser->options().instrumentationKinds; +} + +void BytecodeEmitter::initFromBodyPosition(TokenPos bodyPosition) { + setScriptStartOffsetIfUnset(bodyPosition.begin); + setFunctionBodyEndPos(bodyPosition.end); +} + +bool BytecodeEmitter::init() { + if (!parent) { + if (!stencil.prepareStorageFor(cx, compilationState)) { + return false; + } + } + return perScriptData_.init(cx); +} + +bool BytecodeEmitter::init(TokenPos bodyPosition) { + initFromBodyPosition(bodyPosition); + return init(); +} + +template +T* BytecodeEmitter::findInnermostNestableControl() const { + return NestableControl::findNearest(innermostNestableControl); +} + +template bool */> +T* BytecodeEmitter::findInnermostNestableControl(Predicate predicate) const { + return NestableControl::findNearest(innermostNestableControl, predicate); +} + +NameLocation BytecodeEmitter::lookupName(const ParserAtom* name) { + return innermostEmitterScope()->lookup(this, name); +} + +Maybe BytecodeEmitter::locationOfNameBoundInScope( + const ParserAtom* name, EmitterScope* target) { + return innermostEmitterScope()->locationBoundInScope(name, target); +} + +template +Maybe BytecodeEmitter::locationOfNameBoundInScopeType( + const ParserAtom* name, EmitterScope* source) { + EmitterScope* aScope = source; + while (!aScope->scope(this).is()) { + aScope = aScope->enclosingInFrame(); + } + return source->locationBoundInScope(name, aScope); +} + +bool BytecodeEmitter::markStepBreakpoint() { + if (skipBreakpointSrcNotes()) { + return true; + } + + if (!emitInstrumentation(InstrumentationKind::Breakpoint)) { + return false; + } + + if (!newSrcNote(SrcNoteType::StepSep)) { + return false; + } + + if (!newSrcNote(SrcNoteType::Breakpoint)) { + return false; + } + + // We track the location of the most recent separator for use in + // markSimpleBreakpoint. Note that this means that the position must already + // be set before markStepBreakpoint is called. + bytecodeSection().updateSeparatorPosition(); + + return true; +} + +bool BytecodeEmitter::markSimpleBreakpoint() { + if (skipBreakpointSrcNotes()) { + return true; + } + + // If a breakable call ends up being the same location as the most recent + // expression start, we need to skip marking it breakable in order to avoid + // having two breakpoints with the same line/column position. + // Note: This assumes that the position for the call has already been set. + if (!bytecodeSection().isDuplicateLocation()) { + if (!emitInstrumentation(InstrumentationKind::Breakpoint)) { + return false; + } + + if (!newSrcNote(SrcNoteType::Breakpoint)) { + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitCheck(JSOp op, ptrdiff_t delta, + BytecodeOffset* offset) { + size_t oldLength = bytecodeSection().code().length(); + *offset = BytecodeOffset(oldLength); + + size_t newLength = oldLength + size_t(delta); + if (MOZ_UNLIKELY(newLength > MaxBytecodeLength)) { + ReportAllocationOverflow(cx); + return false; + } + + if (!bytecodeSection().code().growByUninitialized(delta)) { + return false; + } + + if (BytecodeOpHasIC(op)) { + // Even if every bytecode op is a JOF_IC op and the function has ARGC_LIMIT + // arguments, numICEntries cannot overflow. + static_assert(MaxBytecodeLength + 1 /* this */ + ARGC_LIMIT <= UINT32_MAX, + "numICEntries must not overflow"); + bytecodeSection().incrementNumICEntries(); + } + + return true; +} + +#ifdef DEBUG +bool BytecodeEmitter::checkStrictOrSloppy(JSOp op) { + if (IsCheckStrictOp(op) && !sc->strict()) { + return false; + } + if (IsCheckSloppyOp(op) && sc->strict()) { + return false; + } + return true; +} +#endif + +bool BytecodeEmitter::emit1(JSOp op) { + MOZ_ASSERT(checkStrictOrSloppy(op)); + + BytecodeOffset offset; + if (!emitCheck(op, 1, &offset)) { + return false; + } + + jsbytecode* code = bytecodeSection().code(offset); + code[0] = jsbytecode(op); + bytecodeSection().updateDepth(offset); + return true; +} + +bool BytecodeEmitter::emit2(JSOp op, uint8_t op1) { + MOZ_ASSERT(checkStrictOrSloppy(op)); + + BytecodeOffset offset; + if (!emitCheck(op, 2, &offset)) { + return false; + } + + jsbytecode* code = bytecodeSection().code(offset); + code[0] = jsbytecode(op); + code[1] = jsbytecode(op1); + bytecodeSection().updateDepth(offset); + return true; +} + +bool BytecodeEmitter::emit3(JSOp op, jsbytecode op1, jsbytecode op2) { + MOZ_ASSERT(checkStrictOrSloppy(op)); + + /* These should filter through emitVarOp. */ + MOZ_ASSERT(!IsArgOp(op)); + MOZ_ASSERT(!IsLocalOp(op)); + + BytecodeOffset offset; + if (!emitCheck(op, 3, &offset)) { + return false; + } + + jsbytecode* code = bytecodeSection().code(offset); + code[0] = jsbytecode(op); + code[1] = op1; + code[2] = op2; + bytecodeSection().updateDepth(offset); + return true; +} + +bool BytecodeEmitter::emitN(JSOp op, size_t extra, BytecodeOffset* offset) { + MOZ_ASSERT(checkStrictOrSloppy(op)); + ptrdiff_t length = 1 + ptrdiff_t(extra); + + BytecodeOffset off; + if (!emitCheck(op, length, &off)) { + return false; + } + + jsbytecode* code = bytecodeSection().code(off); + code[0] = jsbytecode(op); + /* The remaining |extra| bytes are set by the caller */ + + /* + * Don't updateDepth if op's use-count comes from the immediate + * operand yet to be stored in the extra bytes after op. + */ + if (CodeSpec(op).nuses >= 0) { + bytecodeSection().updateDepth(off); + } + + if (offset) { + *offset = off; + } + return true; +} + +bool BytecodeEmitter::emitJumpTargetOp(JSOp op, BytecodeOffset* off) { + MOZ_ASSERT(BytecodeIsJumpTarget(op)); + + // Record the current IC-entry index at start of this op. + uint32_t numEntries = bytecodeSection().numICEntries(); + + size_t n = GetOpLength(op) - 1; + MOZ_ASSERT(GetOpLength(op) >= 1 + ICINDEX_LEN); + + if (!emitN(op, n, off)) { + return false; + } + + SET_ICINDEX(bytecodeSection().code(*off), numEntries); + return true; +} + +bool BytecodeEmitter::emitJumpTarget(JumpTarget* target) { + BytecodeOffset off = bytecodeSection().offset(); + + // Alias consecutive jump targets. + if (bytecodeSection().lastTargetOffset().valid() && + off == bytecodeSection().lastTargetOffset() + + BytecodeOffsetDiff(JSOpLength_JumpTarget)) { + target->offset = bytecodeSection().lastTargetOffset(); + return true; + } + + target->offset = off; + bytecodeSection().setLastTargetOffset(off); + + BytecodeOffset opOff; + return emitJumpTargetOp(JSOp::JumpTarget, &opOff); +} + +bool BytecodeEmitter::emitJumpNoFallthrough(JSOp op, JumpList* jump) { + BytecodeOffset offset; + if (!emitCheck(op, 5, &offset)) { + return false; + } + + jsbytecode* code = bytecodeSection().code(offset); + code[0] = jsbytecode(op); + MOZ_ASSERT(!jump->offset.valid() || + (0 <= jump->offset.value() && jump->offset < offset)); + jump->push(bytecodeSection().code(BytecodeOffset(0)), offset); + bytecodeSection().updateDepth(offset); + return true; +} + +bool BytecodeEmitter::emitJump(JSOp op, JumpList* jump) { + if (!emitJumpNoFallthrough(op, jump)) { + return false; + } + if (BytecodeFallsThrough(op)) { + JumpTarget fallthrough; + if (!emitJumpTarget(&fallthrough)) { + return false; + } + } + return true; +} + +void BytecodeEmitter::patchJumpsToTarget(JumpList jump, JumpTarget target) { + MOZ_ASSERT( + !jump.offset.valid() || + (0 <= jump.offset.value() && jump.offset <= bytecodeSection().offset())); + MOZ_ASSERT(0 <= target.offset.value() && + target.offset <= bytecodeSection().offset()); + MOZ_ASSERT_IF( + jump.offset.valid() && + target.offset + BytecodeOffsetDiff(4) <= bytecodeSection().offset(), + BytecodeIsJumpTarget(JSOp(*bytecodeSection().code(target.offset)))); + jump.patchAll(bytecodeSection().code(BytecodeOffset(0)), target); +} + +bool BytecodeEmitter::emitJumpTargetAndPatch(JumpList jump) { + if (!jump.offset.valid()) { + return true; + } + JumpTarget target; + if (!emitJumpTarget(&target)) { + return false; + } + patchJumpsToTarget(jump, target); + 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) { + return emitCall(op, argc, pn ? Some(pn->pn_pos.begin) : Nothing()); +} + +bool BytecodeEmitter::emitDupAt(unsigned slotFromTop, unsigned count) { + MOZ_ASSERT(slotFromTop < unsigned(bytecodeSection().stackDepth())); + 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 >= Bit(24)) { + reportError(nullptr, JSMSG_TOO_MANY_LOCALS); + return false; + } + + for (unsigned i = 0; i < count; i++) { + BytecodeOffset off; + if (!emitN(JSOp::DupAt, 3, &off)) { + return false; + } + + jsbytecode* pc = bytecodeSection().code(off); + SET_UINT24(pc, slotFromTop); + } + + return true; +} + +bool BytecodeEmitter::emitPopN(unsigned n) { + MOZ_ASSERT(n != 0); + + if (n == 1) { + return emit1(JSOp::Pop); + } + + // 2 JSOp::Pop instructions (2 bytes) are shorter than JSOp::PopN (3 bytes). + if (n == 2) { + return emit1(JSOp::Pop) && emit1(JSOp::Pop); + } + + 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) { + return emit2(JSOp::CheckIsObj, uint8_t(kind)); +} + +bool BytecodeEmitter::emitBuiltinObject(BuiltinObjectKind kind) { + return emit2(JSOp::BuiltinObject, uint8_t(kind)); +} + +/* Updates line number notes, not column notes. */ +bool BytecodeEmitter::updateLineNumberNotes(uint32_t offset) { + if (skipLocationSrcNotes()) { + return true; + } + + ErrorReporter* er = &parser->errorReporter(); + bool onThisLine; + if (!er->isOnThisLine(offset, bytecodeSection().currentLine(), &onThisLine)) { + er->errorNoOffset(JSMSG_OUT_OF_MEMORY); + return false; + } + + if (!onThisLine) { + unsigned line = er->lineAt(offset); + unsigned delta = line - bytecodeSection().currentLine(); + + // If we use a `SetLine` note below, we want it to be relative to the + // scripts initial line number for better chance of sharing. + unsigned initialLine = sc->extent().lineno; + MOZ_ASSERT(line >= initialLine); + + /* + * Encode any change in the current source line number by using + * either several SrcNoteType::NewLine notes or just one + * SrcNoteType::SetLine note, whichever consumes less space. + * + * NB: We handle backward line number deltas (possible with for + * loops where the update part is emitted after the body, but its + * line number is <= any line number in the body) here by letting + * unsigned delta_ wrap to a very large number, which triggers a + * SrcNoteType::SetLine. + */ + bytecodeSection().setCurrentLine(line, offset); + if (delta >= SrcNote::SetLine::lengthFor(line, initialLine)) { + if (!newSrcNote2(SrcNoteType::SetLine, + SrcNote::SetLine::toOperand(line, initialLine))) { + return false; + } + } else { + do { + if (!newSrcNote(SrcNoteType::NewLine)) { + return false; + } + } while (--delta != 0); + } + + bytecodeSection().updateSeparatorPositionIfPresent(); + } + return true; +} + +/* Updates the line number and column number information in the source notes. */ +bool BytecodeEmitter::updateSourceCoordNotes(uint32_t offset) { + if (!updateLineNumberNotes(offset)) { + return false; + } + + if (skipLocationSrcNotes()) { + return true; + } + + uint32_t columnIndex = parser->errorReporter().columnAt(offset); + MOZ_ASSERT(columnIndex <= ColumnLimit); + + // Assert colspan is always representable. + static_assert((0 - ptrdiff_t(ColumnLimit)) >= SrcNote::ColSpan::MinColSpan); + static_assert((ptrdiff_t(ColumnLimit) - 0) <= SrcNote::ColSpan::MaxColSpan); + + ptrdiff_t colspan = + ptrdiff_t(columnIndex) - ptrdiff_t(bytecodeSection().lastColumn()); + + if (colspan != 0) { + if (!newSrcNote2(SrcNoteType::ColSpan, + SrcNote::ColSpan::toOperand(colspan))) { + return false; + } + bytecodeSection().setLastColumn(columnIndex, offset); + bytecodeSection().updateSeparatorPositionIfPresent(); + } + return true; +} + +Maybe BytecodeEmitter::getOffsetForLoop(ParseNode* nextpn) { + if (!nextpn) { + return Nothing(); + } + + // Try to give the JSOp::LoopHead the same line number as the next + // instruction. nextpn is often a block, in which case the next instruction + // typically comes from the first statement inside. + if (nextpn->is()) { + nextpn = nextpn->as().scopeBody(); + } + if (nextpn->isKind(ParseNodeKind::StatementList)) { + if (ParseNode* firstStatement = nextpn->as().head()) { + nextpn = firstStatement; + } + } + + return Some(nextpn->pn_pos.begin); +} + +bool BytecodeEmitter::emitUint16Operand(JSOp op, uint32_t operand) { + MOZ_ASSERT(operand <= UINT16_MAX); + if (!emit3(op, UINT16_LO(operand), UINT16_HI(operand))) { + return false; + } + return true; +} + +bool BytecodeEmitter::emitUint32Operand(JSOp op, uint32_t operand) { + BytecodeOffset off; + if (!emitN(op, 4, &off)) { + return false; + } + SET_UINT32(bytecodeSection().code(off), operand); + return true; +} + +namespace { + +class NonLocalExitControl { + public: + enum Kind { + // IteratorClose is handled especially inside the exception unwinder. + Throw, + + // A 'continue' statement does not call IteratorClose for the loop it + // is continuing, i.e. excluding the target loop. + Continue, + + // A 'break' or 'return' statement does call IteratorClose for the + // loop it is breaking out of or returning from, i.e. including the + // target loop. + Break, + Return + }; + + private: + BytecodeEmitter* bce_; + const uint32_t savedScopeNoteIndex_; + const int savedDepth_; + uint32_t openScopeNoteIndex_; + Kind kind_; + + NonLocalExitControl(const NonLocalExitControl&) = delete; + + MOZ_MUST_USE bool leaveScope(EmitterScope* scope); + + public: + NonLocalExitControl(BytecodeEmitter* bce, Kind kind) + : bce_(bce), + savedScopeNoteIndex_(bce->bytecodeSection().scopeNoteList().length()), + savedDepth_(bce->bytecodeSection().stackDepth()), + openScopeNoteIndex_(bce->innermostEmitterScope()->noteIndex()), + kind_(kind) {} + + ~NonLocalExitControl() { + for (uint32_t n = savedScopeNoteIndex_; + n < bce_->bytecodeSection().scopeNoteList().length(); n++) { + bce_->bytecodeSection().scopeNoteList().recordEnd( + n, bce_->bytecodeSection().offset()); + } + bce_->bytecodeSection().setStackDepth(savedDepth_); + } + + MOZ_MUST_USE bool prepareForNonLocalJump(NestableControl* target); + + MOZ_MUST_USE bool prepareForNonLocalJumpToOutermost() { + return prepareForNonLocalJump(nullptr); + } +}; + +bool NonLocalExitControl::leaveScope(EmitterScope* es) { + if (!es->leave(bce_, /* nonLocal = */ true)) { + return false; + } + + // As we pop each scope due to the non-local jump, emit notes that + // record the extent of the enclosing scope. These notes will have + // their ends recorded in ~NonLocalExitControl(). + GCThingIndex enclosingScopeIndex = ScopeNote::NoScopeIndex; + if (es->enclosingInFrame()) { + enclosingScopeIndex = es->enclosingInFrame()->index(); + } + if (!bce_->bytecodeSection().scopeNoteList().append( + enclosingScopeIndex, bce_->bytecodeSection().offset(), + openScopeNoteIndex_)) { + return false; + } + openScopeNoteIndex_ = bce_->bytecodeSection().scopeNoteList().length() - 1; + + return true; +} + +/* + * Emit additional bytecode(s) for non-local jumps. + */ +bool NonLocalExitControl::prepareForNonLocalJump(NestableControl* target) { + EmitterScope* es = bce_->innermostEmitterScope(); + int npops = 0; + + AutoCheckUnstableEmitterScope cues(bce_); + + // For 'continue', 'break', and 'return' statements, emit IteratorClose + // bytecode inline. 'continue' statements do not call IteratorClose for + // the loop they are continuing. + bool emitIteratorClose = + kind_ == Continue || kind_ == Break || kind_ == Return; + bool emitIteratorCloseAtTarget = emitIteratorClose && kind_ != Continue; + + auto flushPops = [&npops](BytecodeEmitter* bce) { + if (npops && !bce->emitPopN(npops)) { + return false; + } + npops = 0; + return true; + }; + + // If we are closing multiple for-of loops, the resulting FOR_OF_ITERCLOSE + // trynotes must be appropriately nested. Each FOR_OF_ITERCLOSE starts when + // we close the corresponding for-of iterator, and continues until the + // actual jump. + Vector forOfIterCloseScopeStarts(bce_->cx); + + // Walk the nestable control stack and patch jumps. + for (NestableControl* control = bce_->innermostNestableControl; + control != target; control = control->enclosing()) { + // Walk the scope stack and leave the scopes we entered. Leaving a scope + // may emit administrative ops like JSOp::PopLexicalEnv but never anything + // that manipulates the stack. + for (; es != control->emitterScope(); es = es->enclosingInFrame()) { + if (!leaveScope(es)) { + return false; + } + } + + switch (control->kind()) { + case StatementKind::Finally: { + TryFinallyControl& finallyControl = control->as(); + if (finallyControl.emittingSubroutine()) { + /* + * There's a [exception or hole, retsub pc-index] pair and the + * possible return value on the stack that we need to pop. + */ + npops += 3; + } else { + if (!flushPops(bce_)) { + return false; + } + if (!bce_->emitGoSub(&finallyControl.gosubs)) { + // [stack] ... + return false; + } + } + break; + } + + case StatementKind::ForOfLoop: + if (emitIteratorClose) { + if (!flushPops(bce_)) { + return false; + } + BytecodeOffset tryNoteStart; + ForOfLoopControl& loopinfo = control->as(); + if (!loopinfo.emitPrepareForNonLocalJumpFromScope( + bce_, *es, + /* isTarget = */ false, &tryNoteStart)) { + // [stack] ... + return false; + } + if (!forOfIterCloseScopeStarts.append(tryNoteStart)) { + return false; + } + } else { + // The iterator next method, the iterator, and the current + // value are on the stack. + npops += 3; + } + break; + + case StatementKind::ForInLoop: + if (!flushPops(bce_)) { + return false; + } + + // The iterator and the current value are on the stack. + if (!bce_->emit1(JSOp::EndIter)) { + // [stack] ... + return false; + } + break; + + default: + break; + } + } + + if (!flushPops(bce_)) { + return false; + } + + if (target && emitIteratorCloseAtTarget && target->is()) { + BytecodeOffset tryNoteStart; + ForOfLoopControl& loopinfo = target->as(); + if (!loopinfo.emitPrepareForNonLocalJumpFromScope(bce_, *es, + /* isTarget = */ true, + &tryNoteStart)) { + // [stack] ... UNDEF UNDEF UNDEF + return false; + } + if (!forOfIterCloseScopeStarts.append(tryNoteStart)) { + return false; + } + } + + EmitterScope* targetEmitterScope = + target ? target->emitterScope() : bce_->varEmitterScope; + for (; es != targetEmitterScope; es = es->enclosingInFrame()) { + if (!leaveScope(es)) { + return false; + } + } + + // Close FOR_OF_ITERCLOSE trynotes. + BytecodeOffset end = bce_->bytecodeSection().offset(); + for (BytecodeOffset start : forOfIterCloseScopeStarts) { + if (!bce_->addTryNote(TryNoteKind::ForOfIterClose, 0, start, end)) { + return false; + } + } + + return true; +} + +} // anonymous namespace + +bool BytecodeEmitter::emitGoto(NestableControl* target, JumpList* jumplist, + GotoKind kind) { + NonLocalExitControl nle(this, kind == GotoKind::Continue + ? NonLocalExitControl::Continue + : NonLocalExitControl::Break); + + if (!nle.prepareForNonLocalJump(target)) { + return false; + } + + return emitJump(JSOp::Goto, jumplist); +} + +AbstractScopePtr BytecodeEmitter::innermostScope() const { + return innermostEmitterScope()->scope(this); +} + +ScopeIndex BytecodeEmitter::innermostScopeIndex() const { + return *innermostEmitterScope()->scopeIndex(this); +} + +bool BytecodeEmitter::emitGCIndexOp(JSOp op, GCThingIndex index) { + MOZ_ASSERT(checkStrictOrSloppy(op)); + + constexpr size_t OpLength = 1 + GCTHING_INDEX_LEN; + MOZ_ASSERT(GetOpLength(op) == OpLength); + + BytecodeOffset offset; + if (!emitCheck(op, OpLength, &offset)) { + return false; + } + + jsbytecode* code = bytecodeSection().code(offset); + code[0] = jsbytecode(op); + SET_GCTHING_INDEX(code, index); + bytecodeSection().updateDepth(offset); + return true; +} + +bool BytecodeEmitter::emitAtomOp(JSOp op, const ParserAtom* atom, + ShouldInstrument shouldInstrument) { + MOZ_ASSERT(atom); + + // .generator lookups should be emitted as JSOp::GetAliasedVar instead of + // JSOp::GetName etc, to bypass |with| objects on the scope chain. + // It's safe to emit .this lookups though because |with| objects skip + // those. + MOZ_ASSERT_IF(op == JSOp::GetName || op == JSOp::GetGName, + atom != cx->parserNames().dotGenerator); + + GCThingIndex index; + if (!makeAtomIndex(atom, &index)) { + return false; + } + + return emitAtomOp(op, index, shouldInstrument); +} + +bool BytecodeEmitter::emitAtomOp(JSOp op, GCThingIndex atomIndex, + ShouldInstrument shouldInstrument) { + MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM); + + if (shouldInstrument != ShouldInstrument::No && + !emitInstrumentationForOpcode(op, atomIndex)) { + return false; + } + + return emitGCIndexOp(op, atomIndex); +} + +bool BytecodeEmitter::emitInternedScopeOp(GCThingIndex index, JSOp op) { + MOZ_ASSERT(JOF_OPTYPE(op) == JOF_SCOPE); + MOZ_ASSERT(index < perScriptData().gcThingList().length()); + return emitGCIndexOp(op, index); +} + +bool BytecodeEmitter::emitInternedObjectOp(GCThingIndex index, JSOp op) { + MOZ_ASSERT(JOF_OPTYPE(op) == JOF_OBJECT); + MOZ_ASSERT(index < perScriptData().gcThingList().length()); + return emitGCIndexOp(op, index); +} + +bool BytecodeEmitter::emitObjectPairOp(GCThingIndex index1, GCThingIndex index2, + JSOp op) { + MOZ_ASSERT(index1 + 1 == index2, "object pair indices must be adjacent"); + return emitInternedObjectOp(index1, op); +} + +bool BytecodeEmitter::emitRegExp(GCThingIndex index) { + return emitGCIndexOp(JSOp::RegExp, index); +} + +bool BytecodeEmitter::emitLocalOp(JSOp op, uint32_t slot) { + MOZ_ASSERT(JOF_OPTYPE(op) != JOF_ENVCOORD); + MOZ_ASSERT(IsLocalOp(op)); + + BytecodeOffset off; + if (!emitN(op, LOCALNO_LEN, &off)) { + return false; + } + + SET_LOCALNO(bytecodeSection().code(off), slot); + return true; +} + +bool BytecodeEmitter::emitArgOp(JSOp op, uint16_t slot) { + MOZ_ASSERT(IsArgOp(op)); + BytecodeOffset off; + if (!emitN(op, ARGNO_LEN, &off)) { + return false; + } + + SET_ARGNO(bytecodeSection().code(off), slot); + return true; +} + +bool BytecodeEmitter::emitEnvCoordOp(JSOp op, EnvironmentCoordinate ec) { + MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ENVCOORD); + + constexpr size_t N = ENVCOORD_HOPS_LEN + ENVCOORD_SLOT_LEN; + MOZ_ASSERT(GetOpLength(op) == 1 + N); + + BytecodeOffset off; + if (!emitN(op, N, &off)) { + return false; + } + + jsbytecode* pc = bytecodeSection().code(off); + SET_ENVCOORD_HOPS(pc, ec.hops()); + pc += ENVCOORD_HOPS_LEN; + SET_ENVCOORD_SLOT(pc, ec.slot()); + pc += ENVCOORD_SLOT_LEN; + return true; +} + +JSOp BytecodeEmitter::strictifySetNameOp(JSOp op) { + switch (op) { + case JSOp::SetName: + if (sc->strict()) { + op = JSOp::StrictSetName; + } + break; + case JSOp::SetGName: + if (sc->strict()) { + op = JSOp::StrictSetGName; + } + break; + default:; + } + return op; +} + +bool BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) { + if (!CheckRecursionLimit(cx)) { + return false; + } + +restart: + + switch (pn->getKind()) { + // Trivial cases with no side effects. + case ParseNodeKind::EmptyStmt: + case ParseNodeKind::TrueExpr: + case ParseNodeKind::FalseExpr: + case ParseNodeKind::NullExpr: + case ParseNodeKind::RawUndefinedExpr: + case ParseNodeKind::Elision: + case ParseNodeKind::Generator: + MOZ_ASSERT(pn->is()); + *answer = false; + return true; + + case ParseNodeKind::ObjectPropertyName: + case ParseNodeKind::PrivateName: // no side effects, unlike + // ParseNodeKind::Name + case ParseNodeKind::StringExpr: + case ParseNodeKind::TemplateStringExpr: + MOZ_ASSERT(pn->is()); + *answer = false; + return true; + + case ParseNodeKind::RegExpExpr: + MOZ_ASSERT(pn->is()); + *answer = false; + return true; + + case ParseNodeKind::NumberExpr: + MOZ_ASSERT(pn->is()); + *answer = false; + return true; + + case ParseNodeKind::BigIntExpr: + MOZ_ASSERT(pn->is()); + *answer = false; + return true; + + // |this| can throw in derived class constructors, including nested arrow + // functions or eval. + case ParseNodeKind::ThisExpr: + MOZ_ASSERT(pn->is()); + *answer = sc->needsThisTDZChecks(); + return true; + + // Trivial binary nodes with more token pos holders. + case ParseNodeKind::NewTargetExpr: + case ParseNodeKind::ImportMetaExpr: { + MOZ_ASSERT(pn->as().left()->isKind(ParseNodeKind::PosHolder)); + MOZ_ASSERT( + pn->as().right()->isKind(ParseNodeKind::PosHolder)); + *answer = false; + return true; + } + + case ParseNodeKind::BreakStmt: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + case ParseNodeKind::ContinueStmt: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + case ParseNodeKind::DebuggerStmt: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // Watch out for getters! + case ParseNodeKind::OptionalDotExpr: + case ParseNodeKind::DotExpr: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // Unary cases with side effects only if the child has them. + case ParseNodeKind::TypeOfExpr: + case ParseNodeKind::VoidExpr: + case ParseNodeKind::NotExpr: + return checkSideEffects(pn->as().kid(), answer); + + // Even if the name expression is effect-free, performing ToPropertyKey on + // it might not be effect-free: + // + // RegExp.prototype.toString = () => { throw 42; }; + // ({ [/regex/]: 0 }); // ToPropertyKey(/regex/) throws 42 + // + // function Q() { + // ({ [new.target]: 0 }); + // } + // Q.toString = () => { throw 17; }; + // new Q; // new.target will be Q, ToPropertyKey(Q) throws 17 + case ParseNodeKind::ComputedName: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // Looking up or evaluating the associated name could throw. + case ParseNodeKind::TypeOfNameExpr: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // This unary case has side effects on the enclosing object, sure. But + // that's not the question this function answers: it's whether the + // operation may have a side effect on something *other* than the result + // of the overall operation in which it's embedded. The answer to that + // is no, because an object literal having a mutated prototype only + // produces a value, without affecting anything else. + case ParseNodeKind::MutateProto: + return checkSideEffects(pn->as().kid(), answer); + + // Unary cases with obvious side effects. + case ParseNodeKind::PreIncrementExpr: + case ParseNodeKind::PostIncrementExpr: + case ParseNodeKind::PreDecrementExpr: + case ParseNodeKind::PostDecrementExpr: + case ParseNodeKind::ThrowStmt: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // These might invoke valueOf/toString, even with a subexpression without + // side effects! Consider |+{ valueOf: null, toString: null }|. + case ParseNodeKind::BitNotExpr: + case ParseNodeKind::PosExpr: + case ParseNodeKind::NegExpr: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // This invokes the (user-controllable) iterator protocol. + case ParseNodeKind::Spread: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + case ParseNodeKind::InitialYield: + case ParseNodeKind::YieldStarExpr: + case ParseNodeKind::YieldExpr: + case ParseNodeKind::AwaitExpr: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // Deletion generally has side effects, even if isolated cases have none. + case ParseNodeKind::DeleteNameExpr: + case ParseNodeKind::DeletePropExpr: + case ParseNodeKind::DeleteElemExpr: + case ParseNodeKind::DeleteOptionalChainExpr: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // Deletion of a non-Reference expression has side effects only through + // evaluating the expression. + case ParseNodeKind::DeleteExpr: { + ParseNode* expr = pn->as().kid(); + return checkSideEffects(expr, answer); + } + + case ParseNodeKind::ExpressionStmt: + return checkSideEffects(pn->as().kid(), answer); + + // Binary cases with obvious side effects. + case ParseNodeKind::InitExpr: + *answer = true; + return true; + + case ParseNodeKind::AssignExpr: + case ParseNodeKind::AddAssignExpr: + case ParseNodeKind::SubAssignExpr: + case ParseNodeKind::CoalesceAssignExpr: + case ParseNodeKind::OrAssignExpr: + case ParseNodeKind::AndAssignExpr: + case ParseNodeKind::BitOrAssignExpr: + case ParseNodeKind::BitXorAssignExpr: + case ParseNodeKind::BitAndAssignExpr: + case ParseNodeKind::LshAssignExpr: + case ParseNodeKind::RshAssignExpr: + case ParseNodeKind::UrshAssignExpr: + case ParseNodeKind::MulAssignExpr: + case ParseNodeKind::DivAssignExpr: + case ParseNodeKind::ModAssignExpr: + case ParseNodeKind::PowAssignExpr: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + case ParseNodeKind::SetThis: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + case ParseNodeKind::StatementList: + // Strict equality operations and short circuit operators are well-behaved + // and perform no conversions. + case ParseNodeKind::CoalesceExpr: + case ParseNodeKind::OrExpr: + case ParseNodeKind::AndExpr: + case ParseNodeKind::StrictEqExpr: + case ParseNodeKind::StrictNeExpr: + // Any subexpression of a comma expression could be effectful. + case ParseNodeKind::CommaExpr: + MOZ_ASSERT(!pn->as().empty()); + [[fallthrough]]; + // Subcomponents of a literal may be effectful. + case ParseNodeKind::ArrayExpr: + case ParseNodeKind::ObjectExpr: + for (ParseNode* item : pn->as().contents()) { + if (!checkSideEffects(item, answer)) { + return false; + } + if (*answer) { + return true; + } + } + return true; + + // Most other binary operations (parsed as lists in SpiderMonkey) may + // perform conversions triggering side effects. Math operations perform + // ToNumber and may fail invoking invalid user-defined toString/valueOf: + // |5 < { toString: null }|. |instanceof| throws if provided a + // non-object constructor: |null instanceof null|. |in| throws if given + // a non-object RHS: |5 in null|. + case ParseNodeKind::BitOrExpr: + case ParseNodeKind::BitXorExpr: + case ParseNodeKind::BitAndExpr: + case ParseNodeKind::EqExpr: + case ParseNodeKind::NeExpr: + case ParseNodeKind::LtExpr: + case ParseNodeKind::LeExpr: + case ParseNodeKind::GtExpr: + case ParseNodeKind::GeExpr: + case ParseNodeKind::InstanceOfExpr: + case ParseNodeKind::InExpr: + case ParseNodeKind::LshExpr: + case ParseNodeKind::RshExpr: + case ParseNodeKind::UrshExpr: + case ParseNodeKind::AddExpr: + case ParseNodeKind::SubExpr: + case ParseNodeKind::MulExpr: + case ParseNodeKind::DivExpr: + case ParseNodeKind::ModExpr: + case ParseNodeKind::PowExpr: + MOZ_ASSERT(pn->as().count() >= 2); + *answer = true; + return true; + + case ParseNodeKind::PropertyDefinition: + case ParseNodeKind::Case: { + BinaryNode* node = &pn->as(); + if (!checkSideEffects(node->left(), answer)) { + return false; + } + if (*answer) { + return true; + } + return checkSideEffects(node->right(), answer); + } + + // More getters. + case ParseNodeKind::OptionalElemExpr: + case ParseNodeKind::ElemExpr: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // These affect visible names in this code, or in other code. + case ParseNodeKind::ImportDecl: + case ParseNodeKind::ExportFromStmt: + case ParseNodeKind::ExportDefaultStmt: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // Likewise. + case ParseNodeKind::ExportStmt: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + case ParseNodeKind::CallImportExpr: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // Every part of a loop might be effect-free, but looping infinitely *is* + // an effect. (Language lawyer trivia: C++ says threads can be assumed + // to exit or have side effects, C++14 [intro.multithread]p27, so a C++ + // implementation's equivalent of the below could set |*answer = false;| + // if all loop sub-nodes set |*answer = false|!) + case ParseNodeKind::DoWhileStmt: + case ParseNodeKind::WhileStmt: + case ParseNodeKind::ForStmt: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // Declarations affect the name set of the relevant scope. + case ParseNodeKind::VarStmt: + case ParseNodeKind::ConstDecl: + case ParseNodeKind::LetDecl: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + case ParseNodeKind::IfStmt: + case ParseNodeKind::ConditionalExpr: { + TernaryNode* node = &pn->as(); + if (!checkSideEffects(node->kid1(), answer)) { + return false; + } + if (*answer) { + return true; + } + if (!checkSideEffects(node->kid2(), answer)) { + return false; + } + if (*answer) { + return true; + } + if ((pn = node->kid3())) { + goto restart; + } + return true; + } + + // Function calls can invoke non-local code. + case ParseNodeKind::NewExpr: + case ParseNodeKind::CallExpr: + case ParseNodeKind::OptionalCallExpr: + case ParseNodeKind::TaggedTemplateExpr: + case ParseNodeKind::SuperCallExpr: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // Function arg lists can contain arbitrary expressions. Technically + // this only causes side-effects if one of the arguments does, but since + // the call being made will always trigger side-effects, it isn't needed. + case ParseNodeKind::Arguments: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + case ParseNodeKind::OptionalChain: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + case ParseNodeKind::PipelineExpr: + MOZ_ASSERT(pn->as().count() >= 2); + *answer = true; + return true; + + // Classes typically introduce names. Even if no name is introduced, + // the heritage and/or class body (through computed property names) + // usually have effects. + case ParseNodeKind::ClassDecl: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // |with| calls |ToObject| on its expression and so throws if that value + // is null/undefined. + case ParseNodeKind::WithStmt: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + case ParseNodeKind::ReturnStmt: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + case ParseNodeKind::Name: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // Shorthands could trigger getters: the |x| in the object literal in + // |with ({ get x() { throw 42; } }) ({ x });|, for example, triggers + // one. (Of course, it isn't necessary to use |with| for a shorthand to + // trigger a getter.) + case ParseNodeKind::Shorthand: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + case ParseNodeKind::Function: + MOZ_ASSERT(pn->is()); + /* + * A named function, contrary to ES3, is no longer effectful, because + * we bind its name lexically (using JSOp::Callee) instead of creating + * an Object instance and binding a readonly, permanent property in it + * (the object and binding can be detected and hijacked or captured). + * This is a bug fix to ES3; it is fixed in ES3.1 drafts. + */ + *answer = false; + return true; + + case ParseNodeKind::Module: + *answer = false; + return true; + + case ParseNodeKind::TryStmt: { + TryNode* tryNode = &pn->as(); + if (!checkSideEffects(tryNode->body(), answer)) { + return false; + } + if (*answer) { + return true; + } + if (LexicalScopeNode* catchScope = tryNode->catchScope()) { + if (!checkSideEffects(catchScope, answer)) { + return false; + } + if (*answer) { + return true; + } + } + if (ParseNode* finallyBlock = tryNode->finallyBlock()) { + if (!checkSideEffects(finallyBlock, answer)) { + return false; + } + } + return true; + } + + case ParseNodeKind::Catch: { + BinaryNode* catchClause = &pn->as(); + if (ParseNode* name = catchClause->left()) { + if (!checkSideEffects(name, answer)) { + return false; + } + if (*answer) { + return true; + } + } + return checkSideEffects(catchClause->right(), answer); + } + + case ParseNodeKind::SwitchStmt: { + SwitchStatement* switchStmt = &pn->as(); + if (!checkSideEffects(&switchStmt->discriminant(), answer)) { + return false; + } + return *answer || + checkSideEffects(&switchStmt->lexicalForCaseList(), answer); + } + + case ParseNodeKind::LabelStmt: + return checkSideEffects(pn->as().statement(), answer); + + case ParseNodeKind::LexicalScope: + return checkSideEffects(pn->as().scopeBody(), answer); + + // We could methodically check every interpolated expression, but it's + // probably not worth the trouble. Treat template strings as effect-free + // only if they don't contain any substitutions. + case ParseNodeKind::TemplateStringListExpr: { + ListNode* list = &pn->as(); + MOZ_ASSERT(!list->empty()); + MOZ_ASSERT((list->count() % 2) == 1, + "template strings must alternate template and substitution " + "parts"); + *answer = list->count() > 1; + return true; + } + + // This should be unreachable but is left as-is for now. + case ParseNodeKind::ParamsBody: + *answer = true; + return true; + + case ParseNodeKind::ForIn: // by ParseNodeKind::For + case ParseNodeKind::ForOf: // by ParseNodeKind::For + case ParseNodeKind::ForHead: // by ParseNodeKind::For + case ParseNodeKind::ClassMethod: // by ParseNodeKind::ClassDecl + case ParseNodeKind::ClassField: // by ParseNodeKind::ClassDecl + case ParseNodeKind::ClassNames: // by ParseNodeKind::ClassDecl + case ParseNodeKind::ClassMemberList: // by ParseNodeKind::ClassDecl + case ParseNodeKind::ImportSpecList: // by ParseNodeKind::Import + case ParseNodeKind::ImportSpec: // by ParseNodeKind::Import + case ParseNodeKind::ExportBatchSpecStmt: // by ParseNodeKind::Export + case ParseNodeKind::ExportSpecList: // by ParseNodeKind::Export + case ParseNodeKind::ExportSpec: // by ParseNodeKind::Export + case ParseNodeKind::CallSiteObj: // by ParseNodeKind::TaggedTemplate + case ParseNodeKind::PosHolder: // by ParseNodeKind::NewTarget + case ParseNodeKind::SuperBase: // by ParseNodeKind::Elem and others + case ParseNodeKind::PropertyNameExpr: // by ParseNodeKind::Dot + MOZ_CRASH("handled by parent nodes"); + + case ParseNodeKind::LastUnused: + case ParseNodeKind::Limit: + MOZ_CRASH("invalid node kind"); + } + + MOZ_CRASH( + "invalid, unenumerated ParseNodeKind value encountered in " + "BytecodeEmitter::checkSideEffects"); +} + +bool BytecodeEmitter::isInLoop() { + return findInnermostNestableControl(); +} + +bool BytecodeEmitter::checkSingletonContext() { + MOZ_ASSERT_IF(sc->treatAsRunOnce(), sc->isTopLevelContext()); + return sc->treatAsRunOnce() && !isInLoop(); +} + +bool BytecodeEmitter::needsImplicitThis() { + // Short-circuit if there is an enclosing 'with' scope. + if (sc->inWith()) { + return true; + } + + // Otherwise see if the current point is under a 'with'. + for (EmitterScope* es = innermostEmitterScope(); es; + es = es->enclosingInFrame()) { + if (es->scope(this).kind() == ScopeKind::With) { + return true; + } + } + + return false; +} + +size_t BytecodeEmitter::countThisEnvironmentHops() { + unsigned numHops = 0; + + for (BytecodeEmitter* current = this; current; current = current->parent) { + for (EmitterScope* es = current->innermostEmitterScope(); es; + es = es->enclosingInFrame()) { + if (es->scope(current).is()) { + if (!es->scope(current).isArrow()) { + // The Parser is responsible for marking the environment as either + // closed-over or used-by-eval which ensure that is must exist. + MOZ_ASSERT(es->scope(current).hasEnvironment()); + return numHops; + } + } + if (es->scope(current).hasEnvironment()) { + numHops++; + } + } + } + + // The "this" environment exists outside of the compilation, but the + // `ScopeContext` recorded the number of additional hops needed, so add + // those in now. + MOZ_ASSERT(sc->allowSuperProperty()); + numHops += compilationState.scopeContext.enclosingThisEnvironmentHops; + return numHops; +} + +bool BytecodeEmitter::emitThisEnvironmentCallee() { + // Get the innermost enclosing function that has a |this| binding. + + // Directly load callee from the frame if possible. + if (sc->isFunctionBox() && !sc->asFunctionBox()->isArrow()) { + return emit1(JSOp::Callee); + } + + // We have to load the callee from the environment chain. + size_t numHops = countThisEnvironmentHops(); + + static_assert( + ENVCOORD_HOPS_LIMIT - 1 <= UINT8_MAX, + "JSOp::EnvCallee operand size should match ENVCOORD_HOPS_LIMIT"); + + // Note: we need to check numHops here because we don't call + // checkEnvironmentChainLength in all cases (like 'eval'). + if (numHops >= ENVCOORD_HOPS_LIMIT - 1) { + reportError(nullptr, JSMSG_TOO_DEEP, js_function_str); + return false; + } + + return emit2(JSOp::EnvCallee, numHops); +} + +bool BytecodeEmitter::emitSuperBase() { + if (!emitThisEnvironmentCallee()) { + return false; + } + + return emit1(JSOp::SuperBase); +} + +void BytecodeEmitter::reportNeedMoreArgsError(ParseNode* pn, + const char* errorName, + const char* requiredArgs, + const char* pluralizer, + const ListNode* argsList) { + char actualArgsStr[40]; + SprintfLiteral(actualArgsStr, "%u", argsList->count()); + reportError(pn, JSMSG_MORE_ARGS_NEEDED, errorName, requiredArgs, pluralizer, + actualArgsStr); +} + +void BytecodeEmitter::reportError(ParseNode* pn, unsigned errorNumber, ...) { + uint32_t offset = pn ? pn->pn_pos.begin : *scriptStartOffset; + + va_list args; + va_start(args, errorNumber); + + parser->errorReporter().errorWithNotesAtVA(nullptr, AsVariant(offset), + errorNumber, &args); + + va_end(args); +} + +void BytecodeEmitter::reportError(const Maybe& maybeOffset, + unsigned errorNumber, ...) { + uint32_t offset = maybeOffset ? *maybeOffset : *scriptStartOffset; + + va_list args; + va_start(args, errorNumber); + + parser->errorReporter().errorWithNotesAtVA(nullptr, AsVariant(offset), + errorNumber, &args); + + va_end(args); +} + +bool BytecodeEmitter::addObjLiteralData(ObjLiteralWriter& writer, + GCThingIndex* outIndex) { + size_t len = writer.getCode().size(); + auto* code = stencil.alloc.newArrayUninitialized(len); + if (!code) { + js::ReportOutOfMemory(cx); + return false; + } + memcpy(code, writer.getCode().data(), len); + + ObjLiteralIndex objIndex(stencil.objLiteralData.length()); + if (uint32_t(objIndex) >= TaggedScriptThingIndex::IndexLimit) { + ReportAllocationOverflow(cx); + return false; + } + if (!stencil.objLiteralData.emplaceBack(code, len, writer.getFlags())) { + js::ReportOutOfMemory(cx); + return false; + } + + return perScriptData().gcThingList().append(objIndex, outIndex); +} + +bool BytecodeEmitter::iteratorResultShape(GCThingIndex* outShape) { + ObjLiteralFlags flags; + + ObjLiteralWriter writer; + writer.beginObject(flags); + + using WellKnownName = js::frontend::WellKnownParserAtoms; + for (auto name : {&WellKnownName::value, &WellKnownName::done}) { + const ParserAtom* propName = cx->parserNames().*name; + writer.setPropName(propName); + + if (!writer.propWithUndefinedValue(cx)) { + return false; + } + } + + return addObjLiteralData(writer, outShape); +} + +bool BytecodeEmitter::emitPrepareIteratorResult() { + GCThingIndex shape; + if (!iteratorResultShape(&shape)) { + return false; + } + return emitGCIndexOp(JSOp::NewObject, shape); +} + +bool BytecodeEmitter::emitFinishIteratorResult(bool done) { + if (!emitAtomOp(JSOp::InitProp, cx->parserNames().value)) { + return false; + } + if (!emit1(done ? JSOp::True : JSOp::False)) { + return false; + } + if (!emitAtomOp(JSOp::InitProp, cx->parserNames().done)) { + return false; + } + return true; +} + +bool BytecodeEmitter::emitGetNameAtLocation(const ParserAtom* name, + const NameLocation& loc) { + NameOpEmitter noe(this, name, loc, NameOpEmitter::Kind::Get); + if (!noe.emitGet()) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitGetName(NameNode* name) { + MOZ_ASSERT(name->isKind(ParseNodeKind::Name)); + + const ParserAtom* nameAtom = name->name(); + return emitGetName(nameAtom); +} + +bool BytecodeEmitter::emitGetPrivateName(NameNode* name) { + MOZ_ASSERT(name->isKind(ParseNodeKind::PrivateName)); + return emitGetPrivateName(name->name()); +} + +bool BytecodeEmitter::emitGetPrivateName(const ParserAtom* nameAtom) { + // The parser ensures the private name is present on the environment chain. + NameLocation location = lookupName(nameAtom); + MOZ_ASSERT(location.kind() == NameLocation::Kind::FrameSlot || + location.kind() == NameLocation::Kind::EnvironmentCoordinate || + location.kind() == NameLocation::Kind::Dynamic); + + return emitGetNameAtLocation(nameAtom, location); +} + +bool BytecodeEmitter::emitTDZCheckIfNeeded(const ParserAtom* name, + const NameLocation& loc, + ValueIsOnStack isOnStack) { + // Dynamic accesses have TDZ checks built into their VM code and should + // never emit explicit TDZ checks. + MOZ_ASSERT(loc.hasKnownSlot()); + MOZ_ASSERT(loc.isLexical()); + + Maybe check = + innermostTDZCheckCache->needsTDZCheck(this, name); + if (!check) { + return false; + } + + // We've already emitted a check in this basic block. + if (*check == DontCheckTDZ) { + return true; + } + + // If the value is not on the stack, we have to load it first. + if (isOnStack == ValueIsOnStack::No) { + if (loc.kind() == NameLocation::Kind::FrameSlot) { + if (!emitLocalOp(JSOp::GetLocal, loc.frameSlot())) { + return false; + } + } else { + if (!emitEnvCoordOp(JSOp::GetAliasedVar, loc.environmentCoordinate())) { + return false; + } + } + } + + // Emit the lexical check. + if (loc.kind() == NameLocation::Kind::FrameSlot) { + if (!emitLocalOp(JSOp::CheckLexical, loc.frameSlot())) { + return false; + } + } else { + if (!emitEnvCoordOp(JSOp::CheckAliasedLexical, + loc.environmentCoordinate())) { + return false; + } + } + + // Pop the value if needed. + if (isOnStack == ValueIsOnStack::No) { + if (!emit1(JSOp::Pop)) { + return false; + } + } + + return innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ); +} + +bool BytecodeEmitter::emitPropLHS(PropertyAccess* prop) { + MOZ_ASSERT(!prop->isSuper()); + + ParseNode* expr = &prop->expression(); + + if (!expr->is() || expr->as().isSuper()) { + // The non-optimized case. + return emitTree(expr); + } + + // If the object operand is also a dotted property reference, reverse the + // list linked via expression() temporarily so we can iterate over it from + // the bottom up (reversing again as we go), to avoid excessive recursion. + PropertyAccess* pndot = &expr->as(); + ParseNode* pnup = nullptr; + ParseNode* pndown; + for (;;) { + // Reverse pndot->expression() to point up, not down. + pndown = &pndot->expression(); + pndot->setExpression(pnup); + if (!pndown->is() || + pndown->as().isSuper()) { + break; + } + pnup = pndot; + pndot = &pndown->as(); + } + + // pndown is a primary expression, not a dotted property reference. + if (!emitTree(pndown)) { + return false; + } + + while (true) { + // Walk back up the list, emitting annotated name ops. + if (!emitAtomOp(JSOp::GetProp, pndot->key().atom(), + ShouldInstrument::Yes)) { + return false; + } + + // Reverse the pndot->expression() link again. + pnup = pndot->maybeExpression(); + pndot->setExpression(pndown); + pndown = pndot; + if (!pnup) { + break; + } + pndot = &pnup->as(); + } + return true; +} + +bool BytecodeEmitter::emitPropIncDec(UnaryNode* incDec) { + PropertyAccess* prop = &incDec->kid()->as(); + bool isSuper = prop->isSuper(); + ParseNodeKind kind = incDec->getKind(); + PropOpEmitter poe( + this, + kind == ParseNodeKind::PostIncrementExpr + ? PropOpEmitter::Kind::PostIncrement + : kind == ParseNodeKind::PreIncrementExpr + ? PropOpEmitter::Kind::PreIncrement + : kind == ParseNodeKind::PostDecrementExpr + ? PropOpEmitter::Kind::PostDecrement + : PropOpEmitter::Kind::PreDecrement, + isSuper ? PropOpEmitter::ObjKind::Super : PropOpEmitter::ObjKind::Other); + if (!poe.prepareForObj()) { + return false; + } + if (isSuper) { + UnaryNode* base = &prop->expression().as(); + if (!emitGetThisForSuperBase(base)) { + // [stack] THIS + return false; + } + } else { + if (!emitPropLHS(prop)) { + // [stack] OBJ + return false; + } + } + if (!poe.emitIncDec(prop->key().atom())) { + // [stack] RESULT + return false; + } + + return true; +} + +bool BytecodeEmitter::emitNameIncDec(UnaryNode* incDec) { + MOZ_ASSERT(incDec->kid()->isKind(ParseNodeKind::Name)); + + ParseNodeKind kind = incDec->getKind(); + NameNode* name = &incDec->kid()->as(); + const ParserAtom* nameAtom = name->atom(); + NameOpEmitter noe(this, nameAtom, + kind == ParseNodeKind::PostIncrementExpr + ? NameOpEmitter::Kind::PostIncrement + : kind == ParseNodeKind::PreIncrementExpr + ? NameOpEmitter::Kind::PreIncrement + : kind == ParseNodeKind::PostDecrementExpr + ? NameOpEmitter::Kind::PostDecrement + : NameOpEmitter::Kind::PreDecrement); + if (!noe.emitIncDec()) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitElemOpBase(JSOp op, + ShouldInstrument shouldInstrument) { + GCThingIndex unused; + if (shouldInstrument != ShouldInstrument::No && + !emitInstrumentationForOpcode(op, unused)) { + return false; + } + + if (!emit1(op)) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitElemObjAndKey(PropertyByValue* elem, bool isSuper, + ElemOpEmitter& eoe) { + if (isSuper) { + if (!eoe.prepareForObj()) { + // [stack] + return false; + } + UnaryNode* base = &elem->expression().as(); + if (!emitGetThisForSuperBase(base)) { + // [stack] THIS + return false; + } + if (!eoe.prepareForKey()) { + // [stack] THIS + return false; + } + if (!emitTree(&elem->key())) { + // [stack] THIS KEY + return false; + } + + return true; + } + + if (!eoe.prepareForObj()) { + // [stack] + return false; + } + if (!emitTree(&elem->expression())) { + // [stack] OBJ + return false; + } + if (!eoe.prepareForKey()) { + // [stack] OBJ? OBJ + return false; + } + if (!emitTree(&elem->key())) { + // [stack] OBJ? OBJ KEY + return false; + } + + return true; +} + +bool BytecodeEmitter::emitElemIncDec(UnaryNode* incDec) { + PropertyByValue* elemExpr = &incDec->kid()->as(); + bool isSuper = elemExpr->isSuper(); + bool isPrivate = elemExpr->key().isKind(ParseNodeKind::PrivateName); + ParseNodeKind kind = incDec->getKind(); + ElemOpEmitter eoe( + this, + kind == ParseNodeKind::PostIncrementExpr + ? ElemOpEmitter::Kind::PostIncrement + : kind == ParseNodeKind::PreIncrementExpr + ? ElemOpEmitter::Kind::PreIncrement + : kind == ParseNodeKind::PostDecrementExpr + ? ElemOpEmitter::Kind::PostDecrement + : ElemOpEmitter::Kind::PreDecrement, + isSuper ? ElemOpEmitter::ObjKind::Super : ElemOpEmitter::ObjKind::Other, + isPrivate ? NameVisibility::Private : NameVisibility::Public); + if (!emitElemObjAndKey(elemExpr, isSuper, eoe)) { + // [stack] # if Super + // [stack] THIS KEY + // [stack] # otherwise + // [stack] OBJ KEY + return false; + } + if (!eoe.emitIncDec()) { + // [stack] RESULT + return false; + } + + return true; +} + +bool BytecodeEmitter::emitCallIncDec(UnaryNode* incDec) { + MOZ_ASSERT(incDec->isKind(ParseNodeKind::PreIncrementExpr) || + incDec->isKind(ParseNodeKind::PostIncrementExpr) || + incDec->isKind(ParseNodeKind::PreDecrementExpr) || + incDec->isKind(ParseNodeKind::PostDecrementExpr)); + + ParseNode* call = incDec->kid(); + MOZ_ASSERT(call->isKind(ParseNodeKind::CallExpr)); + if (!emitTree(call)) { + // [stack] CALLRESULT + return false; + } + if (!emit1(JSOp::ToNumeric)) { + // [stack] N + return false; + } + + // The increment/decrement has no side effects, so proceed to throw for + // invalid assignment target. + return emit2(JSOp::ThrowMsg, uint8_t(ThrowMsgKind::AssignToCall)); +} + +bool BytecodeEmitter::emitDouble(double d) { + BytecodeOffset offset; + if (!emitCheck(JSOp::Double, 9, &offset)) { + return false; + } + + jsbytecode* code = bytecodeSection().code(offset); + code[0] = jsbytecode(JSOp::Double); + SET_INLINE_VALUE(code, DoubleValue(d)); + bytecodeSection().updateDepth(offset); + return true; +} + +bool BytecodeEmitter::emitNumberOp(double dval) { + int32_t ival; + if (NumberIsInt32(dval, &ival)) { + if (ival == 0) { + return emit1(JSOp::Zero); + } + if (ival == 1) { + return emit1(JSOp::One); + } + if ((int)(int8_t)ival == ival) { + return emit2(JSOp::Int8, uint8_t(int8_t(ival))); + } + + uint32_t u = uint32_t(ival); + if (u < Bit(16)) { + if (!emitUint16Operand(JSOp::Uint16, u)) { + return false; + } + } else if (u < Bit(24)) { + BytecodeOffset off; + if (!emitN(JSOp::Uint24, 3, &off)) { + return false; + } + SET_UINT24(bytecodeSection().code(off), u); + } else { + BytecodeOffset off; + if (!emitN(JSOp::Int32, 4, &off)) { + return false; + } + SET_INT32(bytecodeSection().code(off), ival); + } + return true; + } + + return emitDouble(dval); +} + +/* + * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. + * LLVM is deciding to inline this function which uses a lot of stack space + * into emitTree which is recursive and uses relatively little stack space. + */ +MOZ_NEVER_INLINE bool BytecodeEmitter::emitSwitch(SwitchStatement* switchStmt) { + LexicalScopeNode& lexical = switchStmt->lexicalForCaseList(); + MOZ_ASSERT(lexical.isKind(ParseNodeKind::LexicalScope)); + ListNode* cases = &lexical.scopeBody()->as(); + MOZ_ASSERT(cases->isKind(ParseNodeKind::StatementList)); + + SwitchEmitter se(this); + if (!se.emitDiscriminant(Some(switchStmt->discriminant().pn_pos.begin))) { + return false; + } + + if (!markStepBreakpoint()) { + return false; + } + if (!emitTree(&switchStmt->discriminant())) { + return false; + } + + // Enter the scope before pushing the switch BreakableControl since all + // breaks are under this scope. + + if (!lexical.isEmptyScope()) { + if (!se.emitLexical(lexical.scopeBindings())) { + return false; + } + + // A switch statement may contain hoisted functions inside its + // cases. The PNX_FUNCDEFS flag is propagated from the STATEMENTLIST + // bodies of the cases to the case list. + if (cases->hasTopLevelFunctionDeclarations()) { + for (ParseNode* item : cases->contents()) { + CaseClause* caseClause = &item->as(); + ListNode* statements = caseClause->statementList(); + if (statements->hasTopLevelFunctionDeclarations()) { + if (!emitHoistedFunctionsInList(statements)) { + return false; + } + } + } + } + } else { + MOZ_ASSERT(!cases->hasTopLevelFunctionDeclarations()); + } + + SwitchEmitter::TableGenerator tableGen(this); + uint32_t caseCount = cases->count() - (switchStmt->hasDefault() ? 1 : 0); + if (caseCount == 0) { + tableGen.finish(0); + } else { + for (ParseNode* item : cases->contents()) { + CaseClause* caseClause = &item->as(); + if (caseClause->isDefault()) { + continue; + } + + ParseNode* caseValue = caseClause->caseExpression(); + + if (caseValue->getKind() != ParseNodeKind::NumberExpr) { + tableGen.setInvalid(); + break; + } + + int32_t i; + if (!NumberEqualsInt32(caseValue->as().value(), &i)) { + tableGen.setInvalid(); + break; + } + + if (!tableGen.addNumber(i)) { + return false; + } + } + + tableGen.finish(caseCount); + } + + if (!se.validateCaseCount(caseCount)) { + return false; + } + + bool isTableSwitch = tableGen.isValid(); + if (isTableSwitch) { + if (!se.emitTable(tableGen)) { + return false; + } + } else { + if (!se.emitCond()) { + return false; + } + + // Emit code for evaluating cases and jumping to case statements. + for (ParseNode* item : cases->contents()) { + CaseClause* caseClause = &item->as(); + if (caseClause->isDefault()) { + continue; + } + + if (!se.prepareForCaseValue()) { + return false; + } + + ParseNode* caseValue = caseClause->caseExpression(); + // If the expression is a literal, suppress line number emission so + // that debugging works more naturally. + if (!emitTree( + caseValue, ValueUsage::WantValue, + caseValue->isLiteral() ? SUPPRESS_LINENOTE : EMIT_LINENOTE)) { + return false; + } + + if (!se.emitCaseJump()) { + return false; + } + } + } + + // Emit code for each case's statements. + for (ParseNode* item : cases->contents()) { + CaseClause* caseClause = &item->as(); + if (caseClause->isDefault()) { + if (!se.emitDefaultBody()) { + return false; + } + } else { + if (isTableSwitch) { + ParseNode* caseValue = caseClause->caseExpression(); + MOZ_ASSERT(caseValue->isKind(ParseNodeKind::NumberExpr)); + + NumericLiteral* literal = &caseValue->as(); +#ifdef DEBUG + // Use NumberEqualsInt32 here because switches compare using + // strict equality, which will equate -0 and +0. In contrast + // NumberIsInt32 would return false for -0. + int32_t v; + MOZ_ASSERT(mozilla::NumberEqualsInt32(literal->value(), &v)); +#endif + int32_t i = int32_t(literal->value()); + + if (!se.emitCaseBody(i, tableGen)) { + return false; + } + } else { + if (!se.emitCaseBody()) { + return false; + } + } + } + + if (!emitTree(caseClause->statementList())) { + return false; + } + } + + if (!se.emitEnd()) { + return false; + } + + return true; +} + +bool BytecodeEmitter::allocateResumeIndex(BytecodeOffset offset, + uint32_t* resumeIndex) { + static constexpr uint32_t MaxResumeIndex = BitMask(24); + + static_assert( + MaxResumeIndex < uint32_t(AbstractGeneratorObject::RESUME_INDEX_RUNNING), + "resumeIndex should not include magic AbstractGeneratorObject " + "resumeIndex values"); + static_assert( + MaxResumeIndex <= INT32_MAX / sizeof(uintptr_t), + "resumeIndex * sizeof(uintptr_t) must fit in an int32. JIT code relies " + "on this when loading resume entries from BaselineScript"); + + *resumeIndex = bytecodeSection().resumeOffsetList().length(); + if (*resumeIndex > MaxResumeIndex) { + reportError(nullptr, JSMSG_TOO_MANY_RESUME_INDEXES); + return false; + } + + return bytecodeSection().resumeOffsetList().append(offset.value()); +} + +bool BytecodeEmitter::allocateResumeIndexRange( + mozilla::Span offsets, uint32_t* firstResumeIndex) { + *firstResumeIndex = 0; + + for (size_t i = 0, len = offsets.size(); i < len; i++) { + uint32_t resumeIndex; + if (!allocateResumeIndex(offsets[i], &resumeIndex)) { + return false; + } + if (i == 0) { + *firstResumeIndex = resumeIndex; + } + } + + return true; +} + +bool BytecodeEmitter::emitYieldOp(JSOp op) { + // All yield operations pop or suspend the current frame. + if (!emitInstrumentation(InstrumentationKind::Exit)) { + return false; + } + + if (op == JSOp::FinalYieldRval) { + return emit1(JSOp::FinalYieldRval); + } + + MOZ_ASSERT(op == JSOp::InitialYield || op == JSOp::Yield || + op == JSOp::Await); + + BytecodeOffset off; + if (!emitN(op, 3, &off)) { + return false; + } + + if (op == JSOp::InitialYield || op == JSOp::Yield) { + bytecodeSection().addNumYields(); + } + + uint32_t resumeIndex; + if (!allocateResumeIndex(bytecodeSection().offset(), &resumeIndex)) { + return false; + } + + SET_RESUMEINDEX(bytecodeSection().code(off), resumeIndex); + + if (!emitInstrumentation(InstrumentationKind::Entry)) { + return false; + } + + BytecodeOffset unusedOffset; + return emitJumpTargetOp(JSOp::AfterYield, &unusedOffset); +} + +bool BytecodeEmitter::emitPushResumeKind(GeneratorResumeKind kind) { + return emit2(JSOp::ResumeKind, uint8_t(kind)); +} + +bool BytecodeEmitter::emitSetThis(BinaryNode* setThisNode) { + // ParseNodeKind::SetThis is used to update |this| after a super() call + // in a derived class constructor. + + MOZ_ASSERT(setThisNode->isKind(ParseNodeKind::SetThis)); + MOZ_ASSERT(setThisNode->left()->isKind(ParseNodeKind::Name)); + + const ParserAtom* name = setThisNode->left()->as().name(); + + // The 'this' binding is not lexical, but due to super() semantics this + // initialization needs to be treated as a lexical one. + NameLocation loc = lookupName(name); + NameLocation lexicalLoc; + if (loc.kind() == NameLocation::Kind::FrameSlot) { + lexicalLoc = NameLocation::FrameSlot(BindingKind::Let, loc.frameSlot()); + } else if (loc.kind() == NameLocation::Kind::EnvironmentCoordinate) { + EnvironmentCoordinate coord = loc.environmentCoordinate(); + uint8_t hops = AssertedCast(coord.hops()); + lexicalLoc = NameLocation::EnvironmentCoordinate(BindingKind::Let, hops, + coord.slot()); + } else { + MOZ_ASSERT(loc.kind() == NameLocation::Kind::Dynamic); + lexicalLoc = loc; + } + + NameOpEmitter noe(this, name, lexicalLoc, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + // [stack] + return false; + } + + // Emit the new |this| value. + if (!emitTree(setThisNode->right())) { + // [stack] 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)) { + // [stack] NEWTHIS THIS + return false; + } + if (!emit1(JSOp::CheckThisReinit)) { + // [stack] NEWTHIS THIS + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] NEWTHIS + return false; + } + if (!noe.emitAssignment()) { + // [stack] NEWTHIS + return false; + } + + if (!emitInitializeInstanceMembers()) { + return false; + } + + return true; +} + +bool BytecodeEmitter::defineHoistedTopLevelFunctions(ParseNode* body) { + MOZ_ASSERT(inPrologue()); + MOZ_ASSERT(sc->isGlobalContext() || (sc->isEvalContext() && !sc->strict())); + MOZ_ASSERT(body->is() || body->is()); + + if (body->is()) { + body = body->as().scopeBody(); + MOZ_ASSERT(body->is()); + } + + if (!body->as().hasTopLevelFunctionDeclarations()) { + return true; + } + + return emitHoistedFunctionsInList(&body->as()); +} + +// For Global and sloppy-Eval scripts, this performs most of the steps of the +// spec's [GlobalDeclarationInstantiation] and [EvalDeclarationInstantiation] +// operations. +// +// Note that while strict-Eval is handled in the same part of the spec, it never +// fails for global-redeclaration checks so those scripts initialize directly in +// their bytecode. +bool BytecodeEmitter::emitDeclarationInstantiation(ParseNode* body) { + if (sc->isModuleContext()) { + // ES Modules have dedicated variable and lexial environments and therefore + // do not have to perform redeclaration checks. We initialize their bindings + // elsewhere in bytecode. + return true; + } + + if (sc->isEvalContext() && sc->strict()) { + // Strict Eval has a dedicated variables (and lexical) environment and + // therefore does not have to perform redeclaration checks. We initialize + // their bindings elsewhere in the bytecode. + return true; + } + + // If we have no variables bindings, then we are done! + if (sc->isGlobalContext()) { + if (!sc->asGlobalContext()->bindings) { + return true; + } + } else { + MOZ_ASSERT(sc->isEvalContext()); + + if (!sc->asEvalContext()->bindings) { + return true; + } + } + +#if DEBUG + // There should be no emitted functions yet. + for (const auto& thing : perScriptData().gcThingList().objects()) { + MOZ_ASSERT(thing.isEmptyGlobalScope() || thing.isScope()); + } +#endif + + // Emit the hoisted functions to gc-things list. There is no bytecode + // generated yet to bind them. + if (!defineHoistedTopLevelFunctions(body)) { + return false; + } + + // Save the last GCThingIndex emitted. The hoisted functions are contained in + // the gc-things list up until this point. This set of gc-things also contain + // initial scopes (of which there must be at least one). + MOZ_ASSERT(perScriptData().gcThingList().length() > 0); + GCThingIndex lastFun = + GCThingIndex(perScriptData().gcThingList().length() - 1); + +#if DEBUG + for (const auto& thing : perScriptData().gcThingList().objects()) { + MOZ_ASSERT(thing.isEmptyGlobalScope() || thing.isScope() || + thing.isFunction()); + } +#endif + + // Check for declaration conflicts and initialize the bindings. + if (!emitGCIndexOp(JSOp::GlobalOrEvalDeclInstantiation, lastFun)) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitScript(ParseNode* body) { + AutoFrontendTraceLog traceLog(cx, TraceLogger_BytecodeEmission, + parser->errorReporter(), body); + + setScriptStartOffsetIfUnset(body->pn_pos.begin); + + MOZ_ASSERT(inPrologue()); + + TDZCheckCache tdzCache(this); + EmitterScope emitterScope(this); + Maybe topLevelAwait; + if (sc->isGlobalContext()) { + if (!emitterScope.enterGlobal(this, sc->asGlobalContext())) { + return false; + } + } else if (sc->isEvalContext()) { + if (!emitterScope.enterEval(this, sc->asEvalContext())) { + return false; + } + } else { + MOZ_ASSERT(sc->isModuleContext()); + if (!emitterScope.enterModule(this, sc->asModuleContext())) { + return false; + } + if (sc->asModuleContext()->isAsync()) { + topLevelAwait.emplace(this); + } + } + + setFunctionBodyEndPos(body->pn_pos.end); + + bool isSloppyEval = sc->isEvalContext() && !sc->strict(); + if (isSloppyEval && body->is() && + !body->as().isEmptyScope()) { + // Sloppy eval scripts may emit hoisted functions bindings with a + // `JSOp::GlobalOrEvalDeclInstantiation` opcode below. If this eval needs a + // top-level lexical environment, we must ensure that environment is created + // before those functions are created and bound. + // + // This differs from the global-script case below because the global-lexical + // environment exists outside the script itself. In the case of strict eval + // scripts, the `emitterScope` above is already sufficient. + EmitterScope lexicalEmitterScope(this); + LexicalScopeNode* scope = &body->as(); + + if (!lexicalEmitterScope.enterLexical(this, ScopeKind::Lexical, + scope->scopeBindings())) { + return false; + } + + if (!emitDeclarationInstantiation(scope->scopeBody())) { + return false; + } + + if (!switchToMain()) { + return false; + } + + ParseNode* scopeBody = scope->scopeBody(); + if (!emitLexicalScopeBody(scopeBody, EMIT_LINENOTE)) { + return false; + } + + if (!updateSourceCoordNotes(scopeBody->pn_pos.end)) { + return false; + } + + if (!lexicalEmitterScope.leave(this)) { + return false; + } + } else { + if (!emitDeclarationInstantiation(body)) { + return false; + } + if (topLevelAwait) { + if (!topLevelAwait->prepareForModule()) { + return false; + } + } + + if (!switchToMain()) { + return false; + } + + if (topLevelAwait) { + if (!topLevelAwait->prepareForBody()) { + return false; + } + } + + if (!emitTree(body)) { + // [stack] + return false; + } + + if (!updateSourceCoordNotes(body->pn_pos.end)) { + return false; + } + } + + if (topLevelAwait) { + if (!topLevelAwait->emitEnd()) { + return false; + } + } + + if (!markSimpleBreakpoint()) { + return false; + } + + if (!emitReturnRval()) { + return false; + } + + if (!emitterScope.leave(this)) { + return false; + } + + if (!NameFunctions(cx, compilationState.parserAtoms, body)) { + return false; + } + + // Create a Stencil and convert it into a JSScript. + return intoScriptStencil(CompilationStencil::TopLevelIndex); +} + +js::UniquePtr BytecodeEmitter::createImmutableScriptData( + JSContext* cx) { + uint32_t nslots; + if (!getNslots(&nslots)) { + return nullptr; + } + + bool isFunction = sc->isFunctionBox(); + uint16_t funLength = isFunction ? sc->asFunctionBox()->length() : 0; + + return ImmutableScriptData::new_( + cx, mainOffset(), maxFixedSlots, nslots, bodyScopeIndex, + bytecodeSection().numICEntries(), isFunction, funLength, + bytecodeSection().code(), bytecodeSection().notes(), + bytecodeSection().resumeOffsetList().span(), + bytecodeSection().scopeNoteList().span(), + bytecodeSection().tryNoteList().span()); +} + +bool BytecodeEmitter::getNslots(uint32_t* nslots) { + uint64_t nslots64 = + maxFixedSlots + static_cast(bytecodeSection().maxStackDepth()); + if (nslots64 > UINT32_MAX) { + reportError(nullptr, JSMSG_NEED_DIET, js_script_str); + return false; + } + *nslots = nslots64; + return true; +} + +bool BytecodeEmitter::emitFunctionScript(FunctionNode* funNode) { + MOZ_ASSERT(inPrologue()); + ListNode* paramsBody = &funNode->body()->as(); + MOZ_ASSERT(paramsBody->isKind(ParseNodeKind::ParamsBody)); + FunctionBox* funbox = sc->asFunctionBox(); + AutoFrontendTraceLog traceLog(cx, TraceLogger_BytecodeEmission, + parser->errorReporter(), funbox); + + setScriptStartOffsetIfUnset(paramsBody->pn_pos.begin); + + // [stack] + + FunctionScriptEmitter fse(this, funbox, Some(paramsBody->pn_pos.begin), + Some(paramsBody->pn_pos.end)); + if (!fse.prepareForParameters()) { + // [stack] + return false; + } + + if (!emitFunctionFormalParameters(paramsBody)) { + // [stack] + return false; + } + + if (!fse.prepareForBody()) { + // [stack] + return false; + } + + if (!emitTree(paramsBody->last())) { + // [stack] + return false; + } + + if (!fse.emitEndBody()) { + // [stack] + return false; + } + + if (funbox->index() == CompilationStencil::TopLevelIndex) { + if (!NameFunctions(cx, compilationState.parserAtoms, funNode)) { + return false; + } + } + + return fse.intoStencil(); +} + +bool BytecodeEmitter::emitDestructuringLHSRef(ParseNode* target, + size_t* emitted) { + *emitted = 0; + + if (target->isKind(ParseNodeKind::Spread)) { + target = target->as().kid(); + } else if (target->isKind(ParseNodeKind::AssignExpr)) { + target = target->as().left(); + } + + // No need to recur into ParseNodeKind::Array and + // ParseNodeKind::Object subpatterns here, since + // emitSetOrInitializeDestructuring does the recursion when + // setting or initializing value. Getting reference doesn't recur. + if (target->isKind(ParseNodeKind::Name) || + target->isKind(ParseNodeKind::ArrayExpr) || + target->isKind(ParseNodeKind::ObjectExpr)) { + return true; + } + +#ifdef DEBUG + int depth = bytecodeSection().stackDepth(); +#endif + + switch (target->getKind()) { + case ParseNodeKind::DotExpr: { + 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)) { + // [stack] THIS SUPERBASE + return false; + } + // SUPERBASE is pushed onto THIS in poe.prepareForRhs below. + *emitted = 2; + } else { + if (!emitTree(&prop->expression())) { + // [stack] OBJ + return false; + } + *emitted = 1; + } + if (!poe.prepareForRhs()) { + // [stack] # if Super + // [stack] THIS SUPERBASE + // [stack] # otherwise + // [stack] OBJ + return false; + } + break; + } + + case ParseNodeKind::ElemExpr: { + PropertyByValue* elem = &target->as(); + bool isSuper = elem->isSuper(); + bool isPrivate = elem->key().isKind(ParseNodeKind::PrivateName); + ElemOpEmitter eoe( + this, ElemOpEmitter::Kind::SimpleAssignment, + isSuper ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other, + isPrivate ? NameVisibility::Private : NameVisibility::Public); + if (!emitElemObjAndKey(elem, isSuper, eoe)) { + // [stack] # if Super + // [stack] THIS KEY + // [stack] # otherwise + // [stack] OBJ KEY + return false; + } + if (isSuper) { + // SUPERBASE is pushed onto KEY in eoe.prepareForRhs below. + *emitted = 3; + } else { + *emitted = 2; + } + if (!eoe.prepareForRhs()) { + // [stack] # if Super + // [stack] THIS KEY SUPERBASE + // [stack] # otherwise + // [stack] OBJ KEY + return false; + } + break; + } + + case ParseNodeKind::CallExpr: + MOZ_ASSERT_UNREACHABLE( + "Parser::reportIfNotValidSimpleAssignmentTarget " + "rejects function calls as assignment " + "targets in destructuring assignments"); + break; + + default: + MOZ_CRASH("emitDestructuringLHSRef: bad lhs kind"); + } + + MOZ_ASSERT(bytecodeSection().stackDepth() == depth + int(*emitted)); + + return true; +} + +bool BytecodeEmitter::emitSetOrInitializeDestructuring( + ParseNode* target, DestructuringFlavor flav) { + // Now emit the lvalue opcode sequence. If the lvalue is a nested + // destructuring initialiser-form, call ourselves to handle it, then pop + // the matched value. Otherwise emit an lvalue bytecode sequence followed + // by an assignment op. + if (target->isKind(ParseNodeKind::Spread)) { + target = target->as().kid(); + } else if (target->isKind(ParseNodeKind::AssignExpr)) { + target = target->as().left(); + } + if (target->isKind(ParseNodeKind::ArrayExpr) || + target->isKind(ParseNodeKind::ObjectExpr)) { + if (!emitDestructuringOps(&target->as(), flav)) { + return false; + } + // Per its post-condition, emitDestructuringOps has left the + // to-be-destructured value on top of the stack. + if (!emit1(JSOp::Pop)) { + return false; + } + } else { + switch (target->getKind()) { + case ParseNodeKind::Name: { + const ParserAtom* name = target->as().name(); + NameLocation loc = lookupName(name); + NameOpEmitter::Kind kind; + switch (flav) { + case DestructuringFlavor::Declaration: + kind = NameOpEmitter::Kind::Initialize; + break; + + case DestructuringFlavor::Assignment: + kind = NameOpEmitter::Kind::SimpleAssignment; + break; + } + + NameOpEmitter noe(this, name, loc, kind); + if (!noe.prepareForRhs()) { + // [stack] 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)) { + // [stack] ENV V + return false; + } + } else { + // In cases of emitting a frame slot or environment slot, + // nothing needs be done. + } + if (!noe.emitAssignment()) { + // [stack] V + return false; + } + + break; + } + + case ParseNodeKind::DotExpr: { + // The reference is already pushed by emitDestructuringLHSRef. + // [stack] # if Super + // [stack] THIS SUPERBASE VAL + // [stack] # otherwise + // [stack] 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; + } + // [stack] # VAL + if (!poe.emitAssignment(prop->key().atom())) { + return false; + } + break; + } + + case ParseNodeKind::ElemExpr: { + // The reference is already pushed by emitDestructuringLHSRef. + // [stack] # if Super + // [stack] THIS KEY SUPERBASE VAL + // [stack] # otherwise + // [stack] OBJ KEY VAL + PropertyByValue* elem = &target->as(); + bool isSuper = elem->isSuper(); + bool isPrivate = elem->key().isKind(ParseNodeKind::PrivateName); + ElemOpEmitter eoe( + this, ElemOpEmitter::Kind::SimpleAssignment, + isSuper ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other, + isPrivate ? NameVisibility::Private : NameVisibility::Public); + if (!eoe.skipObjAndKeyAndRhs()) { + return false; + } + if (!eoe.emitAssignment()) { + // [stack] VAL + return false; + } + break; + } + + case ParseNodeKind::CallExpr: + MOZ_ASSERT_UNREACHABLE( + "Parser::reportIfNotValidSimpleAssignmentTarget " + "rejects function calls as assignment " + "targets in destructuring assignments"); + break; + + default: + MOZ_CRASH("emitSetOrInitializeDestructuring: bad lhs kind"); + } + + // Pop the assigned value. + if (!emit1(JSOp::Pop)) { + // [stack] # empty + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitIteratorNext( + const Maybe& callSourceCoordOffset, + IteratorKind iterKind /* = IteratorKind::Sync */, + bool allowSelfHosted /* = false */) { + // TODO: migrate Module code to cpp, to avoid having the extra check here. + MOZ_ASSERT(allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting || + (sc->isModuleContext() && sc->asModuleContext()->isAsync()), + ".next() iteration is prohibited in non-module self-hosted code " + "because it" + "can run user-modifiable iteration code"); + + // [stack] ... NEXT ITER + MOZ_ASSERT(bytecodeSection().stackDepth() >= 2); + + if (!emitCall(JSOp::Call, 0, callSourceCoordOffset)) { + // [stack] ... RESULT + return false; + } + + if (iterKind == IteratorKind::Async) { + if (!emitAwaitInInnermostScope()) { + // [stack] ... RESULT + return false; + } + } + + if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) { + // [stack] ... RESULT + return false; + } + return true; +} + +bool BytecodeEmitter::emitPushNotUndefinedOrNull() { + // [stack] V + MOZ_ASSERT(bytecodeSection().stackDepth() > 0); + + if (!emit1(JSOp::Dup)) { + // [stack] V V + return false; + } + if (!emit1(JSOp::Undefined)) { + // [stack] V V UNDEFINED + return false; + } + if (!emit1(JSOp::StrictNe)) { + // [stack] V NEQ + return false; + } + + JumpList undefinedOrNullJump; + if (!emitJump(JSOp::And, &undefinedOrNullJump)) { + // [stack] V NEQ + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] V + return false; + } + if (!emit1(JSOp::Dup)) { + // [stack] V V + return false; + } + if (!emit1(JSOp::Null)) { + // [stack] V V NULL + return false; + } + if (!emit1(JSOp::StrictNe)) { + // [stack] V NEQ + return false; + } + + if (!emitJumpTargetAndPatch(undefinedOrNullJump)) { + // [stack] V NOT-UNDEF-OR-NULL + return false; + } + + return true; +} + +bool BytecodeEmitter::emitIteratorCloseInScope( + EmitterScope& currentScope, + IteratorKind iterKind /* = IteratorKind::Sync */, + CompletionKind completionKind /* = CompletionKind::Normal */, + bool allowSelfHosted /* = false */) { + MOZ_ASSERT( + allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting, + ".close() on iterators is prohibited in self-hosted code because it " + "can run user-modifiable iteration code"); + + // 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()) { + // [stack] ... ITER + return false; + } + } + + if (!emit1(JSOp::Dup)) { + // [stack] ... ITER ITER + return false; + } + + // Steps 1-2 are assertions, step 3 is implicit. + + // Step 4. + // + // Get the "return" method. + if (!emitAtomOp(JSOp::GetProp, cx->parserNames().return_)) { + // [stack] ... ITER RET + return false; + } + + // Step 5. + // + // Do nothing if "return" is undefined or null. + InternalIfEmitter ifReturnMethodIsDefined(this); + if (!emitPushNotUndefinedOrNull()) { + // [stack] ... ITER RET NOT-UNDEF-OR-NULL + return false; + } + + if (!ifReturnMethodIsDefined.emitThenElse()) { + // [stack] ... ITER RET + return false; + } + + // Steps 5.c, 7. + // + // Call the "return" method. + if (!emit1(JSOp::Swap)) { + // [stack] ... RET ITER + return false; + } + + if (!emitCall(JSOp::Call, 0)) { + // [stack] ... RESULT + return false; + } + + // 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)) { + // [stack] ... RESULT RVAL + return false; + } + if (!emit1(JSOp::Swap)) { + // [stack] ... RVAL RESULT + return false; + } + } + + if (!emitAwaitInScope(currentScope)) { + // [stack] ... RVAL? RESULT + return false; + } + + if (completionKind != CompletionKind::Throw) { + if (!emit1(JSOp::Swap)) { + // [stack] ... RESULT RVAL + return false; + } + if (!emit1(JSOp::SetRval)) { + // [stack] ... RESULT + return false; + } + } + } + + // Step 6 (Handled in caller). + + // Step 8. + if (completionKind != CompletionKind::Throw) { + // Check that the "return" result is an object. + if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) { + // [stack] ... RESULT + return false; + } + } + + if (!ifReturnMethodIsDefined.emitElse()) { + // [stack] ... ITER RET + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] ... ITER + return false; + } + + if (!ifReturnMethodIsDefined.emitEnd()) { + return false; + } + + if (completionKind == CompletionKind::Throw) { + if (!tryCatch->emitCatch()) { + // [stack] ... ITER EXC + return false; + } + + // Just ignore the exception thrown by call and await. + if (!emit1(JSOp::Pop)) { + // [stack] ... ITER + return false; + } + + if (!tryCatch->emitEnd()) { + // [stack] ... ITER + return false; + } + } + + // Step 9 (Handled in caller). + + return emit1(JSOp::Pop); + // [stack] ... +} + +template +bool BytecodeEmitter::wrapWithDestructuringTryNote(int32_t iterDepth, + InnerEmitter emitter) { + MOZ_ASSERT(bytecodeSection().stackDepth() >= iterDepth); + + // Pad a nop at the beginning of the bytecode covered by the trynote so + // that when unwinding environments, we may unwind to the scope + // corresponding to the pc *before* the start, in case the first bytecode + // emitted by |emitter| is the start of an inner scope. See comment above + // UnwindEnvironmentToTryPc. + if (!emit1(JSOp::TryDestructuring)) { + return false; + } + + BytecodeOffset start = bytecodeSection().offset(); + if (!emitter(this)) { + return false; + } + BytecodeOffset end = bytecodeSection().offset(); + if (start != end) { + return addTryNote(TryNoteKind::Destructuring, iterDepth, start, end); + } + return true; +} + +bool BytecodeEmitter::emitDefault(ParseNode* defaultExpr, ParseNode* pattern) { + // [stack] VALUE + + DefaultEmitter de(this); + if (!de.prepareForDefault()) { + // [stack] + return false; + } + if (!emitInitializer(defaultExpr, pattern)) { + // [stack] DEFAULTVALUE + return false; + } + if (!de.emitEnd()) { + // [stack] VALUE/DEFAULTVALUE + return false; + } + return true; +} + +bool BytecodeEmitter::emitAnonymousFunctionWithName(ParseNode* node, + const ParserAtom* name) { + MOZ_ASSERT(node->isDirectRHSAnonFunction()); + + if (node->is()) { + // Function doesn't have 'name' property at this point. + // Set function's name at compile time. + if (!setFunName(node->as().funbox(), name)) { + return false; + } + + return emitTree(node); + } + + MOZ_ASSERT(node->is()); + + return emitClass(&node->as(), ClassNameKind::InferredName, name); +} + +bool BytecodeEmitter::emitAnonymousFunctionWithComputedName( + ParseNode* node, FunctionPrefixKind prefixKind) { + MOZ_ASSERT(node->isDirectRHSAnonFunction()); + + if (node->is()) { + if (!emitTree(node)) { + // [stack] NAME FUN + return false; + } + if (!emitDupAt(1)) { + // [stack] NAME FUN NAME + return false; + } + if (!emit2(JSOp::SetFunName, uint8_t(prefixKind))) { + // [stack] NAME FUN + return false; + } + return true; + } + + MOZ_ASSERT(node->is()); + MOZ_ASSERT(prefixKind == FunctionPrefixKind::None); + + return emitClass(&node->as(), ClassNameKind::ComputedName); +} + +bool BytecodeEmitter::setFunName(FunctionBox* funbox, const ParserAtom* 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 (funbox->hasInferredName()) { + MOZ_ASSERT(!funbox->emitBytecode); + MOZ_ASSERT(funbox->displayAtom() == name); + + return true; + } + + funbox->setInferredName(name); + return true; +} + +bool BytecodeEmitter::emitInitializer(ParseNode* initializer, + ParseNode* pattern) { + if (initializer->isDirectRHSAnonFunction()) { + MOZ_ASSERT(!pattern->isInParens()); + const ParserAtom* name = pattern->as().name(); + if (!emitAnonymousFunctionWithName(initializer, name)) { + return false; + } + } else { + if (!emitTree(initializer)) { + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitDestructuringOpsArray(ListNode* pattern, + DestructuringFlavor flav) { + MOZ_ASSERT(pattern->isKind(ParseNodeKind::ArrayExpr)); + MOZ_ASSERT(bytecodeSection().stackDepth() != 0); + + // Here's pseudo code for |let [a, b, , c=y, ...d] = x;| + // + // Lines that are annotated "covered by trynote" mean that upon throwing + // an exception, IteratorClose is called on iter only if done is false. + // + // let x, y; + // let a, b, c, d; + // let iter, next, lref, result, done, value; // stack values + // + // iter = x[Symbol.iterator](); + // next = iter.next; + // + // // ==== emitted by loop for a ==== + // lref = GetReference(a); // covered by trynote + // + // result = Call(next, iter); + // done = result.done; + // + // if (done) + // value = undefined; + // else + // value = result.value; + // + // SetOrInitialize(lref, value); // covered by trynote + // + // // ==== emitted by loop for b ==== + // lref = GetReference(b); // covered by trynote + // + // if (done) { + // value = undefined; + // } else { + // result = Call(next, iter); + // done = result.done; + // if (done) + // value = undefined; + // else + // value = result.value; + // } + // + // SetOrInitialize(lref, value); // covered by trynote + // + // // ==== emitted by loop for elision ==== + // if (done) { + // value = undefined; + // } else { + // result = Call(next, iter); + // done = result.done; + // if (done) + // value = undefined; + // else + // value = result.value; + // } + // + // // ==== emitted by loop for c ==== + // lref = GetReference(c); // covered by trynote + // + // if (done) { + // value = undefined; + // } else { + // result = Call(next, iter); + // done = result.done; + // if (done) + // value = undefined; + // else + // value = result.value; + // } + // + // if (value === undefined) + // value = y; // covered by trynote + // + // SetOrInitialize(lref, value); // covered by trynote + // + // // ==== emitted by loop for d ==== + // lref = GetReference(d); // covered by trynote + // + // if (done) + // value = []; + // else + // value = [...iter]; + // + // SetOrInitialize(lref, value); // covered by trynote + // + // // === emitted after loop === + // if (!done) + // IteratorClose(iter); + + // Use an iterator to destructure the RHS, instead of index lookup. We + // must leave the *original* value on the stack. + if (!emit1(JSOp::Dup)) { + // [stack] ... OBJ OBJ + return false; + } + if (!emitIterator()) { + // [stack] ... OBJ NEXT ITER + return false; + } + + // For an empty pattern [], call IteratorClose unconditionally. Nothing + // else needs to be done. + if (!pattern->head()) { + if (!emit1(JSOp::Swap)) { + // [stack] ... OBJ ITER NEXT + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] ... OBJ ITER + return false; + } + + return emitIteratorCloseInInnermostScope(); + // [stack] ... OBJ + } + + // Push an initial FALSE value for DONE. + if (!emit1(JSOp::False)) { + // [stack] ... OBJ NEXT ITER FALSE + return false; + } + + // TryNoteKind::Destructuring expects the iterator and the done value + // to be the second to top and the top of the stack, respectively. + // IteratorClose is called upon exception only if done is false. + int32_t tryNoteDepth = bytecodeSection().stackDepth(); + + for (ParseNode* member : pattern->contents()) { + bool isFirst = member == pattern->head(); + DebugOnly hasNext = !!member->pn_next; + + size_t emitted = 0; + + // Spec requires LHS reference to be evaluated first. + ParseNode* lhsPattern = member; + if (lhsPattern->isKind(ParseNodeKind::AssignExpr)) { + lhsPattern = lhsPattern->as().left(); + } + + bool isElision = lhsPattern->isKind(ParseNodeKind::Elision); + if (!isElision) { + auto emitLHSRef = [lhsPattern, &emitted](BytecodeEmitter* bce) { + return bce->emitDestructuringLHSRef(lhsPattern, &emitted); + // [stack] ... OBJ NEXT ITER DONE LREF* + }; + if (!wrapWithDestructuringTryNote(tryNoteDepth, emitLHSRef)) { + return false; + } + } + + // Pick the DONE value to the top of the stack. + if (emitted) { + if (!emitPickN(emitted)) { + // [stack] ... OBJ NEXT ITER LREF* DONE + return false; + } + } + + if (isFirst) { + // If this element is the first, DONE is always FALSE, so pop it. + // + // Non-first elements should emit if-else depending on the + // member pattern, below. + if (!emit1(JSOp::Pop)) { + // [stack] ... OBJ NEXT ITER LREF* + return false; + } + } + + if (member->isKind(ParseNodeKind::Spread)) { + InternalIfEmitter ifThenElse(this); + if (!isFirst) { + // If spread is not the first element of the pattern, + // iterator can already be completed. + // [stack] ... OBJ NEXT ITER LREF* DONE + + if (!ifThenElse.emitThenElse()) { + // [stack] ... OBJ NEXT ITER LREF* + return false; + } + + if (!emitUint32Operand(JSOp::NewArray, 0)) { + // [stack] ... OBJ NEXT ITER LREF* ARRAY + return false; + } + if (!ifThenElse.emitElse()) { + // [stack] ... OBJ NEXT ITER LREF* + return false; + } + } + + // If iterator is not completed, create a new array with the rest + // of the iterator. + if (!emitDupAt(emitted + 1, 2)) { + // [stack] ... OBJ NEXT ITER LREF* NEXT + return false; + } + if (!emitUint32Operand(JSOp::NewArray, 0)) { + // [stack] ... OBJ NEXT ITER LREF* NEXT ITER ARRAY + return false; + } + if (!emitNumberOp(0)) { + // [stack] ... OBJ NEXT ITER LREF* NEXT ITER ARRAY INDEX + return false; + } + if (!emitSpread()) { + // [stack] ... OBJ NEXT ITER LREF* ARRAY INDEX + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] ... OBJ NEXT ITER LREF* ARRAY + return false; + } + + if (!isFirst) { + if (!ifThenElse.emitEnd()) { + return false; + } + MOZ_ASSERT(ifThenElse.pushed() == 1); + } + + // At this point the iterator is done. Unpick a TRUE value for DONE above + // ITER. + if (!emit1(JSOp::True)) { + // [stack] ... OBJ NEXT ITER LREF* ARRAY TRUE + return false; + } + if (!emit2(JSOp::Unpick, emitted + 1)) { + // [stack] ... OBJ NEXT ITER TRUE LREF* ARRAY + return false; + } + + auto emitAssignment = [member, flav](BytecodeEmitter* bce) { + return bce->emitSetOrInitializeDestructuring(member, flav); + // [stack] ... OBJ NEXT ITER TRUE + }; + if (!wrapWithDestructuringTryNote(tryNoteDepth, emitAssignment)) { + return false; + } + + MOZ_ASSERT(!hasNext); + break; + } + + ParseNode* pndefault = nullptr; + if (member->isKind(ParseNodeKind::AssignExpr)) { + pndefault = member->as().right(); + } + + MOZ_ASSERT(!member->isKind(ParseNodeKind::Spread)); + + InternalIfEmitter ifAlreadyDone(this); + if (!isFirst) { + // [stack] ... OBJ NEXT ITER LREF* DONE + + if (!ifAlreadyDone.emitThenElse()) { + // [stack] ... OBJ NEXT ITER LREF* + return false; + } + + if (!emit1(JSOp::Undefined)) { + // [stack] ... OBJ NEXT ITER LREF* UNDEF + return false; + } + if (!emit1(JSOp::NopDestructuring)) { + // [stack] ... OBJ NEXT ITER LREF* UNDEF + return false; + } + + // The iterator is done. Unpick a TRUE value for DONE above ITER. + if (!emit1(JSOp::True)) { + // [stack] ... OBJ NEXT ITER LREF* UNDEF TRUE + return false; + } + if (!emit2(JSOp::Unpick, emitted + 1)) { + // [stack] ... OBJ NEXT ITER TRUE LREF* UNDEF + return false; + } + + if (!ifAlreadyDone.emitElse()) { + // [stack] ... OBJ NEXT ITER LREF* + return false; + } + } + + if (!emitDupAt(emitted + 1, 2)) { + // [stack] ... OBJ NEXT ITER LREF* NEXT + return false; + } + if (!emitIteratorNext(Some(pattern->pn_pos.begin))) { + // [stack] ... OBJ NEXT ITER LREF* RESULT + return false; + } + if (!emit1(JSOp::Dup)) { + // [stack] ... OBJ NEXT ITER LREF* RESULT RESULT + return false; + } + if (!emitAtomOp(JSOp::GetProp, cx->parserNames().done)) { + // [stack] ... OBJ NEXT ITER LREF* RESULT DONE + return false; + } + + if (!emit1(JSOp::Dup)) { + // [stack] ... OBJ NEXT ITER LREF* RESULT DONE DONE + return false; + } + if (!emit2(JSOp::Unpick, emitted + 2)) { + // [stack] ... OBJ NEXT ITER DONE LREF* RESULT DONE + return false; + } + + InternalIfEmitter ifDone(this); + if (!ifDone.emitThenElse()) { + // [stack] ... OBJ NEXT ITER DONE LREF* RESULT + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] ... OBJ NEXT ITER DONE LREF* + return false; + } + if (!emit1(JSOp::Undefined)) { + // [stack] ... OBJ NEXT ITER DONE LREF* UNDEF + return false; + } + if (!emit1(JSOp::NopDestructuring)) { + // [stack] ... OBJ NEXT ITER DONE LREF* UNDEF + return false; + } + + if (!ifDone.emitElse()) { + // [stack] ... OBJ NEXT ITER DONE LREF* RESULT + return false; + } + + if (!emitAtomOp(JSOp::GetProp, cx->parserNames().value)) { + // [stack] ... OBJ NEXT ITER DONE LREF* VALUE + return false; + } + + if (!ifDone.emitEnd()) { + return false; + } + MOZ_ASSERT(ifDone.pushed() == 0); + + if (!isFirst) { + if (!ifAlreadyDone.emitEnd()) { + return false; + } + MOZ_ASSERT(ifAlreadyDone.pushed() == 2); + } + + if (pndefault) { + auto emitDefault = [pndefault, lhsPattern](BytecodeEmitter* bce) { + return bce->emitDefault(pndefault, lhsPattern); + // [stack] ... OBJ NEXT ITER DONE LREF* VALUE + }; + + if (!wrapWithDestructuringTryNote(tryNoteDepth, emitDefault)) { + return false; + } + } + + if (!isElision) { + auto emitAssignment = [lhsPattern, flav](BytecodeEmitter* bce) { + return bce->emitSetOrInitializeDestructuring(lhsPattern, flav); + // [stack] ... OBJ NEXT ITER DONE + }; + + if (!wrapWithDestructuringTryNote(tryNoteDepth, emitAssignment)) { + return false; + } + } else { + if (!emit1(JSOp::Pop)) { + // [stack] ... OBJ NEXT ITER DONE + return false; + } + } + } + + // The last DONE value is on top of the stack. If not DONE, call + // IteratorClose. + // [stack] ... OBJ NEXT ITER DONE + + InternalIfEmitter ifDone(this); + if (!ifDone.emitThenElse()) { + // [stack] ... OBJ NEXT ITER + return false; + } + if (!emitPopN(2)) { + // [stack] ... OBJ + return false; + } + if (!ifDone.emitElse()) { + // [stack] ... OBJ NEXT ITER + return false; + } + if (!emit1(JSOp::Swap)) { + // [stack] ... OBJ ITER NEXT + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] ... OBJ ITER + return false; + } + if (!emitIteratorCloseInInnermostScope()) { + // [stack] ... OBJ + return false; + } + if (!ifDone.emitEnd()) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitComputedPropertyName(UnaryNode* computedPropName) { + MOZ_ASSERT(computedPropName->isKind(ParseNodeKind::ComputedName)); + return emitTree(computedPropName->kid()) && emit1(JSOp::ToPropertyKey); +} + +bool BytecodeEmitter::emitDestructuringOpsObject(ListNode* pattern, + DestructuringFlavor flav) { + MOZ_ASSERT(pattern->isKind(ParseNodeKind::ObjectExpr)); + + // [stack] ... RHS + MOZ_ASSERT(bytecodeSection().stackDepth() > 0); + + if (!emit1(JSOp::CheckObjCoercible)) { + // [stack] ... RHS + return false; + } + + bool needsRestPropertyExcludedSet = + pattern->count() > 1 && pattern->last()->isKind(ParseNodeKind::Spread); + if (needsRestPropertyExcludedSet) { + if (!emitDestructuringObjRestExclusionSet(pattern)) { + // [stack] ... RHS SET + return false; + } + + if (!emit1(JSOp::Swap)) { + // [stack] ... SET RHS + return false; + } + } + + for (ParseNode* member : pattern->contents()) { + ParseNode* subpattern; + if (member->isKind(ParseNodeKind::MutateProto) || + member->isKind(ParseNodeKind::Spread)) { + subpattern = member->as().kid(); + } else { + MOZ_ASSERT(member->isKind(ParseNodeKind::PropertyDefinition) || + member->isKind(ParseNodeKind::Shorthand)); + subpattern = member->as().right(); + } + + ParseNode* lhs = subpattern; + MOZ_ASSERT_IF(member->isKind(ParseNodeKind::Spread), + !lhs->isKind(ParseNodeKind::AssignExpr)); + if (lhs->isKind(ParseNodeKind::AssignExpr)) { + lhs = lhs->as().left(); + } + + size_t emitted; + if (!emitDestructuringLHSRef(lhs, &emitted)) { + // [stack] ... SET? RHS LREF* + return false; + } + + // Duplicate the value being destructured to use as a reference base. + if (!emitDupAt(emitted)) { + // [stack] ... SET? RHS LREF* RHS + return false; + } + + if (member->isKind(ParseNodeKind::Spread)) { + if (!updateSourceCoordNotes(member->pn_pos.begin)) { + return false; + } + + if (!emit1(JSOp::NewInit)) { + // [stack] ... SET? RHS LREF* RHS TARGET + return false; + } + if (!emit1(JSOp::Dup)) { + // [stack] ... SET? RHS LREF* RHS TARGET TARGET + return false; + } + if (!emit2(JSOp::Pick, 2)) { + // [stack] ... SET? RHS LREF* TARGET TARGET RHS + return false; + } + + if (needsRestPropertyExcludedSet) { + if (!emit2(JSOp::Pick, emitted + 4)) { + // [stack] ... RHS LREF* TARGET TARGET RHS SET + return false; + } + } + + CopyOption option = needsRestPropertyExcludedSet ? CopyOption::Filtered + : CopyOption::Unfiltered; + if (!emitCopyDataProperties(option)) { + // [stack] ... RHS LREF* TARGET + return false; + } + + // Destructure TARGET per this member's lhs. + if (!emitSetOrInitializeDestructuring(lhs, flav)) { + // [stack] ... RHS + return false; + } + + MOZ_ASSERT(member == pattern->last(), "Rest property is always last"); + break; + } + + // Now push the property name currently being matched, which is the + // current property name "label" on the left of a colon in the object + // initialiser. + bool needsGetElem = true; + + if (member->isKind(ParseNodeKind::MutateProto)) { + if (!emitAtomOp(JSOp::GetProp, cx->parserNames().proto)) { + // [stack] ... SET? RHS LREF* PROP + return false; + } + needsGetElem = false; + } else { + MOZ_ASSERT(member->isKind(ParseNodeKind::PropertyDefinition) || + member->isKind(ParseNodeKind::Shorthand)); + + ParseNode* key = member->as().left(); + if (key->isKind(ParseNodeKind::NumberExpr)) { + if (!emitNumberOp(key->as().value())) { + // [stack]... SET? RHS LREF* RHS KEY + return false; + } + } else if (key->isKind(ParseNodeKind::BigIntExpr)) { + if (!emitBigIntOp(&key->as())) { + // [stack]... SET? RHS LREF* RHS KEY + return false; + } + } else if (key->isKind(ParseNodeKind::ObjectPropertyName) || + key->isKind(ParseNodeKind::StringExpr)) { + if (!emitAtomOp(JSOp::GetProp, key->as().atom(), + ShouldInstrument::Yes)) { + // [stack] ... SET? RHS LREF* PROP + return false; + } + needsGetElem = false; + } else { + if (!emitComputedPropertyName(&key->as())) { + // [stack] ... SET? RHS LREF* RHS KEY + return false; + } + + // Add the computed property key to the exclusion set. + if (needsRestPropertyExcludedSet) { + if (!emitDupAt(emitted + 3)) { + // [stack] ... SET RHS LREF* RHS KEY SET + return false; + } + if (!emitDupAt(1)) { + // [stack] ... SET RHS LREF* RHS KEY SET KEY + return false; + } + if (!emit1(JSOp::Undefined)) { + // [stack] ... SET RHS LREF* RHS KEY SET KEY UNDEFINED + return false; + } + if (!emit1(JSOp::InitElem)) { + // [stack] ... SET RHS LREF* RHS KEY SET + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] ... SET RHS LREF* RHS KEY + return false; + } + } + } + } + + // Get the property value if not done already. + if (needsGetElem && !emitElemOpBase(JSOp::GetElem)) { + // [stack] ... SET? RHS LREF* PROP + return false; + } + + if (subpattern->isKind(ParseNodeKind::AssignExpr)) { + if (!emitDefault(subpattern->as().right(), lhs)) { + // [stack] ... SET? RHS LREF* VALUE + return false; + } + } + + // Destructure PROP per this member's lhs. + if (!emitSetOrInitializeDestructuring(subpattern, flav)) { + // [stack] ... SET? RHS + return false; + } + } + + return true; +} + +static bool IsDestructuringRestExclusionSetObjLiteralCompatible( + ListNode* pattern) { + int32_t propCount = 0; + for (ParseNode* member : pattern->contents()) { + if (member->isKind(ParseNodeKind::Spread)) { + MOZ_ASSERT(!member->pn_next, "unexpected trailing element after spread"); + break; + } + + propCount++; + + if (member->isKind(ParseNodeKind::MutateProto)) { + continue; + } + + ParseNode* key = member->as().left(); + if (key->isKind(ParseNodeKind::ObjectPropertyName) || + key->isKind(ParseNodeKind::StringExpr)) { + continue; + } + + // Number and BigInt keys aren't yet supported. Computed property names need + // to be added dynamically. + MOZ_ASSERT(key->isKind(ParseNodeKind::NumberExpr) || + key->isKind(ParseNodeKind::BigIntExpr) || + key->isKind(ParseNodeKind::ComputedName)); + return false; + } + + if (propCount >= PropertyTree::MAX_HEIGHT) { + // JSOp::NewObject cannot accept dictionary-mode objects. + return false; + } + + return true; +} + +bool BytecodeEmitter::emitDestructuringObjRestExclusionSet(ListNode* pattern) { + MOZ_ASSERT(pattern->isKind(ParseNodeKind::ObjectExpr)); + MOZ_ASSERT(pattern->last()->isKind(ParseNodeKind::Spread)); + + // See if we can use ObjLiteral to construct the exclusion set object. + if (IsDestructuringRestExclusionSetObjLiteralCompatible(pattern)) { + if (!emitDestructuringRestExclusionSetObjLiteral(pattern)) { + // [stack] OBJ + return false; + } + } else { + // Take the slow but sure way and start off with a blank object. + if (!emit1(JSOp::NewInit)) { + // [stack] OBJ + return false; + } + } + + const ParserAtom* pnatom = nullptr; + for (ParseNode* member : pattern->contents()) { + if (member->isKind(ParseNodeKind::Spread)) { + MOZ_ASSERT(!member->pn_next, "unexpected trailing element after spread"); + break; + } + + bool isIndex = false; + if (member->isKind(ParseNodeKind::MutateProto)) { + pnatom = cx->parserNames().proto; + } else { + ParseNode* key = member->as().left(); + if (key->isKind(ParseNodeKind::NumberExpr)) { + if (!emitNumberOp(key->as().value())) { + return false; + } + isIndex = true; + } else if (key->isKind(ParseNodeKind::BigIntExpr)) { + if (!emitBigIntOp(&key->as())) { + return false; + } + isIndex = true; + } else if (key->isKind(ParseNodeKind::ObjectPropertyName) || + key->isKind(ParseNodeKind::StringExpr)) { + pnatom = key->as().atom(); + } else { + // Otherwise this is a computed property name which needs to + // be added dynamically. + MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName)); + continue; + } + } + + // Initialize elements with |undefined|. + if (!emit1(JSOp::Undefined)) { + return false; + } + + if (isIndex) { + if (!emit1(JSOp::InitElem)) { + return false; + } + } else { + if (!emitAtomOp(JSOp::InitProp, pnatom)) { + return false; + } + } + } + + return true; +} + +bool BytecodeEmitter::emitDestructuringOps(ListNode* pattern, + DestructuringFlavor flav) { + if (pattern->isKind(ParseNodeKind::ArrayExpr)) { + return emitDestructuringOpsArray(pattern, flav); + } + return emitDestructuringOpsObject(pattern, flav); +} + +bool BytecodeEmitter::emitTemplateString(ListNode* templateString) { + bool pushedString = false; + + for (ParseNode* item : templateString->contents()) { + bool isString = (item->getKind() == ParseNodeKind::StringExpr || + item->getKind() == ParseNodeKind::TemplateStringExpr); + + // Skip empty strings. These are very common: a template string like + // `${a}${b}` has three empty strings and without this optimization + // we'd emit four JSOp::Add operations instead of just one. + if (isString && item->as().atom()->length() == 0) { + continue; + } + + if (!isString) { + // We update source notes before emitting the expression + if (!updateSourceCoordNotes(item->pn_pos.begin)) { + return false; + } + } + + if (!emitTree(item)) { + return false; + } + + if (!isString) { + // We need to convert the expression to a string + if (!emit1(JSOp::ToString)) { + return false; + } + } + + if (pushedString) { + // We've pushed two strings onto the stack. Add them together, leaving + // just one. + if (!emit1(JSOp::Add)) { + return false; + } + } else { + pushedString = true; + } + } + + if (!pushedString) { + // All strings were empty, this can happen for something like `${""}`. + // Just push an empty string. + if (!emitAtomOp(JSOp::String, cx->parserNames().empty)) { + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitDeclarationList(ListNode* declList) { + for (ParseNode* decl : declList->contents()) { + ParseNode* pattern; + ParseNode* initializer; + if (decl->isKind(ParseNodeKind::Name)) { + pattern = decl; + initializer = nullptr; + } else { + AssignmentNode* assignNode = &decl->as(); + pattern = assignNode->left(); + initializer = assignNode->right(); + } + + if (pattern->isKind(ParseNodeKind::Name)) { + // initializer can be null here. + if (!emitSingleDeclaration(declList, &pattern->as(), + initializer)) { + return false; + } + } else { + MOZ_ASSERT(pattern->isKind(ParseNodeKind::ArrayExpr) || + pattern->isKind(ParseNodeKind::ObjectExpr)); + MOZ_ASSERT(initializer != nullptr); + + if (!updateSourceCoordNotes(initializer->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitTree(initializer)) { + return false; + } + + if (!emitDestructuringOps(&pattern->as(), + DestructuringFlavor::Declaration)) { + return false; + } + + if (!emit1(JSOp::Pop)) { + return false; + } + } + } + return true; +} + +bool BytecodeEmitter::emitSingleDeclaration(ListNode* declList, NameNode* decl, + ParseNode* initializer) { + MOZ_ASSERT(decl->isKind(ParseNodeKind::Name)); + + // Nothing to do for initializer-less 'var' declarations, as there's no TDZ. + if (!initializer && declList->isKind(ParseNodeKind::VarStmt)) { + return true; + } + + const ParserAtom* nameAtom = decl->name(); + NameOpEmitter noe(this, nameAtom, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + // [stack] ENV? + return false; + } + if (!initializer) { + // Lexical declarations are initialized to undefined without an + // initializer. + MOZ_ASSERT(declList->isKind(ParseNodeKind::LetDecl), + "var declarations without initializers handled above, " + "and const declarations must have initializers"); + if (!emit1(JSOp::Undefined)) { + // [stack] ENV? UNDEF + return false; + } + } else { + MOZ_ASSERT(initializer); + + if (!updateSourceCoordNotes(initializer->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitInitializer(initializer, decl)) { + // [stack] ENV? V + return false; + } + } + if (!noe.emitAssignment()) { + // [stack] V + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] + return false; + } + + return true; +} + +bool BytecodeEmitter::emitAssignmentRhs(ParseNode* rhs, + const ParserAtom* anonFunctionName) { + if (rhs->isDirectRHSAnonFunction()) { + if (anonFunctionName) { + return emitAnonymousFunctionWithName(rhs, anonFunctionName); + } + return emitAnonymousFunctionWithComputedName(rhs, FunctionPrefixKind::None); + } + return emitTree(rhs); +} + +// 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; +} + +static inline JSOp CompoundAssignmentParseNodeKindToJSOp(ParseNodeKind pnk) { + switch (pnk) { + case ParseNodeKind::InitExpr: + return JSOp::Nop; + case ParseNodeKind::AssignExpr: + return JSOp::Nop; + case ParseNodeKind::AddAssignExpr: + return JSOp::Add; + case ParseNodeKind::SubAssignExpr: + return JSOp::Sub; + case ParseNodeKind::BitOrAssignExpr: + return JSOp::BitOr; + case ParseNodeKind::BitXorAssignExpr: + return JSOp::BitXor; + case ParseNodeKind::BitAndAssignExpr: + return JSOp::BitAnd; + case ParseNodeKind::LshAssignExpr: + return JSOp::Lsh; + case ParseNodeKind::RshAssignExpr: + return JSOp::Rsh; + case ParseNodeKind::UrshAssignExpr: + return JSOp::Ursh; + case ParseNodeKind::MulAssignExpr: + return JSOp::Mul; + case ParseNodeKind::DivAssignExpr: + return JSOp::Div; + case ParseNodeKind::ModAssignExpr: + return JSOp::Mod; + case ParseNodeKind::PowAssignExpr: + 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::emitAssignmentOrInit(ParseNodeKind kind, ParseNode* lhs, + ParseNode* rhs) { + JSOp compoundOp = CompoundAssignmentParseNodeKindToJSOp(kind); + bool isCompound = compoundOp != JSOp::Nop; + bool isInit = kind == ParseNodeKind::InitExpr; + + MOZ_ASSERT_IF(isInit, lhs->isKind(ParseNodeKind::DotExpr) || + lhs->isKind(ParseNodeKind::ElemExpr)); + + // |name| is used within NameOpEmitter, so its lifetime must surpass |noe|. + const ParserAtom* name = nullptr; + + Maybe noe; + Maybe poe; + Maybe eoe; + + // Deal with non-name assignments. + uint8_t offset = 1; + + // Purpose of anonFunctionName: + // + // In normal name assignments (`f = function(){}`), an anonymous function gets + // an inferred name based on the left-hand side name node. + // + // 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. + const ParserAtom* anonFunctionName = nullptr; + + switch (lhs->getKind()) { + case ParseNodeKind::Name: { + name = lhs->as().name(); + anonFunctionName = name; + noe.emplace(this, name, + isCompound ? NameOpEmitter::Kind::CompoundAssignment + : NameOpEmitter::Kind::SimpleAssignment); + break; + } + case ParseNodeKind::DotExpr: { + PropertyAccess* prop = &lhs->as(); + bool isSuper = prop->isSuper(); + poe.emplace(this, + isCompound ? PropOpEmitter::Kind::CompoundAssignment + : isInit ? PropOpEmitter::Kind::PropInit + : 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)) { + // [stack] THIS SUPERBASE + return false; + } + // SUPERBASE is pushed onto THIS later in poe->emitGet below. + offset += 2; + } else { + if (!emitTree(&prop->expression())) { + // [stack] OBJ + return false; + } + offset += 1; + } + break; + } + case ParseNodeKind::ElemExpr: { + PropertyByValue* elem = &lhs->as(); + bool isSuper = elem->isSuper(); + bool isPrivate = elem->key().isKind(ParseNodeKind::PrivateName); + eoe.emplace(this, + isCompound ? ElemOpEmitter::Kind::CompoundAssignment + : isInit ? ElemOpEmitter::Kind::PropInit + : ElemOpEmitter::Kind::SimpleAssignment, + isSuper ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other, + isPrivate ? NameVisibility::Private : NameVisibility::Public); + if (!emitElemObjAndKey(elem, isSuper, *eoe)) { + // [stack] # if Super + // [stack] THIS KEY + // [stack] # otherwise + // [stack] OBJ KEY + return false; + } + if (isSuper) { + // SUPERBASE is pushed onto KEY in eoe->emitGet below. + offset += 3; + } else { + offset += 2; + } + break; + } + case ParseNodeKind::ArrayExpr: + case ParseNodeKind::ObjectExpr: + break; + case ParseNodeKind::CallExpr: + if (!emitTree(lhs)) { + return false; + } + + // Assignment to function calls is forbidden, but we have to make the + // call first. Now we can throw. + if (!emit2(JSOp::ThrowMsg, uint8_t(ThrowMsgKind::AssignToCall))) { + return false; + } + + // Rebalance the stack to placate stack-depth assertions. + if (!emit1(JSOp::Pop)) { + return false; + } + break; + default: + MOZ_ASSERT(0); + } + + if (isCompound) { + MOZ_ASSERT(rhs); + switch (lhs->getKind()) { + case ParseNodeKind::DotExpr: { + PropertyAccess* prop = &lhs->as(); + if (!poe->emitGet(prop->key().atom())) { + // [stack] # if Super + // [stack] THIS SUPERBASE PROP + // [stack] # otherwise + // [stack] OBJ PROP + return false; + } + break; + } + case ParseNodeKind::ElemExpr: { + if (!eoe->emitGet()) { + // [stack] KEY THIS OBJ ELEM + return false; + } + break; + } + case ParseNodeKind::CallExpr: + // We just emitted a JSOp::ThrowMsg and popped the call's return + // value. Push a random value to make sure the stack depth is + // correct. + if (!emit1(JSOp::Null)) { + // [stack] NULL + return false; + } + break; + default:; + } + } + + switch (lhs->getKind()) { + case ParseNodeKind::Name: + if (!noe->prepareForRhs()) { + // [stack] ENV? VAL? + return false; + } + offset += noe->emittedBindOp(); + break; + case ParseNodeKind::DotExpr: + if (!poe->prepareForRhs()) { + // [stack] # if Simple Assignment with Super + // [stack] THIS SUPERBASE + // [stack] # if Simple Assignment with other + // [stack] OBJ + // [stack] # if Compound Assignment with Super + // [stack] THIS SUPERBASE PROP + // [stack] # if Compound Assignment with other + // [stack] OBJ PROP + return false; + } + break; + case ParseNodeKind::ElemExpr: + if (!eoe->prepareForRhs()) { + // [stack] # if Simple Assignment with Super + // [stack] THIS KEY SUPERBASE + // [stack] # if Simple Assignment with other + // [stack] OBJ KEY + // [stack] # if Compound Assignment with Super + // [stack] THIS KEY SUPERBASE ELEM + // [stack] # if Compound Assignment with other + // [stack] OBJ KEY ELEM + return false; + } + break; + default: + break; + } + + 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 (isCompound) { + if (!newSrcNote(SrcNoteType::AssignOp)) { + return false; + } + if (!emit1(compoundOp)) { + // [stack] ... VAL + return false; + } + } + + /* Finally, emit the specialized assignment bytecode. */ + switch (lhs->getKind()) { + case ParseNodeKind::Name: { + if (!noe->emitAssignment()) { + // [stack] VAL + return false; + } + break; + } + case ParseNodeKind::DotExpr: { + PropertyAccess* prop = &lhs->as(); + if (!poe->emitAssignment(prop->key().atom())) { + // [stack] VAL + return false; + } + break; + } + case ParseNodeKind::CallExpr: + // We threw above, so nothing to do here. + break; + case ParseNodeKind::ElemExpr: { + if (!eoe->emitAssignment()) { + // [stack] VAL + return false; + } + break; + } + case ParseNodeKind::ArrayExpr: + case ParseNodeKind::ObjectExpr: + if (!emitDestructuringOps(&lhs->as(), + DestructuringFlavor::Assignment)) { + return false; + } + break; + default: + MOZ_ASSERT(0); + } + return true; +} + +bool BytecodeEmitter::emitShortCircuitAssignment(AssignmentNode* node) { + TDZCheckCache tdzCache(this); + + JSOp op; + switch (node->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 = node->left(); + ParseNode* rhs = node->right(); + + // |name| is used within NameOpEmitter, so its lifetime must surpass |noe|. + const ParserAtom* name = nullptr; + + // Select the appropriate emitter based on the left-hand side. + Maybe noe; + Maybe poe; + Maybe eoe; + + int32_t depth = bytecodeSection().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::DotExpr: { + 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().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::ElemExpr: { + PropertyByValue* elem = &lhs->as(); + bool isSuper = elem->isSuper(); + bool isPrivate = elem->key().isKind(ParseNodeKind::PrivateName); + eoe.emplace(this, ElemOpEmitter::Kind::CompoundAssignment, + isSuper ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other, + isPrivate ? NameVisibility::Private : NameVisibility::Public); + + 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(bytecodeSection().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::DotExpr: { + PropertyAccess* prop = &lhs->as(); + + if (!poe->emitAssignment(prop->key().atom())) { + // [stack] RHS + return false; + } + break; + } + + case ParseNodeKind::ElemExpr: { + if (!eoe->emitAssignment()) { + // [stack] RHS + return false; + } + break; + } + + default: + MOZ_CRASH(); + } + + MOZ_ASSERT(bytecodeSection().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. + bytecodeSection().setStackDepth(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(bytecodeSection().stackDepth() == depth + 1); + + return true; +} + +bool BytecodeEmitter::emitCallSiteObjectArray(ListNode* cookedOrRaw, + GCThingIndex* outArrayIndex) { + uint32_t count = cookedOrRaw->count(); + ParseNode* pn = cookedOrRaw->head(); + + // The first element of a call-site node is the raw-values list. Skip over it. + if (cookedOrRaw->isKind(ParseNodeKind::CallSiteObj)) { + MOZ_ASSERT(pn->isKind(ParseNodeKind::ArrayExpr)); + pn = pn->pn_next; + count--; + } else { + MOZ_ASSERT(cookedOrRaw->isKind(ParseNodeKind::ArrayExpr)); + } + + ObjLiteralWriter writer; + + ObjLiteralFlags flags(ObjLiteralFlag::Array); + writer.beginObject(flags); + writer.beginDenseArrayElements(); + + size_t idx; + for (idx = 0; pn; idx++, pn = pn->pn_next) { + MOZ_ASSERT(pn->isKind(ParseNodeKind::TemplateStringExpr) || + pn->isKind(ParseNodeKind::RawUndefinedExpr)); + + if (!emitObjLiteralValue(writer, pn)) { + return false; + } + } + MOZ_ASSERT(idx == count); + + return addObjLiteralData(writer, outArrayIndex); +} + +bool BytecodeEmitter::emitCallSiteObject(CallSiteNode* callSiteObj) { + GCThingIndex cookedIndex; + if (!emitCallSiteObjectArray(callSiteObj, &cookedIndex)) { + return false; + } + + GCThingIndex rawIndex; + if (!emitCallSiteObjectArray(callSiteObj->rawNodes(), &rawIndex)) { + return false; + } + + MOZ_ASSERT(sc->hasCallSiteObj()); + + return emitObjectPairOp(cookedIndex, rawIndex, JSOp::CallSiteObj); +} + +bool BytecodeEmitter::emitCatch(BinaryNode* catchClause) { + // We must be nested under a try-finally statement. + MOZ_ASSERT(innermostNestableControl->is()); + + ParseNode* param = catchClause->left(); + if (!param) { + // Catch parameter was omitted; just discard the exception. + if (!emit1(JSOp::Pop)) { + return false; + } + } else { + switch (param->getKind()) { + case ParseNodeKind::ArrayExpr: + case ParseNodeKind::ObjectExpr: + if (!emitDestructuringOps(¶m->as(), + DestructuringFlavor::Declaration)) { + return false; + } + if (!emit1(JSOp::Pop)) { + return false; + } + break; + + case ParseNodeKind::Name: + if (!emitLexicalInitialization(¶m->as())) { + return false; + } + if (!emit1(JSOp::Pop)) { + return false; + } + break; + + default: + MOZ_ASSERT(0); + } + } + + /* Emit the catch body. */ + return emitTree(catchClause->right()); +} + +// Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See the +// comment on EmitSwitch. +MOZ_NEVER_INLINE bool BytecodeEmitter::emitTry(TryNode* tryNode) { + LexicalScopeNode* catchScope = tryNode->catchScope(); + ParseNode* finallyNode = tryNode->finallyBlock(); + + TryEmitter::Kind kind; + if (catchScope) { + if (finallyNode) { + kind = TryEmitter::Kind::TryCatchFinally; + } else { + kind = TryEmitter::Kind::TryCatch; + } + } else { + MOZ_ASSERT(finallyNode); + kind = TryEmitter::Kind::TryFinally; + } + TryEmitter tryCatch(this, kind, TryEmitter::ControlKind::Syntactic); + + if (!tryCatch.emitTry()) { + return false; + } + + if (!emitTree(tryNode->body())) { + return false; + } + + // If this try has a catch block, emit it. + if (catchScope) { + // The emitted code for a catch block looks like: + // + // [pushlexicalenv] only if any local aliased + // exception + // setlocal 0; pop assign or possibly destructure exception + // < catch block contents > + // debugleaveblock + // [poplexicalenv] only if any local aliased + // if there is a finally block: + // gosub + // goto + if (!tryCatch.emitCatch()) { + return false; + } + + // Emit the lexical scope and catch body. + if (!emitTree(catchScope)) { + return false; + } + } + + // Emit the finally handler, if there is one. + if (finallyNode) { + if (!tryCatch.emitFinally(Some(finallyNode->pn_pos.begin))) { + return false; + } + + if (!emitTree(finallyNode)) { + return false; + } + } + + if (!tryCatch.emitEnd()) { + return false; + } + + return true; +} + +MOZ_MUST_USE bool BytecodeEmitter::emitGoSub(JumpList* jump) { + // Emit the following: + // + // False + // ResumeIndex + // Gosub + // resumeOffset: + // JumpTarget + // + // The order is important: the Baseline Interpreter relies on JSOp::JumpTarget + // setting the frame's ICEntry when resuming at resumeOffset. + + if (!emit1(JSOp::False)) { + return false; + } + + BytecodeOffset off; + if (!emitN(JSOp::ResumeIndex, 3, &off)) { + return false; + } + + if (!emitJumpNoFallthrough(JSOp::Gosub, jump)) { + return false; + } + + uint32_t resumeIndex; + if (!allocateResumeIndex(bytecodeSection().offset(), &resumeIndex)) { + return false; + } + + SET_RESUMEINDEX(bytecodeSection().code(off), resumeIndex); + + JumpTarget target; + return emitJumpTarget(&target); +} + +bool BytecodeEmitter::emitIf(TernaryNode* ifNode) { + IfEmitter ifThenElse(this); + + if (!ifThenElse.emitIf(Some(ifNode->kid1()->pn_pos.begin))) { + return false; + } + +if_again: + ParseNode* testNode = ifNode->kid1(); + auto conditionKind = IfEmitter::ConditionKind::Positive; + if (testNode->isKind(ParseNodeKind::NotExpr)) { + testNode = testNode->as().kid(); + conditionKind = IfEmitter::ConditionKind::Negative; + } + + if (!markStepBreakpoint()) { + return false; + } + + // Emit code for the condition before pushing stmtInfo. + // NOTE: NotExpr of testNode may be unwrapped, and in that case the negation + // is handled by conditionKind. + if (!emitTree(testNode)) { + return false; + } + + ParseNode* elseNode = ifNode->kid3(); + if (elseNode) { + if (!ifThenElse.emitThenElse(conditionKind)) { + return false; + } + } else { + if (!ifThenElse.emitThen(conditionKind)) { + return false; + } + } + + /* Emit code for the then part. */ + if (!emitTree(ifNode->kid2())) { + return false; + } + + if (elseNode) { + if (elseNode->isKind(ParseNodeKind::IfStmt)) { + ifNode = &elseNode->as(); + + if (!ifThenElse.emitElseIf(Some(ifNode->kid1()->pn_pos.begin))) { + return false; + } + + goto if_again; + } + + if (!ifThenElse.emitElse()) { + return false; + } + + /* Emit code for the else part. */ + if (!emitTree(elseNode)) { + return false; + } + } + + if (!ifThenElse.emitEnd()) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitHoistedFunctionsInList(ListNode* stmtList) { + MOZ_ASSERT(stmtList->hasTopLevelFunctionDeclarations()); + + // We can call this multiple times for sloppy eval scopes. + if (stmtList->emittedTopLevelFunctionDeclarations()) { + return true; + } + + stmtList->setEmittedTopLevelFunctionDeclarations(); + + for (ParseNode* stmt : stmtList->contents()) { + ParseNode* maybeFun = stmt; + + if (!sc->strict()) { + while (maybeFun->isKind(ParseNodeKind::LabelStmt)) { + maybeFun = maybeFun->as().statement(); + } + } + + if (maybeFun->is() && + maybeFun->as().functionIsHoisted()) { + if (!emitTree(maybeFun)) { + return false; + } + } + } + + return true; +} + +bool BytecodeEmitter::emitLexicalScopeBody( + ParseNode* body, EmitLineNumberNote emitLineNote /* = EMIT_LINENOTE */) { + if (body->isKind(ParseNodeKind::StatementList) && + body->as().hasTopLevelFunctionDeclarations()) { + // This block contains function statements whose definitions are + // hoisted to the top of the block. Emit these as a separate pass + // before the rest of the block. + if (!emitHoistedFunctionsInList(&body->as())) { + return false; + } + } + + // Line notes were updated by emitLexicalScope or emitScript. + return emitTree(body, ValueUsage::WantValue, emitLineNote); +} + +// Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See +// the comment on emitSwitch. +MOZ_NEVER_INLINE bool BytecodeEmitter::emitLexicalScope( + LexicalScopeNode* lexicalScope) { + LexicalScopeEmitter lse(this); + + ParseNode* body = lexicalScope->scopeBody(); + if (lexicalScope->isEmptyScope()) { + if (!lse.emitEmptyScope()) { + return false; + } + + if (!emitLexicalScopeBody(body)) { + return false; + } + + if (!lse.emitEnd()) { + return false; + } + + return true; + } + + // We are about to emit some bytecode for what the spec calls "declaration + // instantiation". Assign these instructions to the opening `{` of the + // block. (Using the location of each declaration we're instantiating is + // too weird when stepping in the debugger.) + if (!ParseNodeRequiresSpecialLineNumberNotes(body)) { + if (!updateSourceCoordNotes(lexicalScope->pn_pos.begin)) { + return false; + } + } + + ScopeKind kind; + if (body->isKind(ParseNodeKind::Catch)) { + BinaryNode* catchNode = &body->as(); + kind = + (!catchNode->left() || catchNode->left()->isKind(ParseNodeKind::Name)) + ? ScopeKind::SimpleCatch + : ScopeKind::Catch; + } else { + kind = lexicalScope->kind(); + } + + if (!lse.emitScope(kind, lexicalScope->scopeBindings())) { + return false; + } + + if (body->isKind(ParseNodeKind::ForStmt)) { + // for loops need to emit {FRESHEN,RECREATE}LEXICALENV if there are + // lexical declarations in the head. Signal this by passing a + // non-nullptr lexical scope. + if (!emitFor(&body->as(), &lse.emitterScope())) { + return false; + } + } else { + if (!emitLexicalScopeBody(body, SUPPRESS_LINENOTE)) { + return false; + } + } + + if (!lse.emitEnd()) { + return false; + } + return true; +} + +bool BytecodeEmitter::emitWith(BinaryNode* withNode) { + // Ensure that the column of the 'with' is set properly. + if (!updateSourceCoordNotes(withNode->left()->pn_pos.begin)) { + return false; + } + + if (!markStepBreakpoint()) { + return false; + } + + if (!emitTree(withNode->left())) { + return false; + } + + EmitterScope emitterScope(this); + if (!emitterScope.enterWith(this)) { + return false; + } + + if (!emitTree(withNode->right())) { + return false; + } + + return emitterScope.leave(this); +} + +bool BytecodeEmitter::emitCopyDataProperties(CopyOption option) { + DebugOnly depth = bytecodeSection().stackDepth(); + + uint32_t argc; + if (option == CopyOption::Filtered) { + MOZ_ASSERT(depth > 2); + // [stack] TARGET SOURCE SET + argc = 3; + + if (!emitAtomOp(JSOp::GetIntrinsic, cx->parserNames().CopyDataProperties)) { + // [stack] TARGET SOURCE SET COPYDATAPROPERTIES + return false; + } + } else { + MOZ_ASSERT(depth > 1); + // [stack] TARGET SOURCE + argc = 2; + + if (!emitAtomOp(JSOp::GetIntrinsic, + cx->parserNames().CopyDataPropertiesUnfiltered)) { + // [stack] TARGET SOURCE COPYDATAPROPERTIES + return false; + } + } + + if (!emit1(JSOp::Undefined)) { + // [stack] TARGET SOURCE SET? COPYDATAPROPERTIES + // UNDEFINED + return false; + } + if (!emit2(JSOp::Pick, argc + 1)) { + // [stack] SOURCE SET? COPYDATAPROPERTIES UNDEFINED + // TARGET + return false; + } + if (!emit2(JSOp::Pick, argc + 1)) { + // [stack] SET? COPYDATAPROPERTIES UNDEFINED TARGET + // SOURCE + return false; + } + if (option == CopyOption::Filtered) { + if (!emit2(JSOp::Pick, argc + 1)) { + // [stack] COPYDATAPROPERTIES UNDEFINED TARGET SOURCE SET + return false; + } + } + if (!emitCall(JSOp::CallIgnoresRv, argc)) { + // [stack] IGNORED + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] + return false; + } + + MOZ_ASSERT(depth - int(argc) == bytecodeSection().stackDepth()); + return true; +} + +bool BytecodeEmitter::emitBigIntOp(BigIntLiteral* bigint) { + GCThingIndex index; + if (!perScriptData().gcThingList().append(bigint, &index)) { + return false; + } + return emitGCIndexOp(JSOp::BigInt, index); +} + +bool BytecodeEmitter::emitIterator() { + // Convert iterable to iterator. + if (!emit1(JSOp::Dup)) { + // [stack] OBJ OBJ + return false; + } + if (!emit2(JSOp::Symbol, uint8_t(JS::SymbolCode::iterator))) { + // [stack] OBJ OBJ @@ITERATOR + return false; + } + if (!emitElemOpBase(JSOp::GetElem)) { + // [stack] OBJ ITERFN + return false; + } + if (!emit1(JSOp::Swap)) { + // [stack] ITERFN OBJ + return false; + } + if (!emitCall(JSOp::CallIter, 0)) { + // [stack] ITER + return false; + } + if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) { + // [stack] ITER + return false; + } + if (!emit1(JSOp::Dup)) { + // [stack] ITER ITER + return false; + } + if (!emitAtomOp(JSOp::GetProp, cx->parserNames().next)) { + // [stack] ITER NEXT + return false; + } + if (!emit1(JSOp::Swap)) { + // [stack] NEXT ITER + return false; + } + return true; +} + +bool BytecodeEmitter::emitAsyncIterator() { + // Convert iterable to iterator. + if (!emit1(JSOp::Dup)) { + // [stack] OBJ OBJ + return false; + } + if (!emit2(JSOp::Symbol, uint8_t(JS::SymbolCode::asyncIterator))) { + // [stack] OBJ OBJ @@ASYNCITERATOR + return false; + } + if (!emitElemOpBase(JSOp::GetElem)) { + // [stack] OBJ ITERFN + return false; + } + + InternalIfEmitter ifAsyncIterIsUndefined(this); + if (!emitPushNotUndefinedOrNull()) { + // [stack] OBJ ITERFN !UNDEF-OR-NULL + return false; + } + if (!ifAsyncIterIsUndefined.emitThenElse( + IfEmitter::ConditionKind::Negative)) { + // [stack] OBJ ITERFN + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] OBJ + return false; + } + if (!emit1(JSOp::Dup)) { + // [stack] OBJ OBJ + return false; + } + if (!emit2(JSOp::Symbol, uint8_t(JS::SymbolCode::iterator))) { + // [stack] OBJ OBJ @@ITERATOR + return false; + } + if (!emitElemOpBase(JSOp::GetElem)) { + // [stack] OBJ ITERFN + return false; + } + if (!emit1(JSOp::Swap)) { + // [stack] ITERFN OBJ + return false; + } + if (!emitCall(JSOp::CallIter, 0)) { + // [stack] ITER + return false; + } + if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) { + // [stack] ITER + return false; + } + + if (!emit1(JSOp::Dup)) { + // [stack] ITER ITER + return false; + } + if (!emitAtomOp(JSOp::GetProp, cx->parserNames().next)) { + // [stack] ITER SYNCNEXT + return false; + } + + if (!emit1(JSOp::ToAsyncIter)) { + // [stack] ITER + return false; + } + + if (!ifAsyncIterIsUndefined.emitElse()) { + // [stack] OBJ ITERFN + return false; + } + + if (!emit1(JSOp::Swap)) { + // [stack] ITERFN OBJ + return false; + } + if (!emitCall(JSOp::CallIter, 0)) { + // [stack] ITER + return false; + } + if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) { + // [stack] ITER + return false; + } + + if (!ifAsyncIterIsUndefined.emitEnd()) { + // [stack] ITER + return false; + } + + if (!emit1(JSOp::Dup)) { + // [stack] ITER ITER + return false; + } + if (!emitAtomOp(JSOp::GetProp, cx->parserNames().next)) { + // [stack] ITER NEXT + return false; + } + if (!emit1(JSOp::Swap)) { + // [stack] NEXT ITER + return false; + } + + return true; +} + +bool BytecodeEmitter::emitSpread(bool allowSelfHosted) { + LoopControl loopInfo(this, StatementKind::Spread); + + if (!loopInfo.emitLoopHead(this, Nothing())) { + // [stack] NEXT ITER ARR I + return false; + } + + { +#ifdef DEBUG + auto loopDepth = bytecodeSection().stackDepth(); +#endif + + // Spread operations can't contain |continue|, so don't bother setting loop + // and enclosing "update" offsets, as we do with for-loops. + + if (!emitDupAt(3, 2)) { + // [stack] NEXT ITER ARR I NEXT + return false; + } + if (!emitIteratorNext(Nothing(), IteratorKind::Sync, allowSelfHosted)) { + // [stack] NEXT ITER ARR I RESULT + return false; + } + if (!emit1(JSOp::Dup)) { + // [stack] NEXT ITER ARR I RESULT RESULT + return false; + } + if (!emitAtomOp(JSOp::GetProp, cx->parserNames().done)) { + // [stack] NEXT ITER ARR I RESULT DONE + return false; + } + if (!emitJump(JSOp::IfNe, &loopInfo.breaks)) { + // [stack] NEXT ITER ARR I RESULT + return false; + } + + // Emit code to assign result.value to the iteration variable. + if (!emitAtomOp(JSOp::GetProp, cx->parserNames().value)) { + // [stack] NEXT ITER ARR I VALUE + return false; + } + if (!emit1(JSOp::InitElemInc)) { + // [stack] NEXT ITER ARR (I+1) + return false; + } + + if (!loopInfo.emitLoopEnd(this, JSOp::Goto, TryNoteKind::ForOf)) { + // [stack] NEXT ITER ARR (I+1) + return false; + } + + MOZ_ASSERT(bytecodeSection().stackDepth() == loopDepth); + } + + // When we leave the loop body and jump to this point, the result value is + // still on the stack. Account for that by updating the stack depth + // manually. + bytecodeSection().setStackDepth(bytecodeSection().stackDepth() + 1); + + // No continues should occur in spreads. + MOZ_ASSERT(!loopInfo.continues.offset.valid()); + + if (!emit2(JSOp::Pick, 4)) { + // [stack] ITER ARR FINAL_INDEX RESULT NEXT + return false; + } + if (!emit2(JSOp::Pick, 4)) { + // [stack] ARR FINAL_INDEX RESULT NEXT ITER + return false; + } + + return emitPopN(3); + // [stack] ARR FINAL_INDEX +} + +bool BytecodeEmitter::emitInitializeForInOrOfTarget(TernaryNode* forHead) { + MOZ_ASSERT(forHead->isKind(ParseNodeKind::ForIn) || + forHead->isKind(ParseNodeKind::ForOf)); + + MOZ_ASSERT(bytecodeSection().stackDepth() >= 1, + "must have a per-iteration value for initializing"); + + ParseNode* target = forHead->kid1(); + MOZ_ASSERT(!forHead->kid2()); + + // If the for-in/of loop didn't have a variable declaration, per-loop + // initialization is just assigning the iteration value to a target + // expression. + if (!parser->astGenerator().isDeclarationList(target)) { + return emitAssignmentOrInit(ParseNodeKind::AssignExpr, target, nullptr); + // [stack] ... ITERVAL + } + + // Otherwise, per-loop initialization is (possibly) declaration + // initialization. If the declaration is a lexical declaration, it must be + // initialized. If the declaration is a variable declaration, an + // assignment to that name (which does *not* necessarily assign to the + // variable!) must be generated. + + if (!updateSourceCoordNotes(target->pn_pos.begin)) { + return false; + } + + MOZ_ASSERT(target->isForLoopDeclaration()); + target = parser->astGenerator().singleBindingFromDeclaration( + &target->as()); + + NameNode* nameNode = nullptr; + if (target->isKind(ParseNodeKind::Name)) { + nameNode = &target->as(); + } else if (target->isKind(ParseNodeKind::AssignExpr) || + target->isKind(ParseNodeKind::InitExpr)) { + BinaryNode* assignNode = &target->as(); + if (assignNode->left()->is()) { + nameNode = &assignNode->left()->as(); + } + } + + if (nameNode) { + const ParserAtom* nameAtom = nameNode->name(); + NameOpEmitter noe(this, nameAtom, 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(bytecodeSection().stackDepth() >= 2); + if (!emit1(JSOp::Swap)) { + return false; + } + } else { + // In cases of emitting a frame slot or environment slot, + // nothing needs be done. + MOZ_ASSERT(bytecodeSection().stackDepth() >= 1); + } + if (!noe.emitAssignment()) { + return false; + } + + // The caller handles removing the iteration value from the stack. + return true; + } + + MOZ_ASSERT( + !target->isKind(ParseNodeKind::AssignExpr) && + !target->isKind(ParseNodeKind::InitExpr), + "for-in/of loop destructuring declarations can't have initializers"); + + MOZ_ASSERT(target->isKind(ParseNodeKind::ArrayExpr) || + target->isKind(ParseNodeKind::ObjectExpr)); + return emitDestructuringOps(&target->as(), + DestructuringFlavor::Declaration); +} + +bool BytecodeEmitter::emitForOf(ForNode* forOfLoop, + const EmitterScope* headLexicalEmitterScope) { + MOZ_ASSERT(forOfLoop->isKind(ParseNodeKind::ForStmt)); + + TernaryNode* forOfHead = forOfLoop->head(); + MOZ_ASSERT(forOfHead->isKind(ParseNodeKind::ForOf)); + + unsigned iflags = forOfLoop->iflags(); + IteratorKind iterKind = + (iflags & JSITER_FORAWAITOF) ? IteratorKind::Async : IteratorKind::Sync; + MOZ_ASSERT_IF(iterKind == IteratorKind::Async, sc->isSuspendableContext()); + MOZ_ASSERT_IF(iterKind == IteratorKind::Async, + sc->asSuspendableContext()->isAsync()); + + ParseNode* forHeadExpr = forOfHead->kid3(); + + // Certain builtins (e.g. Array.from) are implemented in self-hosting + // as for-of loops. + ForOfEmitter forOf(this, headLexicalEmitterScope, + allowSelfHostedIter(forHeadExpr), iterKind); + + if (!forOf.emitIterated()) { + // [stack] + return false; + } + + if (!updateSourceCoordNotes(forHeadExpr->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitTree(forHeadExpr)) { + // [stack] ITERABLE + return false; + } + + if (headLexicalEmitterScope) { + DebugOnly forOfTarget = forOfHead->kid1(); + MOZ_ASSERT(forOfTarget->isKind(ParseNodeKind::LetDecl) || + forOfTarget->isKind(ParseNodeKind::ConstDecl)); + } + + if (!forOf.emitInitialize(Some(forOfHead->pn_pos.begin))) { + // [stack] NEXT ITER VALUE + return false; + } + + if (!emitInitializeForInOrOfTarget(forOfHead)) { + // [stack] NEXT ITER VALUE + return false; + } + + if (!forOf.emitBody()) { + // [stack] NEXT ITER UNDEF + return false; + } + + // Perform the loop body. + ParseNode* forBody = forOfLoop->body(); + if (!emitTree(forBody)) { + // [stack] NEXT ITER UNDEF + return false; + } + + if (!forOf.emitEnd(Some(forHeadExpr->pn_pos.begin))) { + // [stack] + return false; + } + + return true; +} + +bool BytecodeEmitter::emitForIn(ForNode* forInLoop, + const EmitterScope* headLexicalEmitterScope) { + TernaryNode* forInHead = forInLoop->head(); + MOZ_ASSERT(forInHead->isKind(ParseNodeKind::ForIn)); + + ForInEmitter forIn(this, headLexicalEmitterScope); + + // Annex B: Evaluate the var-initializer expression if present. + // |for (var i = initializer in expr) { ... }| + ParseNode* forInTarget = forInHead->kid1(); + if (parser->astGenerator().isDeclarationList(forInTarget)) { + ParseNode* decl = parser->astGenerator().singleBindingFromDeclaration( + &forInTarget->as()); + if (decl->isKind(ParseNodeKind::AssignExpr) || + decl->isKind(ParseNodeKind::InitExpr)) { + BinaryNode* assignNode = &decl->as(); + if (assignNode->left()->is()) { + NameNode* nameNode = &assignNode->left()->as(); + ParseNode* initializer = assignNode->right(); + MOZ_ASSERT( + forInTarget->isKind(ParseNodeKind::VarStmt), + "for-in initializers are only permitted for |var| declarations"); + + if (!updateSourceCoordNotes(decl->pn_pos.begin)) { + return false; + } + + const ParserAtom* nameAtom = nameNode->name(); + NameOpEmitter noe(this, nameAtom, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + return false; + } + if (!emitInitializer(initializer, nameNode)) { + return false; + } + if (!noe.emitAssignment()) { + return false; + } + + // Pop the initializer. + if (!emit1(JSOp::Pop)) { + return false; + } + } + } + } + + if (!forIn.emitIterated()) { + // [stack] + return false; + } + + // Evaluate the expression being iterated. + ParseNode* expr = forInHead->kid3(); + + if (!updateSourceCoordNotes(expr->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitTree(expr)) { + // [stack] EXPR + return false; + } + + MOZ_ASSERT(forInLoop->iflags() == 0); + + MOZ_ASSERT_IF(headLexicalEmitterScope, + forInTarget->isKind(ParseNodeKind::LetDecl) || + forInTarget->isKind(ParseNodeKind::ConstDecl)); + + if (!forIn.emitInitialize()) { + // [stack] ITER ITERVAL + return false; + } + + if (!emitInitializeForInOrOfTarget(forInHead)) { + // [stack] ITER ITERVAL + return false; + } + + if (!forIn.emitBody()) { + // [stack] ITER ITERVAL + return false; + } + + // Perform the loop body. + ParseNode* forBody = forInLoop->body(); + if (!emitTree(forBody)) { + // [stack] ITER ITERVAL + return false; + } + + if (!forIn.emitEnd(Some(forInHead->pn_pos.begin))) { + // [stack] + return false; + } + + return true; +} + +/* C-style `for (init; cond; update) ...` loop. */ +bool BytecodeEmitter::emitCStyleFor( + ForNode* forNode, const EmitterScope* headLexicalEmitterScope) { + TernaryNode* forHead = forNode->head(); + ParseNode* forBody = forNode->body(); + ParseNode* init = forHead->kid1(); + ParseNode* cond = forHead->kid2(); + ParseNode* update = forHead->kid3(); + bool isLet = init && init->isKind(ParseNodeKind::LetDecl); + + CForEmitter cfor(this, isLet ? headLexicalEmitterScope : nullptr); + + if (!cfor.emitInit(init ? Some(init->pn_pos.begin) : Nothing())) { + // [stack] + return false; + } + + // If the head of this for-loop declared any lexical variables, the parser + // wrapped this ParseNodeKind::For node in a ParseNodeKind::LexicalScope + // representing the implicit scope of those variables. By the time we get + // here, we have already entered that scope. So far, so good. + if (init) { + // Emit the `init` clause, whether it's an expression or a variable + // declaration. (The loop variables were hoisted into an enclosing + // scope, but we still need to emit code for the initializers.) + if (init->isForLoopDeclaration()) { + if (!emitTree(init)) { + // [stack] + return false; + } + } else { + if (!updateSourceCoordNotes(init->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + + // 'init' is an expression, not a declaration. emitTree left its + // value on the stack. + if (!emitTree(init, ValueUsage::IgnoreValue)) { + // [stack] VAL + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] + return false; + } + } + } + + if (!cfor.emitCond(cond ? Some(cond->pn_pos.begin) : Nothing())) { + // [stack] + return false; + } + + if (cond) { + if (!updateSourceCoordNotes(cond->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitTree(cond)) { + // [stack] VAL + return false; + } + } + + if (!cfor.emitBody(cond ? CForEmitter::Cond::Present + : CForEmitter::Cond::Missing)) { + // [stack] + return false; + } + + if (!emitTree(forBody)) { + // [stack] + return false; + } + + if (!cfor.emitUpdate( + update ? CForEmitter::Update::Present : CForEmitter::Update::Missing, + update ? Some(update->pn_pos.begin) : Nothing())) { + // [stack] + return false; + } + + // Check for update code to do before the condition (if any). + if (update) { + if (!updateSourceCoordNotes(update->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitTree(update, ValueUsage::IgnoreValue)) { + // [stack] VAL + return false; + } + } + + if (!cfor.emitEnd(Some(forNode->pn_pos.begin))) { + // [stack] + return false; + } + + return true; +} + +bool BytecodeEmitter::emitFor(ForNode* forNode, + const EmitterScope* headLexicalEmitterScope) { + if (forNode->head()->isKind(ParseNodeKind::ForHead)) { + return emitCStyleFor(forNode, headLexicalEmitterScope); + } + + if (!updateLineNumberNotes(forNode->pn_pos.begin)) { + return false; + } + + if (forNode->head()->isKind(ParseNodeKind::ForIn)) { + return emitForIn(forNode, headLexicalEmitterScope); + } + + MOZ_ASSERT(forNode->head()->isKind(ParseNodeKind::ForOf)); + return emitForOf(forNode, headLexicalEmitterScope); +} + +MOZ_NEVER_INLINE bool BytecodeEmitter::emitFunction( + FunctionNode* funNode, bool needsProto /* = false */, + ListNode* classContentsIfConstructor /* = nullptr */) { + FunctionBox* funbox = funNode->funbox(); + + MOZ_ASSERT((classContentsIfConstructor != nullptr) == + funbox->isClassConstructor()); + + // [stack] + + FunctionEmitter fe(this, funbox, funNode->syntaxKind(), + funNode->functionIsHoisted() + ? FunctionEmitter::IsHoisted::Yes + : FunctionEmitter::IsHoisted::No); + + // Set the |wasEmitted| flag in the funbox once the function has been + // emitted. Function definitions that need hoisting to the top of the + // function will be seen by emitFunction in two places. + if (funbox->wasEmitted()) { + if (!fe.emitAgain()) { + // [stack] + return false; + } + MOZ_ASSERT(funNode->functionIsHoisted()); + return true; + } + + if (funbox->isInterpreted()) { + // Compute the field initializers data and update the funbox. + // + // NOTE: For a lazy function, this will be applied to any existing function + // in UpdateEmittedInnerFunctions(). + if (classContentsIfConstructor) { + mozilla::Maybe memberInitializers = + setupMemberInitializers(classContentsIfConstructor, + FieldPlacement::Instance); + if (!memberInitializers) { + ReportAllocationOverflow(cx); + return false; + } + funbox->setMemberInitializers(*memberInitializers); + } + + if (!funbox->emitBytecode) { + return fe.emitLazy(); + // [stack] FUN? + } + + if (!fe.prepareForNonLazy()) { + // [stack] + return false; + } + + BytecodeEmitter bce2(this, parser, funbox, stencil, compilationState, + emitterMode); + if (!bce2.init(funNode->pn_pos)) { + return false; + } + + /* We measured the max scope depth when we parsed the function. */ + if (!bce2.emitFunctionScript(funNode)) { + return false; + } + + if (!fe.emitNonLazyEnd()) { + // [stack] FUN? + return false; + } + + return true; + } + + if (!fe.emitAsmJSModule()) { + // [stack] + return false; + } + + return true; +} + +bool BytecodeEmitter::emitDo(BinaryNode* doNode) { + ParseNode* bodyNode = doNode->left(); + + DoWhileEmitter doWhile(this); + if (!doWhile.emitBody(Some(doNode->pn_pos.begin), + getOffsetForLoop(bodyNode))) { + return false; + } + + if (!emitTree(bodyNode)) { + return false; + } + + if (!doWhile.emitCond()) { + return false; + } + + ParseNode* condNode = doNode->right(); + if (!updateSourceCoordNotes(condNode->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitTree(condNode)) { + return false; + } + + if (!doWhile.emitEnd()) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitWhile(BinaryNode* whileNode) { + ParseNode* bodyNode = whileNode->right(); + + WhileEmitter wh(this); + + ParseNode* condNode = whileNode->left(); + if (!wh.emitCond(Some(whileNode->pn_pos.begin), getOffsetForLoop(condNode), + Some(whileNode->pn_pos.end))) { + return false; + } + + if (!updateSourceCoordNotes(condNode->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitTree(condNode)) { + return false; + } + + if (!wh.emitBody()) { + return false; + } + if (!emitTree(bodyNode)) { + return false; + } + + if (!wh.emitEnd()) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitBreak(const ParserName* label) { + BreakableControl* target; + if (label) { + // Any statement with the matching label may be the break target. + auto hasSameLabel = [label](LabelControl* labelControl) { + return labelControl->label() == label; + }; + target = findInnermostNestableControl(hasSameLabel); + } else { + auto isNotLabel = [](BreakableControl* control) { + return !control->is(); + }; + target = findInnermostNestableControl(isNotLabel); + } + + return emitGoto(target, &target->breaks, GotoKind::Break); +} + +bool BytecodeEmitter::emitContinue(const ParserName* label) { + LoopControl* target = nullptr; + if (label) { + // Find the loop statement enclosed by the matching label. + NestableControl* control = innermostNestableControl; + while (!control->is() || + control->as().label() != label) { + if (control->is()) { + target = &control->as(); + } + control = control->enclosing(); + } + } else { + target = findInnermostNestableControl(); + } + return emitGoto(target, &target->continues, GotoKind::Continue); +} + +bool BytecodeEmitter::emitGetFunctionThis(NameNode* thisName) { + MOZ_ASSERT(sc->hasFunctionThisBinding()); + MOZ_ASSERT(thisName->isName(cx->parserNames().dotThis)); + + return emitGetFunctionThis(Some(thisName->pn_pos.begin)); +} + +bool BytecodeEmitter::emitGetFunctionThis( + const mozilla::Maybe& offset) { + if (offset) { + if (!updateLineNumberNotes(*offset)) { + return false; + } + } + + if (!emitGetName(cx->parserNames().dotThis)) { + // [stack] THIS + return false; + } + if (sc->needsThisTDZChecks()) { + if (!emit1(JSOp::CheckThis)) { + // [stack] THIS + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitGetThisForSuperBase(UnaryNode* superBase) { + MOZ_ASSERT(superBase->isKind(ParseNodeKind::SuperBase)); + NameNode* nameNode = &superBase->kid()->as(); + return emitGetFunctionThis(nameNode); + // [stack] THIS +} + +bool BytecodeEmitter::emitThisLiteral(ThisLiteral* pn) { + if (ParseNode* kid = pn->kid()) { + NameNode* thisName = &kid->as(); + return emitGetFunctionThis(thisName); + // [stack] THIS + } + + if (sc->thisBinding() == ThisBinding::Module) { + return emit1(JSOp::Undefined); + // [stack] UNDEF + } + + MOZ_ASSERT(sc->thisBinding() == ThisBinding::Global); + return emit1(JSOp::GlobalThis); + // [stack] THIS +} + +bool BytecodeEmitter::emitCheckDerivedClassConstructorReturn() { + MOZ_ASSERT(lookupName(cx->parserNames().dotThis).hasKnownSlot()); + if (!emitGetName(cx->parserNames().dotThis)) { + return false; + } + if (!emit1(JSOp::CheckReturn)) { + return false; + } + return true; +} + +bool BytecodeEmitter::emitReturn(UnaryNode* returnNode) { + if (!updateSourceCoordNotes(returnNode->pn_pos.begin)) { + return false; + } + + bool needsIteratorResult = + sc->isFunctionBox() && sc->asFunctionBox()->needsIteratorResult(); + if (needsIteratorResult) { + if (!emitPrepareIteratorResult()) { + return false; + } + } + + if (!updateSourceCoordNotes(returnNode->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + + /* Push a return value */ + if (ParseNode* expr = returnNode->kid()) { + if (!emitTree(expr)) { + return false; + } + + if (sc->asSuspendableContext()->isAsync() && + sc->asSuspendableContext()->isGenerator()) { + if (!emitAwaitInInnermostScope()) { + return false; + } + } + } else { + /* No explicit return value provided */ + if (!emit1(JSOp::Undefined)) { + return false; + } + } + + if (needsIteratorResult) { + if (!emitFinishIteratorResult(true)) { + return false; + } + } + + // We know functionBodyEndPos is set because "return" is only + // valid in a function, and so we've passed through + // emitFunctionScript. + if (!updateSourceCoordNotes(*functionBodyEndPos)) { + return false; + } + + /* + * EmitNonLocalJumpFixup may add fixup bytecode to close open try + * blocks having finally clauses and to exit intermingled let blocks. + * We can't simply transfer control flow to our caller in that case, + * because we must gosub to those finally clauses from inner to outer, + * with the correct stack pointer (i.e., after popping any with, + * for/in, etc., slots nested inside the finally's try). + * + * In this case we mutate JSOp::Return into JSOp::SetRval and add an + * extra JSOp::RetRval after the fixups. + */ + BytecodeOffset top = bytecodeSection().offset(); + + bool needsFinalYield = + sc->isFunctionBox() && sc->asFunctionBox()->needsFinalYield(); + bool isDerivedClassConstructor = + sc->isFunctionBox() && sc->asFunctionBox()->isDerivedClassConstructor(); + + if (!emit1((needsFinalYield || isDerivedClassConstructor) ? JSOp::SetRval + : JSOp::Return)) { + return false; + } + + // Make sure that we emit this before popping the blocks in + // prepareForNonLocalJump, to ensure that the error is thrown while the + // scope-chain is still intact. + if (isDerivedClassConstructor) { + if (!emitCheckDerivedClassConstructorReturn()) { + return false; + } + } + + NonLocalExitControl nle(this, NonLocalExitControl::Return); + + if (!nle.prepareForNonLocalJumpToOutermost()) { + return false; + } + + if (needsFinalYield) { + // We know that .generator is on the function scope, as we just exited + // all nested scopes. + NameLocation loc = *locationOfNameBoundInScopeType( + cx->parserNames().dotGenerator, varEmitterScope); + + // Resolve the return value before emitting the final yield. + if (sc->asFunctionBox()->needsPromiseResult()) { + if (!emit1(JSOp::GetRval)) { + // [stack] RVAL + return false; + } + if (!emitGetNameAtLocation(cx->parserNames().dotGenerator, loc)) { + // [stack] RVAL GEN + return false; + } + if (!emit2(JSOp::AsyncResolve, + uint8_t(AsyncFunctionResolveKind::Fulfill))) { + // [stack] PROMISE + return false; + } + if (!emit1(JSOp::SetRval)) { + // [stack] + return false; + } + } + + if (!emitGetNameAtLocation(cx->parserNames().dotGenerator, loc)) { + return false; + } + if (!emitYieldOp(JSOp::FinalYieldRval)) { + return false; + } + } else if (isDerivedClassConstructor) { + MOZ_ASSERT(JSOp(bytecodeSection().code()[top.value()]) == JSOp::SetRval); + if (!emitReturnRval()) { + return false; + } + } else if (top + BytecodeOffsetDiff(JSOpLength_Return) != + bytecodeSection().offset() || + // If we are instrumenting, make sure we use RetRval and add any + // instrumentation for the frame exit. + instrumentationKinds) { + bytecodeSection().code()[top.value()] = jsbytecode(JSOp::SetRval); + if (!emitReturnRval()) { + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitGetDotGeneratorInScope(EmitterScope& currentScope) { + if (!sc->isFunction() && sc->isModuleContext() && + sc->asModuleContext()->isAsync()) { + NameLocation loc = *locationOfNameBoundInScopeType( + cx->parserNames().dotGenerator, ¤tScope); + return emitGetNameAtLocation(cx->parserNames().dotGenerator, loc); + } + NameLocation loc = *locationOfNameBoundInScopeType( + cx->parserNames().dotGenerator, ¤tScope); + return emitGetNameAtLocation(cx->parserNames().dotGenerator, loc); +} + +bool BytecodeEmitter::emitInitialYield(UnaryNode* yieldNode) { + if (!emitTree(yieldNode->kid())) { + return false; + } + + if (!emitYieldOp(JSOp::InitialYield)) { + // [stack] RVAL GENERATOR RESUMEKIND + return false; + } + if (!emit1(JSOp::CheckResumeKind)) { + // [stack] RVAL + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] + return false; + } + + return true; +} + +bool BytecodeEmitter::emitYield(UnaryNode* yieldNode) { + MOZ_ASSERT(sc->isFunctionBox()); + MOZ_ASSERT(sc->asFunctionBox()->isGenerator()); + MOZ_ASSERT(yieldNode->isKind(ParseNodeKind::YieldExpr)); + + bool needsIteratorResult = sc->asFunctionBox()->needsIteratorResult(); + if (needsIteratorResult) { + if (!emitPrepareIteratorResult()) { + // [stack] ITEROBJ + return false; + } + } + if (ParseNode* expr = yieldNode->kid()) { + if (!emitTree(expr)) { + // [stack] ITEROBJ? VAL + return false; + } + } else { + if (!emit1(JSOp::Undefined)) { + // [stack] ITEROBJ? UNDEFINED + return false; + } + } + + // 25.5.3.7 AsyncGeneratorYield step 5. + if (sc->asSuspendableContext()->isAsync()) { + MOZ_ASSERT(!needsIteratorResult); + if (!emitAwaitInInnermostScope()) { + // [stack] RESULT + return false; + } + } + + if (needsIteratorResult) { + if (!emitFinishIteratorResult(false)) { + // [stack] ITEROBJ + return false; + } + } + + if (!emitGetDotGeneratorInInnermostScope()) { + // [stack] # if needsIteratorResult + // [stack] ITEROBJ .GENERATOR + // [stack] # else + // [stack] RESULT .GENERATOR + return false; + } + + if (!emitYieldOp(JSOp::Yield)) { + // [stack] YIELDRESULT GENERATOR RESUMEKIND + return false; + } + + if (!emit1(JSOp::CheckResumeKind)) { + // [stack] YIELDRESULT + return false; + } + + return true; +} + +bool BytecodeEmitter::emitAwaitInInnermostScope(UnaryNode* awaitNode) { + MOZ_ASSERT(sc->isSuspendableContext()); + MOZ_ASSERT(awaitNode->isKind(ParseNodeKind::AwaitExpr)); + + if (!emitTree(awaitNode->kid())) { + return false; + } + return emitAwaitInInnermostScope(); +} + +bool BytecodeEmitter::emitAwaitInScope(EmitterScope& currentScope) { + if (!emit1(JSOp::CanSkipAwait)) { + // [stack] VALUE CANSKIP + return false; + } + + if (!emit1(JSOp::MaybeExtractAwaitValue)) { + // [stack] VALUE_OR_RESOLVED CANSKIP + return false; + } + + InternalIfEmitter ifCanSkip(this); + if (!ifCanSkip.emitThen(IfEmitter::ConditionKind::Negative)) { + // [stack] VALUE_OR_RESOLVED + return false; + } + + if (sc->asSuspendableContext()->needsPromiseResult()) { + if (!emitGetDotGeneratorInScope(currentScope)) { + // [stack] VALUE GENERATOR + return false; + } + if (!emit1(JSOp::AsyncAwait)) { + // [stack] PROMISE + return false; + } + } + + if (!emitGetDotGeneratorInScope(currentScope)) { + // [stack] VALUE|PROMISE GENERATOR + return false; + } + if (!emitYieldOp(JSOp::Await)) { + // [stack] RESOLVED GENERATOR RESUMEKIND + return false; + } + if (!emit1(JSOp::CheckResumeKind)) { + // [stack] RESOLVED + return false; + } + + if (!ifCanSkip.emitEnd()) { + return false; + } + + MOZ_ASSERT(ifCanSkip.popped() == 0); + + return true; +} + +// ES2019 draft rev 49b781ec80117b60f73327ef3054703a3111e40c +// 14.4.14 Runtime Semantics: Evaluation +// YieldExpression : yield* AssignmentExpression +bool BytecodeEmitter::emitYieldStar(ParseNode* iter) { + MOZ_ASSERT(sc->isSuspendableContext()); + MOZ_ASSERT(sc->asSuspendableContext()->isGenerator()); + + // Step 1. + IteratorKind iterKind = sc->asSuspendableContext()->isAsync() + ? IteratorKind::Async + : IteratorKind::Sync; + bool needsIteratorResult = sc->asSuspendableContext()->needsIteratorResult(); + + // Steps 2-5. + if (!emitTree(iter)) { + // [stack] ITERABLE + return false; + } + if (iterKind == IteratorKind::Async) { + if (!emitAsyncIterator()) { + // [stack] NEXT ITER + return false; + } + } else { + if (!emitIterator()) { + // [stack] NEXT ITER + return false; + } + } + + // Step 6. + // Start with NormalCompletion(undefined). + if (!emit1(JSOp::Undefined)) { + // [stack] NEXT ITER RECEIVED + return false; + } + if (!emitPushResumeKind(GeneratorResumeKind::Next)) { + // [stack] NEXT ITER RECEIVED RESUMEKIND + return false; + } + + const int32_t startDepth = bytecodeSection().stackDepth(); + MOZ_ASSERT(startDepth >= 4); + + // Step 7 is a loop. + LoopControl loopInfo(this, StatementKind::YieldStar); + if (!loopInfo.emitLoopHead(this, Nothing())) { + // [stack] NEXT ITER RECEIVED RESUMEKIND + return false; + } + + // Step 7.a. Check for Normal completion. + if (!emit1(JSOp::Dup)) { + // [stack] NEXT ITER RECEIVED RESUMEKIND RESUMEKIND + return false; + } + if (!emitPushResumeKind(GeneratorResumeKind::Next)) { + // [stack] NEXT ITER RECEIVED RESUMEKIND RESUMEKIND NORMAL + return false; + } + if (!emit1(JSOp::StrictEq)) { + // [stack] NEXT ITER RECEIVED RESUMEKIND IS_NORMAL + return false; + } + + InternalIfEmitter ifKind(this); + if (!ifKind.emitThenElse()) { + // [stack] NEXT ITER RECEIVED RESUMEKIND + return false; + } + { + if (!emit1(JSOp::Pop)) { + // [stack] NEXT ITER RECEIVED + return false; + } + + // Step 7.a.i. + // result = iter.next(received) + if (!emit2(JSOp::Unpick, 2)) { + // [stack] RECEIVED NEXT ITER + return false; + } + if (!emit1(JSOp::Dup2)) { + // [stack] RECEIVED NEXT ITER NEXT ITER + return false; + } + if (!emit2(JSOp::Pick, 4)) { + // [stack] NEXT ITER NEXT ITER RECEIVED + return false; + } + if (!emitCall(JSOp::Call, 1, iter)) { + // [stack] NEXT ITER RESULT + return false; + } + + // Step 7.a.ii. + if (iterKind == IteratorKind::Async) { + if (!emitAwaitInInnermostScope()) { + // [stack] NEXT ITER RESULT + return false; + } + } + + // Step 7.a.iii. + if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) { + // [stack] NEXT ITER RESULT + return false; + } + + // Bytecode for steps 7.a.iv-vii is emitted after the ifKind if-else because + // it's shared with other branches. + } + + // Step 7.b. Check for Throw completion. + if (!ifKind.emitElseIf(Nothing())) { + // [stack] NEXT ITER RECEIVED RESUMEKIND + return false; + } + if (!emit1(JSOp::Dup)) { + // [stack] NEXT ITER RECEIVED RESUMEKIND RESUMEKIND + return false; + } + if (!emitPushResumeKind(GeneratorResumeKind::Throw)) { + // [stack] NEXT ITER RECEIVED RESUMEKIND RESUMEKIND THROW + return false; + } + if (!emit1(JSOp::StrictEq)) { + // [stack] NEXT ITER RECEIVED RESUMEKIND IS_THROW + return false; + } + if (!ifKind.emitThenElse()) { + // [stack] NEXT ITER RECEIVED RESUMEKIND + return false; + } + { + if (!emit1(JSOp::Pop)) { + // [stack] NEXT ITER RECEIVED + return false; + } + // Step 7.b.i. + if (!emitDupAt(1)) { + // [stack] NEXT ITER RECEIVED ITER + return false; + } + if (!emit1(JSOp::Dup)) { + // [stack] NEXT ITER RECEIVED ITER ITER + return false; + } + if (!emitAtomOp(JSOp::GetProp, cx->parserNames().throw_)) { + // [stack] NEXT ITER RECEIVED ITER THROW + return false; + } + + // Step 7.b.ii. + InternalIfEmitter ifThrowMethodIsNotDefined(this); + if (!emitPushNotUndefinedOrNull()) { + // [stack] NEXT ITER RECEIVED ITER THROW + // [stack] NOT-UNDEF-OR_NULL + return false; + } + + if (!ifThrowMethodIsNotDefined.emitThenElse()) { + // [stack] NEXT ITER RECEIVED ITER THROW + return false; + } + + // Step 7.b.ii.1. + // RESULT = ITER.throw(EXCEPTION) + if (!emit1(JSOp::Swap)) { + // [stack] NEXT ITER RECEIVED THROW ITER + return false; + } + if (!emit2(JSOp::Pick, 2)) { + // [stack] NEXT ITER THROW ITER RECEIVED + return false; + } + if (!emitCall(JSOp::Call, 1, iter)) { + // [stack] NEXT ITER RESULT + return false; + } + + // Step 7.b.ii.2. + if (iterKind == IteratorKind::Async) { + if (!emitAwaitInInnermostScope()) { + // [stack] NEXT ITER RESULT + return false; + } + } + + // Step 7.b.ii.4. + if (!emitCheckIsObj(CheckIsObjectKind::IteratorThrow)) { + // [stack] NEXT ITER RESULT + return false; + } + + // Bytecode for steps 7.b.ii.5-8 is emitted after the ifKind if-else because + // it's shared with other branches. + + // Step 7.b.iii. + if (!ifThrowMethodIsNotDefined.emitElse()) { + // [stack] NEXT ITER RECEIVED ITER THROW + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] NEXT ITER RECEIVED ITER + return false; + } + + // Steps 7.b.iii.1-4. + // + // If the iterator does not have a "throw" method, it calls IteratorClose + // and then throws a TypeError. + if (!emitIteratorCloseInInnermostScope(iterKind, CompletionKind::Normal, + allowSelfHostedIter(iter))) { + // [stack] NEXT ITER RECEIVED ITER + return false; + } + // Steps 7.b.iii.5-6. + if (!emit2(JSOp::ThrowMsg, uint8_t(ThrowMsgKind::IteratorNoThrow))) { + // [stack] NEXT ITER RECEIVED ITER + // [stack] # throw + return false; + } + + if (!ifThrowMethodIsNotDefined.emitEnd()) { + return false; + } + } + + // Step 7.c. It must be a Return completion. + if (!ifKind.emitElse()) { + // [stack] NEXT ITER RECEIVED RESUMEKIND + return false; + } + { + if (!emit1(JSOp::Pop)) { + // [stack] NEXT ITER RECEIVED + return false; + } + + // Step 7.c.i. + // + // Call iterator.return() for receiving a "forced return" completion from + // the generator. + + // Step 7.c.ii. + // + // Get the "return" method. + if (!emitDupAt(1)) { + // [stack] NEXT ITER RECEIVED ITER + return false; + } + if (!emit1(JSOp::Dup)) { + // [stack] NEXT ITER RECEIVED ITER ITER + return false; + } + if (!emitAtomOp(JSOp::GetProp, cx->parserNames().return_)) { + // [stack] NEXT ITER RECEIVED ITER RET + return false; + } + + // Step 7.c.iii. + // + // Do nothing if "return" is undefined or null. + InternalIfEmitter ifReturnMethodIsDefined(this); + if (!emitPushNotUndefinedOrNull()) { + // [stack] NEXT ITER RECEIVED ITER RET NOT-UNDEF-OR_NULL + return false; + } + + // Step 7.c.iv. + // + // Call "return" with the argument passed to Generator.prototype.return. + if (!ifReturnMethodIsDefined.emitThenElse()) { + // [stack] NEXT ITER RECEIVED ITER RET + return false; + } + if (!emit1(JSOp::Swap)) { + // [stack] NEXT ITER RECEIVED RET ITER + return false; + } + if (!emit2(JSOp::Pick, 2)) { + // [stack] NEXT ITER RET ITER RECEIVED + return false; + } + if (needsIteratorResult) { + if (!emitAtomOp(JSOp::GetProp, cx->parserNames().value)) { + // [stack] NEXT ITER RET ITER VAL + return false; + } + } + if (!emitCall(JSOp::Call, 1)) { + // [stack] NEXT ITER RESULT + return false; + } + + // Step 7.c.v. + if (iterKind == IteratorKind::Async) { + if (!emitAwaitInInnermostScope()) { + // [stack] NEXT ITER RESULT + return false; + } + } + + // Step 7.c.vi. + if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) { + // [stack] NEXT ITER RESULT + return false; + } + + // Check if the returned object from iterator.return() is done. If not, + // continue yielding. + + // Steps 7.c.vii-viii. + InternalIfEmitter ifReturnDone(this); + if (!emit1(JSOp::Dup)) { + // [stack] NEXT ITER RESULT RESULT + return false; + } + if (!emitAtomOp(JSOp::GetProp, cx->parserNames().done)) { + // [stack] NEXT ITER RESULT DONE + return false; + } + if (!ifReturnDone.emitThenElse()) { + // [stack] NEXT ITER RESULT + return false; + } + + // Step 7.c.viii.1. + if (!emitAtomOp(JSOp::GetProp, cx->parserNames().value)) { + // [stack] NEXT ITER VALUE + return false; + } + if (needsIteratorResult) { + if (!emitPrepareIteratorResult()) { + // [stack] NEXT ITER VALUE RESULT + return false; + } + if (!emit1(JSOp::Swap)) { + // [stack] NEXT ITER RESULT VALUE + return false; + } + if (!emitFinishIteratorResult(true)) { + // [stack] NEXT ITER RESULT + return false; + } + } + + if (!ifReturnDone.emitElse()) { + // [stack] NEXT ITER RESULT + return false; + } + + // Jump to continue label for steps 7.c.ix-x. + if (!emitJump(JSOp::Goto, &loopInfo.continues)) { + // [stack] NEXT ITER RESULT + return false; + } + + if (!ifReturnDone.emitEnd()) { + // [stack] NEXT ITER RESULT + return false; + } + + // Step 7.c.iii. + if (!ifReturnMethodIsDefined.emitElse()) { + // [stack] NEXT ITER RECEIVED ITER RET + return false; + } + if (!emitPopN(2)) { + // [stack] NEXT ITER RECEIVED + return false; + } + if (iterKind == IteratorKind::Async) { + // Step 7.c.iii.1. + if (!emitAwaitInInnermostScope()) { + // [stack] NEXT ITER RECEIVED + return false; + } + } + if (!ifReturnMethodIsDefined.emitEnd()) { + // [stack] NEXT ITER RECEIVED + return false; + } + + // Perform a "forced generator return". + // + // Step 7.c.iii.2. + // Step 7.c.viii.2. + if (!emitGetDotGeneratorInInnermostScope()) { + // [stack] NEXT ITER RESULT GENOBJ + return false; + } + if (!emitPushResumeKind(GeneratorResumeKind::Return)) { + // [stack] NEXT ITER RESULT GENOBJ RESUMEKIND + return false; + } + if (!emit1(JSOp::CheckResumeKind)) { + // [stack] NEXT ITER RESULT GENOBJ RESUMEKIND + return false; + } + } + + if (!ifKind.emitEnd()) { + // [stack] NEXT ITER RESULT + return false; + } + + // Shared tail for Normal/Throw completions. + // + // Steps 7.a.iv-v. + // Steps 7.b.ii.5-6. + // + // [stack] NEXT ITER RESULT + + // if (result.done) break; + if (!emit1(JSOp::Dup)) { + // [stack] NEXT ITER RESULT RESULT + return false; + } + if (!emitAtomOp(JSOp::GetProp, cx->parserNames().done)) { + // [stack] NEXT ITER RESULT DONE + return false; + } + if (!emitJump(JSOp::IfNe, &loopInfo.breaks)) { + // [stack] NEXT ITER RESULT + return false; + } + + // Steps 7.a.vi-vii. + // Steps 7.b.ii.7-8. + // Steps 7.c.ix-x. + if (!loopInfo.emitContinueTarget(this)) { + // [stack] NEXT ITER RESULT + return false; + } + if (iterKind == IteratorKind::Async) { + if (!emitAtomOp(JSOp::GetProp, cx->parserNames().value)) { + // [stack] NEXT ITER RESULT + return false; + } + if (!emitAwaitInInnermostScope()) { + // [stack] NEXT ITER RESULT + return false; + } + } + if (!emitGetDotGeneratorInInnermostScope()) { + // [stack] NEXT ITER RESULT GENOBJ + return false; + } + if (!emitYieldOp(JSOp::Yield)) { + // [stack] NEXT ITER RVAL GENOBJ RESUMEKIND + return false; + } + if (!emit1(JSOp::Swap)) { + // [stack] NEXT ITER RVAL RESUMEKIND GENOBJ + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] NEXT ITER RVAL RESUMEKIND + return false; + } + if (!loopInfo.emitLoopEnd(this, JSOp::Goto, TryNoteKind::Loop)) { + // [stack] NEXT ITER RVAL RESUMEKIND + return false; + } + + // Jumps to this point have 3 (instead of 4) values on the stack. + MOZ_ASSERT(bytecodeSection().stackDepth() == startDepth); + bytecodeSection().setStackDepth(startDepth - 1); + + // [stack] NEXT ITER RESULT + + // Step 7.a.v.1. + // Step 7.b.ii.6.a. + // + // result.value + if (!emit2(JSOp::Unpick, 2)) { + // [stack] RESULT NEXT ITER + return false; + } + if (!emitPopN(2)) { + // [stack] RESULT + return false; + } + if (!emitAtomOp(JSOp::GetProp, cx->parserNames().value)) { + // [stack] VALUE + return false; + } + + MOZ_ASSERT(bytecodeSection().stackDepth() == startDepth - 3); + + return true; +} + +bool BytecodeEmitter::emitStatementList(ListNode* stmtList) { + for (ParseNode* stmt : stmtList->contents()) { + if (!emitTree(stmt)) { + return false; + } + } + return true; +} + +bool BytecodeEmitter::emitExpressionStatement(UnaryNode* exprStmt) { + MOZ_ASSERT(exprStmt->isKind(ParseNodeKind::ExpressionStmt)); + + /* + * Top-level or called-from-a-native JS_Execute/EvaluateScript, + * debugger, and eval frames may need the value of the ultimate + * expression statement as the script's result, despite the fact + * that it appears useless to the compiler. + * + * API users may also set the JSOPTION_NO_SCRIPT_RVAL option when + * calling JS_Compile* to suppress JSOp::SetRval. + */ + bool wantval = false; + bool useful = false; + if (sc->isTopLevelContext()) { + useful = wantval = !sc->noScriptRval(); + } + + /* Don't eliminate expressions with side effects. */ + ParseNode* expr = exprStmt->kid(); + if (!useful) { + if (!checkSideEffects(expr, &useful)) { + return false; + } + + /* + * Don't eliminate apparently useless expressions if they are labeled + * expression statements. The startOffset() test catches the case + * where we are nesting in emitTree for a labeled compound statement. + */ + if (innermostNestableControl && + innermostNestableControl->is() && + innermostNestableControl->as().startOffset() >= + bytecodeSection().offset()) { + useful = true; + } + } + + if (useful) { + ValueUsage valueUsage = + wantval ? ValueUsage::WantValue : ValueUsage::IgnoreValue; + ExpressionStatementEmitter ese(this, valueUsage); + if (!ese.prepareForExpr(Some(exprStmt->pn_pos.begin))) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitTree(expr, valueUsage)) { + return false; + } + if (!ese.emitEnd()) { + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitDeleteName(UnaryNode* deleteNode) { + MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeleteNameExpr)); + + NameNode* nameExpr = &deleteNode->kid()->as(); + MOZ_ASSERT(nameExpr->isKind(ParseNodeKind::Name)); + + return emitAtomOp(JSOp::DelName, nameExpr->atom()); +} + +bool BytecodeEmitter::emitDeleteProperty(UnaryNode* deleteNode) { + MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeletePropExpr)); + + PropertyAccess* propExpr = &deleteNode->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)) { + // [stack] THIS + return false; + } + } else { + if (!poe.prepareForObj()) { + return false; + } + if (!emitPropLHS(propExpr)) { + // [stack] OBJ + return false; + } + } + + if (!poe.emitDelete(propExpr->key().atom())) { + // [stack] # if Super + // [stack] THIS + // [stack] # otherwise + // [stack] SUCCEEDED + return false; + } + + return true; +} + +bool BytecodeEmitter::emitDeleteElement(UnaryNode* deleteNode) { + MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeleteElemExpr)); + + PropertyByValue* elemExpr = &deleteNode->kid()->as(); + bool isSuper = elemExpr->isSuper(); + DebugOnly isPrivate = + elemExpr->key().isKind(ParseNodeKind::PrivateName); + MOZ_ASSERT(!isPrivate); + ElemOpEmitter eoe( + this, ElemOpEmitter::Kind::Delete, + isSuper ? ElemOpEmitter::ObjKind::Super : ElemOpEmitter::ObjKind::Other, + NameVisibility::Public); // Can't delete a private name. + 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()) { + // [stack] + return false; + } + + UnaryNode* base = &elemExpr->expression().as(); + if (!emitGetThisForSuperBase(base)) { + // [stack] THIS + return false; + } + if (!eoe.prepareForKey()) { + // [stack] THIS + return false; + } + if (!emitTree(&elemExpr->key())) { + // [stack] THIS KEY + return false; + } + } else { + if (!emitElemObjAndKey(elemExpr, false, eoe)) { + // [stack] OBJ KEY + return false; + } + } + if (!eoe.emitDelete()) { + // [stack] # if Super + // [stack] THIS + // [stack] # otherwise + // [stack] SUCCEEDED + return false; + } + + return true; +} + +bool BytecodeEmitter::emitDeleteExpression(UnaryNode* deleteNode) { + MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeleteExpr)); + + ParseNode* expression = deleteNode->kid(); + + // If useless, just emit JSOp::True; otherwise convert |delete | to + // effectively |, true|. + bool useful = false; + if (!checkSideEffects(expression, &useful)) { + return false; + } + + if (useful) { + if (!emitTree(expression)) { + return false; + } + if (!emit1(JSOp::Pop)) { + return false; + } + } + + return emit1(JSOp::True); +} + +bool BytecodeEmitter::emitDeleteOptionalChain(UnaryNode* deleteNode) { + MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeleteOptionalChainExpr)); + + OptionalEmitter oe(this, bytecodeSection().stackDepth()); + + ParseNode* kid = deleteNode->kid(); + switch (kid->getKind()) { + case ParseNodeKind::ElemExpr: + case ParseNodeKind::OptionalElemExpr: { + auto* elemExpr = &kid->as(); + if (!emitDeleteElementInOptChain(elemExpr, oe)) { + // [stack] # If shortcircuit + // [stack] UNDEFINED-OR-NULL + // [stack] # otherwise + // [stack] SUCCEEDED + return false; + } + + break; + } + case ParseNodeKind::DotExpr: + case ParseNodeKind::OptionalDotExpr: { + auto* propExpr = &kid->as(); + if (!emitDeletePropertyInOptChain(propExpr, oe)) { + // [stack] # If shortcircuit + // [stack] UNDEFINED-OR-NULL + // [stack] # otherwise + // [stack] SUCCEEDED + return false; + } + break; + } + default: + MOZ_ASSERT_UNREACHABLE("Unrecognized optional delete ParseNodeKind"); + } + + if (!oe.emitOptionalJumpTarget(JSOp::True)) { + // [stack] # If shortcircuit + // [stack] TRUE + // [stack] # otherwise + // [stack] SUCCEEDED + return false; + } + + return true; +} + +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 (!poe.prepareForObj()) { + // [stack] + return false; + } + if (!emitOptionalTree(&propExpr->expression(), oe)) { + // [stack] OBJ + return false; + } + if (propExpr->isKind(ParseNodeKind::OptionalDotExpr)) { + if (!oe.emitJumpShortCircuit()) { + // [stack] # if Jump + // [stack] UNDEFINED-OR-NULL + // [stack] # otherwise + // [stack] OBJ + return false; + } + } + + if (!poe.emitDelete(propExpr->key().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, NameVisibility::Public); + + if (!eoe.prepareForObj()) { + // [stack] + return false; + } + + if (!emitOptionalTree(&elemExpr->expression(), oe)) { + // [stack] OBJ + return false; + } + + if (elemExpr->isKind(ParseNodeKind::OptionalElemExpr)) { + 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 (!eoe.emitDelete()) { + // [stack] SUCCEEDED + return false; + } + + return true; +} + +static const char* SelfHostedCallFunctionName(const ParserAtom* name, + JSContext* cx) { + if (name == cx->parserNames().callFunction) { + return "callFunction"; + } + if (name == cx->parserNames().callContentFunction) { + return "callContentFunction"; + } + if (name == cx->parserNames().constructContentFunction) { + return "constructContentFunction"; + } + + MOZ_CRASH("Unknown self-hosted call function name"); +} + +bool BytecodeEmitter::emitSelfHostedCallFunction(CallNode* callNode) { + // Special-casing of callFunction to emit bytecode that directly + // invokes the callee with the correct |this| object and arguments. + // callFunction(fun, thisArg, arg0, arg1) thus becomes: + // - emit lookup for fun + // - emit lookup for thisArg + // - emit lookups for arg0, arg1 + // + // argc is set to the amount of actually emitted args and the + // emitting of args below is disabled by setting emitArgs to false. + NameNode* calleeNode = &callNode->left()->as(); + ListNode* argsList = &callNode->right()->as(); + + const char* errorName = SelfHostedCallFunctionName(calleeNode->name(), cx); + + if (argsList->count() < 2) { + reportNeedMoreArgsError(calleeNode, errorName, "2", "s", argsList); + return false; + } + + JSOp callOp = callNode->callOp(); + if (callOp != JSOp::Call) { + reportError(callNode, JSMSG_NOT_CONSTRUCTOR, errorName); + return false; + } + + bool constructing = + calleeNode->name() == cx->parserNames().constructContentFunction; + ParseNode* funNode = argsList->head(); + if (constructing) { + callOp = JSOp::New; + } else if (funNode->isName(cx->parserNames().std_Function_apply)) { + callOp = JSOp::FunApply; + } + + if (!emitTree(funNode)) { + return false; + } + +#ifdef DEBUG + if (emitterMode == BytecodeEmitter::SelfHosting && + calleeNode->name() == cx->parserNames().callFunction) { + if (!emit1(JSOp::DebugCheckSelfHosted)) { + return false; + } + } +#endif + + ParseNode* thisOrNewTarget = funNode->pn_next; + if (constructing) { + // Save off the new.target value, but here emit a proper |this| for a + // constructing call. + if (!emit1(JSOp::IsConstructing)) { + return false; + } + } else { + // It's |this|, emit it. + if (!emitTree(thisOrNewTarget)) { + return false; + } + } + + for (ParseNode* argpn = thisOrNewTarget->pn_next; argpn; + argpn = argpn->pn_next) { + if (!emitTree(argpn)) { + return false; + } + } + + if (constructing) { + if (!emitTree(thisOrNewTarget)) { + return false; + } + } + + uint32_t argc = argsList->count() - 2; + if (!emitCall(callOp, argc)) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitSelfHostedResumeGenerator(BinaryNode* callNode) { + ListNode* argsList = &callNode->right()->as(); + + // Syntax: resumeGenerator(gen, value, 'next'|'throw'|'return') + if (argsList->count() != 3) { + reportNeedMoreArgsError(callNode, "resumeGenerator", "3", "s", argsList); + return false; + } + + ParseNode* genNode = argsList->head(); + if (!emitTree(genNode)) { + // [stack] GENERATOR + return false; + } + + ParseNode* valNode = genNode->pn_next; + if (!emitTree(valNode)) { + // [stack] GENERATOR VALUE + return false; + } + + ParseNode* kindNode = valNode->pn_next; + MOZ_ASSERT(kindNode->isKind(ParseNodeKind::StringExpr)); + GeneratorResumeKind kind = + ParserAtomToResumeKind(cx, kindNode->as().atom()); + MOZ_ASSERT(!kindNode->pn_next); + + if (!emitPushResumeKind(kind)) { + // [stack] GENERATOR VALUE RESUMEKIND + return false; + } + + if (!emit1(JSOp::Resume)) { + // [stack] RVAL + return false; + } + + return true; +} + +bool BytecodeEmitter::emitSelfHostedForceInterpreter() { + // JSScript::hasForceInterpreterOp() relies on JSOp::ForceInterpreter being + // the first bytecode op in the script. + MOZ_ASSERT(bytecodeSection().code().empty()); + + if (!emit1(JSOp::ForceInterpreter)) { + return false; + } + if (!emit1(JSOp::Undefined)) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitSelfHostedAllowContentIter(BinaryNode* callNode) { + ListNode* argsList = &callNode->right()->as(); + + if (argsList->count() != 1) { + reportNeedMoreArgsError(callNode, "allowContentIter", "1", "", argsList); + return false; + } + + // We're just here as a sentinel. Pass the value through directly. + return emitTree(argsList->head()); +} + +bool BytecodeEmitter::emitSelfHostedDefineDataProperty(BinaryNode* callNode) { + ListNode* argsList = &callNode->right()->as(); + + // Only optimize when 3 arguments are passed. + MOZ_ASSERT(argsList->count() == 3); + + ParseNode* objNode = argsList->head(); + if (!emitTree(objNode)) { + return false; + } + + ParseNode* idNode = objNode->pn_next; + if (!emitTree(idNode)) { + return false; + } + + ParseNode* valNode = idNode->pn_next; + if (!emitTree(valNode)) { + return false; + } + + // This will leave the object on the stack instead of pushing |undefined|, + // but that's fine because the self-hosted code doesn't use the return + // value. + return emit1(JSOp::InitElem); +} + +bool BytecodeEmitter::emitSelfHostedHasOwn(BinaryNode* callNode) { + ListNode* argsList = &callNode->right()->as(); + + if (argsList->count() != 2) { + reportNeedMoreArgsError(callNode, "hasOwn", "2", "s", argsList); + return false; + } + + ParseNode* idNode = argsList->head(); + if (!emitTree(idNode)) { + return false; + } + + ParseNode* objNode = idNode->pn_next; + if (!emitTree(objNode)) { + return false; + } + + return emit1(JSOp::HasOwn); +} + +bool BytecodeEmitter::emitSelfHostedGetPropertySuper(BinaryNode* callNode) { + ListNode* argsList = &callNode->right()->as(); + + if (argsList->count() != 3) { + reportNeedMoreArgsError(callNode, "getPropertySuper", "3", "s", argsList); + return false; + } + + ParseNode* objNode = argsList->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::GetElemSuper); +} + +bool BytecodeEmitter::emitSelfHostedToNumeric(BinaryNode* callNode) { + ListNode* argsList = &callNode->right()->as(); + + if (argsList->count() != 1) { + reportNeedMoreArgsError(callNode, "ToNumeric", "1", "", argsList); + return false; + } + + ParseNode* argNode = argsList->head(); + + if (!emitTree(argNode)) { + return false; + } + + return emit1(JSOp::ToNumeric); +} + +bool BytecodeEmitter::emitSelfHostedToString(BinaryNode* callNode) { + ListNode* argsList = &callNode->right()->as(); + + if (argsList->count() != 1) { + reportNeedMoreArgsError(callNode, "ToString", "1", "", argsList); + return false; + } + + ParseNode* argNode = argsList->head(); + + if (!emitTree(argNode)) { + return false; + } + + return emit1(JSOp::ToString); +} + +bool BytecodeEmitter::emitSelfHostedGetBuiltinConstructorOrPrototype( + BinaryNode* callNode, bool isConstructor) { + ListNode* argsList = &callNode->right()->as(); + + if (argsList->count() != 1) { + const char* name = + isConstructor ? "GetBuiltinConstructor" : "GetBuiltinPrototype"; + reportNeedMoreArgsError(callNode, name, "1", "", argsList); + return false; + } + + ParseNode* argNode = argsList->head(); + + if (!argNode->isKind(ParseNodeKind::StringExpr)) { + reportError(callNode, JSMSG_UNEXPECTED_TYPE, "built-in name", + "not a string constant"); + return false; + } + + const ParserAtom* name = argNode->as().atom(); + + BuiltinObjectKind kind; + if (isConstructor) { + kind = BuiltinConstructorForName(cx, name); + } else { + kind = BuiltinPrototypeForName(cx, name); + } + + if (kind == BuiltinObjectKind::None) { + reportError(callNode, JSMSG_UNEXPECTED_TYPE, "built-in name", + "not a valid built-in"); + return false; + } + + return emitBuiltinObject(kind); +} + +bool BytecodeEmitter::emitSelfHostedGetBuiltinConstructor( + BinaryNode* callNode) { + return emitSelfHostedGetBuiltinConstructorOrPrototype( + callNode, /* isConstructor = */ true); +} + +bool BytecodeEmitter::emitSelfHostedGetBuiltinPrototype(BinaryNode* callNode) { + return emitSelfHostedGetBuiltinConstructorOrPrototype( + callNode, /* isConstructor = */ false); +} + +#ifdef DEBUG +bool BytecodeEmitter::checkSelfHostedUnsafeGetReservedSlot( + BinaryNode* callNode) { + ListNode* argsList = &callNode->right()->as(); + + if (argsList->count() != 2) { + reportNeedMoreArgsError(callNode, "UnsafeGetReservedSlot", "2", "", + argsList); + return false; + } + + ParseNode* objNode = argsList->head(); + ParseNode* slotNode = objNode->pn_next; + + // Ensure that the slot argument is fixed, this is required by the JITs. + if (!slotNode->isKind(ParseNodeKind::NumberExpr)) { + reportError(callNode, JSMSG_UNEXPECTED_TYPE, "slot argument", + "not a constant"); + return false; + } + + return true; +} + +bool BytecodeEmitter::checkSelfHostedUnsafeSetReservedSlot( + BinaryNode* callNode) { + ListNode* argsList = &callNode->right()->as(); + + if (argsList->count() != 3) { + reportNeedMoreArgsError(callNode, "UnsafeSetReservedSlot", "3", "", + argsList); + return false; + } + + ParseNode* objNode = argsList->head(); + ParseNode* slotNode = objNode->pn_next; + + // Ensure that the slot argument is fixed, this is required by the JITs. + if (!slotNode->isKind(ParseNodeKind::NumberExpr)) { + reportError(callNode, JSMSG_UNEXPECTED_TYPE, "slot argument", + "not a constant"); + return false; + } + + return true; +} +#endif + +bool BytecodeEmitter::isRestParameter(ParseNode* expr) { + if (!sc->isFunctionBox()) { + return false; + } + + FunctionBox* funbox = sc->asFunctionBox(); + if (!funbox->hasRest()) { + return false; + } + + if (!expr->isKind(ParseNodeKind::Name)) { + return allowSelfHostedIter(expr) && + isRestParameter( + expr->as().right()->as().head()); + } + + const ParserAtom* name = expr->as().name(); + Maybe paramLoc = locationOfNameBoundInFunctionScope(name); + if (paramLoc && lookupName(name) == *paramLoc) { + FunctionScope::ParserData* bindings = funbox->functionScopeBindings(); + if (bindings->slotInfo.nonPositionalFormalStart > 0) { + auto index = + bindings + ->trailingNames[bindings->slotInfo.nonPositionalFormalStart - 1] + .name(); + if (index.isNull()) { + // Rest parameter name can be null when the rest destructuring syntax is + // used: `function f(...[]) {}`. + return false; + } + const ParserAtom* paramName = compilationState.getParserAtomAt(cx, index); + return name == paramName; + } + } + + return false; +} + +/* A version of emitCalleeAndThis for the optional cases: + * * a?.() + * * a?.b() + * * a?.["b"]() + * * (a?.b)() + * + * See emitCallOrNew and emitOptionalCall for more context. + */ +bool BytecodeEmitter::emitOptionalCalleeAndThis(ParseNode* callee, + CallNode* call, + CallOrNewEmitter& cone, + OptionalEmitter& oe) { + if (!CheckRecursionLimit(cx)) { + return false; + } + + switch (ParseNodeKind kind = callee->getKind()) { + case ParseNodeKind::Name: { + const ParserAtom* nameAtom = callee->as().name(); + if (!cone.emitNameCallee(nameAtom)) { + // [stack] CALLEE THIS + return false; + } + break; + } + + case ParseNodeKind::OptionalDotExpr: { + MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); + OptionalPropertyAccess* prop = &callee->as(); + bool isSuper = false; + + PropOpEmitter& poe = cone.prepareForPropCallee(isSuper); + if (!emitOptionalDotExpression(prop, poe, isSuper, oe)) { + // [stack] CALLEE THIS + return false; + } + break; + } + case ParseNodeKind::DotExpr: { + MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); + PropertyAccess* prop = &callee->as(); + bool isSuper = prop->isSuper(); + + PropOpEmitter& poe = cone.prepareForPropCallee(isSuper); + if (!emitOptionalDotExpression(prop, poe, isSuper, oe)) { + // [stack] CALLEE THIS + return false; + } + break; + } + + case ParseNodeKind::OptionalElemExpr: { + OptionalPropertyByValue* elem = &callee->as(); + bool isSuper = false; + bool isPrivate = elem->key().isKind(ParseNodeKind::PrivateName); + ElemOpEmitter& eoe = cone.prepareForElemCallee(isSuper, isPrivate); + if (!emitOptionalElemExpression(elem, eoe, isSuper, oe)) { + // [stack] CALLEE THIS + return false; + } + break; + } + case ParseNodeKind::ElemExpr: { + PropertyByValue* elem = &callee->as(); + bool isSuper = elem->isSuper(); + bool isPrivate = elem->key().isKind(ParseNodeKind::PrivateName); + ElemOpEmitter& eoe = cone.prepareForElemCallee(isSuper, isPrivate); + if (!emitOptionalElemExpression(elem, eoe, isSuper, oe)) { + // [stack] CALLEE THIS + return false; + } + break; + } + + case ParseNodeKind::Function: + if (!cone.prepareForFunctionCallee()) { + return false; + } + if (!emitOptionalTree(callee, oe)) { + // [stack] CALLEE + return false; + } + break; + + case ParseNodeKind::OptionalChain: { + return emitCalleeAndThisForOptionalChain(&callee->as(), call, + cone); + } + + default: + MOZ_RELEASE_ASSERT(kind != ParseNodeKind::SuperBase); + + if (!cone.prepareForOtherCallee()) { + return false; + } + 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, + CallOrNewEmitter& cone) { + switch (callee->getKind()) { + case ParseNodeKind::Name: { + const ParserAtom* nameAtom = callee->as().name(); + if (!cone.emitNameCallee(nameAtom)) { + // [stack] CALLEE THIS + return false; + } + break; + } + case ParseNodeKind::DotExpr: { + MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); + 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)) { + // [stack] THIS + return false; + } + } else { + if (!emitPropLHS(prop)) { + // [stack] OBJ + return false; + } + } + if (!poe.emitGet(prop->key().atom())) { + // [stack] CALLEE THIS? + return false; + } + + break; + } + case ParseNodeKind::ElemExpr: { + MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); + PropertyByValue* elem = &callee->as(); + bool isSuper = elem->isSuper(); + bool isPrivate = elem->key().isKind(ParseNodeKind::PrivateName); + ElemOpEmitter& eoe = cone.prepareForElemCallee(isSuper, isPrivate); + if (!emitElemObjAndKey(elem, isSuper, eoe)) { + // [stack] # if Super + // [stack] THIS? THIS KEY + // [stack] # otherwise + // [stack] OBJ? OBJ KEY + return false; + } + if (!eoe.emitGet()) { + // [stack] CALLEE? THIS + return false; + } + + break; + } + case ParseNodeKind::Function: + if (!cone.prepareForFunctionCallee()) { + return false; + } + if (!emitTree(callee)) { + // [stack] CALLEE + return false; + } + break; + case ParseNodeKind::SuperBase: + MOZ_ASSERT(call->isKind(ParseNodeKind::SuperCallExpr)); + MOZ_ASSERT(parser->astGenerator().isSuperBase(callee)); + if (!cone.emitSuperCallee()) { + // [stack] CALLEE THIS + return false; + } + break; + case ParseNodeKind::OptionalChain: { + return emitCalleeAndThisForOptionalChain(&callee->as(), + &call->as(), cone); + } + default: + if (!cone.prepareForOtherCallee()) { + return false; + } + if (!emitTree(callee)) { + return false; + } + break; + } + + if (!cone.emitThis()) { + // [stack] CALLEE THIS + return false; + } + + return true; +} + +bool BytecodeEmitter::emitPipeline(ListNode* node) { + MOZ_ASSERT(node->count() >= 2); + + if (!emitTree(node->head())) { + // [stack] ARG + return false; + } + + ParseNode* callee = node->head()->pn_next; + CallOrNewEmitter cone(this, JSOp::Call, + CallOrNewEmitter::ArgumentsKind::Other, + ValueUsage::WantValue); + do { + if (!emitCalleeAndThis(callee, node, cone)) { + // [stack] ARG CALLEE THIS + return false; + } + if (!emit2(JSOp::Pick, 2)) { + // [stack] CALLEE THIS ARG + return false; + } + if (!cone.emitEnd(1, Some(node->pn_pos.begin))) { + // [stack] RVAL + return false; + } + + cone.reset(); + } while ((callee = callee->pn_next)); + + return true; +} + +ParseNode* BytecodeEmitter::getCoordNode(ParseNode* callNode, + ParseNode* calleeNode, JSOp op, + ListNode* argsList) { + ParseNode* coordNode = callNode; + if (op == JSOp::Call || op == JSOp::SpreadCall || op == JSOp::FunCall || + op == JSOp::FunApply) { + // Default to using the location of the `(` itself. + // obj[expr]() // expression + // ^ // column coord + coordNode = argsList; + + switch (calleeNode->getKind()) { + case ParseNodeKind::DotExpr: + // Use the position of a property access identifier. + // + // obj().aprop() // expression + // ^ // column coord + // + // Note: Because of the constant folding logic in FoldElement, + // this case also applies for constant string properties. + // + // obj()['aprop']() // expression + // ^ // column coord + coordNode = &calleeNode->as().key(); + break; + case ParseNodeKind::Name: { + // Use the start of callee name unless it is at a separator + // or has no args. + // + // 2 + obj() // expression + // ^ // column coord + // + if (argsList->empty() || + !bytecodeSection().atSeparator(calleeNode->pn_pos.begin)) { + // Use the start of callee names. + coordNode = calleeNode; + } + break; + } + + default: + break; + } + } + return coordNode; +} + +bool BytecodeEmitter::emitArguments(ListNode* argsList, bool isCall, + bool isSpread, CallOrNewEmitter& cone) { + uint32_t argc = argsList->count(); + if (argc >= ARGC_LIMIT) { + reportError(argsList, + isCall ? JSMSG_TOO_MANY_FUN_ARGS : JSMSG_TOO_MANY_CON_ARGS); + return false; + } + if (!isSpread) { + if (!cone.prepareForNonSpreadArguments()) { + // [stack] CALLEE THIS + return false; + } + for (ParseNode* arg : argsList->contents()) { + if (!emitTree(arg)) { + // [stack] CALLEE THIS ARG* + return false; + } + } + } else { + if (cone.wantSpreadOperand()) { + UnaryNode* spreadNode = &argsList->head()->as(); + if (!emitTree(spreadNode->kid())) { + // [stack] CALLEE THIS ARG0 + return false; + } + } + if (!cone.emitSpreadArgumentsTest()) { + // [stack] CALLEE THIS + return false; + } + if (!emitArray(argsList->head(), argc)) { + // [stack] CALLEE THIS ARR + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitOptionalCall(CallNode* callNode, OptionalEmitter& oe, + ValueUsage valueUsage) { + /* + * A modified version of emitCallOrNew that handles optional calls. + * + * These include the following: + * a?.() + * a.b?.() + * a.["b"]?.() + * (a?.b)?.() + * + * See CallOrNewEmitter for more context. + */ + ParseNode* calleeNode = callNode->left(); + ListNode* argsList = &callNode->right()->as(); + bool isSpread = JOF_OPTYPE(callNode->callOp()) == JOF_BYTE; + JSOp op = callNode->callOp(); + uint32_t argc = argsList->count(); + + CallOrNewEmitter cone( + this, op, + isSpread && (argc == 1) && + isRestParameter(argsList->head()->as().kid()) + ? CallOrNewEmitter::ArgumentsKind::SingleSpreadRest + : CallOrNewEmitter::ArgumentsKind::Other, + valueUsage); + + ParseNode* coordNode = getCoordNode(callNode, calleeNode, op, argsList); + + if (!emitOptionalCalleeAndThis(calleeNode, callNode, cone, oe)) { + // [stack] CALLEE THIS + return false; + } + + if (callNode->isKind(ParseNodeKind::OptionalCallExpr)) { + if (!oe.emitJumpShortCircuitForCall()) { + // [stack] CALLEE THIS + return false; + } + } + + if (!emitArguments(argsList, /* isCall = */ true, isSpread, cone)) { + // [stack] CALLEE THIS ARGS... + return false; + } + + if (!cone.emitEnd(argc, Some(coordNode->pn_pos.begin))) { + // [stack] RVAL + return false; + } + + return true; +} + +bool BytecodeEmitter::emitCallOrNew( + CallNode* callNode, ValueUsage valueUsage /* = ValueUsage::WantValue */) { + /* + * Emit callable invocation or operator new (constructor call) code. + * First, emit code for the left operand to evaluate the callable or + * constructable object expression. + * + * Then (or in a call case that has no explicit reference-base + * object) we emit JSOp::Undefined to produce the undefined |this| + * value required for calls (which non-strict mode functions + * will box into the global object). + */ + bool isCall = callNode->isKind(ParseNodeKind::CallExpr) || + callNode->isKind(ParseNodeKind::TaggedTemplateExpr); + ParseNode* calleeNode = callNode->left(); + ListNode* argsList = &callNode->right()->as(); + bool isSpread = JOF_OPTYPE(callNode->callOp()) == 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. + const ParserName* calleeName = calleeNode->as().name(); + if (calleeName == cx->parserNames().callFunction || + calleeName == cx->parserNames().callContentFunction || + calleeName == cx->parserNames().constructContentFunction) { + return emitSelfHostedCallFunction(callNode); + } + if (calleeName == cx->parserNames().resumeGenerator) { + return emitSelfHostedResumeGenerator(callNode); + } + if (calleeName == cx->parserNames().forceInterpreter) { + return emitSelfHostedForceInterpreter(); + } + if (calleeName == cx->parserNames().allowContentIter) { + return emitSelfHostedAllowContentIter(callNode); + } + if (calleeName == cx->parserNames().defineDataPropertyIntrinsic && + argsList->count() == 3) { + return emitSelfHostedDefineDataProperty(callNode); + } + if (calleeName == cx->parserNames().hasOwn) { + return emitSelfHostedHasOwn(callNode); + } + if (calleeName == cx->parserNames().getPropertySuper) { + return emitSelfHostedGetPropertySuper(callNode); + } + if (calleeName == cx->parserNames().ToNumeric) { + return emitSelfHostedToNumeric(callNode); + } + if (calleeName == cx->parserNames().ToString) { + return emitSelfHostedToString(callNode); + } + if (calleeName == cx->parserNames().GetBuiltinConstructor) { + return emitSelfHostedGetBuiltinConstructor(callNode); + } + if (calleeName == cx->parserNames().GetBuiltinPrototype) { + return emitSelfHostedGetBuiltinPrototype(callNode); + } +#ifdef DEBUG + if (calleeName == cx->parserNames().UnsafeGetReservedSlot || + calleeName == cx->parserNames().UnsafeGetObjectFromReservedSlot || + calleeName == cx->parserNames().UnsafeGetInt32FromReservedSlot || + calleeName == cx->parserNames().UnsafeGetStringFromReservedSlot || + calleeName == cx->parserNames().UnsafeGetBooleanFromReservedSlot) { + // Make sure that this call is correct, but don't emit any special code. + if (!checkSelfHostedUnsafeGetReservedSlot(callNode)) { + return false; + } + } + if (calleeName == cx->parserNames().UnsafeSetReservedSlot) { + // Make sure that this call is correct, but don't emit any special code. + if (!checkSelfHostedUnsafeSetReservedSlot(callNode)) { + return false; + } + } +#endif + // Fall through + } + + JSOp op = callNode->callOp(); + uint32_t argc = argsList->count(); + CallOrNewEmitter cone( + this, op, + isSpread && (argc == 1) && + isRestParameter(argsList->head()->as().kid()) + ? CallOrNewEmitter::ArgumentsKind::SingleSpreadRest + : CallOrNewEmitter::ArgumentsKind::Other, + valueUsage); + + if (!emitCalleeAndThis(calleeNode, callNode, cone)) { + // [stack] CALLEE THIS + return false; + } + if (!emitArguments(argsList, isCall, isSpread, cone)) { + // [stack] CALLEE THIS ARGS... + return false; + } + + ParseNode* coordNode = getCoordNode(callNode, calleeNode, op, argsList); + + if (!cone.emitEnd(argc, Some(coordNode->pn_pos.begin))) { + // [stack] RVAL + return false; + } + + return true; +} + +// This list must be kept in the same order in several places: +// - The binary operators in ParseNode.h , +// - the binary operators in TokenKind.h +// - the precedence list in Parser.cpp +static const JSOp ParseNodeKindToJSOp[] = { + // JSOp::Nop is for pipeline operator which does not emit its own JSOp + // but has highest precedence in binary operators + JSOp::Nop, JSOp::Coalesce, JSOp::Or, JSOp::And, JSOp::BitOr, + JSOp::BitXor, JSOp::BitAnd, JSOp::StrictEq, JSOp::Eq, JSOp::StrictNe, + JSOp::Ne, JSOp::Lt, JSOp::Le, JSOp::Gt, JSOp::Ge, + JSOp::Instanceof, JSOp::In, JSOp::Lsh, JSOp::Rsh, JSOp::Ursh, + JSOp::Add, JSOp::Sub, JSOp::Mul, JSOp::Div, JSOp::Mod, + JSOp::Pow}; + +static inline JSOp BinaryOpParseNodeKindToJSOp(ParseNodeKind pnk) { + MOZ_ASSERT(pnk >= ParseNodeKind::BinOpFirst); + MOZ_ASSERT(pnk <= ParseNodeKind::BinOpLast); + int parseNodeFirst = size_t(ParseNodeKind::BinOpFirst); +#ifdef DEBUG + int jsopArraySize = std::size(ParseNodeKindToJSOp); + int parseNodeKindListSize = + size_t(ParseNodeKind::BinOpLast) - parseNodeFirst + 1; + MOZ_ASSERT(jsopArraySize == parseNodeKindListSize); +#endif + return ParseNodeKindToJSOp[size_t(pnk) - parseNodeFirst]; +} + +bool BytecodeEmitter::emitRightAssociative(ListNode* node) { + // ** is the only right-associative operator. + MOZ_ASSERT(node->isKind(ParseNodeKind::PowExpr)); + + // Right-associative operator chain. + for (ParseNode* subexpr : node->contents()) { + if (!emitTree(subexpr)) { + return false; + } + } + for (uint32_t i = 0; i < node->count() - 1; i++) { + if (!emit1(JSOp::Pow)) { + return false; + } + } + return true; +} + +bool BytecodeEmitter::emitLeftAssociative(ListNode* node) { + // Left-associative operator chain. + if (!emitTree(node->head())) { + return false; + } + JSOp op = BinaryOpParseNodeKindToJSOp(node->getKind()); + ParseNode* nextExpr = node->head()->pn_next; + do { + if (!emitTree(nextExpr)) { + return false; + } + if (!emit1(op)) { + return false; + } + } while ((nextExpr = nextExpr->pn_next)); + return true; +} + +/* + * Special `emitTree` for Optional Chaining case. + * Examples of this are `emitOptionalChain`, `emitDeleteOptionalChain` and + * `emitCalleeAndThisForOptionalChain`. + */ +bool BytecodeEmitter::emitOptionalTree( + ParseNode* pn, OptionalEmitter& oe, + ValueUsage valueUsage /* = ValueUsage::WantValue */) { + if (!CheckRecursionLimit(cx)) { + return false; + } + ParseNodeKind kind = pn->getKind(); + switch (kind) { + case ParseNodeKind::OptionalDotExpr: { + OptionalPropertyAccess* prop = &pn->as(); + bool isSuper = false; + PropOpEmitter poe(this, PropOpEmitter::Kind::Get, + PropOpEmitter::ObjKind::Other); + if (!emitOptionalDotExpression(prop, poe, isSuper, oe)) { + return false; + } + break; + } + case ParseNodeKind::DotExpr: { + PropertyAccess* prop = &pn->as(); + bool isSuper = prop->isSuper(); + PropOpEmitter poe(this, PropOpEmitter::Kind::Get, + isSuper ? PropOpEmitter::ObjKind::Super + : PropOpEmitter::ObjKind::Other); + if (!emitOptionalDotExpression(prop, poe, isSuper, oe)) { + return false; + } + break; + } + + case ParseNodeKind::OptionalElemExpr: { + OptionalPropertyByValue* elem = &pn->as(); + bool isSuper = false; + bool isPrivate = elem->key().isKind(ParseNodeKind::PrivateName); + ElemOpEmitter eoe( + this, ElemOpEmitter::Kind::Get, ElemOpEmitter::ObjKind::Other, + isPrivate ? NameVisibility::Private : NameVisibility::Public); + + if (!emitOptionalElemExpression(elem, eoe, isSuper, oe)) { + return false; + } + break; + } + case ParseNodeKind::ElemExpr: { + PropertyByValue* elem = &pn->as(); + bool isSuper = elem->isSuper(); + bool isPrivate = elem->key().isKind(ParseNodeKind::PrivateName); + ElemOpEmitter eoe( + this, ElemOpEmitter::Kind::Get, + isSuper ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other, + isPrivate ? NameVisibility::Private : NameVisibility::Public); + + if (!emitOptionalElemExpression(elem, eoe, isSuper, oe)) { + return false; + } + break; + } + case ParseNodeKind::CallExpr: + case ParseNodeKind::OptionalCallExpr: + if (!emitOptionalCall(&pn->as(), oe, valueUsage)) { + return false; + } + break; + // List of accepted ParseNodeKinds that might appear only at the beginning + // of an Optional Chain. + // For example, a taggedTemplateExpr node might occur if we have + // `test`?.b, with `test` as the taggedTemplateExpr ParseNode. + default: +#ifdef DEBUG + // https://tc39.es/ecma262/#sec-primary-expression + bool isPrimaryExpression = + kind == ParseNodeKind::ThisExpr || kind == ParseNodeKind::Name || + kind == ParseNodeKind::PrivateName || + kind == ParseNodeKind::NullExpr || kind == ParseNodeKind::TrueExpr || + kind == ParseNodeKind::FalseExpr || + kind == ParseNodeKind::NumberExpr || + kind == ParseNodeKind::BigIntExpr || + kind == ParseNodeKind::StringExpr || + kind == ParseNodeKind::ArrayExpr || + kind == ParseNodeKind::ObjectExpr || + kind == ParseNodeKind::Function || kind == ParseNodeKind::ClassDecl || + kind == ParseNodeKind::RegExpExpr || + kind == ParseNodeKind::TemplateStringExpr || + kind == ParseNodeKind::TemplateStringListExpr || + kind == ParseNodeKind::RawUndefinedExpr || pn->isInParens(); + + // https://tc39.es/ecma262/#sec-left-hand-side-expressions + bool isMemberExpression = isPrimaryExpression || + kind == ParseNodeKind::TaggedTemplateExpr || + kind == ParseNodeKind::NewExpr || + kind == ParseNodeKind::NewTargetExpr || + kind == ParseNodeKind::ImportMetaExpr; + + bool isCallExpression = kind == ParseNodeKind::SetThis || + kind == ParseNodeKind::CallImportExpr; + + MOZ_ASSERT(isMemberExpression || isCallExpression, + "Unknown ParseNodeKind for OptionalChain"); +#endif + return emitTree(pn); + } + return true; +} + +// Handle the case of a call made on a OptionalChainParseNode. +// For example `(a?.b)()` and `(a?.b)?.()`. +bool BytecodeEmitter::emitCalleeAndThisForOptionalChain( + UnaryNode* optionalChain, CallNode* callNode, CallOrNewEmitter& cone) { + ParseNode* calleeNode = optionalChain->kid(); + + // Create a new OptionalEmitter, in order to emit the right bytecode + // in isolation. + OptionalEmitter oe(this, bytecodeSection().stackDepth()); + + if (!emitOptionalCalleeAndThis(calleeNode, callNode, cone, oe)) { + // [stack] CALLEE THIS + return false; + } + + // complete the jump if necessary. This will set both the "this" value + // and the "callee" value to undefined, if the callee is undefined. It + // does not matter much what the this value is, the function call will + // fail if it is not optional, and be set to undefined otherwise. + if (!oe.emitOptionalJumpTarget(JSOp::Undefined, + OptionalEmitter::Kind::Reference)) { + // [stack] # If shortcircuit + // [stack] UNDEFINED UNDEFINED + // [stack] # otherwise + // [stack] CALLEE THIS + return false; + } + return true; +} + +bool BytecodeEmitter::emitOptionalChain(UnaryNode* optionalChain, + ValueUsage valueUsage) { + ParseNode* expr = optionalChain->kid(); + + OptionalEmitter oe(this, bytecodeSection().stackDepth()); + + if (!emitOptionalTree(expr, oe, valueUsage)) { + // [stack] VAL + return false; + } + + if (!oe.emitOptionalJumpTarget(JSOp::Undefined)) { + // [stack] # If shortcircuit + // [stack] UNDEFINED + // [stack] # otherwise + // [stack] VAL + return false; + } + + return true; +} + +bool BytecodeEmitter::emitOptionalDotExpression(PropertyAccessBase* prop, + PropOpEmitter& poe, + bool isSuper, + OptionalEmitter& oe) { + if (!poe.prepareForObj()) { + // [stack] + return false; + } + + if (isSuper) { + UnaryNode* base = &prop->expression().as(); + if (!emitGetThisForSuperBase(base)) { + // [stack] OBJ + return false; + } + } else { + if (!emitOptionalTree(&prop->expression(), oe)) { + // [stack] OBJ + return false; + } + } + + if (prop->isKind(ParseNodeKind::OptionalDotExpr)) { + MOZ_ASSERT(!isSuper); + if (!oe.emitJumpShortCircuit()) { + // [stack] # if Jump + // [stack] UNDEFINED-OR-NULL + // [stack] # otherwise + // [stack] OBJ + return false; + } + } + + if (!poe.emitGet(prop->key().atom())) { + // [stack] PROP + return false; + } + + return true; +} + +bool BytecodeEmitter::emitOptionalElemExpression(PropertyByValueBase* elem, + ElemOpEmitter& eoe, + bool isSuper, + OptionalEmitter& oe) { + if (!eoe.prepareForObj()) { + // [stack] + return false; + } + + if (isSuper) { + UnaryNode* base = &elem->expression().as(); + if (!emitGetThisForSuperBase(base)) { + // [stack] OBJ + return false; + } + } else { + if (!emitOptionalTree(&elem->expression(), oe)) { + // [stack] OBJ + return false; + } + } + + if (elem->isKind(ParseNodeKind::OptionalElemExpr)) { + MOZ_ASSERT(!isSuper); + if (!oe.emitJumpShortCircuit()) { + // [stack] # if Jump + // [stack] UNDEFINED-OR-NULL + // [stack] # otherwise + // [stack] OBJ + return false; + } + } + + if (!eoe.prepareForKey()) { + // [stack] OBJ? OBJ + return false; + } + + if (!emitTree(&elem->key())) { + // [stack] OBJ? OBJ KEY + return false; + } + + if (!eoe.emitGet()) { + // [stack] ELEM + return false; + } + + return true; +} + +bool BytecodeEmitter::emitShortCircuit(ListNode* node) { + MOZ_ASSERT(node->isKind(ParseNodeKind::OrExpr) || + node->isKind(ParseNodeKind::CoalesceExpr) || + node->isKind(ParseNodeKind::AndExpr)); + + /* + * JSOp::Or converts the operand on the stack to boolean, leaves the original + * value on the stack and jumps if true; otherwise it falls into the next + * bytecode, which pops the left operand and then evaluates the right operand. + * The jump goes around the right operand evaluation. + * + * JSOp::And converts the operand on the stack to boolean and jumps if false; + * otherwise it falls into the right operand's bytecode. + */ + + TDZCheckCache tdzCache(this); + + /* Left-associative operator chain: avoid too much recursion. */ + ParseNode* expr = node->head(); + + if (!emitTree(expr)) { + return false; + } + + JSOp op; + switch (node->getKind()) { + case ParseNodeKind::OrExpr: + op = JSOp::Or; + break; + case ParseNodeKind::CoalesceExpr: + op = JSOp::Coalesce; + break; + case ParseNodeKind::AndExpr: + op = JSOp::And; + break; + default: + MOZ_CRASH("Unexpected ParseNodeKind"); + } + + JumpList jump; + if (!emitJump(op, &jump)) { + return false; + } + if (!emit1(JSOp::Pop)) { + return false; + } + + /* Emit nodes between the head and the tail. */ + while ((expr = expr->pn_next)->pn_next) { + if (!emitTree(expr)) { + return false; + } + if (!emitJump(op, &jump)) { + return false; + } + if (!emit1(JSOp::Pop)) { + return false; + } + } + if (!emitTree(expr)) { + return false; + } + + if (!emitJumpTargetAndPatch(jump)) { + return false; + } + return true; +} + +bool BytecodeEmitter::emitSequenceExpr( + ListNode* node, ValueUsage valueUsage /* = ValueUsage::WantValue */) { + for (ParseNode* child = node->head();; child = child->pn_next) { + if (!updateSourceCoordNotes(child->pn_pos.begin)) { + return false; + } + if (!emitTree(child, + child->pn_next ? ValueUsage::IgnoreValue : valueUsage)) { + return false; + } + if (!child->pn_next) { + break; + } + if (!emit1(JSOp::Pop)) { + return false; + } + } + return true; +} + +// Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See +// the comment on emitSwitch. +MOZ_NEVER_INLINE bool BytecodeEmitter::emitIncOrDec(UnaryNode* incDec) { + switch (incDec->kid()->getKind()) { + case ParseNodeKind::DotExpr: + return emitPropIncDec(incDec); + case ParseNodeKind::ElemExpr: + return emitElemIncDec(incDec); + case ParseNodeKind::CallExpr: + return emitCallIncDec(incDec); + default: + return emitNameIncDec(incDec); + } +} + +// 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* labeledStmt) { + const ParserAtom* name = labeledStmt->label(); + LabelEmitter label(this); + + label.emitLabel(name); + + if (!emitTree(labeledStmt->statement())) { + return false; + } + if (!label.emitEnd()) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitConditionalExpression( + ConditionalExpression& conditional, + ValueUsage valueUsage /* = ValueUsage::WantValue */) { + CondEmitter cond(this); + if (!cond.emitCond()) { + return false; + } + + ParseNode* conditionNode = &conditional.condition(); + auto conditionKind = IfEmitter::ConditionKind::Positive; + if (conditionNode->isKind(ParseNodeKind::NotExpr)) { + conditionNode = conditionNode->as().kid(); + conditionKind = IfEmitter::ConditionKind::Negative; + } + + // NOTE: NotExpr of conditionNode may be unwrapped, and in that case the + // negation is handled by conditionKind. + if (!emitTree(conditionNode)) { + return false; + } + + if (!cond.emitThenElse(conditionKind)) { + return false; + } + + if (!emitTree(&conditional.thenExpression(), valueUsage)) { + return false; + } + + if (!cond.emitElse()) { + return false; + } + + if (!emitTree(&conditional.elseExpression(), valueUsage)) { + return false; + } + + if (!cond.emitEnd()) { + return false; + } + MOZ_ASSERT(cond.pushed() == 1); + + return true; +} + +// Check for an object-literal property list that can be handled by the +// ObjLiteral writer. We ensure that for each `prop: value` pair, the key is a +// constant name or numeric index, there is no accessor specified, and the value +// can be encoded by an ObjLiteral instruction (constant number, string, +// boolean, null/undefined). +void BytecodeEmitter::isPropertyListObjLiteralCompatible(ListNode* obj, + bool* withValues, + bool* withoutValues) { + bool keysOK = true; + bool valuesOK = true; + int propCount = 0; + + for (ParseNode* propdef : obj->contents()) { + if (!propdef->is()) { + keysOK = false; + break; + } + propCount++; + + BinaryNode* prop = &propdef->as(); + ParseNode* key = prop->left(); + ParseNode* value = prop->right(); + + // Computed keys not OK (ObjLiteral data stores constant keys). + if (key->isKind(ParseNodeKind::ComputedName)) { + keysOK = false; + break; + } + + // BigIntExprs should have been lowered to computed names at parse + // time, and so should be excluded above. + MOZ_ASSERT(!key->isKind(ParseNodeKind::BigIntExpr)); + + // Numeric keys OK as long as they are integers and in range. + if (key->isKind(ParseNodeKind::NumberExpr)) { + double numValue = key->as().value(); + int32_t i = 0; + if (!NumberIsInt32(numValue, &i)) { + keysOK = false; + break; + } + if (!ObjLiteralWriter::arrayIndexInRange(i)) { + keysOK = false; + break; + } + } + + MOZ_ASSERT(key->isKind(ParseNodeKind::ObjectPropertyName) || + key->isKind(ParseNodeKind::StringExpr) || + key->isKind(ParseNodeKind::NumberExpr)); + + AccessorType accessorType = + prop->is() + ? prop->as().accessorType() + : AccessorType::None; + if (accessorType != AccessorType::None) { + keysOK = false; + break; + } + + if (!isRHSObjLiteralCompatible(value)) { + valuesOK = false; + } + } + + if (propCount >= PropertyTree::MAX_HEIGHT) { + // JSOp::NewObject cannot accept dictionary-mode objects. + keysOK = false; + } + + *withValues = keysOK && valuesOK; + *withoutValues = keysOK; +} + +bool BytecodeEmitter::isArrayObjLiteralCompatible(ParseNode* arrayHead) { + for (ParseNode* elem = arrayHead; elem; elem = elem->pn_next) { + if (elem->isKind(ParseNodeKind::Spread)) { + return false; + } + if (!isRHSObjLiteralCompatible(elem)) { + return false; + } + } + return true; +} + +bool BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, + PropListType type) { + // [stack] CTOR? OBJ + + size_t curFieldKeyIndex = 0; + size_t curStaticFieldKeyIndex = 0; + for (ParseNode* propdef : obj->contents()) { + if (propdef->is()) { + MOZ_ASSERT(type == ClassBody); + // Only handle computing field keys here: the .initializers lambda array + // is created elsewhere. + ClassField* field = &propdef->as(); + if (field->name().getKind() == ParseNodeKind::ComputedName) { + const ParserName* fieldKeys = field->isStatic() + ? cx->parserNames().dotStaticFieldKeys + : cx->parserNames().dotFieldKeys; + if (!emitGetName(fieldKeys)) { + // [stack] CTOR? OBJ ARRAY + return false; + } + + ParseNode* nameExpr = field->name().as().kid(); + + if (!emitTree(nameExpr, ValueUsage::WantValue, EMIT_LINENOTE)) { + // [stack] CTOR? OBJ ARRAY KEY + return false; + } + + if (!emit1(JSOp::ToPropertyKey)) { + // [stack] CTOR? OBJ ARRAY KEY + return false; + } + + size_t fieldKeysIndex; + if (field->isStatic()) { + fieldKeysIndex = curStaticFieldKeyIndex++; + } else { + fieldKeysIndex = curFieldKeyIndex++; + } + + if (!emitUint32Operand(JSOp::InitElemArray, fieldKeysIndex)) { + // [stack] CTOR? OBJ ARRAY + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] CTOR? OBJ + return false; + } + } + continue; + } + + if (propdef->is()) { + // Constructors are sometimes wrapped in LexicalScopeNodes. As we + // already handled emitting the constructor, skip it. + MOZ_ASSERT(propdef->as().scopeBody()->isKind( + ParseNodeKind::ClassMethod)); + continue; + } + + // 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 (!pe.prepareForProtoValue(Some(propdef->pn_pos.begin))) { + // [stack] OBJ + return false; + } + if (!emitTree(propdef->as().kid())) { + // [stack] OBJ PROTO + return false; + } + if (!pe.emitMutateProto()) { + // [stack] OBJ + return false; + } + continue; + } + + if (propdef->isKind(ParseNodeKind::Spread)) { + MOZ_ASSERT(type == ObjectLiteral); + // [stack] OBJ + if (!pe.prepareForSpreadOperand(Some(propdef->pn_pos.begin))) { + // [stack] OBJ OBJ + return false; + } + if (!emitTree(propdef->as().kid())) { + // [stack] OBJ OBJ VAL + return false; + } + if (!pe.emitSpread()) { + // [stack] OBJ + return false; + } + continue; + } + + BinaryNode* prop = &propdef->as(); + + ParseNode* key = prop->left(); + ParseNode* propVal = prop->right(); + AccessorType accessorType; + if (prop->is()) { + if (!prop->as().isStatic() && + key->isKind(ParseNodeKind::PrivateName)) { + // Private instance methods are separately added to the .initializers + // array. + continue; + } + + accessorType = prop->as().accessorType(); + } else if (prop->is()) { + accessorType = prop->as().accessorType(); + } else { + accessorType = AccessorType::None; + } + + auto emitValue = [this, &key, &propVal, accessorType, &pe]() { + // [stack] CTOR? OBJ CTOR? KEY? + + if (propVal->isDirectRHSAnonFunction()) { + if (key->isKind(ParseNodeKind::NumberExpr)) { + MOZ_ASSERT(accessorType == AccessorType::None); + + const ParserAtom* keyAtom = key->as().toAtom( + cx, compilationState.parserAtoms); + 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::StringExpr)) { + MOZ_ASSERT(accessorType == AccessorType::None); + + const ParserAtom* keyAtom = key->as().atom(); + if (!emitAnonymousFunctionWithName(propVal, keyAtom)) { + // [stack] CTOR? OBJ CTOR? VAL + return false; + } + } else { + MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName) || + key->isKind(ParseNodeKind::BigIntExpr)); + + // If a function name is a BigInt, then treat it as a computed name + // equivalent to `[ToString(B)]` for some big-int value `B`. + if (key->isKind(ParseNodeKind::BigIntExpr)) { + MOZ_ASSERT(accessorType == AccessorType::None); + if (!emit1(JSOp::ToString)) { + return false; + } + } + + FunctionPrefixKind prefix = + accessorType == AccessorType::None ? FunctionPrefixKind::None + : accessorType == AccessorType::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 (propVal->is() && + propVal->as().funbox()->needsHomeObject()) { + if (!pe.emitInitHomeObject()) { + // [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::NumberExpr) || + key->isKind(ParseNodeKind::BigIntExpr)) { + // [stack] CTOR? OBJ + if (!pe.prepareForIndexPropKey(Some(propdef->pn_pos.begin), kind)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + if (key->isKind(ParseNodeKind::NumberExpr)) { + if (!emitNumberOp(key->as().value())) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + } else { + if (!emitBigIntOp(&key->as())) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + } + if (!pe.prepareForIndexPropValue()) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + if (!emitValue()) { + // [stack] CTOR? OBJ CTOR? KEY VAL + return false; + } + + if (!pe.emitInitIndexOrComputed(accessorType)) { + return false; + } + + continue; + } + + if (key->isKind(ParseNodeKind::ObjectPropertyName) || + key->isKind(ParseNodeKind::StringExpr)) { + // [stack] CTOR? OBJ + + // emitClass took care of constructor already. + if (type == ClassBody && + key->as().atom() == cx->parserNames().constructor && + !propdef->as().isStatic()) { + continue; + } + + if (!pe.prepareForPropValue(Some(propdef->pn_pos.begin), kind)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + + if (!emitValue()) { + // [stack] CTOR? OBJ CTOR? VAL + return false; + } + + const ParserAtom* keyAtom = key->as().atom(); + + if (!pe.emitInit(accessorType, keyAtom)) { + return false; + } + + continue; + } + + MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName) || + key->isKind(ParseNodeKind::PrivateName)); + + // [stack] CTOR? OBJ + + if (!pe.prepareForComputedPropKey(Some(propdef->pn_pos.begin), kind)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + if (key->is()) { + if (!emitGetPrivateName(&key->as())) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + } else { + if (!emitTree(key->as().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; + } + + if (!pe.emitInitIndexOrComputed(accessorType)) { + return false; + } + + if (key->isKind(ParseNodeKind::PrivateName) && + key->as().privateNameKind() == PrivateNameKind::Setter) { + if (!emitGetPrivateName(&key->as())) { + // [stack] THIS NAME + return false; + } + if (!emitAtomOp(JSOp::GetIntrinsic, cx->parserNames().NoPrivateGetter)) { + // [stack] THIS NAME FUN + return false; + } + if (!emit1(JSOp::InitHiddenElemGetter)) { + // [stack] THIS + return false; + } + } + } + + return true; +} + +bool BytecodeEmitter::emitPropertyListObjLiteral(ListNode* obj, + ObjLiteralFlags flags, + bool useObjLiteralValues) { + ObjLiteralWriter writer; + + writer.beginObject(flags); + bool singleton = flags.contains(ObjLiteralFlag::Singleton); + + for (ParseNode* propdef : obj->contents()) { + BinaryNode* prop = &propdef->as(); + ParseNode* key = prop->left(); + + if (key->is()) { + writer.setPropName(key->as().atom()); + } else { + double numValue = key->as().value(); + int32_t i = 0; + DebugOnly numIsInt = + NumberIsInt32(numValue, &i); // checked previously. + MOZ_ASSERT(numIsInt); + MOZ_ASSERT( + ObjLiteralWriter::arrayIndexInRange(i)); // checked previously. + writer.setPropIndex(i); + } + + if (useObjLiteralValues) { + MOZ_ASSERT(singleton); + ParseNode* value = prop->right(); + if (!emitObjLiteralValue(writer, value)) { + return false; + } + } else { + if (!writer.propWithUndefinedValue(cx)) { + return false; + } + } + } + + GCThingIndex index; + if (!addObjLiteralData(writer, &index)) { + return false; + } + + // JSOp::Object may only be used by (top-level) run-once scripts. + MOZ_ASSERT_IF(singleton, stencil.input.options.isRunOnce); + + JSOp op = singleton ? JSOp::Object : JSOp::NewObject; + if (!emitGCIndexOp(op, index)) { + // [stack] OBJ + return false; + } + + return true; +} + +bool BytecodeEmitter::emitDestructuringRestExclusionSetObjLiteral( + ListNode* pattern) { + ObjLiteralFlags flags; + + ObjLiteralWriter writer; + writer.beginObject(flags); + + for (ParseNode* member : pattern->contents()) { + if (member->isKind(ParseNodeKind::Spread)) { + MOZ_ASSERT(!member->pn_next, "unexpected trailing element after spread"); + break; + } + + const ParserAtom* atom = nullptr; + if (member->isKind(ParseNodeKind::MutateProto)) { + atom = cx->parserNames().proto; + } else { + ParseNode* key = member->as().left(); + atom = key->as().atom(); + } + + writer.setPropName(atom); + + if (!writer.propWithUndefinedValue(cx)) { + return false; + } + } + + GCThingIndex index; + if (!addObjLiteralData(writer, &index)) { + return false; + } + + // If we want to squeeze out a little more performance, we could switch to the + // `JSOp::Object` opcode, because the exclusion set object is never exposed to + // the user, so it's safe to bake the object into the bytecode. But first we + // need to make sure this won't interfere with XDR, cf. the + // `RealmBehaviors::singletonsAsTemplates_` flag. + if (!emitGCIndexOp(JSOp::NewObject, index)) { + // [stack] OBJ + return false; + } + + return true; +} + +bool BytecodeEmitter::emitObjLiteralArray(ParseNode* arrayHead) { + MOZ_ASSERT(checkSingletonContext()); + + ObjLiteralWriter writer; + + ObjLiteralFlags flags(ObjLiteralFlag::Array, ObjLiteralFlag::Singleton); + + writer.beginObject(flags); + + writer.beginDenseArrayElements(); + for (ParseNode* elem = arrayHead; elem; elem = elem->pn_next) { + if (!emitObjLiteralValue(writer, elem)) { + return false; + } + } + + GCThingIndex index; + if (!addObjLiteralData(writer, &index)) { + return false; + } + + if (!emitGCIndexOp(JSOp::Object, index)) { + // [stack] OBJ + return false; + } + + return true; +} + +bool BytecodeEmitter::isRHSObjLiteralCompatible(ParseNode* value) { + return value->isKind(ParseNodeKind::NumberExpr) || + value->isKind(ParseNodeKind::TrueExpr) || + value->isKind(ParseNodeKind::FalseExpr) || + value->isKind(ParseNodeKind::NullExpr) || + value->isKind(ParseNodeKind::RawUndefinedExpr) || + value->isKind(ParseNodeKind::StringExpr) || + value->isKind(ParseNodeKind::TemplateStringExpr); +} + +bool BytecodeEmitter::emitObjLiteralValue(ObjLiteralWriter& writer, + ParseNode* value) { + MOZ_ASSERT(isRHSObjLiteralCompatible(value)); + if (value->isKind(ParseNodeKind::NumberExpr)) { + double numValue = value->as().value(); + int32_t i = 0; + js::Value v; + if (NumberIsInt32(numValue, &i)) { + v.setInt32(i); + } else { + v.setDouble(numValue); + } + if (!writer.propWithConstNumericValue(cx, v)) { + return false; + } + } else if (value->isKind(ParseNodeKind::TrueExpr)) { + if (!writer.propWithTrueValue(cx)) { + return false; + } + } else if (value->isKind(ParseNodeKind::FalseExpr)) { + if (!writer.propWithFalseValue(cx)) { + return false; + } + } else if (value->isKind(ParseNodeKind::NullExpr)) { + if (!writer.propWithNullValue(cx)) { + return false; + } + } else if (value->isKind(ParseNodeKind::RawUndefinedExpr)) { + if (!writer.propWithUndefinedValue(cx)) { + return false; + } + } else if (value->isKind(ParseNodeKind::StringExpr) || + value->isKind(ParseNodeKind::TemplateStringExpr)) { + if (!writer.propWithAtomValue(cx, value->as().atom())) { + return false; + } + } else { + MOZ_CRASH("Unexpected parse node"); + } + return true; +} + +mozilla::Maybe BytecodeEmitter::setupMemberInitializers( + ListNode* classMembers, FieldPlacement placement) { + bool isStatic = placement == FieldPlacement::Static; + + size_t numFields = std::count_if( + classMembers->contents().begin(), classMembers->contents().end(), + [&isStatic](ParseNode* member) { + return NeedsFieldInitializer(member, isStatic); + }); + size_t numPrivateMethods = std::count_if( + classMembers->contents().begin(), classMembers->contents().end(), + [&isStatic](ParseNode* member) { + return NeedsMethodInitializer(member, isStatic); + }); + + // If there are more initializers than can be represented, return invalid. + if (numFields + numPrivateMethods > MemberInitializers::MaxInitializers) { + return Nothing(); + } + return Some(MemberInitializers(numFields + numPrivateMethods)); +} + +// Purpose of .fieldKeys: +// Computed field names (`["x"] = 2;`) must be ran at class-evaluation time, +// not object construction time. The transformation to do so is roughly as +// follows: +// +// class C { +// [keyExpr] = valueExpr; +// } +// --> +// let .fieldKeys = [keyExpr]; +// let .initializers = [ +// () => { +// this[.fieldKeys[0]] = valueExpr; +// } +// ]; +// class C { +// constructor() { +// .initializers[0](); +// } +// } +// +// BytecodeEmitter::emitCreateFieldKeys does `let .fieldKeys = [...];` +// BytecodeEmitter::emitPropertyList fills in the elements of the array. +// See GeneralParser::fieldInitializer for the `this[.fieldKeys[0]]` part. +bool BytecodeEmitter::emitCreateFieldKeys(ListNode* obj, + FieldPlacement placement) { + bool isStatic = placement == FieldPlacement::Static; + auto isFieldWithComputedName = [isStatic](ParseNode* propdef) { + return propdef->is() && + propdef->as().isStatic() == isStatic && + propdef->as().name().getKind() == + ParseNodeKind::ComputedName; + }; + + size_t numFieldKeys = std::count_if( + obj->contents().begin(), obj->contents().end(), isFieldWithComputedName); + if (numFieldKeys == 0) { + return true; + } + + const ParserName* fieldKeys = isStatic ? cx->parserNames().dotStaticFieldKeys + : cx->parserNames().dotFieldKeys; + NameOpEmitter noe(this, fieldKeys, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + return false; + } + + if (!emitUint32Operand(JSOp::NewArray, numFieldKeys)) { + // [stack] ARRAY + return false; + } + + if (!noe.emitAssignment()) { + // [stack] ARRAY + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] + return false; + } + + return true; +} + +bool BytecodeEmitter::emitCreateMemberInitializers(ClassEmitter& ce, + ListNode* obj, + FieldPlacement placement) { + // FieldPlacement::Instance + // [stack] HOMEOBJ HERITAGE? + // + // FieldPlacement::Static + // [stack] CTOR HOMEOBJ + mozilla::Maybe memberInitializers = + setupMemberInitializers(obj, placement); + if (!memberInitializers) { + ReportAllocationOverflow(cx); + return false; + } + + size_t numInitializers = memberInitializers->numMemberInitializers; + if (numInitializers == 0) { + return true; + } + + bool isStatic = placement == FieldPlacement::Static; + if (!ce.prepareForMemberInitializers(numInitializers, isStatic)) { + // [stack] HOMEOBJ HERITAGE? ARRAY + // or: + // [stack] CTOR HOMEOBJ ARRAY + return false; + } + + // Private methods could be used in the field initializers, + // so we stamp them onto the instance first. + // Static private methods aren't implemented, so skip this step + // if emitting static initializers. + if (!isStatic && !emitPrivateMethodInitializers(ce, obj)) { + return false; + } + + for (ParseNode* propdef : obj->contents()) { + if (!propdef->is()) { + continue; + } + if (propdef->as().isStatic() != isStatic) { + continue; + } + + FunctionNode* initializer = propdef->as().initializer(); + + if (!ce.prepareForMemberInitializer()) { + return false; + } + if (!emitTree(initializer)) { + // [stack] HOMEOBJ HERITAGE? ARRAY LAMBDA + // or: + // [stack] CTOR HOMEOBJ ARRAY LAMBDA + return false; + } + if (initializer->funbox()->needsHomeObject()) { + MOZ_ASSERT(initializer->funbox()->allowSuperProperty()); + if (!ce.emitMemberInitializerHomeObject(isStatic)) { + // [stack] HOMEOBJ HERITAGE? ARRAY LAMBDA + // or: + // [stack] CTOR HOMEOBJ ARRAY LAMBDA + return false; + } + } + if (!ce.emitStoreMemberInitializer()) { + // [stack] HOMEOBJ HERITAGE? ARRAY + // or: + // [stack] CTOR HOMEOBJ ARRAY + return false; + } + } + + if (!ce.emitMemberInitializersEnd()) { + // [stack] HOMEOBJ HERITAGE? + // or: + // [stack] CTOR HOMEOBJ + return false; + } + + return true; +} + +bool BytecodeEmitter::emitPrivateMethodInitializers(ClassEmitter& ce, + ListNode* obj) { + for (ParseNode* propdef : obj->contents()) { + if (!propdef->is() || propdef->as().isStatic()) { + continue; + } + ParseNode* propName = &propdef->as().name(); + if (!propName->isKind(ParseNodeKind::PrivateName)) { + continue; + } + + if (!ce.prepareForMemberInitializer()) { + // [stack] HOMEOBJ HERITAGE? ARRAY + // or: + // [stack] CTOR HOMEOBJ ARRAY + return false; + } + + // Synthesize a name for the lexical variable that will store the + // private method body. + StringBuffer storedMethodName(cx); + if (!storedMethodName.append(propName->as().atom())) { + return false; + } + AccessorType accessorType = propdef->as().accessorType(); + switch (accessorType) { + case AccessorType::None: + if (!storedMethodName.append(".method")) { + return false; + } + break; + case AccessorType::Getter: + if (!storedMethodName.append(".getter")) { + return false; + } + break; + case AccessorType::Setter: + if (!storedMethodName.append(".setter")) { + return false; + } + break; + default: + MOZ_CRASH("Invalid private method accessor type"); + } + const ParserAtom* storedMethodAtom = + storedMethodName.finishParserAtom(compilationState.parserAtoms); + + // Emit the private method body and store it as a lexical var. + if (!emitFunction(&propdef->as().method())) { + // [stack] HOMEOBJ HERITAGE? ARRAY METHOD + // or: + // [stack] CTOR HOMEOBJ ARRAY METHOD + return false; + } + // The private method body needs to access the home object, + // and the CE knows where that is on the stack. + if (!ce.emitMemberInitializerHomeObject(false)) { + // [stack] HOMEOBJ HERITAGE? ARRAY METHOD + // or: + // [stack] CTOR HOMEOBJ ARRAY METHOD + return false; + } + if (!emitLexicalInitialization(storedMethodAtom)) { + // [stack] HOMEOBJ HERITAGE? ARRAY METHOD + // or: + // [stack] CTOR HOMEOBJ ARRAY METHOD + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] HOMEOBJ HERITAGE? ARRAY + // or: + // [stack] CTOR HOMEOBJ ARRAY + return false; + } + + if (!emitPrivateMethodInitializer(ce, propdef, propName, storedMethodAtom, + accessorType)) { + // [stack] HOMEOBJ HERITAGE? ARRAY + // or: + // [stack] CTOR HOMEOBJ ARRAY + return false; + } + + // Store the emitted initializer function into the .initializers array. + if (!ce.emitStoreMemberInitializer()) { + // [stack] HOMEOBJ HERITAGE? ARRAY + // or: + // [stack] CTOR HOMEOBJ ARRAY + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitPrivateMethodInitializer( + ClassEmitter& ce, ParseNode* prop, ParseNode* propName, + const ParserAtom* storedMethodAtom, AccessorType accessorType) { + // Emit the synthesized initializer function. + FunctionNode* funNode = prop->as().initializerIfPrivate(); + MOZ_ASSERT(funNode); + FunctionBox* funbox = funNode->funbox(); + FunctionEmitter fe(this, funbox, funNode->syntaxKind(), + FunctionEmitter::IsHoisted::No); + if (!fe.prepareForNonLazy()) { + // [stack] + return false; + } + + BytecodeEmitter bce2(this, parser, funbox, stencil, compilationState, + emitterMode); + if (!bce2.init(funNode->pn_pos)) { + return false; + } + ListNode* paramsBody = &funNode->body()->as(); + FunctionScriptEmitter fse(&bce2, funbox, Nothing(), Nothing()); + if (!fse.prepareForParameters()) { + // [stack] + return false; + } + if (!bce2.emitFunctionFormalParameters(paramsBody)) { + // [stack] + return false; + } + if (!fse.prepareForBody()) { + // [stack] + return false; + } + + if (!bce2.emit1(JSOp::FunctionThis)) { + // [stack] THIS + return false; + } + if (!bce2.emitGetPrivateName(&propName->as())) { + // [stack] THIS NAME + return false; + } + if (!bce2.emitGetName(storedMethodAtom)) { + // [stack] THIS NAME METHOD + return false; + } + + PrivateNameKind kind = propName->as().privateNameKind(); + switch (kind) { + case PrivateNameKind::Method: + if (!bce2.emit1(JSOp::InitLockedElem)) { + // [stack] THIS + return false; + } + break; + case PrivateNameKind::Setter: + if (!bce2.emit1(JSOp::InitHiddenElemSetter)) { + // [stack] THIS + return false; + } + if (!bce2.emitGetPrivateName(&propName->as())) { + // [stack] THIS NAME + return false; + } + if (!bce2.emitAtomOp(JSOp::GetIntrinsic, + cx->parserNames().NoPrivateGetter)) { + // [stack] THIS NAME FUN + return false; + } + if (!bce2.emit1(JSOp::InitHiddenElemGetter)) { + // [stack] THIS + return false; + } + break; + case PrivateNameKind::Getter: + case PrivateNameKind::GetterSetter: + if (accessorType == AccessorType::Getter) { + if (!bce2.emit1(JSOp::InitHiddenElemGetter)) { + // [stack] THIS + return false; + } + } else { + if (!bce2.emit1(JSOp::InitHiddenElemSetter)) { + // [stack] THIS + return false; + } + } + break; + default: + MOZ_CRASH("Invalid op"); + } + + // Pop remaining THIS. + if (!bce2.emit1(JSOp::Pop)) { + // [stack] + return false; + } + + if (!fse.emitEndBody()) { + // [stack] + return false; + } + if (!fse.intoStencil()) { + return false; + } + + if (!fe.emitNonLazyEnd()) { + // [stack] HOMEOBJ HERITAGE? ARRAY FUN + // or: + // [stack] CTOR HOMEOBJ ARRAY FUN + return false; + } + + return true; +} + +const MemberInitializers& BytecodeEmitter::findMemberInitializersForCall() { + for (BytecodeEmitter* current = this; current; current = current->parent) { + if (current->sc->isFunctionBox()) { + FunctionBox* funbox = current->sc->asFunctionBox(); + + if (funbox->isArrow()) { + continue; + } + + // If we found a non-arrow / non-constructor we were never allowed to + // expect fields in the first place. + MOZ_RELEASE_ASSERT(funbox->isClassConstructor()); + + MOZ_ASSERT(funbox->memberInitializers().valid); + return funbox->memberInitializers(); + } + } + + MOZ_RELEASE_ASSERT(compilationState.scopeContext.memberInitializers); + return *compilationState.scopeContext.memberInitializers; +} + +bool BytecodeEmitter::emitInitializeInstanceMembers() { + const MemberInitializers& memberInitializers = + findMemberInitializersForCall(); + size_t numInitializers = memberInitializers.numMemberInitializers; + + if (numInitializers == 0) { + return true; + } + + if (!emitGetName(cx->parserNames().dotInitializers)) { + // [stack] ARRAY + return false; + } + + for (size_t index = 0; index < numInitializers; index++) { + if (index < numInitializers - 1) { + // We Dup to keep the array around (it is consumed in the bytecode + // below) for next iterations of this loop, except for the last + // iteration, which avoids an extra Pop at the end of the loop. + if (!emit1(JSOp::Dup)) { + // [stack] ARRAY ARRAY + return false; + } + } + + if (!emitNumberOp(index)) { + // [stack] ARRAY? ARRAY INDEX + return false; + } + + if (!emit1(JSOp::GetElem)) { + // [stack] ARRAY? FUNC + return false; + } + + // This is guaranteed to run after super(), so we don't need TDZ checks. + if (!emitGetName(cx->parserNames().dotThis)) { + // [stack] ARRAY? FUNC THIS + return false; + } + + if (!emitCall(JSOp::CallIgnoresRv, 0)) { + // [stack] ARRAY? RVAL + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] ARRAY? + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitInitializeStaticFields(ListNode* classMembers) { + auto isStaticField = [](ParseNode* propdef) { + return propdef->is() && propdef->as().isStatic(); + }; + size_t numFields = + std::count_if(classMembers->contents().begin(), + classMembers->contents().end(), isStaticField); + + if (numFields == 0) { + return true; + } + + if (!emitGetName(cx->parserNames().dotStaticInitializers)) { + // [stack] CTOR ARRAY + return false; + } + + for (size_t fieldIndex = 0; fieldIndex < numFields; fieldIndex++) { + bool hasNext = fieldIndex < numFields - 1; + if (hasNext) { + // We Dup to keep the array around (it is consumed in the bytecode below) + // for next iterations of this loop, except for the last iteration, which + // avoids an extra Pop at the end of the loop. + if (!emit1(JSOp::Dup)) { + // [stack] CTOR ARRAY ARRAY + return false; + } + } + + if (!emitNumberOp(fieldIndex)) { + // [stack] CTOR ARRAY? ARRAY INDEX + return false; + } + + if (!emit1(JSOp::GetElem)) { + // [stack] CTOR ARRAY? FUNC + return false; + } + + if (!emitDupAt(1 + hasNext)) { + // [stack] CTOR ARRAY? FUNC CTOR + return false; + } + + if (!emitCall(JSOp::CallIgnoresRv, 0)) { + // [stack] CTOR ARRAY? RVAL + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] CTOR ARRAY? + return false; + } + } + + // Overwrite |.staticInitializers| and |.staticFieldKeys| with undefined to + // avoid keeping the arrays alive indefinitely. + auto clearStaticFieldSlot = [&](const ParserName* name) { + NameOpEmitter noe(this, name, NameOpEmitter::Kind::SimpleAssignment); + if (!noe.prepareForRhs()) { + // [stack] ENV? VAL? + return false; + } + + if (!emit1(JSOp::Undefined)) { + // [stack] ENV? VAL? UNDEFINED + return false; + } + + if (!noe.emitAssignment()) { + // [stack] VAL + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] + return false; + } + + return true; + }; + + if (!clearStaticFieldSlot(cx->parserNames().dotStaticInitializers)) { + return false; + } + + auto isStaticFieldWithComputedName = [](ParseNode* propdef) { + return propdef->is() && propdef->as().isStatic() && + propdef->as().name().getKind() == + ParseNodeKind::ComputedName; + }; + + if (std::any_of(classMembers->contents().begin(), + classMembers->contents().end(), + isStaticFieldWithComputedName)) { + if (!clearStaticFieldSlot(cx->parserNames().dotStaticFieldKeys)) { + return false; + } + } + + return true; +} + +// Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See +// the comment on emitSwitch. +MOZ_NEVER_INLINE bool BytecodeEmitter::emitObject(ListNode* objNode) { + // Note: this method uses the ObjLiteralWriter and emits ObjLiteralStencil + // objects into the GCThingList, which will evaluate them into real GC objects + // during JSScript::fullyInitFromEmitter. Eventually we want OBJLITERAL to be + // a real opcode, but for now, performance constraints limit us to evaluating + // object literals at the end of parse, when we're allowed to allocate GC + // things. + // + // There are four cases here, in descending order of preference: + // + // 1. The list of property names is "normal" and constant (no computed + // values, no integer indices), the values are all simple constants + // (numbers, booleans, strings), *and* this occurs in a run-once + // (singleton) context. In this case, we can emit ObjLiteral + // instructions to build an object with values, and the object will be + // attached to a JSOp::Object opcode, whose semantics are for the backend + // to simply steal the object from the script. + // + // 2. The list of property names is "normal" and constant as above, *and* this + // occurs in a run-once (singleton) context, but some values are complex + // (computed expressions, sub-objects, functions, etc.). In this case, we + // can still use JSOp::Object (because singleton context), but the object + // has |undefined| property values and InitProp ops are emitted to set the + // values. + // + // 3. The list of property names is "normal" and constant as above, but this + // occurs in a non-run-once (non-singleton) context. In this case, we can + // use the ObjLiteral functionality to describe an *empty* object (all + // values left undefined) with the right fields, which will become a + // JSOp::NewObject opcode using this template object to speed the creation + // of the object each time it executes (stealing its shape, etc.). The + // emitted bytecode still needs InitProp ops to set the values in this + // case. + // + // 4. Any other case. As a fallback, we use NewInit to create a new, empty + // object (i.e., `{}`) and then emit bytecode to initialize its properties + // one-by-one. + + bool useObjLiteral = false; + bool useObjLiteralValues = false; + isPropertyListObjLiteralCompatible(objNode, &useObjLiteralValues, + &useObjLiteral); + + // [stack] + // + ObjectEmitter oe(this); + if (useObjLiteral) { + bool singleton = checkSingletonContext() && + !objNode->hasNonConstInitializer() && objNode->head(); + + ObjLiteralFlags flags; + if (singleton) { + // Case 1 or 2. + flags += ObjLiteralFlag::Singleton; + } else { + // Case 3. + useObjLiteralValues = false; + } + + // Use an ObjLiteral op. This will record ObjLiteral insns in the + // objLiteralWriter's buffer and add a fixup to the list of ObjLiteral + // fixups so that at GC-publish time at the end of parse, the full (case 1 + // or 2) or template-without-values (case 3) object can be allocated and + // the bytecode can be patched to refer to it. + if (!emitPropertyListObjLiteral(objNode, flags, useObjLiteralValues)) { + // [stack] OBJ + return false; + } + // Put the ObjectEmitter in the right state. This tells it that there will + // already be an object on the stack as a result of the (eventual) + // NewObject or Object op, and prepares it to emit values if needed. + if (!oe.emitObjectWithTemplateOnStack()) { + // [stack] OBJ + return false; + } + if (!useObjLiteralValues) { + // Case 2 or 3 above: we still need to emit bytecode to fill in the + // object's property values. + if (!emitPropertyList(objNode, oe, ObjectLiteral)) { + // [stack] OBJ + return false; + } + } + } else { + // Case 4 above: no ObjLiteral use, just bytecode to build the object from + // scratch. + if (!oe.emitObject(objNode->count())) { + // [stack] OBJ + return false; + } + if (!emitPropertyList(objNode, oe, ObjectLiteral)) { + // [stack] OBJ + return false; + } + } + + if (!oe.emitEnd()) { + // [stack] OBJ + return false; + } + + return true; +} + +bool BytecodeEmitter::emitArrayLiteral(ListNode* array) { + // Emit JSOp::Object if the array consists entirely of primitive values and we + // are in a singleton context. + if (checkSingletonContext() && !array->hasNonConstInitializer() && + array->head() && isArrayObjLiteralCompatible(array->head())) { + return emitObjLiteralArray(array->head()); + } + + return emitArray(array->head(), array->count()); +} + +bool BytecodeEmitter::emitArray(ParseNode* arrayHead, uint32_t count) { + /* + * Emit code for [a, b, c] that is equivalent to constructing a new + * array and in source order evaluating each element value and adding + * it to the array, without invoking latent setters. We use the + * JSOp::NewInit and JSOp::InitElemArray bytecodes to ignore setters and + * to avoid dup'ing and popping the array as each element is added, as + * JSOp::SetElem/JSOp::SetProp would do. + */ + + uint32_t nspread = 0; + for (ParseNode* elem = arrayHead; elem; elem = elem->pn_next) { + if (elem->isKind(ParseNodeKind::Spread)) { + nspread++; + } + } + + // Array literal's length is limited to NELEMENTS_LIMIT in parser. + static_assert(NativeObject::MAX_DENSE_ELEMENTS_COUNT <= INT32_MAX, + "array literals' maximum length must not exceed limits " + "required by BaselineCompiler::emit_NewArray, " + "BaselineCompiler::emit_InitElemArray, " + "and DoSetElemFallback's handling of JSOp::InitElemArray"); + MOZ_ASSERT(count >= nspread); + MOZ_ASSERT(count <= NativeObject::MAX_DENSE_ELEMENTS_COUNT, + "the parser must throw an error if the array exceeds maximum " + "length"); + + // For arrays with spread, this is a very pessimistic allocation, the + // minimum possible final size. + if (!emitUint32Operand(JSOp::NewArray, count - nspread)) { + // [stack] ARRAY + return false; + } + + ParseNode* elem = arrayHead; + uint32_t index; + bool afterSpread = false; + for (index = 0; elem; index++, elem = elem->pn_next) { + if (!afterSpread && elem->isKind(ParseNodeKind::Spread)) { + afterSpread = true; + if (!emitNumberOp(index)) { + // [stack] ARRAY INDEX + return false; + } + } + if (!updateSourceCoordNotes(elem->pn_pos.begin)) { + return false; + } + + bool allowSelfHostedIterFlag = false; + if (elem->isKind(ParseNodeKind::Elision)) { + if (!emit1(JSOp::Hole)) { + return false; + } + } else { + ParseNode* expr; + if (elem->isKind(ParseNodeKind::Spread)) { + expr = elem->as().kid(); + + allowSelfHostedIterFlag = allowSelfHostedIter(expr); + } else { + expr = elem; + } + if (!emitTree(expr, ValueUsage::WantValue, EMIT_LINENOTE)) { + // [stack] ARRAY INDEX? VALUE + return false; + } + } + if (elem->isKind(ParseNodeKind::Spread)) { + if (!emitIterator()) { + // [stack] ARRAY INDEX NEXT ITER + return false; + } + if (!emit2(JSOp::Pick, 3)) { + // [stack] INDEX NEXT ITER ARRAY + return false; + } + if (!emit2(JSOp::Pick, 3)) { + // [stack] NEXT ITER ARRAY INDEX + return false; + } + if (!emitSpread(allowSelfHostedIterFlag)) { + // [stack] ARRAY INDEX + return false; + } + } else if (afterSpread) { + if (!emit1(JSOp::InitElemInc)) { + return false; + } + } else { + if (!emitUint32Operand(JSOp::InitElemArray, index)) { + return false; + } + } + } + MOZ_ASSERT(index == count); + if (afterSpread) { + if (!emit1(JSOp::Pop)) { + // [stack] ARRAY + return false; + } + } + return true; +} + +static inline JSOp UnaryOpParseNodeKindToJSOp(ParseNodeKind pnk) { + switch (pnk) { + case ParseNodeKind::ThrowStmt: + return JSOp::Throw; + case ParseNodeKind::VoidExpr: + return JSOp::Void; + case ParseNodeKind::NotExpr: + return JSOp::Not; + case ParseNodeKind::BitNotExpr: + return JSOp::BitNot; + case ParseNodeKind::PosExpr: + return JSOp::Pos; + case ParseNodeKind::NegExpr: + return JSOp::Neg; + default: + MOZ_CRASH("unexpected unary op"); + } +} + +bool BytecodeEmitter::emitUnary(UnaryNode* unaryNode) { + if (!updateSourceCoordNotes(unaryNode->pn_pos.begin)) { + return false; + } + if (!emitTree(unaryNode->kid())) { + return false; + } + return emit1(UnaryOpParseNodeKindToJSOp(unaryNode->getKind())); +} + +bool BytecodeEmitter::emitTypeof(UnaryNode* typeofNode, JSOp op) { + MOZ_ASSERT(op == JSOp::Typeof || op == JSOp::TypeofExpr); + + if (!updateSourceCoordNotes(typeofNode->pn_pos.begin)) { + return false; + } + + if (!emitTree(typeofNode->kid())) { + return false; + } + + return emit1(op); +} + +bool BytecodeEmitter::emitFunctionFormalParameters(ListNode* paramsBody) { + ParseNode* funBody = paramsBody->last(); + FunctionBox* funbox = sc->asFunctionBox(); + + bool hasRest = funbox->hasRest(); + + FunctionParamsEmitter fpe(this, funbox); + for (ParseNode* arg = paramsBody->head(); arg != funBody; + arg = arg->pn_next) { + ParseNode* bindingElement = arg; + ParseNode* initializer = nullptr; + if (arg->isKind(ParseNodeKind::AssignExpr) || + arg->isKind(ParseNodeKind::InitExpr)) { + bindingElement = arg->as().left(); + initializer = arg->as().right(); + } + bool hasInitializer = !!initializer; + bool isRest = hasRest && arg->pn_next == funBody; + bool isDestructuring = !bindingElement->isKind(ParseNodeKind::Name); + + // Left-hand sides are either simple names or destructuring patterns. + MOZ_ASSERT(bindingElement->isKind(ParseNodeKind::Name) || + bindingElement->isKind(ParseNodeKind::ArrayExpr) || + bindingElement->isKind(ParseNodeKind::ObjectExpr)); + + auto emitDefaultInitializer = [this, &initializer, &bindingElement]() { + // [stack] + + if (!this->emitInitializer(initializer, bindingElement)) { + // [stack] DEFAULT + return false; + } + return true; + }; + + auto emitDestructuring = [this, &bindingElement]() { + // [stack] ARG + + if (!this->emitDestructuringOps(&bindingElement->as(), + DestructuringFlavor::Declaration)) { + // [stack] ARG + return false; + } + + return true; + }; + + if (isRest) { + if (isDestructuring) { + if (!fpe.prepareForDestructuringRest()) { + // [stack] + return false; + } + if (!emitDestructuring()) { + // [stack] + return false; + } + if (!fpe.emitDestructuringRestEnd()) { + // [stack] + return false; + } + } else { + const ParserName* paramName = bindingElement->as().name(); + if (!fpe.emitRest(paramName)) { + // [stack] + return false; + } + } + + continue; + } + + if (isDestructuring) { + if (hasInitializer) { + if (!fpe.prepareForDestructuringDefaultInitializer()) { + // [stack] + return false; + } + if (!emitDefaultInitializer()) { + // [stack] + return false; + } + if (!fpe.prepareForDestructuringDefault()) { + // [stack] + return false; + } + if (!emitDestructuring()) { + // [stack] + return false; + } + if (!fpe.emitDestructuringDefaultEnd()) { + // [stack] + return false; + } + } else { + if (!fpe.prepareForDestructuring()) { + // [stack] + return false; + } + if (!emitDestructuring()) { + // [stack] + return false; + } + if (!fpe.emitDestructuringEnd()) { + // [stack] + return false; + } + } + + continue; + } + + if (hasInitializer) { + if (!fpe.prepareForDefault()) { + // [stack] + return false; + } + if (!emitDefaultInitializer()) { + // [stack] + return false; + } + const ParserAtom* paramName = bindingElement->as().name(); + if (!fpe.emitDefaultEnd(paramName)) { + // [stack] + return false; + } + + continue; + } + + const ParserAtom* paramName = bindingElement->as().name(); + if (!fpe.emitSimple(paramName)) { + // [stack] + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitInitializeFunctionSpecialNames() { + FunctionBox* funbox = sc->asFunctionBox(); + + // [stack] + + auto emitInitializeFunctionSpecialName = [](BytecodeEmitter* bce, + const ParserName* name, JSOp op) { + // A special name must be slotful, either on the frame or on the + // call environment. + MOZ_ASSERT(bce->lookupName(name).hasKnownSlot()); + + NameOpEmitter noe(bce, name, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + // [stack] + return false; + } + if (!bce->emit1(op)) { + // [stack] THIS/ARGUMENTS + return false; + } + if (!noe.emitAssignment()) { + // [stack] THIS/ARGUMENTS + return false; + } + if (!bce->emit1(JSOp::Pop)) { + // [stack] + return false; + } + + return true; + }; + + // Do nothing if the function doesn't have an arguments binding. + if (funbox->argumentsHasVarBinding()) { + if (!emitInitializeFunctionSpecialName(this, cx->parserNames().arguments, + JSOp::Arguments)) { + // [stack] + return false; + } + } + + // Do nothing if the function doesn't have a this-binding (this + // happens for instance if it doesn't use this/eval or if it's an + // arrow function). + if (funbox->functionHasThisBinding()) { + if (!emitInitializeFunctionSpecialName(this, cx->parserNames().dotThis, + JSOp::FunctionThis)) { + return false; + } + } + + // Do nothing if the function doesn't implicitly return a promise result. + if (funbox->needsPromiseResult()) { + if (!emitInitializeFunctionSpecialName(this, cx->parserNames().dotGenerator, + JSOp::Generator)) { + // [stack] + return false; + } + } + return true; +} + +bool BytecodeEmitter::emitLexicalInitialization(NameNode* name) { + return emitLexicalInitialization(name->name()); +} + +bool BytecodeEmitter::emitLexicalInitialization(const ParserAtom* name) { + NameOpEmitter noe(this, name, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + return false; + } + + // 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()); + + if (!noe.emitAssignment()) { + return false; + } + + return true; +} + +static MOZ_ALWAYS_INLINE ParseNode* FindConstructor(JSContext* cx, + ListNode* classMethods) { + for (ParseNode* classElement : classMethods->contents()) { + ParseNode* unwrappedElement = classElement; + if (unwrappedElement->is()) { + unwrappedElement = unwrappedElement->as().scopeBody(); + } + if (unwrappedElement->is()) { + ClassMethod& method = unwrappedElement->as(); + ParseNode& methodName = method.name(); + if (!method.isStatic() && + (methodName.isKind(ParseNodeKind::ObjectPropertyName) || + methodName.isKind(ParseNodeKind::StringExpr)) && + methodName.as().atom() == cx->parserNames().constructor) { + return classElement; + } + } + } + return nullptr; +} + +template +bool BytecodeEmitter::emitNewPrivateNames(ListNode* classMembers) { + for (ParseNode* classElement : classMembers->contents()) { + if (!classElement->is()) { + continue; + } + + ParseNode* elementName = &classElement->as().name(); + if (!elementName->isKind(ParseNodeKind::PrivateName)) { + continue; + } + + const ParserAtom* privateName = elementName->as().name(); + + // TODO: Add a new bytecode to create private names. + if (!emitAtomOp(JSOp::GetIntrinsic, cx->parserNames().NewPrivateName)) { + // [stack] HERITAGE NEWPRIVATENAME + return false; + } + + // Push `undefined` as `this` parameter for call. + if (!emit1(JSOp::Undefined)) { + // [stack] HERITAGE NEWPRIVATENAME UNDEFINED + return false; + } + + if (!emitAtomOp(JSOp::String, privateName)) { + // [stack] HERITAGE NEWPRIVATENAME UNDEFINED NAME + return false; + } + + int argc = 1; + if (!emitCall(JSOp::Call, argc)) { + // [stack] HERITAGE PRIVATENAME + return false; + } + + // Add a binding for #name => privatename + if (!emitLexicalInitialization(privateName)) { + // [stack] HERITAGE PRIVATENAME + return false; + } + + // Pop Private name off the stack. + if (!emit1(JSOp::Pop)) { + // [stack] HERITAGE + return false; + } + } + return true; +} + +// This follows ES6 14.5.14 (ClassDefinitionEvaluation) and ES6 14.5.15 +// (BindingClassDeclarationEvaluation). +bool BytecodeEmitter::emitClass( + ClassNode* classNode, + ClassNameKind nameKind /* = ClassNameKind::BindingName */, + const ParserAtom* nameForAnonymousClass /* = nullptr */) { + MOZ_ASSERT((nameKind == ClassNameKind::InferredName) == + bool(nameForAnonymousClass)); + + ParseNode* heritageExpression = classNode->heritage(); + ListNode* classMembers = classNode->memberList(); + ParseNode* constructor = FindConstructor(cx, classMembers); + + // If |nameKind != ClassNameKind::ComputedName| + // [stack] + // Else + // [stack] NAME + + ClassEmitter ce(this); + const ParserAtom* innerName = nullptr; + ClassEmitter::Kind kind = ClassEmitter::Kind::Expression; + if (ClassNames* names = classNode->names()) { + MOZ_ASSERT(nameKind == ClassNameKind::BindingName); + innerName = names->innerBinding()->name(); + MOZ_ASSERT(innerName); + + if (names->outerBinding()) { + MOZ_ASSERT(names->outerBinding()->name()); + MOZ_ASSERT(names->outerBinding()->name() == innerName); + kind = ClassEmitter::Kind::Declaration; + } + } + + if (LexicalScopeNode* scopeBindings = classNode->scopeBindings()) { + if (!ce.emitScope(scopeBindings->scopeBindings())) { + // [stack] + return false; + } + } + + bool isDerived = !!heritageExpression; + if (isDerived) { + if (!updateSourceCoordNotes(classNode->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitTree(heritageExpression)) { + // [stack] HERITAGE + return false; + } + } + + // The class body scope holds any private names. Those mustn't be visible in + // the heritage expression and hence the scope must be emitted after the + // heritage expression. + if (LexicalScopeNode* bodyScopeBindings = classNode->bodyScopeBindings()) { + if (!ce.emitBodyScope(bodyScopeBindings->scopeBindings())) { + // [stack] HERITAGE + return false; + } + + if (!emitNewPrivateNames(classMembers)) { + return false; + } + if (!emitNewPrivateNames(classMembers)) { + return false; + } + } + + bool hasNameOnStack = nameKind == ClassNameKind::ComputedName; + if (isDerived) { + if (!ce.emitDerivedClass(innerName, nameForAnonymousClass, + hasNameOnStack)) { + // [stack] HERITAGE HOMEOBJ + return false; + } + } else { + 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) { + // See |Parser::classMember(...)| for the reason why |.initializers| is + // created within its own scope. + Maybe lse; + FunctionNode* ctor; + if (constructor->is()) { + LexicalScopeNode* constructorScope = &constructor->as(); + + // The constructor scope should only contain the |.initializers| binding. + MOZ_ASSERT(!constructorScope->isEmptyScope()); + MOZ_ASSERT(constructorScope->scopeBindings()->slotInfo.length == 1); + MOZ_ASSERT(constructorScope->scopeBindings()->trailingNames[0].name() == + cx->parserNames().dotInitializers->toIndex()); + + auto needsInitializer = [](ParseNode* propdef) { + return NeedsFieldInitializer(propdef, false) || + NeedsMethodInitializer(propdef, false); + }; + + // As an optimization omit the |.initializers| binding when no instance + // fields or private methods are present. + bool needsInitializers = + std::any_of(classMembers->contents().begin(), + classMembers->contents().end(), needsInitializer); + if (needsInitializers) { + lse.emplace(this); + if (!lse->emitScope(ScopeKind::Lexical, + constructorScope->scopeBindings())) { + return false; + } + + // Any class with field initializers will have a constructor + if (!emitCreateMemberInitializers(ce, classMembers, + FieldPlacement::Instance)) { + return false; + } + } + + ctor = &constructorScope->scopeBody()->as().method(); + } else { + // The |.initializers| binding is never emitted when in self-hosting mode. + MOZ_ASSERT(emitterMode == BytecodeEmitter::SelfHosting); + ctor = &constructor->as().method(); + } + + bool needsHomeObject = ctor->funbox()->needsHomeObject(); + // HERITAGE is consumed inside emitFunction. + if (nameKind == ClassNameKind::InferredName) { + if (!setFunName(ctor->funbox(), nameForAnonymousClass)) { + return false; + } + } + if (!emitFunction(ctor, isDerived, classMembers)) { + // [stack] HOMEOBJ CTOR + return false; + } + if (lse.isSome()) { + if (!lse->emitEnd()) { + return false; + } + lse.reset(); + } + if (!ce.emitInitConstructor(needsHomeObject)) { + // [stack] CTOR HOMEOBJ + return false; + } + } else { + if (!ce.emitInitDefaultConstructor(classNode->pn_pos.begin, + classNode->pn_pos.end)) { + // [stack] CTOR HOMEOBJ + return false; + } + } + + if (!emitCreateFieldKeys(classMembers, FieldPlacement::Instance)) { + return false; + } + + if (!emitCreateMemberInitializers(ce, classMembers, FieldPlacement::Static)) { + return false; + } + + if (!emitCreateFieldKeys(classMembers, FieldPlacement::Static)) { + return false; + } + + if (!emitPropertyList(classMembers, ce, ClassBody)) { + // [stack] CTOR HOMEOBJ + return false; + } + + if (!ce.emitBinding()) { + // [stack] CTOR + return false; + } + + if (!emitInitializeStaticFields(classMembers)) { + // [stack] CTOR + return false; + } + + if (!ce.emitEnd(kind)) { + // [stack] # class declaration + // [stack] + // [stack] # class expression + // [stack] CTOR + return false; + } + + return true; +} + +bool BytecodeEmitter::emitExportDefault(BinaryNode* exportNode) { + MOZ_ASSERT(exportNode->isKind(ParseNodeKind::ExportDefaultStmt)); + + ParseNode* valueNode = exportNode->left(); + if (valueNode->isDirectRHSAnonFunction()) { + MOZ_ASSERT(exportNode->right()); + + if (!emitAnonymousFunctionWithName(valueNode, cx->parserNames().default_)) { + return false; + } + } else { + if (!emitTree(valueNode)) { + return false; + } + } + + if (ParseNode* binding = exportNode->right()) { + if (!emitLexicalInitialization(&binding->as())) { + return false; + } + + if (!emit1(JSOp::Pop)) { + return false; + } + } + + return true; +} + +MOZ_NEVER_INLINE bool BytecodeEmitter::emitInstrumentationSlow( + InstrumentationKind kind, + const std::function& pushOperandsCallback) { + MOZ_ASSERT(instrumentationKinds); + + if (!(instrumentationKinds & (uint32_t)kind)) { + return true; + } + + // Instrumentation is emitted in the form of a call to the realm's + // instrumentation callback, guarded by a test of whether instrumentation is + // currently active in the realm. The callback is invoked with the kind of + // operation which is executing, the current script's instrumentation ID, + // and the offset of the bytecode location after the instrumentation. Some + // operation kinds have more arguments, which will be pushed by + // pushOperandsCallback. + + unsigned initialDepth = bytecodeSection().stackDepth(); + InternalIfEmitter ifEmitter(this); + + if (!emit1(JSOp::InstrumentationActive)) { + return false; + } + // [stack] ACTIVE + + if (!ifEmitter.emitThen()) { + return false; + } + // [stack] + + // Push the instrumentation callback for the current realm as the callee. + if (!emit1(JSOp::InstrumentationCallback)) { + return false; + } + // [stack] CALLBACK + + // Push undefined for the call's |this| value. + if (!emit1(JSOp::Undefined)) { + return false; + } + // [stack] CALLBACK UNDEFINED + + const ParserAtom* atom = RealmInstrumentation::getInstrumentationKindName( + cx, compilationState.parserAtoms, kind); + if (!atom) { + return false; + } + + if (!emitAtomOp(JSOp::String, atom)) { + return false; + } + // [stack] CALLBACK UNDEFINED KIND + + if (!emit1(JSOp::InstrumentationScriptId)) { + return false; + } + // [stack] CALLBACK UNDEFINED KIND SCRIPT + + // Push the offset of the bytecode location following the instrumentation. + BytecodeOffset updateOffset; + if (!emitN(JSOp::Int32, 4, &updateOffset)) { + return false; + } + // [stack] CALLBACK UNDEFINED KIND SCRIPT OFFSET + + unsigned numPushed = bytecodeSection().stackDepth() - initialDepth; + + if (pushOperandsCallback && !pushOperandsCallback(numPushed)) { + return false; + } + // [stack] CALLBACK UNDEFINED KIND SCRIPT OFFSET ...EXTRA_ARGS + + unsigned argc = bytecodeSection().stackDepth() - initialDepth - 2; + if (!emitCall(JSOp::CallIgnoresRv, argc)) { + return false; + } + // [stack] RV + + if (!emit1(JSOp::Pop)) { + return false; + } + // [stack] + + if (!ifEmitter.emitEnd()) { + return false; + } + + SET_INT32(bytecodeSection().code(updateOffset), + bytecodeSection().code().length()); + + return true; +} + +MOZ_NEVER_INLINE bool BytecodeEmitter::emitInstrumentationForOpcodeSlow( + JSOp op, GCThingIndex atomIndex) { + MOZ_ASSERT(instrumentationKinds); + + switch (op) { + case JSOp::GetProp: + return emitInstrumentationSlow( + InstrumentationKind::GetProperty, [=](uint32_t pushed) { + return emitDupAt(pushed) && emitAtomOp(JSOp::String, atomIndex); + }); + case JSOp::SetProp: + case JSOp::StrictSetProp: + return emitInstrumentationSlow( + InstrumentationKind::SetProperty, [=](uint32_t pushed) { + return emitDupAt(pushed + 1) && + emitAtomOp(JSOp::String, atomIndex) && emitDupAt(pushed + 2); + }); + case JSOp::GetElem: + return emitInstrumentationSlow( + InstrumentationKind::GetElement, + [=](uint32_t pushed) { return emitDupAt(pushed + 1, 2); }); + case JSOp::SetElem: + case JSOp::StrictSetElem: + return emitInstrumentationSlow( + InstrumentationKind::SetElement, + [=](uint32_t pushed) { return emitDupAt(pushed + 2, 3); }); + default: + return true; + } +} + +bool BytecodeEmitter::emitTree( + ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::WantValue */, + EmitLineNumberNote emitLineNote /* = EMIT_LINENOTE */) { + if (!CheckRecursionLimit(cx)) { + return false; + } + + /* Emit notes to tell the current bytecode's source line number. + However, a couple trees require special treatment; see the + relevant emitter functions for details. */ + if (emitLineNote == EMIT_LINENOTE && + !ParseNodeRequiresSpecialLineNumberNotes(pn)) { + if (!updateLineNumberNotes(pn->pn_pos.begin)) { + return false; + } + } + + switch (pn->getKind()) { + case ParseNodeKind::Function: + if (!emitFunction(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::ParamsBody: + MOZ_ASSERT_UNREACHABLE( + "ParamsBody should be handled in emitFunctionScript."); + break; + + case ParseNodeKind::IfStmt: + if (!emitIf(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::SwitchStmt: + if (!emitSwitch(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::WhileStmt: + if (!emitWhile(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::DoWhileStmt: + if (!emitDo(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::ForStmt: + if (!emitFor(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::BreakStmt: + // Ensure that the column of the 'break' is set properly. + if (!updateSourceCoordNotes(pn->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + + if (!emitBreak(pn->as().label())) { + return false; + } + break; + + case ParseNodeKind::ContinueStmt: + // Ensure that the column of the 'continue' is set properly. + if (!updateSourceCoordNotes(pn->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + + if (!emitContinue(pn->as().label())) { + return false; + } + break; + + case ParseNodeKind::WithStmt: + if (!emitWith(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::TryStmt: + if (!emitTry(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::Catch: + if (!emitCatch(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::VarStmt: + if (!emitDeclarationList(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::ReturnStmt: + if (!emitReturn(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::YieldStarExpr: + if (!emitYieldStar(pn->as().kid())) { + return false; + } + break; + + case ParseNodeKind::Generator: + if (!emit1(JSOp::Generator)) { + return false; + } + break; + + case ParseNodeKind::InitialYield: + if (!emitInitialYield(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::YieldExpr: + if (!emitYield(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::AwaitExpr: + if (!emitAwaitInInnermostScope(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::StatementList: + if (!emitStatementList(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::EmptyStmt: + break; + + case ParseNodeKind::ExpressionStmt: + if (!emitExpressionStatement(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::LabelStmt: + if (!emitLabeledStatement(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::CommaExpr: + if (!emitSequenceExpr(&pn->as(), valueUsage)) { + return false; + } + break; + + case ParseNodeKind::InitExpr: + case ParseNodeKind::AssignExpr: + case ParseNodeKind::AddAssignExpr: + case ParseNodeKind::SubAssignExpr: + case ParseNodeKind::BitOrAssignExpr: + case ParseNodeKind::BitXorAssignExpr: + case ParseNodeKind::BitAndAssignExpr: + case ParseNodeKind::LshAssignExpr: + case ParseNodeKind::RshAssignExpr: + case ParseNodeKind::UrshAssignExpr: + case ParseNodeKind::MulAssignExpr: + case ParseNodeKind::DivAssignExpr: + case ParseNodeKind::ModAssignExpr: + case ParseNodeKind::PowAssignExpr: { + BinaryNode* assignNode = &pn->as(); + if (!emitAssignmentOrInit(assignNode->getKind(), assignNode->left(), + assignNode->right())) { + return false; + } + break; + } + + case ParseNodeKind::CoalesceAssignExpr: + case ParseNodeKind::OrAssignExpr: + case ParseNodeKind::AndAssignExpr: + if (!emitShortCircuitAssignment(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::ConditionalExpr: + if (!emitConditionalExpression(pn->as(), + valueUsage)) { + return false; + } + break; + + case ParseNodeKind::OrExpr: + case ParseNodeKind::CoalesceExpr: + case ParseNodeKind::AndExpr: + if (!emitShortCircuit(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::AddExpr: + case ParseNodeKind::SubExpr: + case ParseNodeKind::BitOrExpr: + case ParseNodeKind::BitXorExpr: + case ParseNodeKind::BitAndExpr: + case ParseNodeKind::StrictEqExpr: + case ParseNodeKind::EqExpr: + case ParseNodeKind::StrictNeExpr: + case ParseNodeKind::NeExpr: + case ParseNodeKind::LtExpr: + case ParseNodeKind::LeExpr: + case ParseNodeKind::GtExpr: + case ParseNodeKind::GeExpr: + case ParseNodeKind::InExpr: + case ParseNodeKind::InstanceOfExpr: + case ParseNodeKind::LshExpr: + case ParseNodeKind::RshExpr: + case ParseNodeKind::UrshExpr: + case ParseNodeKind::MulExpr: + case ParseNodeKind::DivExpr: + case ParseNodeKind::ModExpr: + if (!emitLeftAssociative(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::PowExpr: + if (!emitRightAssociative(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::PipelineExpr: + if (!emitPipeline(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::TypeOfNameExpr: + if (!emitTypeof(&pn->as(), JSOp::Typeof)) { + return false; + } + break; + + case ParseNodeKind::TypeOfExpr: + if (!emitTypeof(&pn->as(), JSOp::TypeofExpr)) { + return false; + } + break; + + case ParseNodeKind::ThrowStmt: + if (!updateSourceCoordNotes(pn->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + [[fallthrough]]; + case ParseNodeKind::VoidExpr: + case ParseNodeKind::NotExpr: + case ParseNodeKind::BitNotExpr: + case ParseNodeKind::PosExpr: + case ParseNodeKind::NegExpr: + if (!emitUnary(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::PreIncrementExpr: + case ParseNodeKind::PreDecrementExpr: + case ParseNodeKind::PostIncrementExpr: + case ParseNodeKind::PostDecrementExpr: + if (!emitIncOrDec(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::DeleteNameExpr: + if (!emitDeleteName(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::DeletePropExpr: + if (!emitDeleteProperty(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::DeleteElemExpr: + if (!emitDeleteElement(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::DeleteExpr: + if (!emitDeleteExpression(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::DeleteOptionalChainExpr: + if (!emitDeleteOptionalChain(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::OptionalChain: + if (!emitOptionalChain(&pn->as(), valueUsage)) { + return false; + } + break; + + case ParseNodeKind::DotExpr: { + 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)) { + // [stack] THIS + return false; + } + } else { + if (!emitPropLHS(prop)) { + // [stack] OBJ + return false; + } + } + if (!poe.emitGet(prop->key().atom())) { + // [stack] PROP + return false; + } + break; + } + + case ParseNodeKind::ElemExpr: { + PropertyByValue* elem = &pn->as(); + bool isSuper = elem->isSuper(); + bool isPrivate = elem->key().isKind(ParseNodeKind::PrivateName); + ElemOpEmitter eoe( + this, ElemOpEmitter::Kind::Get, + isSuper ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other, + isPrivate ? NameVisibility::Private : NameVisibility::Public); + if (!emitElemObjAndKey(elem, isSuper, eoe)) { + // [stack] # if Super + // [stack] THIS KEY + // [stack] # otherwise + // [stack] OBJ KEY + return false; + } + if (!eoe.emitGet()) { + // [stack] ELEM + return false; + } + + break; + } + + case ParseNodeKind::NewExpr: + case ParseNodeKind::TaggedTemplateExpr: + case ParseNodeKind::CallExpr: + case ParseNodeKind::SuperCallExpr: + if (!emitCallOrNew(&pn->as(), valueUsage)) { + return false; + } + break; + + case ParseNodeKind::LexicalScope: + if (!emitLexicalScope(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::ConstDecl: + case ParseNodeKind::LetDecl: + if (!emitDeclarationList(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::ImportDecl: + MOZ_ASSERT(sc->isModuleContext()); + break; + + case ParseNodeKind::ExportStmt: { + MOZ_ASSERT(sc->isModuleContext()); + UnaryNode* node = &pn->as(); + ParseNode* decl = node->kid(); + if (decl->getKind() != ParseNodeKind::ExportSpecList) { + if (!emitTree(decl)) { + return false; + } + } + break; + } + + case ParseNodeKind::ExportDefaultStmt: + MOZ_ASSERT(sc->isModuleContext()); + if (!emitExportDefault(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::ExportFromStmt: + MOZ_ASSERT(sc->isModuleContext()); + break; + + case ParseNodeKind::CallSiteObj: + if (!emitCallSiteObject(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::ArrayExpr: + if (!emitArrayLiteral(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::ObjectExpr: + if (!emitObject(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::Name: + if (!emitGetName(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::PrivateName: + if (!emitGetPrivateName(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::TemplateStringListExpr: + if (!emitTemplateString(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::TemplateStringExpr: + case ParseNodeKind::StringExpr: + if (!emitAtomOp(JSOp::String, pn->as().atom())) { + return false; + } + break; + + case ParseNodeKind::NumberExpr: + if (!emitNumberOp(pn->as().value())) { + return false; + } + break; + + case ParseNodeKind::BigIntExpr: + if (!emitBigIntOp(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::RegExpExpr: { + GCThingIndex index; + if (!perScriptData().gcThingList().append(&pn->as(), + &index)) { + return false; + } + if (!emitRegExp(index)) { + return false; + } + break; + } + + case ParseNodeKind::TrueExpr: + if (!emit1(JSOp::True)) { + return false; + } + break; + case ParseNodeKind::FalseExpr: + if (!emit1(JSOp::False)) { + return false; + } + break; + case ParseNodeKind::NullExpr: + if (!emit1(JSOp::Null)) { + return false; + } + break; + case ParseNodeKind::RawUndefinedExpr: + if (!emit1(JSOp::Undefined)) { + return false; + } + break; + + case ParseNodeKind::ThisExpr: + if (!emitThisLiteral(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::DebuggerStmt: + if (!updateSourceCoordNotes(pn->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emit1(JSOp::Debugger)) { + return false; + } + break; + + case ParseNodeKind::ClassDecl: + if (!emitClass(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::NewTargetExpr: + if (!emit1(JSOp::NewTarget)) { + return false; + } + break; + + case ParseNodeKind::ImportMetaExpr: + if (!emit1(JSOp::ImportMeta)) { + return false; + } + break; + + case ParseNodeKind::CallImportExpr: + if (!emitTree(pn->as().right()) || + !emit1(JSOp::DynamicImport)) { + return false; + } + break; + + case ParseNodeKind::SetThis: + if (!emitSetThis(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::PropertyNameExpr: + case ParseNodeKind::PosHolder: + MOZ_FALLTHROUGH_ASSERT( + "Should never try to emit ParseNodeKind::PosHolder or ::Property"); + + default: + MOZ_ASSERT(0); + } + + return true; +} + +static bool AllocSrcNote(JSContext* cx, SrcNotesVector& notes, unsigned size, + unsigned* index) { + size_t oldLength = notes.length(); + + if (MOZ_UNLIKELY(oldLength + size > MaxSrcNotesLength)) { + ReportAllocationOverflow(cx); + return false; + } + + if (!notes.growByUninitialized(size)) { + return false; + } + + *index = oldLength; + return true; +} + +bool BytecodeEmitter::addTryNote(TryNoteKind kind, uint32_t stackDepth, + BytecodeOffset start, BytecodeOffset end) { + MOZ_ASSERT(!inPrologue()); + return bytecodeSection().tryNoteList().append(kind, stackDepth, start, end); +} + +bool BytecodeEmitter::newSrcNote(SrcNoteType type, unsigned* indexp) { + // Non-gettable source notes such as column/lineno and debugger should not be + // emitted for prologue / self-hosted. + MOZ_ASSERT_IF(skipLocationSrcNotes() || skipBreakpointSrcNotes(), + type <= SrcNoteType::LastGettable); + + SrcNotesVector& notes = bytecodeSection().notes(); + unsigned index; + + /* + * Compute delta from the last annotated bytecode's offset. If it's too + * big to fit in sn, allocate one or more xdelta notes and reset sn. + */ + BytecodeOffset offset = bytecodeSection().offset(); + ptrdiff_t delta = (offset - bytecodeSection().lastNoteOffset()).value(); + bytecodeSection().setLastNoteOffset(offset); + + auto allocator = [&](unsigned size) -> SrcNote* { + if (!AllocSrcNote(cx, notes, size, &index)) { + return nullptr; + } + return ¬es[index]; + }; + + if (!SrcNoteWriter::writeNote(type, delta, allocator)) { + return false; + } + + if (indexp) { + *indexp = index; + } + return true; +} + +bool BytecodeEmitter::newSrcNote2(SrcNoteType type, ptrdiff_t offset, + unsigned* indexp) { + unsigned index; + if (!newSrcNote(type, &index)) { + return false; + } + if (!newSrcNoteOperand(offset)) { + return false; + } + if (indexp) { + *indexp = index; + } + return true; +} + +bool BytecodeEmitter::newSrcNoteOperand(ptrdiff_t operand) { + if (!SrcNote::isRepresentableOperand(operand)) { + reportError(nullptr, JSMSG_NEED_DIET, js_script_str); + return false; + } + + SrcNotesVector& notes = bytecodeSection().notes(); + + auto allocator = [&](unsigned size) -> SrcNote* { + unsigned index; + if (!AllocSrcNote(cx, notes, size, &index)) { + return nullptr; + } + return ¬es[index]; + }; + + return SrcNoteWriter::writeOperand(operand, allocator); +} + +bool BytecodeEmitter::intoScriptStencil(ScriptIndex scriptIndex) { + js::UniquePtr immutableScriptData = + createImmutableScriptData(cx); + if (!immutableScriptData) { + return false; + } + + MOZ_ASSERT(outermostScope().hasOnChain(ScopeKind::NonSyntactic) == + sc->hasNonSyntacticScope()); + + auto& things = perScriptData().gcThingList().objects(); + if (!compilationState.appendGCThings(cx, scriptIndex, things)) { + return false; + } + + // Hand over the ImmutableScriptData instance generated by BCE. + auto* sharedData = + SharedImmutableScriptData::createWith(cx, std::move(immutableScriptData)); + if (!sharedData) { + return false; + } + + // De-duplicate the bytecode within the runtime. + if (!stencil.sharedData.addAndShare(cx, scriptIndex, sharedData)) { + return false; + } + + ScriptStencil& script = compilationState.scriptData[scriptIndex]; + script.setHasSharedData(); + + // Update flags specific to functions. + if (sc->isFunctionBox()) { + FunctionBox* funbox = sc->asFunctionBox(); + MOZ_ASSERT(&script == &funbox->functionStencil()); + funbox->copyUpdatedImmutableFlags(); + MOZ_ASSERT(script.isFunction()); + } else { + ScriptStencilExtra& scriptExtra = compilationState.scriptExtra[scriptIndex]; + sc->copyScriptFields(script); + sc->copyScriptExtraFields(scriptExtra); + } + + return true; +} + +bool BytecodeEmitter::allowSelfHostedIter(ParseNode* parseNode) { + return emitterMode == BytecodeEmitter::SelfHosting && + parseNode->isKind(ParseNodeKind::CallExpr) && + parseNode->as().left()->isName( + cx->parserNames().allowContentIter); +} diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h new file mode 100644 index 0000000000..d8576f59fe --- /dev/null +++ b/js/src/frontend/BytecodeEmitter.h @@ -0,0 +1,920 @@ +/* -*- 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/. */ + +/* JS bytecode generation. */ + +#ifndef frontend_BytecodeEmitter_h +#define frontend_BytecodeEmitter_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS, MOZ_MUST_USE, MOZ_ALWAYS_INLINE, MOZ_NEVER_INLINE, MOZ_RAII +#include "mozilla/Maybe.h" // mozilla::Maybe, mozilla::Some +#include "mozilla/Span.h" // mozilla::Span +#include "mozilla/Vector.h" // mozilla::Vector + +#include // std::function +#include // ptrdiff_t +#include // uint16_t, uint32_t + +#include "jsapi.h" // CompletionKind + +#include "frontend/AbstractScopePtr.h" // ScopeIndex +#include "frontend/BCEParserHandle.h" // BCEParserHandle +#include "frontend/BytecodeControlStructures.h" // NestableControl +#include "frontend/BytecodeOffset.h" // BytecodeOffset +#include "frontend/BytecodeSection.h" // BytecodeSection, PerScriptData, CGScopeList +#include "frontend/DestructuringFlavor.h" // DestructuringFlavor +#include "frontend/EitherParser.h" // EitherParser +#include "frontend/ErrorReporter.h" // ErrorReporter +#include "frontend/FullParseHandler.h" // FullParseHandler +#include "frontend/IteratorKind.h" // IteratorKind +#include "frontend/JumpList.h" // JumpList, JumpTarget +#include "frontend/NameAnalysisTypes.h" // NameLocation +#include "frontend/NameCollections.h" // AtomIndexMap +#include "frontend/ParseNode.h" // ParseNode and subclasses +#include "frontend/Parser.h" // Parser, PropListType +#include "frontend/ScriptIndex.h" // ScriptIndex +#include "frontend/SharedContext.h" // SharedContext, TopLevelFunction +#include "frontend/SourceNotes.h" // SrcNoteType +#include "frontend/TokenStream.h" // TokenPos +#include "frontend/ValueUsage.h" // ValueUsage +#include "js/RootingAPI.h" // JS::Rooted, JS::Handle +#include "js/TypeDecls.h" // jsbytecode +#include "vm/BuiltinObjectKind.h" // BuiltinObjectKind +#include "vm/BytecodeUtil.h" // JSOp +#include "vm/CheckIsObjectKind.h" // CheckIsObjectKind +#include "vm/FunctionPrefixKind.h" // FunctionPrefixKind +#include "vm/GeneratorResumeKind.h" // GeneratorResumeKind +#include "vm/Instrumentation.h" // InstrumentationKind +#include "vm/JSFunction.h" // JSFunction +#include "vm/JSScript.h" // JSScript, BaseScript, MemberInitializers +#include "vm/Runtime.h" // ReportOutOfMemory +#include "vm/SharedStencil.h" // GCThingIndex +#include "vm/StencilEnums.h" // TryNoteKind +#include "vm/StringType.h" // JSAtom +#include "vm/ThrowMsgKind.h" // ThrowMsgKind, ThrowCondition + +namespace js { +namespace frontend { + +class CallOrNewEmitter; +class ClassEmitter; +class ElemOpEmitter; +class EmitterScope; +class NestableControl; +class PropertyEmitter; +class PropOpEmitter; +class OptionalEmitter; +class TDZCheckCache; +class TryEmitter; +class ScriptStencil; + +enum class ValueIsOnStack { Yes, No }; + +struct MOZ_STACK_CLASS BytecodeEmitter { + // Context shared between parsing and bytecode generation. + SharedContext* const sc = nullptr; + + JSContext* const cx = nullptr; + + // Enclosing function or global context. + BytecodeEmitter* const parent = nullptr; + + BytecodeSection bytecodeSection_; + + public: + BytecodeSection& bytecodeSection() { return bytecodeSection_; } + const BytecodeSection& bytecodeSection() const { return bytecodeSection_; } + + private: + PerScriptData perScriptData_; + + public: + PerScriptData& perScriptData() { return perScriptData_; } + const PerScriptData& perScriptData() const { return perScriptData_; } + + private: + // switchToMain sets this to the bytecode offset of the main section. + mozilla::Maybe mainOffset_ = {}; + + public: + // Private storage for parser wrapper. DO NOT REFERENCE INTERNALLY. May not be + // initialized. Use |parser| instead. + mozilla::Maybe ep_ = {}; + BCEParserHandle* parser = nullptr; + + CompilationStencil& stencil; + CompilationState& compilationState; + + uint32_t maxFixedSlots = 0; /* maximum number of fixed frame slots so far */ + + // Index into scopeList of the body scope. + GCThingIndex bodyScopeIndex = ScopeNote::NoScopeIndex; + + EmitterScope* varEmitterScope = nullptr; + NestableControl* innermostNestableControl = nullptr; + EmitterScope* innermostEmitterScope_ = nullptr; + TDZCheckCache* innermostTDZCheckCache = nullptr; + +#ifdef DEBUG + bool unstableEmitterScope = false; + + friend class AutoCheckUnstableEmitterScope; +#endif + + EmitterScope* innermostEmitterScope() const { + MOZ_ASSERT(!unstableEmitterScope); + return innermostEmitterScopeNoCheck(); + } + EmitterScope* innermostEmitterScopeNoCheck() const { + return innermostEmitterScope_; + } + + // Script contains finally block. + bool hasTryFinally = false; + + enum EmitterMode { + Normal, + + // Emit JSOp::GetIntrinsic instead of JSOp::GetName and assert that + // JSOp::GetName and JSOp::*GName don't ever get emitted. See the comment + // for the field |selfHostingMode| in Parser.h for details. + SelfHosting, + + // Check the static scope chain of the root function for resolving free + // variable accesses in the script. + LazyFunction + }; + + const EmitterMode emitterMode = Normal; + + mozilla::Maybe scriptStartOffset = {}; + + // The end location of a function body that is being emitted. + mozilla::Maybe functionBodyEndPos = {}; + + // Mask of operation kinds which need instrumentation. This is obtained from + // the compile options and copied here for efficiency. + uint32_t instrumentationKinds = 0; + + /* + * Note that BytecodeEmitters are magic: they own the arena "top-of-stack" + * space above their tempMark points. This means that you cannot alloc from + * tempLifoAlloc and save the pointer beyond the next BytecodeEmitter + * destruction. + */ + private: + // Internal constructor, for delegation use only. + BytecodeEmitter(BytecodeEmitter* parent, SharedContext* sc, + CompilationStencil& stencil, + CompilationState& compilationState, EmitterMode emitterMode); + + void initFromBodyPosition(TokenPos bodyPosition); + + /* + * Helper for reporting that we have insufficient args. pluralizer + * should be "s" if requiredArgs is anything other than "1" and "" + * if requiredArgs is "1". + */ + void reportNeedMoreArgsError(ParseNode* pn, const char* errorName, + const char* requiredArgs, const char* pluralizer, + const ListNode* argsList); + + public: + BytecodeEmitter(BytecodeEmitter* parent, BCEParserHandle* handle, + SharedContext* sc, CompilationStencil& stencil, + CompilationState& compilationState, + EmitterMode emitterMode = Normal); + + BytecodeEmitter(BytecodeEmitter* parent, const EitherParser& parser, + SharedContext* sc, CompilationStencil& stencil, + CompilationState& compilationState, + EmitterMode emitterMode = Normal); + + template + BytecodeEmitter(BytecodeEmitter* parent, + Parser* parser, SharedContext* sc, + CompilationStencil& stencil, + CompilationState& compilationState, + EmitterMode emitterMode = Normal) + : BytecodeEmitter(parent, EitherParser(parser), sc, stencil, + compilationState, emitterMode) {} + + MOZ_MUST_USE bool init(); + MOZ_MUST_USE bool init(TokenPos bodyPosition); + + template + T* findInnermostNestableControl() const; + + template bool */> + T* findInnermostNestableControl(Predicate predicate) const; + + NameLocation lookupName(const ParserAtom* name); + + // To implement Annex B and the formal parameter defaults scope semantics + // requires accessing names that would otherwise be shadowed. This method + // returns the access location of a name that is known to be bound in a + // target scope. + mozilla::Maybe locationOfNameBoundInScope( + const ParserAtom* name, EmitterScope* target); + + // Get the location of a name known to be bound in a given scope, + // starting at the source scope. + template + mozilla::Maybe locationOfNameBoundInScopeType( + const ParserAtom* name, EmitterScope* source); + + // Get the location of a name known to be bound in the function scope, + // starting at the source scope. + mozilla::Maybe locationOfNameBoundInFunctionScope( + const ParserAtom* name) { + return locationOfNameBoundInScopeType( + name, innermostEmitterScope()); + } + + void setVarEmitterScope(EmitterScope* emitterScope) { + MOZ_ASSERT(emitterScope); + MOZ_ASSERT(!varEmitterScope); + varEmitterScope = emitterScope; + } + + AbstractScopePtr outermostScope() const { + return perScriptData().gcThingList().firstScope(); + } + AbstractScopePtr innermostScope() const; + ScopeIndex innermostScopeIndex() const; + + MOZ_ALWAYS_INLINE MOZ_MUST_USE bool makeAtomIndex(const ParserAtom* atom, + GCThingIndex* indexp) { + MOZ_ASSERT(perScriptData().atomIndices()); + AtomIndexMap::AddPtr p = perScriptData().atomIndices()->lookupForAdd(atom); + if (p) { + *indexp = GCThingIndex(p->value()); + return true; + } + + GCThingIndex index; + if (!perScriptData().gcThingList().append(atom, &index)) { + return false; + } + + // `atomIndices()` uses uint32_t instead of GCThingIndex, because + // GCThingIndex isn't trivial type. + if (!perScriptData().atomIndices()->add(p, atom, index.index)) { + ReportOutOfMemory(cx); + return false; + } + + *indexp = index; + return true; + } + + bool isInLoop(); + MOZ_MUST_USE bool checkSingletonContext(); + + bool needsImplicitThis(); + + size_t countThisEnvironmentHops(); + MOZ_MUST_USE bool emitThisEnvironmentCallee(); + MOZ_MUST_USE bool emitSuperBase(); + + uint32_t mainOffset() const { return *mainOffset_; } + + bool inPrologue() const { return mainOffset_.isNothing(); } + + MOZ_MUST_USE bool switchToMain() { + MOZ_ASSERT(inPrologue()); + mainOffset_.emplace(bytecodeSection().code().length()); + + return emitInstrumentation(InstrumentationKind::Main); + } + + void setFunctionBodyEndPos(uint32_t pos) { + functionBodyEndPos = mozilla::Some(pos); + } + + void setScriptStartOffsetIfUnset(uint32_t pos) { + if (scriptStartOffset.isNothing()) { + scriptStartOffset = mozilla::Some(pos); + } + } + + void reportError(ParseNode* pn, unsigned errorNumber, ...); + void reportError(const mozilla::Maybe& maybeOffset, + unsigned errorNumber, ...); + + // Fill in a ScriptStencil using this BCE data. + bool intoScriptStencil(ScriptIndex scriptIndex); + + // If pn contains a useful expression, return true with *answer set to true. + // If pn contains a useless expression, return true with *answer set to + // false. Return false on error. + // + // The caller should initialize *answer to false and invoke this function on + // an expression statement or similar subtree to decide whether the tree + // could produce code that has any side effects. For an expression + // statement, we define useless code as code with no side effects, because + // the main effect, the value left on the stack after the code executes, + // will be discarded by a pop bytecode. + MOZ_MUST_USE bool checkSideEffects(ParseNode* pn, bool* answer); + +#ifdef DEBUG + MOZ_MUST_USE bool checkStrictOrSloppy(JSOp op); +#endif + + // Add TryNote to the tryNoteList array. The start and end offset are + // relative to current section. + MOZ_MUST_USE bool addTryNote(TryNoteKind kind, uint32_t stackDepth, + BytecodeOffset start, BytecodeOffset end); + + // Indicates the emitter should not generate location or debugger source + // notes. This lets us avoid generating notes for non-user code. + bool skipLocationSrcNotes() const { + return inPrologue() || (emitterMode == EmitterMode::SelfHosting); + } + bool skipBreakpointSrcNotes() const { + return inPrologue() || (emitterMode == EmitterMode::SelfHosting); + } + + // Append a new source note of the given type (and therefore size) to the + // notes dynamic array, updating noteCount. Return the new note's index + // within the array pointed at by current->notes as outparam. + MOZ_MUST_USE bool newSrcNote(SrcNoteType type, unsigned* indexp = nullptr); + MOZ_MUST_USE bool newSrcNote2(SrcNoteType type, ptrdiff_t operand, + unsigned* indexp = nullptr); + + MOZ_MUST_USE bool newSrcNoteOperand(ptrdiff_t operand); + + // Control whether emitTree emits a line number note. + enum EmitLineNumberNote { EMIT_LINENOTE, SUPPRESS_LINENOTE }; + + // Emit code for the tree rooted at pn. + MOZ_MUST_USE bool emitTree(ParseNode* pn, + ValueUsage valueUsage = ValueUsage::WantValue, + EmitLineNumberNote emitLineNote = EMIT_LINENOTE); + + MOZ_MUST_USE bool emitOptionalTree( + ParseNode* pn, OptionalEmitter& oe, + ValueUsage valueUsage = ValueUsage::WantValue); + + MOZ_MUST_USE bool emitDeclarationInstantiation(ParseNode* body); + + // Emit global, eval, or module code for tree rooted at body. Always + // encompasses the entire source. + MOZ_MUST_USE bool emitScript(ParseNode* body); + + // Calculate the `nslots` value for BCEScriptStencil constructor parameter. + // Fails if it overflows. + MOZ_MUST_USE bool getNslots(uint32_t* nslots); + + // Emit function code for the tree rooted at body. + MOZ_MUST_USE bool emitFunctionScript(FunctionNode* funNode); + + MOZ_MUST_USE bool markStepBreakpoint(); + MOZ_MUST_USE bool markSimpleBreakpoint(); + MOZ_MUST_USE bool updateLineNumberNotes(uint32_t offset); + MOZ_MUST_USE bool updateSourceCoordNotes(uint32_t offset); + + JSOp strictifySetNameOp(JSOp op); + + MOZ_MUST_USE bool emitCheck(JSOp op, ptrdiff_t delta, BytecodeOffset* offset); + + // Emit one bytecode. + MOZ_MUST_USE bool emit1(JSOp op); + + // Emit two bytecodes, an opcode (op) with a byte of immediate operand + // (op1). + MOZ_MUST_USE bool emit2(JSOp op, uint8_t op1); + + // Emit three bytecodes, an opcode with two bytes of immediate operands. + MOZ_MUST_USE bool emit3(JSOp op, jsbytecode op1, jsbytecode op2); + + // 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 theiro 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); + + // Helper to emit JSOp::BuiltinObject. + MOZ_MUST_USE bool emitBuiltinObject(BuiltinObjectKind kind); + + // Push whether the value atop of the stack is non-undefined and non-null. + MOZ_MUST_USE bool emitPushNotUndefinedOrNull(); + + // Emit a bytecode followed by an uint16 immediate operand stored in + // big-endian order. + MOZ_MUST_USE bool emitUint16Operand(JSOp op, uint32_t operand); + + // Emit a bytecode followed by an uint32 immediate operand. + MOZ_MUST_USE bool emitUint32Operand(JSOp op, uint32_t operand); + + // Emit (1 + extra) bytecodes, for N bytes of op and its immediate operand. + MOZ_MUST_USE bool emitN(JSOp op, size_t extra, + BytecodeOffset* offset = nullptr); + + MOZ_MUST_USE bool emitDouble(double dval); + MOZ_MUST_USE bool emitNumberOp(double dval); + + MOZ_MUST_USE bool emitBigIntOp(BigIntLiteral* bigint); + + MOZ_MUST_USE bool emitThisLiteral(ThisLiteral* pn); + MOZ_MUST_USE bool emitGetFunctionThis(NameNode* thisName); + MOZ_MUST_USE bool emitGetFunctionThis(const mozilla::Maybe& offset); + MOZ_MUST_USE bool emitGetThisForSuperBase(UnaryNode* superBase); + MOZ_MUST_USE bool emitSetThis(BinaryNode* setThisNode); + MOZ_MUST_USE bool emitCheckDerivedClassConstructorReturn(); + + // Handle jump opcodes and jump targets. + MOZ_MUST_USE bool emitJumpTargetOp(JSOp op, BytecodeOffset* off); + MOZ_MUST_USE bool emitJumpTarget(JumpTarget* target); + MOZ_MUST_USE bool emitJumpNoFallthrough(JSOp op, JumpList* jump); + MOZ_MUST_USE bool emitJump(JSOp op, JumpList* jump); + 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(UnaryNode* incDec); + + mozilla::Maybe getOffsetForLoop(ParseNode* nextpn); + + enum class GotoKind { Break, Continue }; + MOZ_MUST_USE bool emitGoto(NestableControl* target, JumpList* jumplist, + GotoKind kind); + + MOZ_MUST_USE bool emitGCIndexOp(JSOp op, GCThingIndex index); + + MOZ_MUST_USE bool emitAtomOp( + JSOp op, const ParserAtom* atom, + ShouldInstrument shouldInstrument = ShouldInstrument::No); + MOZ_MUST_USE bool emitAtomOp( + JSOp op, GCThingIndex atomIndex, + ShouldInstrument shouldInstrument = ShouldInstrument::No); + + MOZ_MUST_USE bool emitArrayLiteral(ListNode* array); + MOZ_MUST_USE bool emitArray(ParseNode* arrayHead, uint32_t count); + + MOZ_MUST_USE bool emitInternedScopeOp(GCThingIndex index, JSOp op); + MOZ_MUST_USE bool emitInternedObjectOp(GCThingIndex index, JSOp op); + MOZ_MUST_USE bool emitObjectPairOp(GCThingIndex index1, GCThingIndex index2, + JSOp op); + MOZ_MUST_USE bool emitRegExp(GCThingIndex index); + + MOZ_NEVER_INLINE MOZ_MUST_USE bool emitFunction( + FunctionNode* funNode, bool needsProto = false, + ListNode* classContentsIfConstructor = nullptr); + MOZ_NEVER_INLINE MOZ_MUST_USE bool emitObject(ListNode* objNode); + + MOZ_MUST_USE bool emitHoistedFunctionsInList(ListNode* stmtList); + + // Can we use the object-literal writer either in singleton-object mode (with + // values) or in template mode (field names only, no values) for the property + // list? + void isPropertyListObjLiteralCompatible(ListNode* obj, bool* withValues, + bool* withoutValues); + bool isArrayObjLiteralCompatible(ParseNode* arrayHead); + + MOZ_MUST_USE bool emitPropertyList(ListNode* obj, PropertyEmitter& pe, + PropListType type); + + MOZ_MUST_USE bool emitPropertyListObjLiteral(ListNode* obj, + ObjLiteralFlags flags, + bool useObjLiteralValues); + + MOZ_MUST_USE bool emitDestructuringRestExclusionSetObjLiteral( + ListNode* pattern); + + MOZ_MUST_USE bool emitObjLiteralArray(ParseNode* arrayHead); + + // Is a field value OBJLITERAL-compatible? + MOZ_MUST_USE bool isRHSObjLiteralCompatible(ParseNode* value); + + MOZ_MUST_USE bool emitObjLiteralValue(ObjLiteralWriter& writer, + ParseNode* value); + + enum class FieldPlacement { Instance, Static }; + mozilla::Maybe setupMemberInitializers( + ListNode* classMembers, FieldPlacement placement); + MOZ_MUST_USE bool emitCreateFieldKeys(ListNode* obj, + FieldPlacement placement); + MOZ_MUST_USE bool emitCreateMemberInitializers(ClassEmitter& ce, + ListNode* obj, + FieldPlacement placement); + const MemberInitializers& findMemberInitializersForCall(); + MOZ_MUST_USE bool emitInitializeInstanceMembers(); + MOZ_MUST_USE bool emitInitializeStaticFields(ListNode* classMembers); + + MOZ_MUST_USE bool emitPrivateMethodInitializers(ClassEmitter& ce, + ListNode* obj); + MOZ_MUST_USE bool emitPrivateMethodInitializer( + ClassEmitter& ce, ParseNode* prop, ParseNode* propName, + const ParserAtom* storedMethodAtom, AccessorType accessorType); + + // To catch accidental misuse, emitUint16Operand/emit3 assert that they are + // not used to unconditionally emit JSOp::GetLocal. Variable access should + // instead be emitted using EmitVarOp. In special cases, when the caller + // definitely knows that a given local slot is unaliased, this function may be + // used as a non-asserting version of emitUint16Operand. + MOZ_MUST_USE bool emitLocalOp(JSOp op, uint32_t slot); + + MOZ_MUST_USE bool emitArgOp(JSOp op, uint16_t slot); + MOZ_MUST_USE bool emitEnvCoordOp(JSOp op, EnvironmentCoordinate ec); + + MOZ_MUST_USE bool emitGetNameAtLocation(const ParserAtom* name, + const NameLocation& loc); + MOZ_MUST_USE bool emitGetName(const ParserAtom* name) { + return emitGetNameAtLocation(name, lookupName(name)); + } + MOZ_MUST_USE bool emitGetName(NameNode* name); + MOZ_MUST_USE bool emitGetPrivateName(NameNode* name); + MOZ_MUST_USE bool emitGetPrivateName(const ParserAtom* name); + + MOZ_MUST_USE bool emitTDZCheckIfNeeded(const ParserAtom* name, + const NameLocation& loc, + ValueIsOnStack isOnStack); + + MOZ_MUST_USE bool emitNameIncDec(UnaryNode* incDec); + + MOZ_MUST_USE bool emitDeclarationList(ListNode* declList); + MOZ_MUST_USE bool emitSingleDeclaration(ListNode* declList, NameNode* decl, + ParseNode* initializer); + MOZ_MUST_USE bool emitAssignmentRhs(ParseNode* rhs, + const ParserAtom* anonFunctionName); + MOZ_MUST_USE bool emitAssignmentRhs(uint8_t offset); + + MOZ_MUST_USE bool emitPrepareIteratorResult(); + MOZ_MUST_USE bool emitFinishIteratorResult(bool done); + MOZ_MUST_USE bool iteratorResultShape(GCThingIndex* outShape); + + // Convert and add `writer` data to stencil. + // Iff it suceeds, `outIndex` out parameter is initialized to the index of the + // object in GC things vector. + MOZ_MUST_USE bool addObjLiteralData(ObjLiteralWriter& writer, + GCThingIndex* outIndex); + + MOZ_MUST_USE bool emitGetDotGeneratorInInnermostScope() { + return emitGetDotGeneratorInScope(*innermostEmitterScope()); + } + MOZ_MUST_USE bool emitGetDotGeneratorInScope(EmitterScope& currentScope); + + MOZ_MUST_USE bool allocateResumeIndex(BytecodeOffset offset, + uint32_t* resumeIndex); + MOZ_MUST_USE bool allocateResumeIndexRange( + mozilla::Span offsets, uint32_t* firstResumeIndex); + + MOZ_MUST_USE bool emitInitialYield(UnaryNode* yieldNode); + MOZ_MUST_USE bool emitYield(UnaryNode* yieldNode); + MOZ_MUST_USE bool emitYieldOp(JSOp op); + MOZ_MUST_USE bool emitYieldStar(ParseNode* iter); + MOZ_MUST_USE bool emitAwaitInInnermostScope() { + return emitAwaitInScope(*innermostEmitterScope()); + } + MOZ_MUST_USE bool emitAwaitInInnermostScope(UnaryNode* awaitNode); + MOZ_MUST_USE bool emitAwaitInScope(EmitterScope& currentScope); + + MOZ_MUST_USE bool emitPushResumeKind(GeneratorResumeKind kind); + + MOZ_MUST_USE bool emitPropLHS(PropertyAccess* prop); + MOZ_MUST_USE bool emitPropIncDec(UnaryNode* incDec); + + MOZ_MUST_USE bool emitComputedPropertyName(UnaryNode* computedPropName); + + // 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, Call, IncDec, CompoundAssign, Ref }; + MOZ_MUST_USE bool emitElemOperands(PropertyByValue* elem, + EmitElemOption opts); + + MOZ_MUST_USE bool emitElemObjAndKey(PropertyByValue* elem, bool isSuper, + ElemOpEmitter& eoe); + MOZ_MUST_USE bool emitElemOpBase( + JSOp op, ShouldInstrument shouldInstrument = ShouldInstrument::No); + MOZ_MUST_USE bool emitElemOp(PropertyByValue* elem, JSOp op); + MOZ_MUST_USE bool emitElemIncDec(UnaryNode* incDec); + + MOZ_MUST_USE bool emitCatch(BinaryNode* catchClause); + MOZ_MUST_USE bool emitIf(TernaryNode* ifNode); + MOZ_MUST_USE bool emitWith(BinaryNode* withNode); + + MOZ_NEVER_INLINE MOZ_MUST_USE bool emitLabeledStatement( + const LabeledStatement* labeledStmt); + MOZ_NEVER_INLINE MOZ_MUST_USE bool emitLexicalScope( + LexicalScopeNode* lexicalScope); + MOZ_MUST_USE bool emitLexicalScopeBody( + ParseNode* body, EmitLineNumberNote emitLineNote = EMIT_LINENOTE); + MOZ_NEVER_INLINE MOZ_MUST_USE bool emitSwitch(SwitchStatement* switchStmt); + MOZ_NEVER_INLINE MOZ_MUST_USE bool emitTry(TryNode* tryNode); + + MOZ_MUST_USE bool emitGoSub(JumpList* jump); + + // emitDestructuringLHSRef emits the lhs expression's reference. + // If the lhs expression is object property |OBJ.prop|, it emits |OBJ|. + // If it's object element |OBJ[ELEM]|, it emits |OBJ| and |ELEM|. + // If there's nothing to evaluate for the reference, it emits nothing. + // |emitted| parameter receives the number of values pushed onto the stack. + MOZ_MUST_USE bool emitDestructuringLHSRef(ParseNode* target, size_t* emitted); + + // emitSetOrInitializeDestructuring assumes the lhs expression's reference + // and the to-be-destructured value has been pushed on the stack. It emits + // code to destructure a single lhs expression (either a name or a compound + // []/{} expression). + MOZ_MUST_USE bool emitSetOrInitializeDestructuring(ParseNode* target, + DestructuringFlavor flav); + + // emitDestructuringObjRestExclusionSet emits the property exclusion set + // for the rest-property in an object pattern. + MOZ_MUST_USE bool emitDestructuringObjRestExclusionSet(ListNode* pattern); + + // emitDestructuringOps assumes the to-be-destructured value has been + // pushed on the stack and emits code to destructure each part of a [] or + // {} lhs expression. + MOZ_MUST_USE bool emitDestructuringOps(ListNode* pattern, + DestructuringFlavor flav); + MOZ_MUST_USE bool emitDestructuringOpsArray(ListNode* pattern, + DestructuringFlavor flav); + MOZ_MUST_USE bool emitDestructuringOpsObject(ListNode* pattern, + DestructuringFlavor flav); + + enum class CopyOption { Filtered, Unfiltered }; + + // Calls either the |CopyDataProperties| or the + // |CopyDataPropertiesUnfiltered| intrinsic function, consumes three (or + // two in the latter case) elements from the stack. + MOZ_MUST_USE bool emitCopyDataProperties(CopyOption option); + + // emitIterator expects the iterable to already be on the stack. + // It will replace that stack value with the corresponding iterator + MOZ_MUST_USE bool emitIterator(); + + MOZ_MUST_USE bool emitAsyncIterator(); + + // Pops iterator from the top of the stack. Pushes the result of |.next()| + // onto the stack. + MOZ_MUST_USE bool emitIteratorNext( + const mozilla::Maybe& callSourceCoordOffset, + IteratorKind kind = IteratorKind::Sync, bool allowSelfHosted = false); + MOZ_MUST_USE bool emitIteratorCloseInScope( + EmitterScope& currentScope, IteratorKind iterKind = IteratorKind::Sync, + CompletionKind completionKind = CompletionKind::Normal, + bool allowSelfHosted = false); + MOZ_MUST_USE bool emitIteratorCloseInInnermostScope( + IteratorKind iterKind = IteratorKind::Sync, + CompletionKind completionKind = CompletionKind::Normal, + bool allowSelfHosted = false) { + return emitIteratorCloseInScope(*innermostEmitterScope(), iterKind, + completionKind, allowSelfHosted); + } + + template + MOZ_MUST_USE bool wrapWithDestructuringTryNote(int32_t iterDepth, + InnerEmitter emitter); + + MOZ_MUST_USE bool defineHoistedTopLevelFunctions(ParseNode* body); + + // Check if the value on top of the stack is "undefined". If so, replace + // that value on the stack with the value defined by |defaultExpr|. + // |pattern| is a lhs node of the default expression. If it's an + // identifier and |defaultExpr| is an anonymous function, |SetFunctionName| + // is called at compile time. + MOZ_MUST_USE bool emitDefault(ParseNode* defaultExpr, ParseNode* pattern); + + MOZ_MUST_USE bool emitAnonymousFunctionWithName(ParseNode* node, + const ParserAtom* name); + + MOZ_MUST_USE bool emitAnonymousFunctionWithComputedName( + ParseNode* node, FunctionPrefixKind prefixKind); + + MOZ_MUST_USE bool setFunName(FunctionBox* fun, const ParserAtom* name); + MOZ_MUST_USE bool emitInitializer(ParseNode* initializer, ParseNode* pattern); + + MOZ_MUST_USE bool emitCallSiteObjectArray(ListNode* cookedOrRaw, + GCThingIndex* outArrayIndex); + MOZ_MUST_USE bool emitCallSiteObject(CallSiteNode* callSiteObj); + MOZ_MUST_USE bool emitTemplateString(ListNode* templateString); + MOZ_MUST_USE bool emitAssignmentOrInit(ParseNodeKind kind, ParseNode* lhs, + ParseNode* rhs); + MOZ_MUST_USE bool emitShortCircuitAssignment(AssignmentNode* node); + + MOZ_MUST_USE bool emitReturn(UnaryNode* returnNode); + MOZ_MUST_USE bool emitExpressionStatement(UnaryNode* exprStmt); + MOZ_MUST_USE bool emitStatementList(ListNode* stmtList); + + MOZ_MUST_USE bool emitDeleteName(UnaryNode* deleteNode); + MOZ_MUST_USE bool emitDeleteProperty(UnaryNode* deleteNode); + MOZ_MUST_USE bool emitDeleteElement(UnaryNode* deleteNode); + MOZ_MUST_USE bool emitDeleteExpression(UnaryNode* deleteNode); + + // Optional methods which emit Optional Jump Target + MOZ_MUST_USE bool emitOptionalChain(UnaryNode* expr, ValueUsage valueUsage); + MOZ_MUST_USE bool emitCalleeAndThisForOptionalChain(UnaryNode* expr, + CallNode* callNode, + CallOrNewEmitter& cone); + MOZ_MUST_USE bool emitDeleteOptionalChain(UnaryNode* deleteNode); + + // 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* expr, + PropOpEmitter& poe, bool isSuper, + OptionalEmitter& oe); + MOZ_MUST_USE bool emitOptionalElemExpression(PropertyByValueBase* elem, + ElemOpEmitter& poe, bool isSuper, + OptionalEmitter& oe); + MOZ_MUST_USE bool emitOptionalCall(CallNode* callNode, OptionalEmitter& oe, + ValueUsage valueUsage); + MOZ_MUST_USE bool emitDeletePropertyInOptChain(PropertyAccessBase* propExpr, + OptionalEmitter& oe); + MOZ_MUST_USE bool emitDeleteElementInOptChain(PropertyByValueBase* elemExpr, + OptionalEmitter& oe); + + // |op| must be JSOp::Typeof or JSOp::TypeofExpr. + MOZ_MUST_USE bool emitTypeof(UnaryNode* typeofNode, JSOp op); + + MOZ_MUST_USE bool emitUnary(UnaryNode* unaryNode); + MOZ_MUST_USE bool emitRightAssociative(ListNode* node); + MOZ_MUST_USE bool emitLeftAssociative(ListNode* node); + MOZ_MUST_USE bool emitShortCircuit(ListNode* node); + MOZ_MUST_USE bool emitSequenceExpr( + ListNode* node, ValueUsage valueUsage = ValueUsage::WantValue); + + MOZ_NEVER_INLINE MOZ_MUST_USE bool emitIncOrDec(UnaryNode* incDec); + + MOZ_MUST_USE bool emitConditionalExpression( + ConditionalExpression& conditional, + ValueUsage valueUsage = ValueUsage::WantValue); + + bool isRestParameter(ParseNode* expr); + + MOZ_MUST_USE ParseNode* getCoordNode(ParseNode* callNode, + ParseNode* calleeNode, JSOp op, + ListNode* argsList); + + MOZ_MUST_USE bool emitArguments(ListNode* argsList, bool isCall, + bool isSpread, CallOrNewEmitter& cone); + MOZ_MUST_USE bool emitCallOrNew( + CallNode* callNode, ValueUsage valueUsage = ValueUsage::WantValue); + MOZ_MUST_USE bool emitSelfHostedCallFunction(CallNode* callNode); + MOZ_MUST_USE bool emitSelfHostedResumeGenerator(BinaryNode* callNode); + MOZ_MUST_USE bool emitSelfHostedForceInterpreter(); + MOZ_MUST_USE bool emitSelfHostedAllowContentIter(BinaryNode* callNode); + MOZ_MUST_USE bool emitSelfHostedDefineDataProperty(BinaryNode* callNode); + MOZ_MUST_USE bool emitSelfHostedGetPropertySuper(BinaryNode* callNode); + MOZ_MUST_USE bool emitSelfHostedHasOwn(BinaryNode* callNode); + MOZ_MUST_USE bool emitSelfHostedToNumeric(BinaryNode* callNode); + MOZ_MUST_USE bool emitSelfHostedToString(BinaryNode* callNode); + MOZ_MUST_USE bool emitSelfHostedGetBuiltinConstructor(BinaryNode* callNode); + MOZ_MUST_USE bool emitSelfHostedGetBuiltinPrototype(BinaryNode* callNode); +#ifdef DEBUG + MOZ_MUST_USE bool checkSelfHostedUnsafeGetReservedSlot(BinaryNode* callNode); + MOZ_MUST_USE bool checkSelfHostedUnsafeSetReservedSlot(BinaryNode* callNode); +#endif + + MOZ_MUST_USE bool emitDo(BinaryNode* doNode); + MOZ_MUST_USE bool emitWhile(BinaryNode* whileNode); + + MOZ_MUST_USE bool emitFor( + ForNode* forNode, const EmitterScope* headLexicalEmitterScope = nullptr); + MOZ_MUST_USE bool emitCStyleFor(ForNode* forNode, + const EmitterScope* headLexicalEmitterScope); + MOZ_MUST_USE bool emitForIn(ForNode* forNode, + const EmitterScope* headLexicalEmitterScope); + MOZ_MUST_USE bool emitForOf(ForNode* forNode, + const EmitterScope* headLexicalEmitterScope); + + MOZ_MUST_USE bool emitInitializeForInOrOfTarget(TernaryNode* forHead); + + MOZ_MUST_USE bool emitBreak(const ParserName* label); + MOZ_MUST_USE bool emitContinue(const ParserName* label); + + MOZ_MUST_USE bool emitFunctionFormalParameters(ListNode* paramsBody); + MOZ_MUST_USE bool emitInitializeFunctionSpecialNames(); + MOZ_MUST_USE bool emitLexicalInitialization(NameNode* name); + MOZ_MUST_USE bool emitLexicalInitialization(const ParserAtom* name); + + // Emit bytecode for the spread operator. + // + // emitSpread expects the current index (I) of the array, the array itself + // and the iterator to be on the stack in that order (iterator on the bottom). + // It will pop the iterator and I, then iterate over the iterator by calling + // |.next()| and put the results into the I-th element of array with + // incrementing I, then push the result I (it will be original I + + // iteration count). The stack after iteration will look like |ARRAY INDEX|. + MOZ_MUST_USE bool emitSpread(bool allowSelfHosted = false); + + 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( + ClassNode* classNode, ClassNameKind nameKind = ClassNameKind::BindingName, + const ParserAtom* nameForAnonymousClass = nullptr); + + MOZ_MUST_USE bool emitSuperElemOperands( + PropertyByValue* elem, EmitElemOption opts = EmitElemOption::Get); + MOZ_MUST_USE bool emitSuperGetElem(PropertyByValue* elem, + bool isCall = false); + + MOZ_MUST_USE bool emitCalleeAndThis(ParseNode* callee, ParseNode* call, + CallOrNewEmitter& cone); + + MOZ_MUST_USE bool emitOptionalCalleeAndThis(ParseNode* callee, CallNode* call, + CallOrNewEmitter& cone, + OptionalEmitter& oe); + + MOZ_MUST_USE bool emitPipeline(ListNode* node); + + MOZ_MUST_USE bool emitExportDefault(BinaryNode* exportNode); + + MOZ_MUST_USE bool emitReturnRval() { + return emitInstrumentation(InstrumentationKind::Exit) && + emit1(JSOp::RetRval); + } + + MOZ_MUST_USE bool emitCheckPrivateField(ThrowCondition throwCondition, + ThrowMsgKind msgKind) { + return emit3(JSOp::CheckPrivateField, uint8_t(throwCondition), + uint8_t(msgKind)); + } + + template + MOZ_MUST_USE bool emitNewPrivateNames(ListNode* classMembers); + + MOZ_MUST_USE bool emitInstrumentation(InstrumentationKind kind, + uint32_t npopped = 0) { + return MOZ_LIKELY(!instrumentationKinds) || + emitInstrumentationSlow(kind, std::function()); + } + + MOZ_MUST_USE bool emitInstrumentationForOpcode(JSOp op, + GCThingIndex atomIndex) { + return MOZ_LIKELY(!instrumentationKinds) || + emitInstrumentationForOpcodeSlow(op, atomIndex); + } + + MOZ_MUST_USE js::UniquePtr createImmutableScriptData( + JSContext* cx); + + private: + MOZ_MUST_USE bool emitInstrumentationSlow( + InstrumentationKind kind, + const std::function& pushOperandsCallback); + MOZ_MUST_USE bool emitInstrumentationForOpcodeSlow(JSOp op, + GCThingIndex atomIndex); + + MOZ_MUST_USE bool allowSelfHostedIter(ParseNode* parseNode); + + MOZ_MUST_USE bool emitSelfHostedGetBuiltinConstructorOrPrototype( + BinaryNode* callNode, bool isConstructor); +}; + +class MOZ_RAII AutoCheckUnstableEmitterScope { +#ifdef DEBUG + bool prev_; + BytecodeEmitter* bce_; +#endif + + public: + AutoCheckUnstableEmitterScope() = delete; + explicit AutoCheckUnstableEmitterScope(BytecodeEmitter* bce) +#ifdef DEBUG + : bce_(bce) +#endif + { +#ifdef DEBUG + prev_ = bce_->unstableEmitterScope; + bce_->unstableEmitterScope = true; +#endif + } + ~AutoCheckUnstableEmitterScope() { +#ifdef DEBUG + bce_->unstableEmitterScope = prev_; +#endif + } +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_BytecodeEmitter_h */ diff --git a/js/src/frontend/BytecodeOffset.h b/js/src/frontend/BytecodeOffset.h new file mode 100644 index 0000000000..cb13e9e79a --- /dev/null +++ b/js/src/frontend/BytecodeOffset.h @@ -0,0 +1,153 @@ +/* -*- 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_BytecodeOffset_h +#define frontend_BytecodeOffset_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/CheckedInt.h" // mozilla::CheckedInt + +#include // ptrdiff_t + +namespace js { +namespace frontend { + +class BytecodeOffsetDiff; + +// The offset inside bytecode. +class BytecodeOffset { + private: + static const ptrdiff_t INVALID_OFFSET = -1; + + ptrdiff_t value_ = 0; + + struct Invalid {}; + explicit constexpr BytecodeOffset(Invalid) : value_(INVALID_OFFSET) {} + + public: + constexpr BytecodeOffset() = default; + + explicit BytecodeOffset(ptrdiff_t value) : value_(value) { + MOZ_ASSERT(value >= 0); + } + + static constexpr BytecodeOffset invalidOffset() { + return BytecodeOffset(Invalid()); + } + + bool operator==(const BytecodeOffset& rhs) const { + return value_ == rhs.value_; + } + + bool operator!=(const BytecodeOffset& rhs) const { return !(*this == rhs); } + + inline BytecodeOffsetDiff operator-(const BytecodeOffset& rhs) const; + inline BytecodeOffset operator+(const BytecodeOffsetDiff& diff) const; + + inline BytecodeOffset& operator+=(const BytecodeOffsetDiff& diff); + inline BytecodeOffset& operator-=(const BytecodeOffsetDiff& diff); + + bool operator<(const BytecodeOffset& rhs) const { + MOZ_ASSERT(valid()); + MOZ_ASSERT(rhs.valid()); + return value_ < rhs.value_; + } + bool operator<=(const BytecodeOffset& rhs) const { + MOZ_ASSERT(valid()); + MOZ_ASSERT(rhs.valid()); + return value_ <= rhs.value_; + } + bool operator>(const BytecodeOffset& rhs) const { + MOZ_ASSERT(valid()); + MOZ_ASSERT(rhs.valid()); + return value_ > rhs.value_; + } + bool operator>=(const BytecodeOffset& rhs) const { + MOZ_ASSERT(valid()); + MOZ_ASSERT(rhs.valid()); + return value_ >= rhs.value_; + } + + ptrdiff_t value() const { return value_; } + uint32_t toUint32() const { + MOZ_ASSERT(size_t(uint32_t(value_)) == size_t(value_)); + return uint32_t(value_); + } + + bool valid() const { return value_ != INVALID_OFFSET; } +}; + +class BytecodeOffsetDiff { + private: + friend class BytecodeOffset; + + ptrdiff_t value_ = 0; + + public: + constexpr BytecodeOffsetDiff() = default; + + explicit constexpr BytecodeOffsetDiff(ptrdiff_t value_) : value_(value_) {} + + bool operator==(const BytecodeOffsetDiff& rhs) const { + return value_ == rhs.value_; + } + + bool operator!=(const BytecodeOffsetDiff& rhs) const { + return !(*this == rhs); + } + + BytecodeOffsetDiff operator+(const BytecodeOffsetDiff& rhs) const { + mozilla::CheckedInt result = value_; + result += rhs.value_; + return BytecodeOffsetDiff(result.value()); + } + + ptrdiff_t value() const { return value_; } + uint32_t toUint32() const { + MOZ_ASSERT(size_t(uint32_t(value_)) == size_t(value_)); + return uint32_t(value_); + } +}; + +inline BytecodeOffsetDiff BytecodeOffset::operator-( + const BytecodeOffset& rhs) const { + MOZ_ASSERT(valid()); + MOZ_ASSERT(rhs.valid()); + mozilla::CheckedInt result = value_; + result -= rhs.value_; + return BytecodeOffsetDiff(result.value()); +} + +inline BytecodeOffset BytecodeOffset::operator+( + const BytecodeOffsetDiff& diff) const { + MOZ_ASSERT(valid()); + mozilla::CheckedInt result = value_; + result += diff.value_; + return BytecodeOffset(result.value()); +} + +inline BytecodeOffset& BytecodeOffset::operator+=( + const BytecodeOffsetDiff& diff) { + MOZ_ASSERT(valid()); + mozilla::CheckedInt result = value_; + result += diff.value_; + value_ = result.value(); + return *this; +} + +inline BytecodeOffset& BytecodeOffset::operator-=( + const BytecodeOffsetDiff& diff) { + MOZ_ASSERT(valid()); + mozilla::CheckedInt result = value_; + result -= diff.value_; + value_ = result.value(); + return *this; +} + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_BytecodeOffset_h */ diff --git a/js/src/frontend/BytecodeSection.cpp b/js/src/frontend/BytecodeSection.cpp new file mode 100644 index 0000000000..2391656405 --- /dev/null +++ b/js/src/frontend/BytecodeSection.cpp @@ -0,0 +1,197 @@ +/* -*- 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/BytecodeSection.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/ReverseIterator.h" // mozilla::Reversed + +#include "frontend/AbstractScopePtr.h" // ScopeIndex +#include "frontend/CompilationInfo.h" +#include "frontend/SharedContext.h" // FunctionBox +#include "vm/BytecodeUtil.h" // INDEX_LIMIT, StackUses, StackDefs +#include "vm/GlobalObject.h" +#include "vm/JSContext.h" // JSContext +#include "vm/RegExpObject.h" // RegexpObject +#include "vm/Scope.h" // GlobalScope + +using namespace js; +using namespace js::frontend; + +bool GCThingList::append(FunctionBox* funbox, GCThingIndex* index) { + // Append the function to the vector and return the index in *index. + *index = GCThingIndex(vector.length()); + + if (!vector.emplaceBack(funbox->index())) { + return false; + } + return true; +} + +AbstractScopePtr GCThingList::getScope(size_t index) const { + const TaggedScriptThingIndex& elem = vector[index]; + if (elem.isEmptyGlobalScope()) { + // The empty enclosing scope should be stored by + // CompilationInput::initForSelfHostingGlobal. + MOZ_ASSERT(stencil.input.enclosingScope); + MOZ_ASSERT(!stencil.input.enclosingScope->as().hasBindings()); + return AbstractScopePtr(stencil.input.enclosingScope); + } + return AbstractScopePtr(compilationState, elem.toScope()); +} + +mozilla::Maybe GCThingList::getScopeIndex(size_t index) const { + const TaggedScriptThingIndex& elem = vector[index]; + if (elem.isEmptyGlobalScope()) { + return mozilla::Nothing(); + } + return mozilla::Some(vector[index].toScope()); +} + +bool js::frontend::EmitScriptThingsVector( + JSContext* cx, CompilationInput& input, BaseCompilationStencil& stencil, + CompilationGCOutput& gcOutput, + mozilla::Span things, + mozilla::Span output) { + MOZ_ASSERT(things.size() <= INDEX_LIMIT); + MOZ_ASSERT(things.size() == output.size()); + + auto& atomCache = input.atomCache; + + for (uint32_t i = 0; i < things.size(); i++) { + const auto& thing = things[i]; + switch (thing.tag()) { + case TaggedScriptThingIndex::Kind::ParserAtomIndex: + case TaggedScriptThingIndex::Kind::WellKnown: { + JSAtom* atom = atomCache.getExistingAtomAt(cx, thing.toAtom()); + MOZ_ASSERT(atom); + output[i] = JS::GCCellPtr(atom); + break; + } + case TaggedScriptThingIndex::Kind::Null: + output[i] = JS::GCCellPtr(nullptr); + break; + case TaggedScriptThingIndex::Kind::BigInt: { + BigIntStencil& data = stencil.bigIntData[thing.toBigInt()]; + BigInt* bi = data.createBigInt(cx); + if (!bi) { + return false; + } + output[i] = JS::GCCellPtr(bi); + break; + } + case TaggedScriptThingIndex::Kind::ObjLiteral: { + ObjLiteralStencil& data = stencil.objLiteralData[thing.toObjLiteral()]; + JSObject* obj = data.create(cx, atomCache); + if (!obj) { + return false; + } + output[i] = JS::GCCellPtr(obj); + break; + } + case TaggedScriptThingIndex::Kind::RegExp: { + RegExpStencil& data = stencil.regExpData[thing.toRegExp()]; + RegExpObject* regexp = data.createRegExp(cx, atomCache); + if (!regexp) { + return false; + } + output[i] = JS::GCCellPtr(regexp); + break; + } + case TaggedScriptThingIndex::Kind::Scope: + output[i] = JS::GCCellPtr(gcOutput.scopes[thing.toScope()]); + break; + case TaggedScriptThingIndex::Kind::Function: + output[i] = JS::GCCellPtr(gcOutput.functions[thing.toFunction()]); + break; + case TaggedScriptThingIndex::Kind::EmptyGlobalScope: { + Scope* scope = &cx->global()->emptyGlobalScope(); + output[i] = JS::GCCellPtr(scope); + break; + } + } + } + + return true; +} + +bool CGTryNoteList::append(TryNoteKind kind, uint32_t stackDepth, + BytecodeOffset start, BytecodeOffset end) { + MOZ_ASSERT(start <= end); + + // Offsets are given relative to sections, but we only expect main-section + // to have TryNotes. In finish() we will fixup base offset. + + TryNote note(uint32_t(kind), stackDepth, start.toUint32(), + (end - start).toUint32()); + + return list.append(note); +} + +bool CGScopeNoteList::append(GCThingIndex scopeIndex, BytecodeOffset offset, + uint32_t parent) { + ScopeNote note; + note.index = scopeIndex; + note.start = offset.toUint32(); + note.length = 0; + note.parent = parent; + + return list.append(note); +} + +void CGScopeNoteList::recordEnd(uint32_t index, BytecodeOffset offset) { + recordEndImpl(index, offset.toUint32()); +} + +void CGScopeNoteList::recordEndFunctionBodyVar(uint32_t index) { + recordEndImpl(index, UINT32_MAX); +} + +void CGScopeNoteList::recordEndImpl(uint32_t index, uint32_t offset) { + MOZ_ASSERT(index < length()); + MOZ_ASSERT(list[index].length == 0); + MOZ_ASSERT(offset >= list[index].start); + list[index].length = offset - list[index].start; +} + +JSObject* ObjLiteralStencil::create(JSContext* cx, + CompilationAtomCache& atomCache) const { + return InterpretObjLiteral(cx, atomCache, code_, flags_); +} + +BytecodeSection::BytecodeSection(JSContext* cx, uint32_t lineNum, + uint32_t column) + : code_(cx), + notes_(cx), + lastNoteOffset_(0), + tryNoteList_(cx), + scopeNoteList_(cx), + resumeOffsetList_(cx), + currentLine_(lineNum), + lastColumn_(column) {} + +void BytecodeSection::updateDepth(BytecodeOffset target) { + jsbytecode* pc = code(target); + + int nuses = StackUses(pc); + int ndefs = StackDefs(pc); + + stackDepth_ -= nuses; + MOZ_ASSERT(stackDepth_ >= 0); + stackDepth_ += ndefs; + + if (uint32_t(stackDepth_) > maxStackDepth_) { + maxStackDepth_ = stackDepth_; + } +} + +PerScriptData::PerScriptData(JSContext* cx, + frontend::CompilationStencil& stencil, + frontend::CompilationState& compilationState) + : gcThingList_(cx, stencil, compilationState), + atomIndices_(cx->frontendCollectionPool()) {} + +bool PerScriptData::init(JSContext* cx) { return atomIndices_.acquire(cx); } diff --git a/js/src/frontend/BytecodeSection.h b/js/src/frontend/BytecodeSection.h new file mode 100644 index 0000000000..d68561c783 --- /dev/null +++ b/js/src/frontend/BytecodeSection.h @@ -0,0 +1,418 @@ +/* -*- 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_BytecodeSection_h +#define frontend_BytecodeSection_h + +#include "mozilla/Attributes.h" // MOZ_MUST_USE, MOZ_STACK_CLASS +#include "mozilla/Maybe.h" // mozilla::Maybe +#include "mozilla/Span.h" // mozilla::Span + +#include // ptrdiff_t, size_t +#include // uint16_t, int32_t, uint32_t + +#include "jstypes.h" // JS_PUBLIC_API +#include "NamespaceImports.h" // ValueVector + +#include "frontend/AbstractScopePtr.h" // AbstractScopePtr, ScopeIndex +#include "frontend/BytecodeOffset.h" // BytecodeOffset +#include "frontend/CompilationInfo.h" // CompilationStencil, CompilationGCOutput +#include "frontend/JumpList.h" // JumpTarget +#include "frontend/NameCollections.h" // AtomIndexMap, PooledMapPtr +#include "frontend/ObjLiteral.h" // ObjLiteralStencil +#include "frontend/ParseNode.h" // BigIntLiteral +#include "frontend/SourceNotes.h" // SrcNote +#include "frontend/Stencil.h" // Stencils +#include "gc/Rooting.h" // JS::Rooted +#include "js/GCVariant.h" // GCPolicy +#include "js/GCVector.h" // GCVector +#include "js/TypeDecls.h" // jsbytecode, JSContext +#include "js/Value.h" // JS::Vector +#include "js/Vector.h" // Vector +#include "vm/Opcodes.h" // JSOpLength_JumpTarget +#include "vm/SharedStencil.h" // TryNote, ScopeNote, GCThingIndex +#include "vm/StencilEnums.h" // TryNoteKind + +namespace js { + +class Scope; + +namespace frontend { + +class FunctionBox; + +struct MOZ_STACK_CLASS GCThingList { + // The BCE accumulates TaggedScriptThingIndex items so use a vector type. We + // reserve some stack slots to avoid allocating for most small scripts. + using ScriptThingsStackVector = Vector; + + CompilationStencil& stencil; + CompilationState& compilationState; + ScriptThingsStackVector vector; + + // Index of the first scope in the vector. + mozilla::Maybe firstScopeIndex; + + explicit GCThingList(JSContext* cx, CompilationStencil& stencil, + CompilationState& compilationState) + : stencil(stencil), compilationState(compilationState), vector(cx) {} + + MOZ_MUST_USE bool append(const ParserAtom* atom, GCThingIndex* index) { + *index = GCThingIndex(vector.length()); + atom->markUsedByStencil(); + if (!vector.emplaceBack(atom->toIndex())) { + return false; + } + return true; + } + MOZ_MUST_USE bool append(ScopeIndex scope, GCThingIndex* index) { + *index = GCThingIndex(vector.length()); + if (!vector.emplaceBack(scope)) { + return false; + } + if (!firstScopeIndex) { + firstScopeIndex.emplace(*index); + } + return true; + } + MOZ_MUST_USE bool append(BigIntLiteral* literal, GCThingIndex* index) { + *index = GCThingIndex(vector.length()); + if (!vector.emplaceBack(literal->index())) { + return false; + } + return true; + } + MOZ_MUST_USE bool append(RegExpLiteral* literal, GCThingIndex* index) { + *index = GCThingIndex(vector.length()); + if (!vector.emplaceBack(literal->index())) { + return false; + } + return true; + } + MOZ_MUST_USE bool append(ObjLiteralIndex objlit, GCThingIndex* index) { + *index = GCThingIndex(vector.length()); + if (!vector.emplaceBack(objlit)) { + return false; + } + return true; + } + MOZ_MUST_USE bool append(FunctionBox* funbox, GCThingIndex* index); + + MOZ_MUST_USE bool appendEmptyGlobalScope(GCThingIndex* index) { + *index = GCThingIndex(vector.length()); + EmptyGlobalScopeType emptyGlobalScope; + if (!vector.emplaceBack(emptyGlobalScope)) { + return false; + } + if (!firstScopeIndex) { + firstScopeIndex.emplace(*index); + } + return true; + } + + uint32_t length() const { return vector.length(); } + + const ScriptThingsStackVector& objects() { return vector; } + + AbstractScopePtr getScope(size_t index) const; + + // Index of scope within CompilationStencil or Nothing is the scope is + // EmptyGlobalScopeType. + mozilla::Maybe getScopeIndex(size_t index) const; + + AbstractScopePtr firstScope() const { + MOZ_ASSERT(firstScopeIndex.isSome()); + return getScope(*firstScopeIndex); + } +}; + +MOZ_MUST_USE bool EmitScriptThingsVector( + JSContext* cx, CompilationInput& input, BaseCompilationStencil& stencil, + CompilationGCOutput& gcOutput, + mozilla::Span things, + mozilla::Span output); + +struct CGTryNoteList { + Vector list; + explicit CGTryNoteList(JSContext* cx) : list(cx) {} + + MOZ_MUST_USE bool append(TryNoteKind kind, uint32_t stackDepth, + BytecodeOffset start, BytecodeOffset end); + mozilla::Span span() const { + return {list.begin(), list.length()}; + } + size_t length() const { return list.length(); } +}; + +struct CGScopeNoteList { + Vector list; + explicit CGScopeNoteList(JSContext* cx) : list(cx) {} + + MOZ_MUST_USE bool append(GCThingIndex scopeIndex, BytecodeOffset offset, + uint32_t parent); + void recordEnd(uint32_t index, BytecodeOffset offset); + void recordEndFunctionBodyVar(uint32_t index); + mozilla::Span span() const { + return {list.begin(), list.length()}; + } + size_t length() const { return list.length(); } + + private: + void recordEndImpl(uint32_t index, uint32_t offset); +}; + +struct CGResumeOffsetList { + Vector list; + explicit CGResumeOffsetList(JSContext* cx) : list(cx) {} + + MOZ_MUST_USE bool append(uint32_t offset) { return list.append(offset); } + mozilla::Span span() const { + return {list.begin(), list.length()}; + } + size_t length() const { return list.length(); } +}; + +static constexpr size_t MaxBytecodeLength = INT32_MAX; +static constexpr size_t MaxSrcNotesLength = INT32_MAX; + +// Have a few inline elements, so as to avoid heap allocation for tiny +// sequences. See bug 1390526. +typedef Vector BytecodeVector; +typedef Vector SrcNotesVector; + +// Bytecode and all data directly associated with specific opcode/index inside +// bytecode is stored in this class. +class BytecodeSection { + public: + BytecodeSection(JSContext* cx, uint32_t lineNum, uint32_t column); + + // ---- Bytecode ---- + + BytecodeVector& code() { return code_; } + const BytecodeVector& code() const { return code_; } + + jsbytecode* code(BytecodeOffset offset) { + return code_.begin() + offset.value(); + } + BytecodeOffset offset() const { + return BytecodeOffset(code_.end() - code_.begin()); + } + + // ---- Source notes ---- + + SrcNotesVector& notes() { return notes_; } + const SrcNotesVector& notes() const { return notes_; } + + BytecodeOffset lastNoteOffset() const { return lastNoteOffset_; } + void setLastNoteOffset(BytecodeOffset offset) { lastNoteOffset_ = offset; } + + // ---- Jump ---- + + BytecodeOffset lastTargetOffset() const { return lastTarget_.offset; } + void setLastTargetOffset(BytecodeOffset offset) { + lastTarget_.offset = offset; + } + + // Check if the last emitted opcode is a jump target. + bool lastOpcodeIsJumpTarget() const { + return lastTarget_.offset.valid() && + offset() - lastTarget_.offset == + BytecodeOffsetDiff(JSOpLength_JumpTarget); + } + + // JumpTarget should not be part of the emitted statement, as they can be + // aliased by multiple statements. If we included the jump target as part of + // the statement we might have issues where the enclosing statement might + // not contain all the opcodes of the enclosed statements. + BytecodeOffset lastNonJumpTargetOffset() const { + return lastOpcodeIsJumpTarget() ? lastTarget_.offset : offset(); + } + + // ---- Stack ---- + + int32_t stackDepth() const { return stackDepth_; } + void setStackDepth(int32_t depth) { stackDepth_ = depth; } + + uint32_t maxStackDepth() const { return maxStackDepth_; } + + void updateDepth(BytecodeOffset target); + + // ---- Try notes ---- + + CGTryNoteList& tryNoteList() { return tryNoteList_; }; + const CGTryNoteList& tryNoteList() const { return tryNoteList_; }; + + // ---- Scope ---- + + CGScopeNoteList& scopeNoteList() { return scopeNoteList_; }; + const CGScopeNoteList& scopeNoteList() const { return scopeNoteList_; }; + + // ---- Generator ---- + + CGResumeOffsetList& resumeOffsetList() { return resumeOffsetList_; } + const CGResumeOffsetList& resumeOffsetList() const { + return resumeOffsetList_; + } + + uint32_t numYields() const { return numYields_; } + void addNumYields() { numYields_++; } + + // ---- Line and column ---- + + uint32_t currentLine() const { return currentLine_; } + uint32_t lastColumn() const { return lastColumn_; } + void setCurrentLine(uint32_t line, uint32_t sourceOffset) { + currentLine_ = line; + lastColumn_ = 0; + lastSourceOffset_ = sourceOffset; + } + + void setLastColumn(uint32_t column, uint32_t offset) { + lastColumn_ = column; + lastSourceOffset_ = offset; + } + + void updateSeparatorPosition() { + lastSeparatorCodeOffset_ = code().length(); + lastSeparatorSourceOffset_ = lastSourceOffset_; + lastSeparatorLine_ = currentLine_; + lastSeparatorColumn_ = lastColumn_; + } + + void updateSeparatorPositionIfPresent() { + if (lastSeparatorCodeOffset_ == code().length()) { + lastSeparatorSourceOffset_ = lastSourceOffset_; + lastSeparatorLine_ = currentLine_; + lastSeparatorColumn_ = lastColumn_; + } + } + + bool isDuplicateLocation() const { + return lastSeparatorLine_ == currentLine_ && + lastSeparatorColumn_ == lastColumn_; + } + + bool atSeparator(uint32_t offset) const { + return lastSeparatorSourceOffset_ == offset; + } + + // ---- JIT ---- + + uint32_t numICEntries() const { return numICEntries_; } + void incrementNumICEntries() { + MOZ_ASSERT(numICEntries_ != UINT32_MAX, "Shouldn't overflow"); + numICEntries_++; + } + void setNumICEntries(uint32_t entries) { numICEntries_ = entries; } + + private: + // ---- Bytecode ---- + + // Bytecode. + BytecodeVector code_; + + // ---- Source notes ---- + + // Source notes + SrcNotesVector notes_; + + // Code offset for last source note + BytecodeOffset lastNoteOffset_; + + // ---- Jump ---- + + // Last jump target emitted. + JumpTarget lastTarget_; + + // ---- Stack ---- + + // Maximum number of expression stack slots so far. + uint32_t maxStackDepth_ = 0; + + // Current stack depth in script frame. + int32_t stackDepth_ = 0; + + // ---- Try notes ---- + + // List of emitted try notes. + CGTryNoteList tryNoteList_; + + // ---- Scope ---- + + // List of emitted block scope notes. + CGScopeNoteList scopeNoteList_; + + // ---- Generator ---- + + // Certain ops (yield, await, gosub) have an entry in the script's + // resumeOffsets list. This can be used to map from the op's resumeIndex to + // the bytecode offset of the next pc. This indirection makes it easy to + // resume in the JIT (because BaselineScript stores a resumeIndex => native + // code array). + CGResumeOffsetList resumeOffsetList_; + + // Number of yield instructions emitted. Does not include JSOp::Await. + uint32_t numYields_ = 0; + + // ---- Line and column ---- + + // Line number for srcnotes. + // + // WARNING: If this becomes out of sync with already-emitted srcnotes, + // we can get undefined behavior. + uint32_t currentLine_; + + // Zero-based column index on currentLine_ of last + // SrcNoteType::ColSpan-annotated opcode. + // + // WARNING: If this becomes out of sync with already-emitted srcnotes, + // we can get undefined behavior. + uint32_t lastColumn_ = 0; + + // The last code unit used for srcnotes. + uint32_t lastSourceOffset_ = 0; + + // The offset, line and column numbers of the last opcode for the + // breakpoint for step execution. + uint32_t lastSeparatorCodeOffset_ = 0; + uint32_t lastSeparatorSourceOffset_ = 0; + uint32_t lastSeparatorLine_ = 0; + uint32_t lastSeparatorColumn_ = 0; + + // ---- JIT ---- + + // Number of ICEntries in the script. There's one ICEntry for each JOF_IC op + // and, if the script is a function, for |this| and each formal argument. + uint32_t numICEntries_ = 0; +}; + +// Data that is not directly associated with specific opcode/index inside +// bytecode, but referred from bytecode is stored in this class. +class PerScriptData { + public: + explicit PerScriptData(JSContext* cx, frontend::CompilationStencil& stencil, + frontend::CompilationState& compilationState); + + MOZ_MUST_USE bool init(JSContext* cx); + + GCThingList& gcThingList() { return gcThingList_; } + const GCThingList& gcThingList() const { return gcThingList_; } + + PooledMapPtr& atomIndices() { return atomIndices_; } + const PooledMapPtr& atomIndices() const { return atomIndices_; } + + private: + // List of emitted scopes/objects/bigints. + GCThingList gcThingList_; + + // Map from atom to index. + PooledMapPtr atomIndices_; +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_BytecodeSection_h */ diff --git a/js/src/frontend/CForEmitter.cpp b/js/src/frontend/CForEmitter.cpp new file mode 100644 index 0000000000..338b4fe0f1 --- /dev/null +++ b/js/src/frontend/CForEmitter.cpp @@ -0,0 +1,179 @@ +/* -*- 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/CForEmitter.h" + +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter +#include "frontend/EmitterScope.h" // EmitterScope +#include "vm/Opcodes.h" // JSOp +#include "vm/Scope.h" // ScopeKind +#include "vm/StencilEnums.h" // TryNoteKind + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; + +CForEmitter::CForEmitter(BytecodeEmitter* bce, + const EmitterScope* headLexicalEmitterScopeForLet) + : bce_(bce), + headLexicalEmitterScopeForLet_(headLexicalEmitterScopeForLet) {} + +bool CForEmitter::emitInit(const Maybe& initPos) { + MOZ_ASSERT(state_ == State::Start); + + loopInfo_.emplace(bce_, StatementKind::ForLoop); + + if (initPos) { + if (!bce_->updateSourceCoordNotes(*initPos)) { + return false; + } + } + +#ifdef DEBUG + state_ = State::Init; +#endif + return true; +} + +bool CForEmitter::emitCond(const Maybe& condPos) { + MOZ_ASSERT(state_ == State::Init); + + // ES 13.7.4.8 step 2. The initial freshening. + // + // If an initializer let-declaration may be captured during loop + // iteration, the current scope has an environment. If so, freshen the + // current environment to expose distinct bindings for each loop + // iteration. + if (headLexicalEmitterScopeForLet_) { + // The environment chain only includes an environment for the + // for(;;) loop head's let-declaration *if* a scope binding is + // captured, thus requiring a fresh environment each iteration. If + // a lexical scope exists for the head, it must be the innermost + // one. If that scope has closed-over bindings inducing an + // environment, recreate the current environment. + MOZ_ASSERT(headLexicalEmitterScopeForLet_ == bce_->innermostEmitterScope()); + MOZ_ASSERT(headLexicalEmitterScopeForLet_->scope(bce_).kind() == + ScopeKind::Lexical); + + if (headLexicalEmitterScopeForLet_->hasEnvironment()) { + if (!bce_->emit1(JSOp::FreshenLexicalEnv)) { + return false; + } + } + } + + if (!loopInfo_->emitLoopHead(bce_, condPos)) { + // [stack] + return false; + } + +#ifdef DEBUG + state_ = State::Cond; +#endif + return true; +} + +bool CForEmitter::emitBody(Cond cond) { + MOZ_ASSERT(state_ == State::Cond); + cond_ = cond; + + if (cond_ == Cond::Present) { + if (!bce_->emitJump(JSOp::IfEq, &loopInfo_->breaks)) { + return false; + } + } + + tdzCache_.emplace(bce_); + +#ifdef DEBUG + state_ = State::Body; +#endif + return true; +} + +bool CForEmitter::emitUpdate(Update update, const Maybe& updatePos) { + MOZ_ASSERT(state_ == State::Body); + update_ = update; + tdzCache_.reset(); + + // Set loop and enclosing "update" offsets, for continue. Note that we + // continue to immediately *before* the block-freshening: continuing must + // refresh the block. + if (!loopInfo_->emitContinueTarget(bce_)) { + return false; + } + + // ES 13.7.4.8 step 3.e. The per-iteration freshening. + if (headLexicalEmitterScopeForLet_) { + MOZ_ASSERT(headLexicalEmitterScopeForLet_ == bce_->innermostEmitterScope()); + MOZ_ASSERT(headLexicalEmitterScopeForLet_->scope(bce_).kind() == + ScopeKind::Lexical); + + if (headLexicalEmitterScopeForLet_->hasEnvironment()) { + if (!bce_->emit1(JSOp::FreshenLexicalEnv)) { + return false; + } + } + } + + // The update code may not be executed at all; it needs its own TDZ + // cache. + if (update_ == Update::Present) { + tdzCache_.emplace(bce_); + + if (updatePos) { + if (!bce_->updateSourceCoordNotes(*updatePos)) { + return false; + } + } + } + +#ifdef DEBUG + state_ = State::Update; +#endif + return true; +} + +bool CForEmitter::emitEnd(const Maybe& forPos) { + MOZ_ASSERT(state_ == State::Update); + + if (update_ == Update::Present) { + tdzCache_.reset(); + + // [stack] UPDATE + + if (!bce_->emit1(JSOp::Pop)) { + // [stack] + return false; + } + } + + if (cond_ == Cond::Missing && update_ == Update::Missing) { + // If there is no condition clause and no update clause, mark + // the loop-ending "goto" with the location of the "for". + // This ensures that the debugger will stop on each loop + // iteration. + if (forPos) { + if (!bce_->updateSourceCoordNotes(*forPos)) { + return false; + } + } + } + + // Emit the loop-closing jump. + if (!loopInfo_->emitLoopEnd(bce_, JSOp::Goto, TryNoteKind::Loop)) { + // [stack] + return false; + } + + loopInfo_.reset(); + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} diff --git a/js/src/frontend/CForEmitter.h b/js/src/frontend/CForEmitter.h new file mode 100644 index 0000000000..3c51506914 --- /dev/null +++ b/js/src/frontend/CForEmitter.h @@ -0,0 +1,176 @@ +/* -*- 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_CForEmitter_h +#define frontend_CForEmitter_h + +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS, MOZ_MUST_USE +#include "mozilla/Maybe.h" // mozilla::Maybe + +#include // uint32_t + +#include "frontend/BytecodeControlStructures.h" // LoopControl +#include "frontend/BytecodeOffset.h" // BytecodeOffset +#include "frontend/TDZCheckCache.h" // TDZCheckCache + +namespace js { +namespace frontend { + +struct BytecodeEmitter; +class EmitterScope; + +// Class for emitting bytecode for c-style for block. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `for (init; cond; update) body` +// CForEmitter cfor(this, headLexicalEmitterScopeForLet or nullptr); +// cfor.emitInit(Some(offset_of_init)); +// emit(init); // without pushing value +// cfor.emitCond(Some(offset_of_cond)); +// emit(cond); +// cfor.emitBody(CForEmitter::Cond::Present, Some(offset_of_body)); +// emit(body); +// cfor.emitUpdate(CForEmitter::Update::Present, Some(offset_of_update))); +// emit(update); +// cfor.emitEnd(Some(offset_of_for)); +// +// `for (;;) body` +// CForEmitter cfor(this, nullptr); +// cfor.emitInit(Nothing()); +// cfor.emitCond(Nothing()); +// cfor.emitBody(CForEmitter::Cond::Missing, Some(offset_of_body)); +// emit(body); +// cfor.emitUpdate(CForEmitter::Update::Missing, Nothing()); +// cfor.emitEnd(Some(offset_of_for)); +// +class MOZ_STACK_CLASS CForEmitter { + // Basic structure of the bytecode (not complete). + // + // If `cond` is not empty: + // {init} + // loop: + // JSOp::LoopHead + // {cond} + // JSOp::IfEq break + // {body} + // continue: + // {update} + // JSOp::Goto loop + // break: + // + // If `cond` is empty: + // {init} + // loop: + // JSOp::LoopHead + // {body} + // continue: + // {update} + // JSOp::Goto loop + // break: + // + public: + enum class Cond { Missing, Present }; + enum class Update { Missing, Present }; + + private: + BytecodeEmitter* bce_; + + // Whether the c-style for loop has `cond` and `update`. + Cond cond_ = Cond::Missing; + Update update_ = Update::Missing; + + mozilla::Maybe loopInfo_; + + // The lexical scope to be freshened for each iteration. + // See the comment in `emitCond` for more details. + // + // ### Scope freshening + // + // Each iteration of a `for (let V...)` loop creates a fresh loop variable + // binding for V, even if the loop is a C-style `for(;;)` loop: + // + // var funcs = []; + // for (let i = 0; i < 2; i++) + // funcs.push(function() { return i; }); + // assertEq(funcs[0](), 0); // the two closures capture... + // assertEq(funcs[1](), 1); // ...two different `i` bindings + // + // This is implemented by "freshening" the implicit block -- changing the + // scope chain to a fresh clone of the instantaneous block object -- each + // iteration, just before evaluating the "update" in for(;;) loops. + // + // ECMAScript doesn't freshen in `for (const ...;;)`. Lack of freshening + // isn't directly observable in-language because `const`s can't be mutated, + // but it *can* be observed in the Debugger API. + const EmitterScope* headLexicalEmitterScopeForLet_; + + mozilla::Maybe tdzCache_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ emitInit +------+ emitCond +------+ emitBody +------+ + // | Start |--------->| Init |--------->| Cond |--------->| Body |-+ + // +-------+ +------+ +------+ +------+ | + // | + // +-------------------------------------+ + // | + // | emitUpdate +--------+ emitEnd +-----+ + // +----------->| Update |-------->| End | + // +--------+ +-----+ + enum class State { + // The initial state. + Start, + + // After calling emitInit. + Init, + + // After calling emitCond. + Cond, + + // After calling emitBody. + Body, + + // After calling emitUpdate. + Update, + + // After calling emitEnd. + End + }; + State state_ = State::Start; +#endif + + public: + CForEmitter(BytecodeEmitter* bce, + const EmitterScope* headLexicalEmitterScopeForLet); + + // Parameters are the offset in the source code for each character below: + // + // for ( x = 10 ; x < 20 ; x ++ ) { f(x); } + // ^ ^ ^ ^ + // | | | | + // | | | updatePos + // | | | + // | | condPos + // | | + // | initPos + // | + // forPos + // + // Can be Nothing() if not available. + MOZ_MUST_USE bool emitInit(const mozilla::Maybe& initPos); + MOZ_MUST_USE bool emitCond(const mozilla::Maybe& condPos); + MOZ_MUST_USE bool emitBody(Cond cond); + MOZ_MUST_USE bool emitUpdate(Update update, + const mozilla::Maybe& updatePos); + MOZ_MUST_USE bool emitEnd(const mozilla::Maybe& forPos); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_CForEmitter_h */ diff --git a/js/src/frontend/CallOrNewEmitter.cpp b/js/src/frontend/CallOrNewEmitter.cpp new file mode 100644 index 0000000000..1d82d93dba --- /dev/null +++ b/js/src/frontend/CallOrNewEmitter.cpp @@ -0,0 +1,277 @@ +/* -*- 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/CallOrNewEmitter.h" + +#include "frontend/BytecodeEmitter.h" +#include "frontend/NameOpEmitter.h" +#include "frontend/SharedContext.h" +#include "vm/Opcodes.h" +#include "vm/StringType.h" + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; + +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::CallIgnoresRv; + } + + MOZ_ASSERT(isCall() || isNew() || isSuperCall()); +} + +bool CallOrNewEmitter::emitNameCallee(const ParserAtom* name) { + MOZ_ASSERT(state_ == State::Start); + + NameOpEmitter noe( + bce_, name, + isCall() ? NameOpEmitter::Kind::Call : NameOpEmitter::Kind::Get); + if (!noe.emitGet()) { + // [stack] 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, bool isPrivate) { + MOZ_ASSERT(state_ == State::Start); + + eoe_.emplace(bce_, + isCall() ? ElemOpEmitter::Kind::Call : ElemOpEmitter::Kind::Get, + isSuperElem ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other, + isPrivate ? NameVisibility::Private : NameVisibility::Public); + + state_ = State::ElemCallee; + return *eoe_; +} + +bool CallOrNewEmitter::prepareForFunctionCallee() { + MOZ_ASSERT(state_ == State::Start); + + state_ = State::FunctionCallee; + return true; +} + +bool CallOrNewEmitter::emitSuperCallee() { + MOZ_ASSERT(state_ == State::Start); + + if (!bce_->emitThisEnvironmentCallee()) { + // [stack] CALLEE + return false; + } + if (!bce_->emit1(JSOp::SuperFun)) { + // [stack] CALLEE + return false; + } + if (!bce_->emit1(JSOp::IsConstructing)) { + // [stack] 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: + needsThis = true; + break; + case State::SuperCallee: + break; + case State::OtherCallee: + needsThis = true; + break; + default:; + } + if (needsThis) { + if (isNew() || isSuperCall()) { + if (!bce_->emit1(JSOp::IsConstructing)) { + // [stack] CALLEE THIS + return false; + } + } else { + if (!bce_->emit1(JSOp::Undefined)) { + // [stack] 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. + + // [stack] CALLEE THIS ARG0 + + ifNotOptimizable_.emplace(bce_); + if (!bce_->emit1(JSOp::OptimizeSpreadCall)) { + // [stack] CALLEE THIS ARG0 OPTIMIZED + return false; + } + if (!ifNotOptimizable_->emitThen(IfEmitter::ConditionKind::Negative)) { + // [stack] CALLEE THIS ARG0 + return false; + } + if (!bce_->emit1(JSOp::Pop)) { + // [stack] 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()) { + // [stack] CALLEE THIS ARR + return false; + } + + ifNotOptimizable_.reset(); + } + if (isNew() || isSuperCall()) { + if (isSuperCall()) { + if (!bce_->emit1(JSOp::NewTarget)) { + // [stack] 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)) { + // [stack] CALLEE THIS ARR CALLEE + return false; + } + } + } + if (beginPos) { + if (!bce_->updateSourceCoordNotes(*beginPos)) { + return false; + } + } + if (!bce_->markSimpleBreakpoint()) { + return false; + } + if (!isSpread()) { + if (!bce_->emitCall(op_, argc)) { + // [stack] RVAL + return false; + } + } else { + if (!bce_->emit1(op_)) { + // [stack] RVAL + return false; + } + } + + if (isEval() && beginPos) { + uint32_t lineNum = bce_->parser->errorReporter().lineAt(*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 0000000000..9469a751a2 --- /dev/null +++ b/js/src/frontend/CallOrNewEmitter.h @@ -0,0 +1,318 @@ +/* -*- 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_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/BytecodeUtil.h" +#include "vm/Opcodes.h" + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// 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(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(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(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(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 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::CallIgnoresRv || + 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(const ParserAtom* name); + MOZ_MUST_USE PropOpEmitter& prepareForPropCallee(bool isSuperProp); + MOZ_MUST_USE ElemOpEmitter& prepareForElemCallee(bool isSuperElem, + bool isPrivateElem); + 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/CompilationInfo.h b/js/src/frontend/CompilationInfo.h new file mode 100644 index 0000000000..88bb6612b5 --- /dev/null +++ b/js/src/frontend/CompilationInfo.h @@ -0,0 +1,706 @@ +/* -*- 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_CompilationInfo_h +#define frontend_CompilationInfo_h + +#include "mozilla/AlreadyAddRefed.h" // already_AddRefed +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" // RefPtr +#include "mozilla/Span.h" + +#include "builtin/ModuleObject.h" +#include "ds/LifoAlloc.h" +#include "frontend/ParserAtom.h" +#include "frontend/ScriptIndex.h" // ScriptIndex +#include "frontend/SharedContext.h" +#include "frontend/Stencil.h" +#include "frontend/UsedNameTracker.h" +#include "js/GCVector.h" +#include "js/HashTable.h" +#include "js/RealmOptions.h" +#include "js/SourceText.h" +#include "js/Transcoding.h" +#include "js/Vector.h" +#include "js/WasmModule.h" +#include "vm/GlobalObject.h" // GlobalObject +#include "vm/JSContext.h" +#include "vm/JSFunction.h" // JSFunction +#include "vm/JSScript.h" // SourceExtent +#include "vm/Realm.h" +#include "vm/SharedStencil.h" // SharedImmutableScriptData + +namespace js { + +class JSONPrinter; + +namespace frontend { + +// ScopeContext hold information derivied from the scope and environment chains +// to try to avoid the parser needing to traverse VM structures directly. +struct ScopeContext { + // If this eval is in response to Debugger.Frame.eval, we may have an + // incomplete scope chain. In order to provide a better debugging experience, + // we inspect the (optional) environment chain to determine it's enclosing + // FunctionScope if there is one. If there is no such scope, we use the + // orignal scope provided. + // + // NOTE: This is used to compute the ThisBinding kind and to allow access to + // private fields, while other contextual information only uses the + // actual scope passed to the compile. + JS::Rooted effectiveScope; + + // The type of binding required for `this` of the top level context, as + // indicated by the enclosing scopes of this parse. + // + // NOTE: This is computed based on the effective scope (defined above). + ThisBinding thisBinding = ThisBinding::Global; + + // Eval and arrow scripts inherit certain syntax allowances from their + // enclosing scripts. + bool allowNewTarget = false; + bool allowSuperProperty = false; + bool allowSuperCall = false; + bool allowArguments = true; + + // Eval and arrow scripts also inherit the "this" environment -- used by + // `super` expressions -- from their enclosing script. We count the number of + // environment hops needed to get from enclosing scope to the nearest + // appropriate environment. This value is undefined if the script we are + // compiling is not an eval or arrow-function. + uint32_t enclosingThisEnvironmentHops = 0; + + // Class field initializer info if we are nested within a class constructor. + // We may be an combination of arrow and eval context within the constructor. + mozilla::Maybe memberInitializers = {}; + + // Indicates there is a 'class' or 'with' scope on enclosing scope chain. + bool inClass = false; + bool inWith = false; + + explicit ScopeContext(JSContext* cx, InheritThis inheritThis, Scope* scope, + JSObject* enclosingEnv = nullptr) + : effectiveScope(cx, determineEffectiveScope(scope, enclosingEnv)) { + if (inheritThis == InheritThis::Yes) { + computeThisBinding(effectiveScope); + computeThisEnvironment(scope); + } + computeInScope(scope); + } + + private: + void computeThisBinding(Scope* scope); + void computeThisEnvironment(Scope* scope); + void computeInScope(Scope* scope); + + static Scope* determineEffectiveScope(Scope* scope, JSObject* environment); +}; + +struct CompilationAtomCache { + public: + using AtomCacheVector = JS::GCVector; + + private: + // Atoms lowered into or converted from BaseCompilationStencil.parserAtomData. + // + // This field is here instead of in CompilationGCOutput because atoms lowered + // from JSAtom is part of input (enclosing scope bindings, lazy function name, + // etc), and having 2 vectors in both input/output is error prone. + AtomCacheVector atoms_; + + public: + JSAtom* getExistingAtomAt(ParserAtomIndex index) const; + JSAtom* getExistingAtomAt(JSContext* cx, + TaggedParserAtomIndex taggedIndex) const; + JSAtom* getAtomAt(ParserAtomIndex index) const; + bool hasAtomAt(ParserAtomIndex index) const; + bool setAtomAt(JSContext* cx, ParserAtomIndex index, JSAtom* atom); + bool allocate(JSContext* cx, size_t length); + bool extendIfNecessary(JSContext* cx, size_t length); + + void stealBuffer(AtomCacheVector& atoms); + void releaseBuffer(AtomCacheVector& atoms); + + void trace(JSTracer* trc); +} JS_HAZ_GC_POINTER; + +// Input of the compilation, including source and enclosing context. +struct CompilationInput { + const JS::ReadOnlyCompileOptions& options; + + CompilationAtomCache atomCache; + + BaseScript* lazy = nullptr; + + ScriptSourceHolder source_; + + // * If we're compiling standalone function, the non-null enclosing scope of + // the function + // * If we're compiling eval, the non-null enclosing scope of the `eval`. + // * If we're compiling module, null that means empty global scope + // (See EmitterScope::checkEnvironmentChainLength) + // * If we're compiling self-hosted JS, an empty global scope. + // This scope is also used for EmptyGlobalScopeType in + // BaseCompilationStencil.gcThings. + // See the comment in initForSelfHostingGlobal. + // * Null otherwise + Scope* enclosingScope = nullptr; + + explicit CompilationInput(const JS::ReadOnlyCompileOptions& options) + : options(options) {} + + private: + bool initScriptSource(JSContext* cx); + + public: + bool initForGlobal(JSContext* cx) { return initScriptSource(cx); } + + bool initForSelfHostingGlobal(JSContext* cx) { + if (!initScriptSource(cx)) { + return false; + } + + // This enclosing scope is also recorded as EmptyGlobalScopeType in + // BaseCompilationStencil.gcThings even though corresponding ScopeStencil + // isn't generated. + // + // Store the enclosing scope here in order to access it from + // inner scopes' ScopeStencil::enclosing. + enclosingScope = &cx->global()->emptyGlobalScope(); + return true; + } + + bool initForStandaloneFunction(JSContext* cx, + HandleScope functionEnclosingScope) { + if (!initScriptSource(cx)) { + return false; + } + enclosingScope = functionEnclosingScope; + return true; + } + + bool initForEval(JSContext* cx, HandleScope evalEnclosingScope) { + if (!initScriptSource(cx)) { + return false; + } + enclosingScope = evalEnclosingScope; + return true; + } + + bool initForModule(JSContext* cx) { + if (!initScriptSource(cx)) { + return false; + } + // The `enclosingScope` is the emptyGlobalScope. + return true; + } + + void initFromLazy(BaseScript* lazyScript) { + lazy = lazyScript; + enclosingScope = lazy->function()->enclosingScope(); + } + + ScriptSource* source() { return source_.get(); } + + void setSource(ScriptSource* ss) { return source_.reset(ss); } + + template + MOZ_MUST_USE bool assignSource(JSContext* cx, + JS::SourceText& sourceBuffer) { + return source()->assignSource(cx, options, sourceBuffer); + } + + void trace(JSTracer* trc); +} JS_HAZ_GC_POINTER; + +struct CompilationStencil; + +struct MOZ_RAII CompilationState { + // Until we have dealt with Atoms in the front end, we need to hold + // onto them. + Directives directives; + + ScopeContext scopeContext; + + UsedNameTracker usedNames; + LifoAllocScope& allocScope; + + CompilationInput& input; + + // Temporary space to accumulate stencil data. + // Copied to BaseCompilationStencil by `finish` method. + // + // See corresponding BaseCompilationStencil fields for desription. + Vector regExpData; + Vector scriptData; + Vector scriptExtra; + Vector scopeData; + Vector scopeNames; + Vector gcThingData; + + // Table of parser atoms for this compilation. + ParserAtomsTable parserAtoms; + + // The number of functions that *will* have bytecode. + // This doesn't count top-level non-function script. + // + // This should be counted while parsing, and should be passed to + // BaseCompilationStencil.prepareStorageFor *before* start emitting bytecode. + size_t nonLazyFunctionCount = 0; + + CompilationState(JSContext* cx, LifoAllocScope& frontendAllocScope, + const JS::ReadOnlyCompileOptions& options, + CompilationStencil& stencil, + InheritThis inheritThis = InheritThis::No, + Scope* enclosingScope = nullptr, + JSObject* enclosingEnv = nullptr); + + bool finish(JSContext* cx, CompilationStencil& stencil); + + const ParserAtom* getParserAtomAt(JSContext* cx, + TaggedParserAtomIndex taggedIndex) const; + + // Allocate space for `length` gcthings, and return the address of the + // first element to `cursor` to initialize on the caller. + bool allocateGCThingsUninitialized(JSContext* cx, ScriptIndex scriptIndex, + size_t length, + TaggedScriptThingIndex** cursor); + + bool appendGCThings(JSContext* cx, ScriptIndex scriptIndex, + mozilla::Span things); +}; + +// Store shared data for non-lazy script. +struct SharedDataContainer { + // NOTE: While stored, we must hold a ref-count and care must be taken when + // updating or clearing the pointer. + using SingleSharedDataPtr = SharedImmutableScriptData*; + + using SharedDataVector = + Vector, 0, js::SystemAllocPolicy>; + using SharedDataVectorPtr = SharedDataVector*; + + using SharedDataMap = + HashMap, + mozilla::DefaultHasher, js::SystemAllocPolicy>; + using SharedDataMapPtr = SharedDataMap*; + + private: + enum { + SingleTag = 0, + VectorTag = 1, + MapTag = 2, + + TagMask = 3, + }; + + uintptr_t data_ = 0; + + public: + // Defaults to SingleSharedData for delazification vector. + SharedDataContainer() = default; + + ~SharedDataContainer(); + + bool initVector(JSContext* cx); + bool initMap(JSContext* cx); + + bool isEmpty() const { return (data_) == SingleTag; } + bool isSingle() const { return (data_ & TagMask) == SingleTag; } + bool isVector() const { return (data_ & TagMask) == VectorTag; } + bool isMap() const { return (data_ & TagMask) == MapTag; } + + void setSingle(already_AddRefed&& data) { + MOZ_ASSERT(isEmpty()); + data_ = reinterpret_cast(data.take()); + MOZ_ASSERT(isSingle()); + } + + SingleSharedDataPtr asSingle() { + MOZ_ASSERT(isSingle()); + MOZ_ASSERT(!isEmpty()); + static_assert(SingleTag == 0); + return reinterpret_cast(data_); + } + SharedDataVectorPtr asVector() { + MOZ_ASSERT(isVector()); + return reinterpret_cast(data_ & ~TagMask); + } + SharedDataMapPtr asMap() { + MOZ_ASSERT(isMap()); + return reinterpret_cast(data_ & ~TagMask); + } + + bool prepareStorageFor(JSContext* cx, size_t nonLazyScriptCount, + size_t allScriptCount); + + // Returns index-th script's shared data, or nullptr if it doesn't have. + js::SharedImmutableScriptData* get(ScriptIndex index); + + // Add data for index-th script and share it with VM. + bool addAndShare(JSContext* cx, ScriptIndex index, + js::SharedImmutableScriptData* data); + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump(); + void dump(js::JSONPrinter& json); + void dumpFields(js::JSONPrinter& json); +#endif +}; + +// The top level struct of stencil. +struct BaseCompilationStencil { + // Hold onto the RegExpStencil, BigIntStencil, and ObjLiteralStencil that are + // allocated during parse to ensure correct destruction. + mozilla::Span regExpData; + Vector bigIntData; + Vector objLiteralData; + + // Stencil for all function and non-function scripts. The TopLevelIndex is + // reserved for the top-level script. This top-level may or may not be a + // function. + mozilla::Span scriptData; + SharedDataContainer sharedData; + mozilla::Span gcThingData; + + // scopeData and scopeNames have the same size, and i-th scopeNames contains + // the names for the bindings contained in the slot defined by i-th scopeData. + mozilla::Span scopeData; + mozilla::Span scopeNames; + + // List of parser atoms for this compilation. + // This may contain nullptr entries when round-tripping with XDR if the atom + // was generated in original parse but not used by stencil. + ParserAtomSpan parserAtomData; + + // FunctionKey is an identifier that identifies a function within the source + // next in a reproducible way. It allows us to match delazification data with + // initial parse data, even across different runs. This is only used for + // delazification stencils. + using FunctionKey = uint32_t; + + static constexpr FunctionKey NullFunctionKey = 0; + + FunctionKey functionKey = NullFunctionKey; + + BaseCompilationStencil() = default; + + // We need a move-constructor to work with Rooted. + BaseCompilationStencil(BaseCompilationStencil&& other) = default; + + const ParserAtom* getParserAtomAt(JSContext* cx, + TaggedParserAtomIndex taggedIndex) const; + + bool prepareStorageFor(JSContext* cx, CompilationState& compilationState) { + // NOTE: At this point CompilationState shouldn't be finished, and + // BaseCompilationStencil.scriptData field should be empty. + // Use CompilationState.scriptData as data source. + MOZ_ASSERT(scriptData.empty()); + size_t allScriptCount = compilationState.scriptData.length(); + size_t nonLazyScriptCount = compilationState.nonLazyFunctionCount; + if (!compilationState.scriptData[0].isFunction()) { + nonLazyScriptCount++; + } + return sharedData.prepareStorageFor(cx, nonLazyScriptCount, allScriptCount); + } + + static FunctionKey toFunctionKey(const SourceExtent& extent) { + // In eval("x=>1"), the arrow function will have a sourceStart of 0 which + // conflicts with the NullFunctionKey, so shift all keys by 1 instead. + auto result = extent.sourceStart + 1; + MOZ_ASSERT(result != NullFunctionKey); + return result; + } + + bool isInitialStencil() const { return functionKey == NullFunctionKey; } + + bool isCompilationStencil() const { return isInitialStencil(); } + inline CompilationStencil& asCompilationStencil(); + inline const CompilationStencil& asCompilationStencil() const; + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump(); + void dump(js::JSONPrinter& json); + void dumpFields(js::JSONPrinter& json); +#endif +}; + +// The output of GC allocation from stencil. +struct CompilationGCOutput { + // The resulting outermost script for the compilation powered + // by this CompilationStencil. + JSScript* script = nullptr; + + // The resulting module object if there is one. + ModuleObject* module = nullptr; + + // A Rooted vector to handle tracing of JSFunction* and Atoms within. + // + // If the top level script isn't a function, the item at TopLevelIndex is + // nullptr. + JS::GCVector functions; + + // References to scopes are controlled via AbstractScopePtr, which holds onto + // an index (and CompilationStencil reference). + JS::GCVector scopes; + + // The result ScriptSourceObject. This is unused in delazifying parses. + ScriptSourceObject* sourceObject = nullptr; + + CompilationGCOutput() = default; + + void trace(JSTracer* trc); +} JS_HAZ_GC_POINTER; + +class ScriptStencilIterable { + public: + class ScriptAndFunction { + public: + const ScriptStencil& script; + const ScriptStencilExtra* scriptExtra; + JSFunction* function; + ScriptIndex index; + + ScriptAndFunction() = delete; + ScriptAndFunction(const ScriptStencil& script, + const ScriptStencilExtra* scriptExtra, + JSFunction* function, ScriptIndex index) + : script(script), + scriptExtra(scriptExtra), + function(function), + index(index) {} + }; + + class Iterator { + size_t index_ = 0; + const BaseCompilationStencil& stencil_; + CompilationGCOutput& gcOutput_; + + Iterator(const BaseCompilationStencil& stencil, + CompilationGCOutput& gcOutput, size_t index) + : index_(index), stencil_(stencil), gcOutput_(gcOutput) { + MOZ_ASSERT(index == stencil.scriptData.size()); + } + + public: + explicit Iterator(const BaseCompilationStencil& stencil, + CompilationGCOutput& gcOutput) + : stencil_(stencil), gcOutput_(gcOutput) { + skipTopLevelNonFunction(); + } + + Iterator operator++() { + next(); + assertFunction(); + return *this; + } + + void next() { + MOZ_ASSERT(index_ < stencil_.scriptData.size()); + index_++; + } + + void assertFunction() { + if (index_ < stencil_.scriptData.size()) { + MOZ_ASSERT(stencil_.scriptData[index_].isFunction()); + } + } + + void skipTopLevelNonFunction() { + MOZ_ASSERT(index_ == 0); + if (stencil_.scriptData.size()) { + if (!stencil_.scriptData[0].isFunction()) { + next(); + assertFunction(); + } + } + } + + bool operator!=(const Iterator& other) const { + return index_ != other.index_; + } + + inline ScriptAndFunction operator*(); + + static Iterator end(const BaseCompilationStencil& stencil, + CompilationGCOutput& gcOutput) { + return Iterator(stencil, gcOutput, stencil.scriptData.size()); + } + }; + + const BaseCompilationStencil& stencil_; + CompilationGCOutput& gcOutput_; + + explicit ScriptStencilIterable(const BaseCompilationStencil& stencil, + CompilationGCOutput& gcOutput) + : stencil_(stencil), gcOutput_(gcOutput) {} + + Iterator begin() const { return Iterator(stencil_, gcOutput_); } + + Iterator end() const { return Iterator::end(stencil_, gcOutput_); } +}; + +// Input and output of compilation to stencil. +struct CompilationStencil : public BaseCompilationStencil { + static constexpr ScriptIndex TopLevelIndex = ScriptIndex(0); + + // This holds allocations that do not require destructors to be run but are + // live until the stencil is released. + LifoAlloc alloc; + + // Parameterized chunk size to use for LifoAlloc. + static constexpr size_t LifoAllocChunkSize = 512; + + CompilationInput input; + + // Initial-compilation-specific data for each script. + mozilla::Span scriptExtra; + + // Module metadata if this is a module compile. + mozilla::Maybe moduleMetadata; + + // AsmJS modules generated by parsing. + HashMap, + mozilla::DefaultHasher, js::SystemAllocPolicy> + asmJS; + + // Set to true once prepareForInstantiate is called. + // NOTE: This field isn't XDR-encoded. + bool preparationIsPerformed = false; + + // Track the state of key allocations and roll them back as parts of parsing + // get retried. This ensures iteration during stencil instantiation does not + // encounter discarded frontend state. + struct RewindToken { + // Temporarily share this token struct with CompilationState. + size_t scriptDataLength = 0; + + size_t asmJSCount = 0; + }; + + RewindToken getRewindToken(CompilationState& state); + void rewind(CompilationState& state, const RewindToken& pos); + + // Construct a CompilationStencil + CompilationStencil(JSContext* cx, const JS::ReadOnlyCompileOptions& options) + : alloc(LifoAllocChunkSize), input(options) {} + + static MOZ_MUST_USE bool prepareInputAndStencilForInstantiate( + JSContext* cx, CompilationInput& input, BaseCompilationStencil& stencil); + static MOZ_MUST_USE bool prepareGCOutputForInstantiate( + JSContext* cx, BaseCompilationStencil& stencil, + CompilationGCOutput& gcOutput); + + static MOZ_MUST_USE bool prepareForInstantiate(JSContext* cx, + CompilationStencil& stencil, + CompilationGCOutput& gcOutput); + static MOZ_MUST_USE bool instantiateStencils(JSContext* cx, + CompilationStencil& stencil, + CompilationGCOutput& gcOutput); + static MOZ_MUST_USE bool instantiateStencilsAfterPreparation( + JSContext* cx, CompilationInput& input, BaseCompilationStencil& stencil, + CompilationGCOutput& gcOutput); + + MOZ_MUST_USE bool serializeStencils(JSContext* cx, JS::TranscodeBuffer& buf, + bool* succeededOut = nullptr); + + // Move constructor is necessary to use Rooted, but must be explicit in + // order to steal the LifoAlloc data + CompilationStencil(CompilationStencil&& other) noexcept + : BaseCompilationStencil(std::move(other)), + alloc(LifoAllocChunkSize), + input(std::move(other.input)) { + // Steal the data from the LifoAlloc. + alloc.steal(&other.alloc); + } + + // To avoid any misuses, make sure this is neither copyable or assignable. + CompilationStencil(const CompilationStencil&) = delete; + CompilationStencil& operator=(const CompilationStencil&) = delete; + CompilationStencil& operator=(CompilationStencil&&) = delete; + + static ScriptStencilIterable functionScriptStencils( + BaseCompilationStencil& stencil, CompilationGCOutput& gcOutput) { + return ScriptStencilIterable(stencil, gcOutput); + } + + void trace(JSTracer* trc); + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump(); + void dump(js::JSONPrinter& json); + void dumpFields(js::JSONPrinter& json); +#endif +}; + +inline CompilationStencil& BaseCompilationStencil::asCompilationStencil() { + MOZ_ASSERT(isCompilationStencil()); + return *static_cast(this); +} + +inline const CompilationStencil& BaseCompilationStencil::asCompilationStencil() + const { + MOZ_ASSERT(isCompilationStencil()); + return *reinterpret_cast(this); +} + +inline ScriptStencilIterable::ScriptAndFunction +ScriptStencilIterable::Iterator::operator*() { + ScriptIndex index = ScriptIndex(index_); + const ScriptStencil& script = stencil_.scriptData[index]; + const ScriptStencilExtra* scriptExtra = nullptr; + if (stencil_.isInitialStencil()) { + scriptExtra = &stencil_.asCompilationStencil().scriptExtra[index]; + } + return ScriptAndFunction(script, scriptExtra, gcOutput_.functions[index], + index); +} + +// A set of stencils, for XDR purpose. +// This contains the initial compilation, and a vector of delazification. +struct CompilationStencilSet : public CompilationStencil { + private: + using ScriptIndexVector = Vector; + + MOZ_MUST_USE bool buildDelazificationIndices(JSContext* cx); + + public: + Vector delazifications; + ScriptIndexVector delazificationIndices; + CompilationAtomCache::AtomCacheVector delazificationAtomCache; + + CompilationStencilSet(JSContext* cx, + const JS::ReadOnlyCompileOptions& options) + : CompilationStencil(cx, options) {} + + // Move constructor is necessary to use Rooted. + CompilationStencilSet(CompilationStencilSet&& other) = default; + + // To avoid any misuses, make sure this is neither copyable or assignable. + CompilationStencilSet(const CompilationStencilSet&) = delete; + CompilationStencilSet& operator=(const CompilationStencilSet&) = delete; + CompilationStencilSet& operator=(CompilationStencilSet&&) = delete; + + MOZ_MUST_USE bool prepareForInstantiate( + JSContext* cx, CompilationGCOutput& gcOutput, + CompilationGCOutput& gcOutputForDelazification); + MOZ_MUST_USE bool instantiateStencils( + JSContext* cx, CompilationGCOutput& gcOutput, + CompilationGCOutput& gcOutputForDelazification); + MOZ_MUST_USE bool instantiateStencilsAfterPreparation( + JSContext* cx, CompilationGCOutput& gcOutput, + CompilationGCOutput& gcOutputForDelazification); + + MOZ_MUST_USE bool deserializeStencils(JSContext* cx, + const JS::TranscodeRange& range, + bool* succeededOut); +}; + +} // namespace frontend +} // namespace js + +#endif // frontend_CompilationInfo_h diff --git a/js/src/frontend/DefaultEmitter.cpp b/js/src/frontend/DefaultEmitter.cpp new file mode 100644 index 0000000000..a9e8512369 --- /dev/null +++ b/js/src/frontend/DefaultEmitter.cpp @@ -0,0 +1,76 @@ +/* -*- 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/DefaultEmitter.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter +#include "vm/Opcodes.h" // JSOp + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; +using mozilla::Nothing; + +DefaultEmitter::DefaultEmitter(BytecodeEmitter* bce) : bce_(bce) {} + +bool DefaultEmitter::prepareForDefault() { + MOZ_ASSERT(state_ == State::Start); + + // [stack] VALUE + + ifUndefined_.emplace(bce_); + if (!ifUndefined_->emitIf(Nothing())) { + return false; + } + + if (!bce_->emit1(JSOp::Dup)) { + // [stack] VALUE VALUE + return false; + } + if (!bce_->emit1(JSOp::Undefined)) { + // [stack] VALUE VALUE UNDEFINED + return false; + } + if (!bce_->emit1(JSOp::StrictEq)) { + // [stack] VALUE EQ? + return false; + } + + if (!ifUndefined_->emitThen()) { + // [stack] VALUE + return false; + } + + if (!bce_->emit1(JSOp::Pop)) { + // [stack] + return false; + } + +#ifdef DEBUG + state_ = State::Default; +#endif + return true; +} + +bool DefaultEmitter::emitEnd() { + MOZ_ASSERT(state_ == State::Default); + + // [stack] DEFAULTVALUE + + if (!ifUndefined_->emitEnd()) { + // [stack] VALUE/DEFAULTVALUE + return false; + } + ifUndefined_.reset(); + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} diff --git a/js/src/frontend/DefaultEmitter.h b/js/src/frontend/DefaultEmitter.h new file mode 100644 index 0000000000..3bba3b7c97 --- /dev/null +++ b/js/src/frontend/DefaultEmitter.h @@ -0,0 +1,65 @@ +/* -*- 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_DefaultEmitter_h +#define frontend_DefaultEmitter_h + +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS, MOZ_MUST_USE +#include "mozilla/Maybe.h" // Maybe + +#include "frontend/IfEmitter.h" // IfEmitter + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting default parameter or default value. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `x = 10` in `function (x = 10) {}` +// // the value of arguments[0] is on the stack +// DefaultEmitter de(this); +// de.prepareForDefault(); +// emit(10); +// de.emitEnd(); +// +class MOZ_STACK_CLASS DefaultEmitter { + BytecodeEmitter* bce_; + + mozilla::Maybe ifUndefined_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ prepareForDefault +---------+ emitEnd +-----+ + // | Start |------------------>| Default |-------->| End | + // +-------+ +---------+ +-----+ + enum class State { + // The initial state. + Start, + + // After calling prepareForDefault. + Default, + + // After calling emitEnd. + End + }; + State state_ = State::Start; +#endif + + public: + explicit DefaultEmitter(BytecodeEmitter* bce); + + MOZ_MUST_USE bool prepareForDefault(); + MOZ_MUST_USE bool emitEnd(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_LabelEmitter_h */ diff --git a/js/src/frontend/DestructuringFlavor.h b/js/src/frontend/DestructuringFlavor.h new file mode 100644 index 0000000000..d9d8fb26e1 --- /dev/null +++ b/js/src/frontend/DestructuringFlavor.h @@ -0,0 +1,24 @@ +/* -*- 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_DestructuringFlavor_h +#define frontend_DestructuringFlavor_h + +namespace js { +namespace frontend { + +enum class DestructuringFlavor { + // Destructuring into a declaration. + Declaration, + + // Destructuring as part of an AssignmentExpression. + Assignment +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_DestructuringFlavor_h */ diff --git a/js/src/frontend/DoWhileEmitter.cpp b/js/src/frontend/DoWhileEmitter.cpp new file mode 100644 index 0000000000..def0d1945c --- /dev/null +++ b/js/src/frontend/DoWhileEmitter.cpp @@ -0,0 +1,76 @@ +/* -*- 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/DoWhileEmitter.h" + +#include "frontend/BytecodeEmitter.h" +#include "frontend/SourceNotes.h" +#include "vm/Opcodes.h" +#include "vm/StencilEnums.h" // TryNoteKind + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; +using mozilla::Nothing; + +DoWhileEmitter::DoWhileEmitter(BytecodeEmitter* bce) : bce_(bce) {} + +bool DoWhileEmitter::emitBody(const Maybe& doPos, + const Maybe& bodyPos) { + MOZ_ASSERT(state_ == State::Start); + + // Ensure that the column of the 'do' is set properly. + if (doPos) { + if (!bce_->updateSourceCoordNotes(*doPos)) { + return false; + } + } + + // We need a nop here to make it possible to set a breakpoint on `do`. + if (!bce_->emit1(JSOp::Nop)) { + return false; + } + + loopInfo_.emplace(bce_, StatementKind::DoLoop); + + if (!loopInfo_->emitLoopHead(bce_, bodyPos)) { + return false; + } + +#ifdef DEBUG + state_ = State::Body; +#endif + return true; +} + +bool DoWhileEmitter::emitCond() { + MOZ_ASSERT(state_ == State::Body); + + if (!loopInfo_->emitContinueTarget(bce_)) { + return false; + } + +#ifdef DEBUG + state_ = State::Cond; +#endif + return true; +} + +bool DoWhileEmitter::emitEnd() { + MOZ_ASSERT(state_ == State::Cond); + + if (!loopInfo_->emitLoopEnd(bce_, JSOp::IfNe, TryNoteKind::Loop)) { + return false; + } + + loopInfo_.reset(); + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} diff --git a/js/src/frontend/DoWhileEmitter.h b/js/src/frontend/DoWhileEmitter.h new file mode 100644 index 0000000000..7bea693444 --- /dev/null +++ b/js/src/frontend/DoWhileEmitter.h @@ -0,0 +1,83 @@ +/* -*- 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_DoWhileEmitter_h +#define frontend_DoWhileEmitter_h + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" + +#include + +#include "frontend/BytecodeControlStructures.h" + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting bytecode for do-while loop. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `do body while (cond);` +// DoWhileEmitter doWhile(this); +// doWhile.emitBody(Some(offset_of_do), Some(offset_of_body)); +// emit(body); +// doWhile.emitCond(); +// emit(cond); +// doWhile.emitEnd(); +// +class MOZ_STACK_CLASS DoWhileEmitter { + BytecodeEmitter* bce_; + + mozilla::Maybe loopInfo_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ emitBody +------+ emitCond +------+ emitEnd +-----+ + // | Start |--------->| Body |--------->| Cond |--------->| End | + // +-------+ +------+ +------+ +-----+ + enum class State { + // The initial state. + Start, + + // After calling emitBody. + Body, + + // After calling emitCond. + Cond, + + // After calling emitEnd. + End + }; + State state_ = State::Start; +#endif + + public: + explicit DoWhileEmitter(BytecodeEmitter* bce); + + // Parameters are the offset in the source code for each character below: + // + // do { ... } while ( x < 20 ); + // ^ ^ + // | | + // | bodyPos + // | + // doPos + // + // Can be Nothing() if not available. + MOZ_MUST_USE bool emitBody(const mozilla::Maybe& doPos, + const mozilla::Maybe& bodyPos); + MOZ_MUST_USE bool emitCond(); + MOZ_MUST_USE bool emitEnd(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_DoWhileEmitter_h */ diff --git a/js/src/frontend/EitherParser.h b/js/src/frontend/EitherParser.h new file mode 100644 index 0000000000..7c8b8548cc --- /dev/null +++ b/js/src/frontend/EitherParser.h @@ -0,0 +1,167 @@ +/* -*- 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/. */ + +/* + * A variant-like class abstracting operations on a Parser with a given + * ParseHandler but unspecified character type. + */ + +#ifndef frontend_EitherParser_h +#define frontend_EitherParser_h + +#include "mozilla/Attributes.h" +#include "mozilla/Tuple.h" +#include "mozilla/Utf8.h" +#include "mozilla/Variant.h" + +#include +#include + +#include "frontend/BCEParserHandle.h" +#include "frontend/Parser.h" +#include "frontend/TokenStream.h" + +namespace js { + +namespace detail { + +template