/*
   +----------------------------------------------------------------------+
   | HipHop for PHP                                                       |
   +----------------------------------------------------------------------+
   | Copyright (c) 2010-2014 Facebook, Inc. (http://www.facebook.com)     |
   +----------------------------------------------------------------------+
   | This source file is subject to version 3.01 of the PHP license,      |
   | that is bundled with this package in the file LICENSE, and is        |
   | available through the world-wide-web at the following url:           |
   | http://www.php.net/license/3_01.txt                                  |
   | If you did not receive a copy of the PHP license and are unable to   |
   | obtain it through the world-wide-web, please send a note to          |
   | license@php.net so we can mail you a copy immediately.               |
   +----------------------------------------------------------------------+
*/

#ifndef incl_HPHP_RUNTIME_VM_TRANSLATOR_HOPT_HHBCTRANSLATOR_H_
#define incl_HPHP_RUNTIME_VM_TRANSLATOR_HOPT_HHBCTRANSLATOR_H_

#include <vector>
#include <memory>
#include <stack>
#include <utility>

#include "folly/Optional.h"

#include "hphp/util/assertions.h"
#include "hphp/util/ringbuffer.h"
#include "hphp/runtime/vm/bytecode.h"
#include "hphp/runtime/vm/member-operations.h"
#include "hphp/runtime/vm/jit/guard-relaxation.h"
#include "hphp/runtime/vm/jit/ir-builder.h"
#include "hphp/runtime/vm/jit/runtime-type.h"
#include "hphp/runtime/vm/jit/translator.h"
#include "hphp/runtime/vm/srckey.h"

namespace HPHP {
namespace JIT {

struct PropInfo;

//////////////////////////////////////////////////////////////////////

/*
 * This module is responsible for determining high-level HHBC->IR
 * compilation strategy.
 *
 * For each bytecode Foo in HHBC, there is a function in this class
 * called emitFoo which handles translating it into HHIR.
 *
 * Additionally, while transating bytecode, this module manages a
 * virtual execution stack modelling the state of the stack since the
 * last time we emitted an IR instruction that materialized it
 * (e.g. SpillStack or SpillFrame).
 *
 * HhbcTranslator is where we make optimiations that relate to overall
 * knowledge of the runtime and HHBC.  For example, decisions like
 * whether to use IR instructions that have constant Class*'s (for a
 * AttrUnique class) instead of loading a Class* from RDS are
 * made at this level.
 */
struct HhbcTranslator {
  HhbcTranslator(Offset startOffset,
                 uint32_t initialSpOffsetFromFp,
                 bool inGenerator,
                 const Func* func);

  // Accessors.
  IRBuilder& irBuilder() const { return *m_irb.get(); }
  IRUnit& unit() { return m_unit; }

  // In between each emit* call, irtranslator indicates the new
  // bytecode offset (or whether we're finished) using this API.
  void setBcOff(Offset newOff, bool lastBcOff, bool maybeStartBlock = false);

  // End a bytecode block and do the right thing with fallthrough.
  void endBlock(Offset next);

  void end();
  void end(Offset nextPc);

  // Tracelet guards.
  void guardTypeStack(uint32_t stackIndex, Type type, bool outerOnly);
  void guardTypeLocal(uint32_t locId,      Type type, bool outerOnly);
  void guardTypeLocation(const RegionDesc::Location& loc, Type type,
                         bool outerOnly);
  void guardRefs(int64_t entryArDelta,
                 const std::vector<bool>& mask,
                 const std::vector<bool>& vals);

  // Interface to irtranslator for predicted and inferred types.
  void assertTypeLocal(uint32_t localIndex, Type type);
  void assertTypeStack(uint32_t stackIndex, Type type);
  void checkTypeLocal(uint32_t localIndex, Type type, Offset dest = -1);
  void checkTypeStack(uint32_t idx, Type type, Offset dest);
  void checkTypeTopOfStack(Type type, Offset nextByteCode);
  void assertType(const RegionDesc::Location& loc, Type type);
  void checkType(const RegionDesc::Location& loc, Type type,
                         Offset dest);
  void assertClass(const RegionDesc::Location& loc, const Class* cls);

  RuntimeType rttFromLocation(const Location& loc);

