/* -*- 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 "vm/Opcodes.h" using namespace js; using namespace js::frontend; 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(TaggedParserAtomIndex name) { MOZ_ASSERT(state_ == State::Start); // [stack] NameOpEmitter noe( bce_, name, isCall() ? NameOpEmitter::Kind::Call : NameOpEmitter::Kind::Get); if (!noe.emitGet()) { // [stack] # if isCall() // [stack] CALLEE THIS // [stack] # if isNew() or isSuperCall() // [stack] CALLEE return false; } state_ = State::NameCallee; return true; } [[nodiscard]] PropOpEmitter& CallOrNewEmitter::prepareForPropCallee( bool isSuperProp) { MOZ_ASSERT(state_ == State::Start); MOZ_ASSERT(bce_->emitterMode != BytecodeEmitter::SelfHosting); // [stack] poe_.emplace(bce_, isCall() ? PropOpEmitter::Kind::Call : PropOpEmitter::Kind::Get, isSuperProp ? PropOpEmitter::ObjKind::Super : PropOpEmitter::ObjKind::Other); state_ = State::PropCallee; return *poe_; } [[nodiscard]] ElemOpEmitter& CallOrNewEmitter::prepareForElemCallee( bool isSuperElem) { MOZ_ASSERT(state_ == State::Start); MOZ_ASSERT(bce_->emitterMode != BytecodeEmitter::SelfHosting); // [stack] eoe_.emplace(bce_, isCall() ? ElemOpEmitter::Kind::Call : ElemOpEmitter::Kind::Get, isSuperElem ? ElemOpEmitter::ObjKind::Super : ElemOpEmitter::ObjKind::Other); state_ = State::ElemCallee; return *eoe_; } PrivateOpEmitter& CallOrNewEmitter::prepareForPrivateCallee( TaggedParserAtomIndex privateName) { MOZ_ASSERT(state_ == State::Start); MOZ_ASSERT(bce_->emitterMode != BytecodeEmitter::SelfHosting); // [stack] xoe_.emplace( bce_, isCall() ? PrivateOpEmitter::Kind::Call : PrivateOpEmitter::Kind::Get, privateName); state_ = State::PrivateCallee; return *xoe_; } bool CallOrNewEmitter::prepareForFunctionCallee() { MOZ_ASSERT(state_ == State::Start); MOZ_ASSERT(bce_->emitterMode != BytecodeEmitter::SelfHosting); // [stack] state_ = State::FunctionCallee; return true; } bool CallOrNewEmitter::emitSuperCallee() { MOZ_ASSERT(state_ == State::Start); MOZ_ASSERT(bce_->emitterMode != BytecodeEmitter::SelfHosting); // [stack] if (!bce_->emitThisEnvironmentCallee()) { // [stack] CALLEE return false; } if (!bce_->emit1(JSOp::SuperFun)) { // [stack] SUPER_FUN return false; } if (!bce_->emit1(JSOp::IsConstructing)) { // [stack] SUPER_FUN IS_CONSTRUCTING return false; } state_ = State::SuperCallee; return true; } bool CallOrNewEmitter::prepareForOtherCallee() { MOZ_ASSERT(state_ == State::Start); MOZ_ASSERT(bce_->emitterMode != BytecodeEmitter::SelfHosting); // [stack] state_ = State::OtherCallee; return true; } bool CallOrNewEmitter::emitThis() { MOZ_ASSERT(state_ == State::NameCallee || state_ == State::PropCallee || state_ == State::ElemCallee || state_ == State::PrivateCallee || state_ == State::FunctionCallee || state_ == State::SuperCallee || state_ == State::OtherCallee); // [stack] # if isCall() // [stack] CALLEE THIS? // [stack] # if isNew() or isSuperCall() // [stack] CALLEE 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::PrivateCallee: xoe_.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 IS_CONSTRUCTING return false; } } else { if (!bce_->emit1(JSOp::Undefined)) { // [stack] CALLEE THIS return false; } } } // [stack] CALLEE THIS state_ = State::This; return true; } bool CallOrNewEmitter::prepareForNonSpreadArguments() { MOZ_ASSERT(state_ == State::This); MOZ_ASSERT(!isSpread()); // [stack] CALLEE THIS 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()); // [stack] CALLEE THIS state_ = State::WantSpreadOperand; return isSingleSpread() || isPassthroughRest(); } bool CallOrNewEmitter::prepareForSpreadArguments() { MOZ_ASSERT(state_ == State::WantSpreadOperand); MOZ_ASSERT(isSpread()); MOZ_ASSERT(!isSingleSpread() && !isPassthroughRest()); // [stack] CALLEE THIS state_ = State::Arguments; return true; } bool CallOrNewEmitter::emitSpreadArgumentsTest() { // Caller should check wantSpreadOperand before this. MOZ_ASSERT(state_ == State::WantSpreadOperand); MOZ_ASSERT(isSpread()); MOZ_ASSERT(isSingleSpread() || isPassthroughRest()); // [stack] CALLEE THIS ARG0 if (isSingleSpread()) { // Emit a preparation code to optimize the spread call: // // g(...args); // // If the spread operand is a packed array, skip the spread // operation and pass it directly to spread call operation. // See the comment in OptimizeSpreadCall in Interpreter.cpp // for the optimizable conditions. // [stack] CALLEE THIS ARG0 ifNotOptimizable_.emplace(bce_); if (!bce_->emit1(JSOp::Dup)) { // [stack] CALLEE THIS ARG0 ARG0 return false; } if (!bce_->emit1(JSOp::OptimizeSpreadCall)) { // [stack] CALLEE THIS ARG0 ARRAY_OR_UNDEF return false; } if (!bce_->emit1(JSOp::Dup)) { // [stack] CALLEE THIS ARG0 ARRAY_OR_UNDEF ARRAY_OR_UNDEF return false; } if (!bce_->emit1(JSOp::Undefined)) { // [stack] CALLEE THIS ARG0 ARRAY_OR_UNDEF ARRAY_OR_UNDEF UNDEF return false; } if (!bce_->emit1(JSOp::StrictEq)) { // [stack] CALLEE THIS ARG0 ARRAY_OR_UNDEF EQ return false; } if (!ifNotOptimizable_->emitThenElse()) { // [stack] CALLEE THIS ARG0 ARRAY_OR_UNDEF return false; } if (!bce_->emit1(JSOp::Pop)) { // [stack] CALLEE THIS ARG0 return false; } } state_ = State::SpreadArgumentsTest; return true; } bool CallOrNewEmitter::wantSpreadIteration() { MOZ_ASSERT(state_ == State::SpreadArgumentsTest); MOZ_ASSERT(isSpread()); state_ = State::SpreadIteration; return !isPassthroughRest(); } bool CallOrNewEmitter::emitSpreadArgumentsTestEnd() { MOZ_ASSERT(state_ == State::SpreadIteration); MOZ_ASSERT(isSpread()); if (isSingleSpread()) { if (!ifNotOptimizable_->emitElse()) { // [stack] CALLEE THIS ARG0 ARRAY_OR_UNDEF return false; } if (!bce_->emit1(JSOp::Swap)) { // [stack] CALLEE THIS ARRAY_OR_UNDEF ARG0 return false; } if (!bce_->emit1(JSOp::Pop)) { // [stack] CALLEE THIS ARRAY_OR_UNDEF return false; } if (!ifNotOptimizable_->emitEnd()) { // [stack] CALLEE THIS ARR return false; } ifNotOptimizable_.reset(); } state_ = State::Arguments; return true; } bool CallOrNewEmitter::emitEnd(uint32_t argc, uint32_t beginPos) { MOZ_ASSERT(state_ == State::Arguments); // [stack] # if isCall() // [stack] CALLEE THIS ARG0 ... ARGN // [stack] # if isNew() or isSuperCall() // [stack] CALLEE IS_CONSTRUCTING ARG0 ... ARGN NEW.TARGET? 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()) { uint32_t lineNum = bce_->errorReporter().lineAt(beginPos); if (!bce_->emitUint32Operand(JSOp::Lineno, lineNum)) { // [stack] RVAL return false; } } state_ = State::End; return true; }