summaryrefslogtreecommitdiffstats
path: root/js/src/frontend/CallOrNewEmitter.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--js/src/frontend/CallOrNewEmitter.cpp277
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;
+}