  // Inlining-related functions.
  void beginInlining(unsigned numArgs,
                     const Func* target,
                     Offset returnBcOffset,
                     Type retTypePred = Type::Gen);
  bool isInlining() const;
  int inliningDepth() const;
  void profileFunctionEntry(const char* category);
  void profileInlineFunctionShape(const std::string& str);
  void profileSmallFunctionShape(const std::string& str);
  void profileFailedInlShape(const std::string& str);

  // Other public functions for irtranslator.
  void setThisAvailable();
  void emitInterpOne(const NormalizedInstruction&);
  void emitInterpOne(int popped);
  void emitInterpOne(Type t, int popped);
  void emitInterpOne(folly::Optional<Type> t, int popped, int pushed,
                     InterpOneData& id);
  std::string showStack() const;
  bool hasExit() const {
    return m_hasExit;
  }

  /*
   * An emit* function for each HHBC opcode.
   */

  void emitPrint();
  void emitThis();
  void emitCheckThis();
  void emitBareThis(int notice);
  void emitInitThisLoc(int32_t id);
  void emitArray(int arrayId);
  void emitNewArray(int capacity);
  void emitNewPackedArray(int n);
  void emitNewStructArray(uint32_t n, StringData** keys);
  void emitNewCol(int capacity);
  void emitClone();

  void emitArrayAdd();
  void emitAddElemC();
  void emitAddNewElemC();
  void emitNewCol(int type, int numElems);
  void emitColAddElemC();
  void emitColAddNewElemC();
  void emitDefCns(uint32_t id);
  void emitCnsCommon(uint32_t id, uint32_t fallbackId, bool error);
  void emitCns(uint32_t id);
  void emitCnsE(uint32_t id);
  void emitCnsU(uint32_t id, uint32_t fallbackId);
  void emitConcat();
  void emitDefCls(int id, Offset after);
  void emitDefFunc(int id);

  void emitLateBoundCls();
  void emitSelf();
  void emitParent();

