/* 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 <stdint.h>

#include "frontend/ElemOpEmitter.h"
#include "frontend/IfEmitter.h"
#include "frontend/PrivateOpEmitter.h"
#include "frontend/PropOpEmitter.h"
#include "frontend/ValueUsage.h"
#include "vm/BytecodeUtil.h"
#include "vm/Opcodes.h"

namespace js {
namespace frontend {

struct BytecodeEmitter;
class TaggedParserAtomIndex;

// 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, 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, 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, offset_of_callee);
//
//   `callee.#method(arg);`
//     CallOrNewEmitter cone(this, JSOp::Call,
//                           CallOrNewEmitter::ArgumentsKind::Other,
//                           ValueUsage::WantValue);
//     PrivateOpEmitter& xoe = cone.prepareForPrivateCallee();
//     ... emit `callee.#method` with `xoe` here...
//     cone.prepareForNonSpreadArguments();
//     emit(arg);
//     cone.emitEnd(1, 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, 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, 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, offset_of_callee);
//
//   `print(...arg);`
//     CallOrNewEmitter cone(this, JSOp::SpreadCall,
//                           CallOrNewEmitter::ArgumentsKind::SingleSpread,
//                           ValueUsage::WantValue);
//     cone.emitNameCallee(print);
//     cone.emitThis();
//     if (cone.wantSpreadOperand()) {
//       emit(arg)
//     }
//     cone.emitSpreadArgumentsTest();
//     emit([...arg]);
//     cone.emitEnd(1, 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, offset_of_callee);
//
class MOZ_STACK_CLASS CallOrNewEmitter {
 public:
  enum class ArgumentsKind {
    Other,

    // Specify this for the following case:
    //
    //   g(...input);
    //
    // This enables optimization to avoid allocating an intermediate array
    // for spread operation.
    //
    // wantSpreadOperand() returns true when this is specified.
    SingleSpread,

    // Used for default derived class constructors:
    //
    //   constructor(...args) {
    //      super(...args);
    //   }
    //
    // The rest-parameter is directly passed through to the `super` call without
    // using the iteration protocol.
    PassthroughRest,
  };

 private:
  BytecodeEmitter* bce_;

  // The opcode for the call or new.
  JSOp op_;

  // Whether the call is a spread call with single parameter or not.
  // See the comment in emitSpreadArgumentsTest for more details.
  ArgumentsKind argumentsKind_;

  // The branch for spread call optimization.
  mozilla::Maybe<InternalIfEmitter> ifNotOptimizable_;

  mozilla::Maybe<PropOpEmitter> poe_;
  mozilla::Maybe<ElemOpEmitter> eoe_;
  mozilla::Maybe<PrivateOpEmitter> xoe_;

  // The state of this emitter.
  //
  // +-------+   emitNameCallee           +------------+
  // | Start |-+------------------------->| NameCallee |------+
  // +-------+ |                          +------------+      |
  //           |                                              |
  //           | prepareForPropCallee     +------------+      v
  //           +------------------------->| PropCallee |----->+
  //           |                          +------------+      |
  //           |                                              |
  //           | prepareForElemCallee     +------------+      v
  //           +------------------------->| ElemCallee |----->+
  //           |                          +------------+      |
  //           |                                              |
  //           | prepareForPrivateCallee  +---------------+   v
  //           +------------------------->| PrivateCallee |-->+
  //           |                          +---------------+   |
  //           |                                              |
  //           | prepareForFunctionCallee +----------------+  v
  //           +------------------------->| FunctionCallee |->+
  //           |                          +----------------+  |
  //           |                                              |
  //           | emitSuperCallee          +-------------+     v
  //           +------------------------->| SuperCallee |---->+
  //           |                          +-------------+     |
  //           |                                              |
  //           | prepareForOtherCallee    +-------------+     v
  //           +------------------------->| OtherCallee |---->+
  //                                      +-------------+     |
  //                                                          |
  // +--------------------------------------------------------+
  // |
  // | emitThis +------+
  // +--------->| This |-+
  //            +------+ |
  //                     |
  // +-------------------+
  // |
  // | [!isSpread]
  // |   prepareForNonSpreadArguments    +-----------+ emitEnd +-----+
  // +------------------------------->+->| Arguments |-------->| End |
  // |                                ^  +-----------+         +-----+
  // |                                |
  // |                                +<------------------------------------+
  // |                                |                                     |
  // |                                | emitSpreadArgumentsTestEnd          |
  // |                                |                                     |
  // |                                |         +-----------------+         |
  // |                                +---------| SpreadIteration |------+  |
  // |                                          +-----------------+      |  |
  // |                                +----------------------------------+  |
  // |                                |                                     |
  // |                                | wantSpreadIteration                 |
  // |                                |                                     |
  // |                                |         +---------------------+     |
  // |                                +---------| SpreadArgumentsTest |--+  |
  // |                                          +---------------------+  |  |
  // | [isSpread]                                                        |  |
  // |   wantSpreadOperand +-------------------+ emitSpreadArgumentsTest |  |
  // +-------------------->| WantSpreadOperand |-------------------------+  |
  // |                     +-------------------+                            |
  // |                                                                      |
  // |                                                                      |
  // |                                                                      |
  // | [isSpread]                                                           |
  // |   prepareForSpreadArguments                                          |
  // +----------------------------------------------------------------------+
  enum class State {
    // The initial state.
    Start,

    // After calling emitNameCallee.
    NameCallee,

    // After calling prepareForPropCallee.
    PropCallee,

    // After calling prepareForElemCallee.
    ElemCallee,

    // After calling prepareForPrivateCallee.
    PrivateCallee,

    // After calling prepareForFunctionCallee.
    FunctionCallee,

    // After calling emitSuperCallee.
    SuperCallee,

    // After calling prepareForOtherCallee.
    OtherCallee,

    // After calling emitThis.
    This,

    // After calling wantSpreadOperand.
    WantSpreadOperand,

    // After calling emitSpreadArgumentsTest.
    SpreadArgumentsTest,

    // After calling wantSpreadIteration.
    SpreadIteration,

    // After calling prepareForNonSpreadArguments.
    Arguments,

    // After calling emitEnd.
    End
  };
  State state_ = State::Start;

 public:
  CallOrNewEmitter(BytecodeEmitter* bce, JSOp op, ArgumentsKind argumentsKind,
                   ValueUsage valueUsage);

 private:
  [[nodiscard]] bool isCall() const {
    return op_ == JSOp::Call || op_ == JSOp::CallIgnoresRv ||
           op_ == JSOp::SpreadCall || isEval();
  }

  [[nodiscard]] bool isNew() const {
    return op_ == JSOp::New || op_ == JSOp::SpreadNew;
  }

  [[nodiscard]] bool isSuperCall() const {
    return op_ == JSOp::SuperCall || op_ == JSOp::SpreadSuperCall;
  }

  [[nodiscard]] bool isEval() const {
    return op_ == JSOp::Eval || op_ == JSOp::StrictEval ||
           op_ == JSOp::SpreadEval || op_ == JSOp::StrictSpreadEval;
  }

  [[nodiscard]] bool isSpread() const { return IsSpreadOp(op_); }

  [[nodiscard]] bool isSingleSpread() const {
    return argumentsKind_ == ArgumentsKind::SingleSpread;
  }

  [[nodiscard]] bool isPassthroughRest() const {
    return argumentsKind_ == ArgumentsKind::PassthroughRest;
  }

 public:
  [[nodiscard]] bool emitNameCallee(TaggedParserAtomIndex name);
  [[nodiscard]] PropOpEmitter& prepareForPropCallee(bool isSuperProp);
  [[nodiscard]] ElemOpEmitter& prepareForElemCallee(bool isSuperElem);
  [[nodiscard]] PrivateOpEmitter& prepareForPrivateCallee(
      TaggedParserAtomIndex privateName);
  [[nodiscard]] bool prepareForFunctionCallee();
  [[nodiscard]] bool emitSuperCallee();
  [[nodiscard]] bool prepareForOtherCallee();

  [[nodiscard]] bool emitThis();

  [[nodiscard]] bool prepareForNonSpreadArguments();
  [[nodiscard]] bool prepareForSpreadArguments();

  // See the usage in the comment at the top of the class.
  [[nodiscard]] bool wantSpreadOperand();
  [[nodiscard]] bool emitSpreadArgumentsTest();
  [[nodiscard]] bool emitSpreadArgumentsTestEnd();
  [[nodiscard]] bool wantSpreadIteration();

  // Parameters are the offset in the source code for each character below:
  //
  //   callee(arg);
  //   ^
  //   |
  //   beginPos
  [[nodiscard]] bool emitEnd(uint32_t argc, uint32_t beginPos);
};

} /* namespace frontend */
} /* namespace js */

#endif /* frontend_CallOrNewEmitter_h */
