diff options
Diffstat (limited to '')
-rw-r--r-- | js/src/frontend/CallOrNewEmitter.cpp | 277 |
1 files changed, 277 insertions, 0 deletions
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<uint32_t>& 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; +} |