  void emitString(int strId);
  void emitInt(int64_t val);
  void emitDouble(double val);
  void emitNullUninit();
  void emitNull();
  void emitTrue();
  void emitFalse();
  void emitCGetL(int32_t id);
  void emitFPassL(int32_t id);
  void emitPushL(uint32_t id);
  void emitCGetL2(int32_t id);
  void emitCGetS();
  void emitCGetG();
  void emitMInstr(const NormalizedInstruction& ni);
  void emitVGetL(int32_t id);
  void emitVGetS();
  void emitVGetG();
  void emitSetL(int32_t id);
  void emitSetS();
  void emitSetG();
  void emitBindL(int32_t id);
  void emitBindS();
  void emitBindG();
  void emitUnsetL(int32_t id);
  void emitIssetL(int32_t id);
  void emitIssetS();
  void emitIssetG();
  void emitEmptyL(int32_t id);
  void emitEmptyS();
  void emitEmptyG();
  // The subOp param can be one of either
  // Add, Sub, Mul, Div, Mod, Shl, Shr, Concat, BitAnd, BitOr, BitXor
  void emitSetOpL(Op subOp, uint32_t id);
  // the pre, inc, and over params encode the 8 possible sub opcodes
  void emitIncDecL(bool pre, bool inc, bool over, uint32_t id);
  void emitPopA();
  void emitPopC();
  void emitPopV();
  void emitPopR();
  void emitDup();
  void emitUnboxR();
  void emitJmpZ(Offset taken, Offset next, bool bothPaths);
  void emitJmpNZ(Offset taken, Offset next, bool bothPaths);
  void emitJmp(int32_t offset, bool breakTracelet, bool noSurprise);
  void emitGt()    { emitCmp(Gt);    }
  void emitGte()   { emitCmp(Gte);   }
  void emitLt()    { emitCmp(Lt);    }
  void emitLte()   { emitCmp(Lte);   }
  void emitEq()    { emitCmp(Eq);    }
  void emitNeq()   { emitCmp(Neq);   }
  void emitSame()  { emitCmp(Same);  }
  void emitNSame() { emitCmp(NSame); }
  void emitFPassCOp();
  void emitFPassR();
  void emitFPassV();
  void emitFPushCufIter(int32_t numParams, int32_t itId);
  void emitFPushCufOp(Op op, int32_t numArgs);
  bool emitFPushCufArray(SSATmp* callable, int32_t numParams);
  void emitFPushCufUnknown(Op op, int32_t numArgs);
  void emitFPushActRec(SSATmp* func, SSATmp* objOrClass, int32_t numArgs,
                       const StringData* invName = nullptr);
  void emitFPushFuncCommon(const Func* func,
                           const StringData* name,
                           const StringData* fallback,
                           int32_t numParams);
  void emitFPushFuncD(int32_t numParams, int32_t funcId);
  void emitFPushFuncU(int32_t numParams,
                      int32_t funcId,
                      int32_t fallbackFuncId);
  void emitFPushFunc(int32_t numParams);
  void emitFPushFuncObj(int32_t numParams);
  void emitFPushFuncArr(int32_t numParams);
  SSATmp* genClsMethodCtx(const Func* callee, const Class* cls);
  void emitFPushClsMethod(int32_t numParams);
  void emitFPushClsMethodD(int32_t numParams,
                           int32_t methodNameStrId,
                           int32_t clssNamedEntityPairId);
  void emitFPushObjMethodD(int32_t numParams,
                           int32_t methodNameStrId);
  void emitFPushObjMethodCommon(SSATmp* obj,
                                const StringData* methodName,
                                int32_t numParams,
                                bool shouldFatal,
                                SSATmp* extraSpill = nullptr);
  void emitFPushClsMethodF(int32_t numParams);
  SSATmp* emitAllocObjFast(const Class* cls);
  void emitFPushCtorD(int32_t numParams, int32_t classNameStrId);
  void emitFPushCtor(int32_t numParams);
  void emitFPushCtorCommon(SSATmp* cls,
                           SSATmp* obj,
                           const Func* func,
                           int32_t numParams);
  void emitCreateCl(int32_t numParams, int32_t classNameStrId);
  void emitFCallArray(const Offset pcOffset, const Offset after,
                      bool destroyLocals);
  void emitFCall(uint32_t numParams, Offset returnBcOffset,
                 const Func* callee, bool destroyLocals);
  void emitFCallBuiltin(uint32_t numArgs, uint32_t numNonDefault,
                        int32_t funcId, bool destroyLocals);
  void emitClsCnsD(int32_t cnsNameStrId, int32_t clsNameStrId, Type outPred);
  void emitClsCns(int32_t cnsNameStrId);
  void emitAKExists();
  void emitAGetC();
  void emitAGetL(int localId);
  void emitIsScalarL(int id);
  void emitIsScalarC();
  void emitVerifyTypeImpl(int32_t id);
  void emitVerifyParamType(int32_t paramId);
  void emitVerifyRetTypeC();
  void emitVerifyRetTypeV();
  void emitInstanceOfD(int classNameStrId);
  void emitInstanceOf();
  void emitNop() {}
  void emitCastBool();
  void emitCastInt();
  void emitCastDouble();
  void emitCastString();
  void emitCastArray();
  void emitCastObject();

  void emitSwitch(const ImmVector&, int64_t base, bool bounded);
  void emitSSwitch(const ImmVector&);

  // freeInline indicates whether we should be doing decrefs inlined in
  // the TC, or using the generic decref helper.
  void emitRetC(bool freeInline);
  void emitRetV(bool freeInline);

  // miscelaneous ops
  void emitFloor();
  void emitCeil();
  void emitCheckProp(Id propId);
  void emitInitProp(Id propId, InitPropOp op);
  void emitAssertTL(int32_t id, AssertTOp);
  void emitAssertTStk(int32_t offset, AssertTOp);
  void emitAssertObjL(int32_t id, Id, AssertObjOp);
  void emitAssertObjStk(int32_t offset, Id, AssertObjOp);
  void emitPredictTL(int32_t id, AssertTOp);
  void emitPredictTStk(int32_t offset, AssertTOp);

  // arithmetic ops
  void emitAdd();
  void emitSub();
  void emitMul();
  void emitBitAnd();
  void emitBitOr();
  void emitBitXor();
  void emitBitNot();
  void emitAbs();
  void emitMod();
  void emitDiv();
  void emitSqrt();
  void emitShl();
  void emitShr();
  void emitAddO();
  void emitSubO();
  void emitMulO();

  // boolean ops
  void emitXor();
  void emitNot();
  void emitNativeImpl();

  void emitClassExists();
  void emitInterfaceExists();
  void emitTraitExists();

  void emitStaticLocInit(uint32_t varId, uint32_t litStrId);
  void emitStaticLoc(uint32_t varId, uint32_t litStrId);
  void emitReqDoc(const StringData* name);

