diff options
Diffstat (limited to 'js/src/frontend/NameOpEmitter.cpp')
-rw-r--r-- | js/src/frontend/NameOpEmitter.cpp | 481 |
1 files changed, 481 insertions, 0 deletions
diff --git a/js/src/frontend/NameOpEmitter.cpp b/js/src/frontend/NameOpEmitter.cpp new file mode 100644 index 0000000000..f36c7a2028 --- /dev/null +++ b/js/src/frontend/NameOpEmitter.cpp @@ -0,0 +1,481 @@ +/* -*- 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/NameOpEmitter.h" + +#include "frontend/AbstractScopePtr.h" +#include "frontend/BytecodeEmitter.h" +#include "frontend/ParserAtom.h" // ParserAtom +#include "frontend/SharedContext.h" +#include "frontend/TDZCheckCache.h" +#include "frontend/ValueUsage.h" +#include "js/Value.h" +#include "vm/Opcodes.h" + +using namespace js; +using namespace js::frontend; + +NameOpEmitter::NameOpEmitter(BytecodeEmitter* bce, TaggedParserAtomIndex name, + Kind kind) + : bce_(bce), kind_(kind), name_(name), loc_(bce_->lookupName(name_)) {} + +NameOpEmitter::NameOpEmitter(BytecodeEmitter* bce, TaggedParserAtomIndex name, + const NameLocation& loc, Kind kind) + : bce_(bce), kind_(kind), name_(name), loc_(loc) {} + +bool NameOpEmitter::emitGet() { + MOZ_ASSERT(state_ == State::Start); + + switch (loc_.kind()) { + case NameLocation::Kind::Dynamic: + if (!bce_->emitAtomOp(JSOp::GetName, name_)) { + // [stack] VAL + return false; + } + break; + case NameLocation::Kind::Global: { + MOZ_ASSERT(bce_->outermostScope().hasNonSyntacticScopeOnChain() == + bce_->sc->hasNonSyntacticScope()); + if (bce_->sc->hasNonSyntacticScope()) { + if (!bce_->emitAtomOp(JSOp::GetName, name_)) { + // [stack] VAL + return false; + } + } else { + // Some names on the global are not configurable and have fixed values + // which we can emit instead. + if (name_ == TaggedParserAtomIndex::WellKnown::undefined()) { + if (!bce_->emit1(JSOp::Undefined)) { + return false; + } + } else if (name_ == TaggedParserAtomIndex::WellKnown::NaN()) { + if (!bce_->emitDouble(JS::GenericNaN())) { + return false; + } + } else if (name_ == TaggedParserAtomIndex::WellKnown::Infinity()) { + if (!bce_->emitDouble(JS::Infinity())) { + return false; + } + } else { + if (!bce_->emitAtomOp(JSOp::GetGName, name_)) { + // [stack] VAL + return false; + } + } + } + break; + } + case NameLocation::Kind::Intrinsic: + if (name_ == TaggedParserAtomIndex::WellKnown::undefined()) { + if (!bce_->emit1(JSOp::Undefined)) { + // [stack] Undefined + return false; + } + } else { + if (!bce_->emitAtomOp(JSOp::GetIntrinsic, name_)) { + // [stack] VAL + return false; + } + } + break; + case NameLocation::Kind::NamedLambdaCallee: + if (!bce_->emit1(JSOp::Callee)) { + // [stack] VAL + return false; + } + break; + case NameLocation::Kind::Import: + if (!bce_->emitAtomOp(JSOp::GetImport, name_)) { + // [stack] VAL + return false; + } + break; + case NameLocation::Kind::ArgumentSlot: + if (!bce_->emitArgOp(JSOp::GetArg, loc_.argumentSlot())) { + // [stack] VAL + return false; + } + break; + case NameLocation::Kind::FrameSlot: + if (!bce_->emitLocalOp(JSOp::GetLocal, loc_.frameSlot())) { + // [stack] VAL + return false; + } + if (loc_.isLexical()) { + if (!bce_->emitTDZCheckIfNeeded(name_, loc_, ValueIsOnStack::Yes)) { + // [stack] VAL + return false; + } + } + break; + case NameLocation::Kind::EnvironmentCoordinate: + case NameLocation::Kind::DebugEnvironmentCoordinate: + if (!bce_->emitEnvCoordOp( + loc_.kind() == NameLocation::Kind::EnvironmentCoordinate + ? JSOp::GetAliasedVar + : JSOp::GetAliasedDebugVar, + loc_.environmentCoordinate())) { + // [stack] VAL + return false; + } + if (loc_.isLexical()) { + if (!bce_->emitTDZCheckIfNeeded(name_, loc_, ValueIsOnStack::Yes)) { + // [stack] VAL + return false; + } + } + break; + case NameLocation::Kind::DynamicAnnexBVar: + MOZ_CRASH( + "Synthesized vars for Annex B.3.3 should only be used in " + "initialization"); + } + + if (isCall()) { + MOZ_ASSERT(bce_->outermostScope().hasNonSyntacticScopeOnChain() == + bce_->sc->hasNonSyntacticScope()); + switch (loc_.kind()) { + case NameLocation::Kind::Dynamic: + case NameLocation::Kind::Global: + MOZ_ASSERT(bce_->emitterMode != BytecodeEmitter::SelfHosting); + if (bce_->needsImplicitThis() || bce_->sc->hasNonSyntacticScope()) { + MOZ_ASSERT_IF(bce_->needsImplicitThis(), + loc_.kind() == NameLocation::Kind::Dynamic); + if (!bce_->emitAtomOp(JSOp::ImplicitThis, name_)) { + // [stack] CALLEE THIS + return false; + } + } else { + if (!bce_->emit1(JSOp::Undefined)) { + // [stack] CALLEE UNDEF + return false; + } + } + break; + case NameLocation::Kind::Intrinsic: + case NameLocation::Kind::NamedLambdaCallee: + case NameLocation::Kind::Import: + case NameLocation::Kind::ArgumentSlot: + case NameLocation::Kind::FrameSlot: + case NameLocation::Kind::EnvironmentCoordinate: + if (bce_->emitterMode == BytecodeEmitter::SelfHosting) { + if (!bce_->emitDebugCheckSelfHosted()) { + // [stack] CALLEE + return false; + } + } + if (!bce_->emit1(JSOp::Undefined)) { + // [stack] CALLEE UNDEF + return false; + } + break; + case NameLocation::Kind::DebugEnvironmentCoordinate: + MOZ_CRASH( + "DebugEnvironmentCoordinate should only be used to get the private " + "brand, and so should never call."); + break; + case NameLocation::Kind::DynamicAnnexBVar: + MOZ_CRASH( + "Synthesized vars for Annex B.3.3 should only be used in " + "initialization"); + } + } + +#ifdef DEBUG + state_ = State::Get; +#endif + return true; +} + +bool NameOpEmitter::prepareForRhs() { + MOZ_ASSERT(state_ == State::Start); + + switch (loc_.kind()) { + case NameLocation::Kind::Dynamic: + case NameLocation::Kind::Import: + case NameLocation::Kind::DynamicAnnexBVar: + if (!bce_->makeAtomIndex(name_, ParserAtom::Atomize::Yes, &atomIndex_)) { + return false; + } + if (loc_.kind() == NameLocation::Kind::DynamicAnnexBVar) { + // Annex B vars always go on the nearest variable environment, + // even if lexical environments in between contain same-named + // bindings. + if (!bce_->emit1(JSOp::BindVar)) { + // [stack] ENV + return false; + } + } else { + if (!bce_->emitAtomOp(JSOp::BindName, atomIndex_)) { + // [stack] ENV + return false; + } + } + emittedBindOp_ = true; + break; + case NameLocation::Kind::Global: + if (!bce_->makeAtomIndex(name_, ParserAtom::Atomize::Yes, &atomIndex_)) { + return false; + } + MOZ_ASSERT(bce_->outermostScope().hasNonSyntacticScopeOnChain() == + bce_->sc->hasNonSyntacticScope()); + + if (loc_.isLexical() && isInitialize()) { + // InitGLexical always gets the global lexical scope. It doesn't + // need a BindName/BindGName. + MOZ_ASSERT(bce_->innermostScope().is<GlobalScope>()); + } else if (bce_->sc->hasNonSyntacticScope()) { + if (!bce_->emitAtomOp(JSOp::BindName, atomIndex_)) { + // [stack] ENV + return false; + } + emittedBindOp_ = true; + } else { + if (!bce_->emitAtomOp(JSOp::BindGName, atomIndex_)) { + // [stack] ENV + return false; + } + emittedBindOp_ = true; + } + break; + case NameLocation::Kind::Intrinsic: + break; + case NameLocation::Kind::NamedLambdaCallee: + break; + case NameLocation::Kind::ArgumentSlot: + break; + case NameLocation::Kind::FrameSlot: + break; + case NameLocation::Kind::DebugEnvironmentCoordinate: + break; + case NameLocation::Kind::EnvironmentCoordinate: + break; + } + + // For compound assignments, first get the LHS value, then emit + // the RHS and the op. + if (isCompoundAssignment() || isIncDec()) { + if (loc_.kind() == NameLocation::Kind::Dynamic) { + // For dynamic accesses we need to emit GetBoundName instead of + // GetName for correctness: looking up @@unscopables on the + // environment chain (due to 'with' environments) must only happen + // once. + // + // GetBoundName uses the environment already pushed on the stack + // from the earlier BindName. + if (!bce_->emit1(JSOp::Dup)) { + // [stack] ENV ENV + return false; + } + if (!bce_->emitAtomOp(JSOp::GetBoundName, name_)) { + // [stack] ENV V + return false; + } + } else { + if (!emitGet()) { + // [stack] ENV? V + return false; + } + } + } + +#ifdef DEBUG + state_ = State::Rhs; +#endif + return true; +} + +#if defined(__clang__) && defined(XP_WIN) && \ + (defined(_M_X64) || defined(__x86_64__)) +// Work around a CPU bug. See bug 1524257. +__attribute__((__aligned__(32))) +#endif +bool NameOpEmitter::emitAssignment() { + MOZ_ASSERT(state_ == State::Rhs); + + switch (loc_.kind()) { + case NameLocation::Kind::Dynamic: + case NameLocation::Kind::Import: + case NameLocation::Kind::DynamicAnnexBVar: + if (!bce_->emitAtomOp(bce_->strictifySetNameOp(JSOp::SetName), + atomIndex_)) { + return false; + } + break; + case NameLocation::Kind::Global: { + JSOp op; + if (emittedBindOp_) { + MOZ_ASSERT(bce_->outermostScope().hasNonSyntacticScopeOnChain() == + bce_->sc->hasNonSyntacticScope()); + if (bce_->sc->hasNonSyntacticScope()) { + op = bce_->strictifySetNameOp(JSOp::SetName); + } else { + op = bce_->strictifySetNameOp(JSOp::SetGName); + } + } else { + op = JSOp::InitGLexical; + } + if (!bce_->emitAtomOp(op, atomIndex_)) { + return false; + } + break; + } + case NameLocation::Kind::Intrinsic: + if (!bce_->emitAtomOp(JSOp::SetIntrinsic, name_)) { + return false; + } + break; + case NameLocation::Kind::NamedLambdaCallee: + // Assigning to the named lambda is a no-op in sloppy mode but + // throws in strict mode. + if (bce_->sc->strict()) { + if (!bce_->emitAtomOp(JSOp::ThrowSetConst, name_)) { + return false; + } + } + break; + case NameLocation::Kind::ArgumentSlot: + if (!bce_->emitArgOp(JSOp::SetArg, loc_.argumentSlot())) { + return false; + } + break; + case NameLocation::Kind::FrameSlot: { + JSOp op = JSOp::SetLocal; + // Lexicals, Synthetics and Private Methods have very similar handling + // around a variety of areas, including initialization. + if (loc_.isLexical() || loc_.isPrivateMethod() || loc_.isSynthetic()) { + if (isInitialize()) { + op = JSOp::InitLexical; + } else { + if (loc_.isConst()) { + op = JSOp::ThrowSetConst; + } + if (!bce_->emitTDZCheckIfNeeded(name_, loc_, ValueIsOnStack::No)) { + return false; + } + } + } + if (op == JSOp::ThrowSetConst) { + if (!bce_->emitAtomOp(op, name_)) { + return false; + } + } else { + if (!bce_->emitLocalOp(op, loc_.frameSlot())) { + return false; + } + } + if (op == JSOp::InitLexical) { + if (!bce_->innermostTDZCheckCache->noteTDZCheck(bce_, name_, + DontCheckTDZ)) { + return false; + } + } + break; + } + case NameLocation::Kind::EnvironmentCoordinate: { + JSOp op = JSOp::SetAliasedVar; + // Lexicals, Synthetics and Private Methods have very similar handling + // around a variety of areas, including initialization. + if (loc_.isLexical() || loc_.isPrivateMethod() || loc_.isSynthetic()) { + if (isInitialize()) { + op = JSOp::InitAliasedLexical; + } else { + if (loc_.isConst()) { + op = JSOp::ThrowSetConst; + } + if (!bce_->emitTDZCheckIfNeeded(name_, loc_, ValueIsOnStack::No)) { + return false; + } + } + } + if (loc_.bindingKind() == BindingKind::NamedLambdaCallee) { + // Assigning to the named lambda is a no-op in sloppy mode and throws + // in strict mode. + op = JSOp::ThrowSetConst; + if (bce_->sc->strict()) { + if (!bce_->emitAtomOp(op, name_)) { + return false; + } + } + } else { + if (op == JSOp::ThrowSetConst) { + if (!bce_->emitAtomOp(op, name_)) { + return false; + } + } else { + if (!bce_->emitEnvCoordOp(op, loc_.environmentCoordinate())) { + return false; + } + } + } + if (op == JSOp::InitAliasedLexical) { + if (!bce_->innermostTDZCheckCache->noteTDZCheck(bce_, name_, + DontCheckTDZ)) { + return false; + } + } + break; + } + case NameLocation::Kind::DebugEnvironmentCoordinate: + MOZ_CRASH("Shouldn't be assigning to a private brand"); + break; + } + +#ifdef DEBUG + state_ = State::Assignment; +#endif + return true; +} + +bool NameOpEmitter::emitIncDec(ValueUsage valueUsage) { + MOZ_ASSERT(state_ == State::Start); + + JSOp incOp = isInc() ? JSOp::Inc : JSOp::Dec; + if (!prepareForRhs()) { + // [stack] ENV? V + return false; + } + if (!bce_->emit1(JSOp::ToNumeric)) { + // [stack] ENV? N + return false; + } + if (isPostIncDec() && valueUsage == ValueUsage::WantValue) { + if (!bce_->emit1(JSOp::Dup)) { + // [stack] ENV? N N + return false; + } + } + if (!bce_->emit1(incOp)) { + // [stack] ENV? N? N+1 + return false; + } + if (isPostIncDec() && emittedBindOp() && + valueUsage == ValueUsage::WantValue) { + if (!bce_->emit2(JSOp::Pick, 2)) { + // [stack] N N+1 ENV + return false; + } + if (!bce_->emit1(JSOp::Swap)) { + // [stack] N ENV N+1 + return false; + } + } + if (!emitAssignment()) { + // [stack] N? N+1 + return false; + } + if (isPostIncDec() && valueUsage == ValueUsage::WantValue) { + if (!bce_->emit1(JSOp::Pop)) { + // [stack] N + return false; + } + } + +#ifdef DEBUG + state_ = State::IncDec; +#endif + return true; +} |