  // iterators
  void emitIterInit(uint32_t iterId,
                    int targetOffset,
                    uint32_t valLocalId,
                    bool invertCond);
  void emitIterInitK(uint32_t iterId,
                     int targetOffset,
                     uint32_t valLocalId,
                     uint32_t keyLocalId,
                     bool invertCond);
  void emitIterNext(uint32_t iterId,
                    int targetOffset,
                    uint32_t valLocalId,
                    bool invertCond);
  void emitIterNextK(uint32_t iterId,
                     int targetOffset,
                     uint32_t valLocalId,
                     uint32_t keyLocalId,
                     bool invertCond);
  void emitMIterInit(uint32_t iterId, int targetOffset, uint32_t valLocalId);
  void emitMIterInitK(uint32_t iterId,
                     int targetOffset,
                     uint32_t valLocalId,
                     uint32_t keyLocalId);
  void emitMIterNext(uint32_t iterId, int targetOffset, uint32_t valLocalId);
  void emitMIterNextK(uint32_t iterId,
                     int targetOffset,
                     uint32_t valLocalId,
                     uint32_t keyLocalId);
  void emitWIterInit(uint32_t iterId,
                     int targetOffset,
                     uint32_t valLocalId,
                     bool invertCond);
  void emitWIterInitK(uint32_t iterId,
                      int targetOffset,
                      uint32_t valLocalId,
                      uint32_t keyLocalId,
                      bool invertCond);
  void emitWIterNext(uint32_t iterId,
                     int targetOffset,
                     uint32_t valLocalId,
                     bool invertCond);
  void emitWIterNextK(uint32_t iterId,
                      int targetOffset,
                      uint32_t valLocalId,
                      uint32_t keyLocalId,
                      bool invertCond);

  void emitIterFree(uint32_t iterId);
  void emitMIterFree(uint32_t iterId);
  void emitDecodeCufIter(uint32_t iterId, int targetOffset);
  void emitCIterFree(uint32_t iterId);
  void emitIterBreak(const ImmVector& iv, uint32_t offset, bool breakTracelet);
  void emitVerifyParamType(uint32_t paramId);

  // continuations
  void emitCreateCont(Offset resumeOffset);
  void emitContReturnControl();
  void emitContSuspendImpl(Offset resumeOffset);
  void emitContSuspend(Offset resumeOffset);
  void emitContSuspendK(Offset resumeOffset);
  void emitContRetC();
  void emitContCheck(bool checkStarted);
  void emitContValid();
  void emitContKey();
  void emitContCurrent();
  void emitContStopped();

  // async functions
  void emitAsyncAwait();
  void emitAsyncESuspend(Offset resumeOffset, int iters);
  void emitAsyncWrapResult();

  void emitStrlen();
  void emitIncStat(int32_t counter, int32_t value, bool force);
  void emitIncTransCounter();
  void emitIncProfCounter(TransID transId);
  void emitCheckCold(TransID transId);
  void emitRB(Trace::RingBufferType t, SrcKey sk, int level = 1);
  void emitRB(Trace::RingBufferType t, std::string msg, int level = 1) {
    emitRB(t, makeStaticString(msg), level);
  }
  void emitRB(Trace::RingBufferType t, const StringData* msg, int level = 1);
  void emitDbgAssertRetAddr();
  void emitIdx();
  void emitIdxCommon(Opcode opc, Block* catchBlock = nullptr);
  void emitArrayIdx();
  void emitIsTypeC(DataType t);
  void emitIsTypeL(uint32_t id, DataType t);

private:
  /*
   * MInstrTranslator is responsible for translating one of the vector
   * instructions (CGetM, SetM, IssetM, etc..) into hhir.
   */
  class MInstrTranslator {
   public:
    MInstrTranslator(const NormalizedInstruction& ni,
                     HhbcTranslator& ht);
    void emit();

   private:
    void checkMIState();
    void emitMPre();
    void emitFinalMOp();
    void emitMPost();
    void emitSideExits(SSATmp* catchSp, int nStack);
    void emitMTrace();

    // Bases
    void emitBaseOp();
    void emitBaseLCR();
    void emitBaseH();
    void emitBaseG();
    void emitBaseN();
    void emitBaseS();

    // Intermediate Operations
    void emitIntermediateOp();
    void emitProp();
    void emitPropGeneric();
    void emitPropSpecialized(const MInstrAttr mia, PropInfo propInfo);
    void emitElem();
    void emitElemArray(SSATmp* key, bool warn);
    void emitNewElem();
    void emitRatchetRefs();

    // Final Operations
#   define MII(instr, ...)                      \
    void emit##instr##Elem();                   \
    void emit##instr##Prop();
    MINSTRS
#   undef MII
    void emitIssetEmptyElem(bool isEmpty);
    void emitIssetEmptyProp(bool isEmpty);
    void emitNotSuppNewElem();
    void emitVGetNewElem();
    void emitSetNewElem();
    void emitSetWithRefNewElem();
    void emitSetOpNewElem();
    void emitIncDecNewElem();
    void emitBindNewElem();
    void emitArraySet(SSATmp* key, SSATmp* value);
    void emitArrayGet(SSATmp* key);
    void emitArrayIsset();
    void emitPackedArrayGet(SSATmp* key);
    void emitPackedArrayIsset();
    void emitStringGet(SSATmp* key);
    void emitStringIsset();
    void emitVectorSet(SSATmp* key, SSATmp* value);
    void emitVectorGet(SSATmp* key);
    void emitVectorIsset();
    void emitPairGet(SSATmp* key);
    void emitPairIsset();
    void emitMapSet(SSATmp* key, SSATmp* value);
    void emitMapGet(SSATmp* key);
    void emitMapIsset();

    // Generate a catch trace that does not perform any final DecRef operations
    // on scratch space, and return its first block.
    Block* makeEmptyCatch() {
      return m_ht.makeCatch();
    }

    // Generate a catch trace that will contain DecRef instructions for tvRef
    // and/or tvRef2 as required; return the first block.
    Block* makeCatch() {
      auto b = makeEmptyCatch();
      m_failedVec.push_back(b);
      return b;
    }

    // Generate a catch trace that will free any scratch space used and perform
    // a side-exit from a failed set operation, return the first block.
    Block* makeCatchSet() {
      assert(!m_failedSetBlock);
      m_failedSetBlock = makeCatch();

      // This catch trace will be modified in emitMPost to end with a side
      // exit, and TryEndCatch will fall through to that side exit if an
      // InvalidSetMException is thrown.
      m_failedSetBlock->back().setOpcode(TryEndCatch);
      return m_failedSetBlock;
    }

    void prependToTraces(IRInstruction* inst) {
      for (auto b : m_failedVec) {
        b->prepend(m_unit.cloneInstruction(inst));
      }
    }

    // Misc Helpers
    void numberStackInputs();
    void setNoMIState() { m_needMIS = false; }
    SSATmp* genMisPtr();
    SSATmp* getInput(unsigned i, TypeConstraint tc);
    SSATmp* getBase(TypeConstraint tc);
    SSATmp* getKey();
    SSATmp* getValue();
    SSATmp* getValAddr();
    void    constrainBase(TypeConstraint tc, SSATmp* value = nullptr);
    SSATmp* checkInitProp(SSATmp* baseAsObj,
                          SSATmp* propAddr,
                          PropInfo propOffset,
                          bool warn,
                          bool define);
    Class* contextClass() const;

    /*
     * genStk is a wrapper around IRBuilder::gen() to deal with instructions
     * that may modify the stack. It inspects the opcode and the types of the
     * inputs, replacing the opcode with the version that returns a new StkPtr
     * if appropriate.
     */
    template<typename... Srcs>
    SSATmp* genStk(Opcode op, Block* taken, Srcs... srcs);

    /* Various predicates about the current instruction */
    bool isSimpleBase();
    bool isSingleMember();

    enum class SimpleOp {
      // the opcode is not in a simple form or not on a proper collection type
      None,
      // simple opcode on Array
      Array,
      // simple opcode on Packed Array
      PackedArray,
      // simple opcode on String
      String,
      // simple opcode on Vector* (c_Vector* or c_ImmVector*)
      Vector,
      // simple opcode on Map* (c_Map*)
      Map,
      // simple opcode on Map* (c_Pair*)
      Pair
    };
    SimpleOp simpleCollectionOp();
    void constrainCollectionOpBase();

    bool generateMVal() const;
    bool needFirstRatchet() const;
    bool needFinalRatchet() const;
    unsigned nLogicalRatchets() const;
    int ratchetInd() const;

    template<class... Args>
    SSATmp* cns(Args&&... args) {
      return m_unit.cns(std::forward<Args>(args)...);
    }

    template<class... Args>
    SSATmp* gen(Args&&... args) {
      return m_irb.gen(std::forward<Args>(args)...);
    }

    const NormalizedInstruction& m_ni;
    HhbcTranslator& m_ht;
    IRBuilder& m_irb;
    IRUnit& m_unit;
    const MInstrInfo& m_mii;
    const BCMarker m_marker;
    hphp_hash_map<unsigned, unsigned> m_stackInputs;

    unsigned m_mInd;
    unsigned m_iInd;

    bool m_needMIS;

    /* The base for any accesses to the current MInstrState. */
    SSATmp* m_misBase;

    /* The value of the base for the next member operation. Starts as the base
     * for the whole instruction and is updated as the translator makes
     * progress. */
    SSATmp* m_base;

    /* The result of the vector instruction. nullptr if the current instruction
     * doesn't produce a result. */
    SSATmp* m_result;

    /* If set, contains a value of type CountedStr|Nullptr. If a runtime test
     * determines that the value is not Nullptr, we incorrectly predicted the
     * output type of the instruction and must side exit. */
    SSATmp* m_strTestResult;

    /* If set, contains the catch target for the final set operation of this
     * instruction. The operations that set this member may need to return an
     * unexpected type, in which case they'll throw an InvalidSetMException. To
     * handle this, emitMPost adds code to the catch trace to fish the correct
     * value out of the exception and side exit. */
    Block* m_failedSetBlock;

    /* Contains a list of all catch blocks created in building the vector.
     * Each block must be appended with cleanup tasks (generally just DecRef)
     * to be performed if an exception occurs during the course of the vector
     * operation */
    std::vector<Block*> m_failedVec;
  };

private: // forwarding utilities
  template<class... Args>
  SSATmp* cns(Args&&... args) {
    return m_unit.cns(std::forward<Args>(args)...);
  }

  template<class... Args>
  SSATmp* gen(Args&&... args) {
    return m_irb->gen(std::forward<Args>(args)...);
  }

private:
  /*
   * Emit helpers.
   */
  void emitBindMem(SSATmp* ptr, SSATmp* src);
  void emitEmptyMem(SSATmp* ptr);
  void emitIncDecMem(bool pre, bool inc, SSATmp* ptr, Block* exit);
  void checkStrictlyInteger(SSATmp*& key, KeyType& keyType,
                            bool& checkForInt);
  Type assertObjType(const StringData*);
  void destroyName(SSATmp* name);
  SSATmp* ldClsPropAddr(Block* catchBlock, SSATmp* cls, SSATmp* name);
  void emitUnboxRAux();
  void emitAGet(SSATmp* src, Block* catchBlock);
  void emitRetFromInlined(Type type);
  SSATmp* emitDecRefLocalsInline(SSATmp* retVal);
  void emitRet(Type type, bool freeInline);
  void emitCmp(Opcode opc);
  SSATmp* emitJmpCondHelper(int32_t offset, bool negate, SSATmp* src);
  void emitJmpHelper(int32_t taken, int32_t next, bool negate,
                     bool bothPaths, SSATmp* src);
  SSATmp* emitIncDec(bool pre, bool inc, bool over, SSATmp* src);
  template<class Lambda>
  SSATmp* emitIterInitCommon(int offset, Lambda genFunc, bool invertCond);
  BCMarker makeMarker(Offset bcOff);
  void updateMarker();
  template<class Lambda>
  SSATmp* emitMIterInitCommon(int offset, Lambda genFunc);
  SSATmp* staticTVCns(const TypedValue*);
  void emitJmpSurpriseCheck();
  void emitRetSurpriseCheck(SSATmp* retVal, bool inGenerator);
  void classExistsImpl(ClassKind);

  folly::Optional<Type> interpOutputType(const NormalizedInstruction&,
                                         folly::Optional<Type>&) const;
  smart::vector<InterpOneData::LocalType>
  interpOutputLocals(const NormalizedInstruction&, bool& smashAll,
                     folly::Optional<Type> pushedType);

private: // Exit trace creation routines.
  Block* makeExit(Offset targetBcOff = -1);
  Block* makeExit(Offset targetBcOff, std::vector<SSATmp*>& spillValues);
  Block* makeExitWarn(Offset targetBcOff, std::vector<SSATmp*>& spillValues,
                      const StringData* warning);

  SSATmp* promoteBool(SSATmp* src);
  Opcode promoteBinaryDoubles(Op op, SSATmp*& src1, SSATmp*& src2);

  void emitBinaryBitOp(Op op);
  void emitBinaryArith(Op op);

  /*
   * Create a custom side exit---that is, an exit that does some
   * amount work before leaving the trace.
   *
   * The exit trace will spill things with for the current bytecode instruction.
   *
   * Then it will do an ExceptionBarrier, followed by whatever is done by the
   * CustomExit() function. Any instructions emitted by the custom exit will go
   * to the exit trace, and it may return an additional SSATmp* to spill on the
   * stack. If there is no additional SSATmp*, it should return nullptr.
   *
   * TODO(#2447661): this should be way better than this, should allow
   * using gen/push/spillStack/etc.
   */
  template<class ExitLambda>
  Block* makeSideExit(Offset targetBcOff, ExitLambda exit);

  /*
   * Generates an exit trace which will continue execution without HHIR.
   * This should be used in situations that HHIR cannot handle -- ideally
   * only in slow paths.
   */
  Block* makeExitSlow();
  Block* makeExitOpt(TransID transId);

  template<typename Body>
  Block* makeCatchImpl(Body body);
  Block* makeCatch(std::vector<SSATmp*> extraSpill =
                   std::vector<SSATmp*>(),
                   int64_t numPop = 0);
  Block* makeCatchNoSpill();

  /*
   * Create a block for a branch target that will be generated later.
   */
  Block* makeBlock(Offset);

  /*
   * Implementation for the above.  Takes spillValues, target offset,
   * and a flag for whether to make a no-IR exit.
   *
   * Also takes a CustomExit() function that may perform more operations and
   * optionally return a single additional SSATmp* (otherwise nullptr) to spill
   * on the stack before exiting.
   */
  enum class ExitFlag {
    Interp,     // will bail to the interpreter to execute at least one BC instr
    JIT,        // will attempt to use the JIT to create a new translation
    // DelayedMarker means to use the current instruction marker
    // instead of one for targetBcOff.
    DelayedMarker,
  };
  typedef std::function<SSATmp* ()> CustomExit;
  Block* makeExitImpl(Offset targetBcOff, ExitFlag flag,
                      std::vector<SSATmp*>& spillValues, const CustomExit&);

public:
  /*
   * Accessors for the current function being compiled and its
   * class and unit.
   */
  const Func* curFunc()     const { return m_bcStateStack.back().func; }
  Class*      curClass()    const { return curFunc()->cls(); }
  Unit*       curUnit()     const { return curFunc()->unit(); }
  Offset      bcOff()       const { return m_bcStateStack.back().bcOff; }
  SrcKey      curSrcKey()   const { return SrcKey(curFunc(), bcOff()); }
  bool        inGenerator() const { return m_bcStateStack.back().inGenerator; }
  size_t      spOffset()    const;
  Type        topType(uint32_t i, TypeConstraint c = DataTypeSpecific) const;

private:
  /*
   * Predicates for testing information about the relationship of a
   * class to the current context class.
   */
  bool classIsUniqueOrCtxParent(const Class*) const;
  bool classIsPersistentOrCtxParent(const Class*) const;

  /*
   * Return the SrcKey for the next HHBC (whether it is in this
   * tracelet or not).
   */
  SrcKey nextSrcKey() const {
    SrcKey srcKey(curFunc(), bcOff());
    srcKey.advance(curFunc()->unit());
    return srcKey;
  }

  /*
   * Return the bcOffset of the next instruction (whether it is in
   * this tracelet or not).
   */
  Offset nextBcOff() const { return nextSrcKey().offset(); }

  /*
   * Helpers for resolving bytecode immediate ids.
   */
  ArrayData*  lookupArrayId(int arrId);
  StringData* lookupStringId(int strId);
  Func*       lookupFuncId(int funcId);
  PreClass*   lookupPreClassId(int preClassId);
  const NamedEntityPair& lookupNamedEntityPairId(int id);
  const NamedEntity* lookupNamedEntityId(int id);

  /*
   * Eval stack helpers.
   */
  SSATmp* push(SSATmp* tmp);
  SSATmp* pushIncRef(SSATmp* tmp, TypeConstraint tc = DataTypeCountness);
  SSATmp* pop(Type type, TypeConstraint tc = DataTypeSpecific);
  void    popDecRef(Type type, TypeConstraint tc = DataTypeCountness);
  void    discard(unsigned n);
  SSATmp* popC(TypeConstraint tc = DataTypeSpecific) {
    return pop(Type::Cell, tc);
  }
  SSATmp* popV() { return pop(Type::BoxedCell); }
  SSATmp* popR() { return pop(Type::Gen);       }
  SSATmp* popA() { return pop(Type::Cls);       }
  SSATmp* popF(TypeConstraint tc = DataTypeSpecific) {
    return pop(Type::Gen, tc);
  }
  SSATmp* top(TypeConstraint tc, uint32_t offset = 0) const;
  SSATmp* top(Type type, uint32_t index = 0,
              TypeConstraint tc = DataTypeSpecific);
  SSATmp* topC(uint32_t i = 0, TypeConstraint tc = DataTypeSpecific) {
    return top(Type::Cell, i, tc);
  }
  SSATmp* topV(uint32_t i = 0) { return top(Type::BoxedCell, i); }
  SSATmp* topR(uint32_t i = 0) { return top(Type::Gen, i); }
  std::vector<SSATmp*> peekSpillValues() const;
  SSATmp* emitSpillStack(SSATmp* sp,
                         const std::vector<SSATmp*>& spillVals,
                         int64_t extraOffset = 0);
  SSATmp* spillStack();
  void    exceptionBarrier();
  SSATmp* ldStackAddr(int32_t offset, TypeConstraint tc);
  void    extendStack(uint32_t index, Type type);
  void    replace(uint32_t index, SSATmp* tmp);

  /*
   * Local instruction helpers.
   */
  SSATmp* ldLoc(uint32_t id, TypeConstraint constraint);
  SSATmp* ldLocAddr(uint32_t id, TypeConstraint constraint);
private:
  SSATmp* ldLocInner(uint32_t id, Block* exit,
                     TypeConstraint constraint);
  SSATmp* ldLocInnerWarn(uint32_t id, Block* target,
                         TypeConstraint constraint,
                         Block* catchBlock = nullptr);
public:
  SSATmp* pushStLoc(uint32_t id, Block* exit, SSATmp* newVal);
  SSATmp* stLoc(uint32_t id, Block* exit, SSATmp* newVal);
  SSATmp* stLocNRC(uint32_t id, Block* exit, SSATmp* newVal);
  SSATmp* stLocImpl(uint32_t id, Block*, SSATmp* newVal, bool doRefCount);

private:
  // Tracks information about the current bytecode offset and which
  // function we are in.  Goes in m_bcStateStack; we push and pop as
  // we deal with inlined calls.
  struct BcState {
    explicit BcState(Offset bcOff, bool inGenerator, const Func* func)
      : bcOff(bcOff)
      , inGenerator(inGenerator)
      , func(func)
    {}

    Offset bcOff;
    bool inGenerator;
    const Func* func;
  };

private:
  IRUnit m_unit;
  std::unique_ptr<IRBuilder> const m_irb;

  std::vector<BcState> m_bcStateStack;

  // The first HHBC offset for this tracelet
  const Offset m_startBcOff;

  // True if we're on the last HHBC opcode that will be emitted for
  // this tracelet.
  bool m_lastBcOff;

  // True if we've emitted an instruction that already handled
  // end-of-tracelet duties.  (E.g. emitRetC, etc.)  If it's not true,
  // we'll create a generic ReqBindJmp instruction after we're done.
  bool m_hasExit;

  /*
   * The FPI stack is used for inlining---when we start inlining at an
   * FCall, we look in here to find a definition of the StkPtr,offset
   * that can be used after the inlined callee "returns".
   */
  std::stack<std::pair<SSATmp*,int32_t>> m_fpiStack;

  /*
   * When we know that a call site is being inlined we add its StkPtr
   * offset pair to this stack to prevent it from being erroneously
   * popped during an FCall.
   *
   * XXX: There should be a better way to do this.  We don't allow
   * the tracelet to break during inlining so if we're careful it
   * should be possible to make sure FPush* and FCall[Array|Builtin]
   * is always matched with corresponding push()/pop().
   */
  std::stack<std::pair<SSATmp*,int32_t>> m_fpiActiveStack;
};

//////////////////////////////////////////////////////////////////////

}} // namespace HPHP::JIT

#endif
