/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 * 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 jit_IonControlFlow_h
#define jit_IonControlFlow_h

#include "mozilla/Array.h"

#include "jsbytecode.h"

#include "jit/BytecodeAnalysis.h"
#include "jit/FixedList.h"
#include "jit/JitAllocPolicy.h"
#include "js/TypeDecls.h"

namespace js {
namespace jit {

class CFGControlInstruction;

// Adds MFoo::New functions which are mirroring the arguments of the
// constructors. Opcodes which are using this macro can be called with a
// TempAllocator, or the fallible version of the TempAllocator.
#define TRIVIAL_CFG_NEW_WRAPPERS                                              \
    template <typename... Args>                                               \
    static CFGThisOpcode* New(TempAllocator& alloc, Args&&... args) {         \
        return new(alloc) CFGThisOpcode(mozilla::Forward<Args>(args)...);     \
    }                                                                         \
    template <typename... Args>                                               \
    static CFGThisOpcode* New(TempAllocator::Fallible alloc, Args&&... args)  \
    {                                                                         \
        return new(alloc) CFGThisOpcode(mozilla::Forward<Args>(args)...);     \
    }

class CFGSpace
{
    static const size_t DEFAULT_CHUNK_SIZE = 4096;

  protected:
    LifoAlloc allocator_;
  public:

    explicit CFGSpace()
      : allocator_(DEFAULT_CHUNK_SIZE)
    {}

    LifoAlloc& lifoAlloc() {
        return allocator_;
    }

    size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
        return allocator_.sizeOfExcludingThis(mallocSizeOf);
    }
};

class CFGBlock : public TempObject
{
    size_t id_;
    jsbytecode* start;
    jsbytecode* stop;
    CFGControlInstruction* end;
    bool inWorkList;

  public:
    explicit CFGBlock(jsbytecode* start)
      : id_(-1),
        start(start),
        stop(nullptr),
        end(nullptr),
        inWorkList(false)
    {}

    static CFGBlock* New(TempAllocator& alloc, jsbytecode* start) {
        return new(alloc) CFGBlock(start);
    }

    void operator=(const CFGBlock&) = delete;

    jsbytecode* startPc() const {
        return start;
    }
    void setStartPc(jsbytecode* startPc) {
        start = startPc;
    }
    jsbytecode* stopPc() const {
        MOZ_ASSERT(stop);
        return stop;
    }
    void setStopPc(jsbytecode* stopPc) {
        stop = stopPc;
    }
    CFGControlInstruction* stopIns() const {
        MOZ_ASSERT(end);
        return end;
    }
    void setStopIns(CFGControlInstruction* stopIns) {
        end = stopIns;
    }
    bool isInWorkList() const {
        return inWorkList;
    }
    void setInWorklist() {
        MOZ_ASSERT(!inWorkList);
        inWorkList = true;
    }
    void clearInWorkList() {
        MOZ_ASSERT(inWorkList);
        inWorkList = false;
    }
    size_t id() const {
        return id_;
    }
    void setId(size_t id) {
        id_ = id;
    }
};

#define CFG_CONTROL_OPCODE_LIST(_)                                          \
    _(Test)                                                                 \
    _(Compare)                                                              \
    _(Goto)                                                                 \
    _(Return)                                                               \
    _(RetRVal)                                                              \
    _(LoopEntry)                                                            \
    _(BackEdge)                                                             \
    _(TableSwitch)                                                          \
    _(Try)                                                                  \
    _(Throw)

// Forward declarations of MIR types.
#define FORWARD_DECLARE(type) class CFG##type;
 CFG_CONTROL_OPCODE_LIST(FORWARD_DECLARE)
#undef FORWARD_DECLARE

#define CFG_CONTROL_HEADER(type_name)                                        \
    static const Type classOpcode = CFGControlInstruction::Type_##type_name; \
    using CFGThisOpcode = CFG##type_name;                                    \
    Type type() const override {                                             \
        return classOpcode;                                                  \
    }                                                                        \
    const char* Name() const override {                                      \
        return #type_name;                                                   \
    }                                                                        \


class CFGControlInstruction : public TempObject
{
  public:
    enum Type {
#   define DEFINE_TYPES(type) Type_##type,
        CFG_CONTROL_OPCODE_LIST(DEFINE_TYPES)
#   undef DEFINE_TYPES
    };

    virtual size_t numSuccessors() const = 0;
    virtual CFGBlock* getSuccessor(size_t i) const = 0;
    virtual void replaceSuccessor(size_t i, CFGBlock* successor) = 0;
    virtual Type type() const = 0;
    virtual const char* Name() const = 0;

    template<typename CFGType> bool is() const {
        return type() == CFGType::classOpcode;
    }
    template<typename CFGType> CFGType* to() {
        MOZ_ASSERT(this->is<CFGType>());
        return static_cast<CFGType*>(this);
    }
    template<typename CFGType> const CFGType* to() const {
        MOZ_ASSERT(this->is<CFGType>());
        return static_cast<const CFGType*>(this);
    }
#   define TYPE_CASTS(type)             \
    bool is##type() const {             \
        return this->is<CFG##type>();   \
    }                                   \
    CFG##type* to##type() {             \
        return this->to<CFG##type>();   \
    }                                   \
    const CFG##type* to##type() const { \
        return this->to<CFG##type>();   \
    }
    CFG_CONTROL_OPCODE_LIST(TYPE_CASTS)
#   undef TYPE_CASTS
};

template <size_t Successors>
class CFGAryControlInstruction : public CFGControlInstruction
{
    mozilla::Array<CFGBlock*, Successors> successors_;

  public:
    size_t numSuccessors() const final override {
        return Successors;
    }
    CFGBlock* getSuccessor(size_t i) const final override {
        return successors_[i];
    }
    void replaceSuccessor(size_t i, CFGBlock* succ) final override {
        successors_[i] = succ;
    }
};

class CFGTry : public CFGControlInstruction
{
    CFGBlock* tryBlock_;
    jsbytecode* catchStartPc_;
    CFGBlock* mergePoint_;

    CFGTry(CFGBlock* successor, jsbytecode* catchStartPc, CFGBlock* mergePoint)
      : tryBlock_(successor),
        catchStartPc_(catchStartPc),
        mergePoint_(mergePoint)
    { }

  public:
    CFG_CONTROL_HEADER(Try)
    TRIVIAL_CFG_NEW_WRAPPERS

    static CFGTry* CopyWithNewTargets(TempAllocator& alloc, CFGTry* old,
                                      CFGBlock* tryBlock, CFGBlock* merge)
    {
        return new(alloc) CFGTry(tryBlock, old->catchStartPc(), merge);
    }

    size_t numSuccessors() const final override {
        return 2;
    }
    CFGBlock* getSuccessor(size_t i) const final override {
        MOZ_ASSERT(i < numSuccessors());
        return (i == 0) ? tryBlock_ : mergePoint_;
    }
    void replaceSuccessor(size_t i, CFGBlock* succ) final override {
        MOZ_ASSERT(i < numSuccessors());
        if (i == 0)
            tryBlock_ = succ;
        else
            mergePoint_ = succ;
    }

    CFGBlock* tryBlock() const {
        return getSuccessor(0);
    }

    jsbytecode* catchStartPc() const {
        return catchStartPc_;
    }

    CFGBlock* afterTryCatchBlock() const {
        return getSuccessor(1);
    }
};

class CFGTableSwitch : public CFGControlInstruction
{
    Vector<CFGBlock*, 4, JitAllocPolicy> successors_;
    size_t low_;
    size_t high_;

    CFGTableSwitch(TempAllocator& alloc, size_t low, size_t high)
      : successors_(alloc),
        low_(low),
        high_(high)
    {}

  public:
    CFG_CONTROL_HEADER(TableSwitch);

    static CFGTableSwitch* New(TempAllocator& alloc, size_t low, size_t high) {
        return new(alloc) CFGTableSwitch(alloc, low, high);
    }

    size_t numSuccessors() const final override {
        return successors_.length();
    }
    CFGBlock* getSuccessor(size_t i) const final override {
        MOZ_ASSERT(i < numSuccessors());
        return successors_[i];
    }
    void replaceSuccessor(size_t i, CFGBlock* succ) final override {
        MOZ_ASSERT(i < numSuccessors());
        successors_[i] = succ;
    }

    bool addDefault(CFGBlock* defaultCase) {
        MOZ_ASSERT(successors_.length() == 0);
        return successors_.append(defaultCase);
    }

    bool addCase(CFGBlock* caseBlock) {
        MOZ_ASSERT(successors_.length() > 0);
        return successors_.append(caseBlock);
    }

    CFGBlock* defaultCase() const {
        return getSuccessor(0);
    }

    CFGBlock* getCase(size_t i) const {
        return getSuccessor(i + 1);
    }

    size_t high() const {
        return high_;
    }

    size_t low() const {
        return low_;
    }
};

/**
 * CFGCompare
 *
 * PEEK
 * PEEK
 * STRICTEQ
 *    POP truePopAmount
 *    JUMP succ1
 * STRICTNEQ
 *    POP falsePopAmount
 *    JUMP succ2
 */
class CFGCompare : public CFGAryControlInstruction<2>
{
    const size_t truePopAmount_;
    const size_t falsePopAmount_;

    CFGCompare(CFGBlock* succ1, size_t truePopAmount, CFGBlock* succ2, size_t falsePopAmount)
      : truePopAmount_(truePopAmount),
        falsePopAmount_(falsePopAmount)
    {
        replaceSuccessor(0, succ1);
        replaceSuccessor(1, succ2);
    }

  public:
    CFG_CONTROL_HEADER(Compare);

    static CFGCompare* NewFalseBranchIsDefault(TempAllocator& alloc, CFGBlock* case_,
                                               CFGBlock* default_)
    {
        // True and false branch both go to a body and don't need the lhs and
        // rhs to the compare. Pop them.
        return new(alloc) CFGCompare(case_, 2, default_, 2);
    }

    static CFGCompare* NewFalseBranchIsNextCompare(TempAllocator& alloc, CFGBlock* case_,
                                                   CFGBlock* nextCompare)
    {
        // True branch goes to the body and don't need the lhs and
        // rhs to the compare anymore. Pop them. The next compare still
        // needs the lhs.
        return new(alloc) CFGCompare(case_, 2, nextCompare, 1);
    }

    static CFGCompare* CopyWithNewTargets(TempAllocator& alloc, CFGCompare* old,
                                          CFGBlock* succ1, CFGBlock* succ2)
    {
        return new(alloc) CFGCompare(succ1, old->truePopAmount(), succ2, old->falsePopAmount());
    }

    CFGBlock* trueBranch() const {
        return getSuccessor(0);
    }
    CFGBlock* falseBranch() const {
        return getSuccessor(1);
    }
    size_t truePopAmount() const {
        return truePopAmount_;
    }
    size_t falsePopAmount() const {
        return falsePopAmount_;
    }
};

/**
 * CFGTest
 *
 * POP / PEEK (depending on keepCondition_)
 * IFEQ JUMP succ1
 * IFNEQ JUMP succ2
 *
 */
class CFGTest : public CFGAryControlInstruction<2>
{
    // By default the condition gets popped. This variable
    // keeps track if we want to keep the condition.
    bool keepCondition_;

    CFGTest(CFGBlock* succ1, CFGBlock* succ2)
      : keepCondition_(false)
    {
        replaceSuccessor(0, succ1);
        replaceSuccessor(1, succ2);
    }
    CFGTest(CFGBlock* succ1, CFGBlock* succ2, bool keepCondition)
      : keepCondition_(keepCondition)
    {
        replaceSuccessor(0, succ1);
        replaceSuccessor(1, succ2);
    }

  public:
    CFG_CONTROL_HEADER(Test);
    TRIVIAL_CFG_NEW_WRAPPERS

    static CFGTest* CopyWithNewTargets(TempAllocator& alloc, CFGTest* old,
                                       CFGBlock* succ1, CFGBlock* succ2)
    {
        return new(alloc) CFGTest(succ1, succ2, old->mustKeepCondition());
    }

    CFGBlock* trueBranch() const {
        return getSuccessor(0);
    }
    CFGBlock* falseBranch() const {
        return getSuccessor(1);
    }
    void keepCondition() {
        keepCondition_ = true;
    }
    bool mustKeepCondition() const {
        return keepCondition_;
    }
};

/**
 * CFGReturn
 *
 * POP
 * RETURN popped value
 *
 */
class CFGReturn : public CFGAryControlInstruction<0>
{
  public:
    CFG_CONTROL_HEADER(Return);
    TRIVIAL_CFG_NEW_WRAPPERS
};

/**
 * CFGRetRVal
 *
 * RETURN the value in the return value slot
 *
 */
class CFGRetRVal : public CFGAryControlInstruction<0>
{
  public:
    CFG_CONTROL_HEADER(RetRVal);
    TRIVIAL_CFG_NEW_WRAPPERS
};

/**
 * CFGThrow
 *
 * POP
 * THROW popped value
 *
 */
class CFGThrow : public CFGAryControlInstruction<0>
{
  public:
    CFG_CONTROL_HEADER(Throw);
    TRIVIAL_CFG_NEW_WRAPPERS
};

class CFGUnaryControlInstruction : public CFGAryControlInstruction<1>
{
  public:
    explicit CFGUnaryControlInstruction(CFGBlock* block) {
        MOZ_ASSERT(block);
        replaceSuccessor(0, block);
    }

    CFGBlock* successor() const {
        return getSuccessor(0);
    }
};

/**
 * CFGGOTO
 *
 * POP (x popAmount)
 * JMP block
 *
 */
class CFGGoto : public CFGUnaryControlInstruction
{
    const size_t popAmount_;

    explicit CFGGoto(CFGBlock* block)
      : CFGUnaryControlInstruction(block),
        popAmount_(0)
    {}

    CFGGoto(CFGBlock* block, size_t popAmount_)
      : CFGUnaryControlInstruction(block),
        popAmount_(popAmount_)
    {}

  public:
    CFG_CONTROL_HEADER(Goto);
    TRIVIAL_CFG_NEW_WRAPPERS

    static CFGGoto* CopyWithNewTargets(TempAllocator& alloc, CFGGoto* old, CFGBlock* block)
    {
        return new(alloc) CFGGoto(block, old->popAmount());
    }

    size_t popAmount() const {
        return popAmount_;
    }
};

/**
 * CFGBackEdge
 *
 * Jumps back to the start of the loop.
 *
 * JMP block
 *
 */
class CFGBackEdge : public CFGUnaryControlInstruction
{
    explicit CFGBackEdge(CFGBlock* block)
      : CFGUnaryControlInstruction(block)
    {}

  public:
    CFG_CONTROL_HEADER(BackEdge);
    TRIVIAL_CFG_NEW_WRAPPERS

    static CFGBackEdge* CopyWithNewTargets(TempAllocator& alloc, CFGBackEdge* old, CFGBlock* block)
    {
        return new(alloc) CFGBackEdge(block);
    }
};

/**
 * CFGLOOPENTRY
 *
 * Indicates the jumping block is the start of a loop.
 * That block is the only block allowed to have a backedge.
 *
 * JMP block
 *
 */
class CFGLoopEntry : public CFGUnaryControlInstruction
{
    bool canOsr_;
    bool isForIn_;
    size_t stackPhiCount_;
    jsbytecode* loopStopPc_;

    CFGLoopEntry(CFGBlock* block, size_t stackPhiCount)
      : CFGUnaryControlInstruction(block),
        canOsr_(false),
        isForIn_(false),
        stackPhiCount_(stackPhiCount),
        loopStopPc_(nullptr)
    {}

    CFGLoopEntry(CFGBlock* block, bool canOsr, bool isForIn, size_t stackPhiCount,
                 jsbytecode* loopStopPc)
      : CFGUnaryControlInstruction(block),
        canOsr_(canOsr),
        isForIn_(isForIn),
        stackPhiCount_(stackPhiCount),
        loopStopPc_(loopStopPc)
    {}

  public:
    CFG_CONTROL_HEADER(LoopEntry);
    TRIVIAL_CFG_NEW_WRAPPERS

    static CFGLoopEntry* CopyWithNewTargets(TempAllocator& alloc, CFGLoopEntry* old,
                                            CFGBlock* loopEntry)
    {
        return new(alloc) CFGLoopEntry(loopEntry, old->canOsr(), old->isForIn(),
                                       old->stackPhiCount(), old->loopStopPc());
    }

    void setCanOsr() {
        canOsr_ = true;
    }

    bool canOsr() const {
        return canOsr_;
    }

    void setIsForIn() {
        isForIn_ = true;
    }
    bool isForIn() const {
        return isForIn_;
    }

    size_t stackPhiCount() const {
        return stackPhiCount_;
    }

    jsbytecode* loopStopPc() const {
        MOZ_ASSERT(loopStopPc_);
        return loopStopPc_;
    }

    void setLoopStopPc(jsbytecode* loopStopPc) {
        loopStopPc_ = loopStopPc;
    }
};

typedef Vector<CFGBlock*, 4, JitAllocPolicy> CFGBlockVector;

class ControlFlowGraph : public TempObject
{
    // A list of blocks in RPO, containing per block a pc-range and
    // a control instruction.
    Vector<CFGBlock, 4, JitAllocPolicy> blocks_;

    explicit ControlFlowGraph(TempAllocator& alloc)
      : blocks_(alloc)
    {}

  public:
    static ControlFlowGraph* New(TempAllocator& alloc) {
        return new(alloc) ControlFlowGraph(alloc);
    }

    ControlFlowGraph(const ControlFlowGraph&) = delete;
    void operator=(const ControlFlowGraph&) = delete;

    void dump(GenericPrinter& print, JSScript* script);
    bool init(TempAllocator& alloc, const CFGBlockVector& blocks);

    const CFGBlock* block(size_t i) const {
        return &blocks_[i];
    }

    size_t numBlocks() const {
        return blocks_.length();
    }
};

class ControlFlowGenerator
{
    static int CmpSuccessors(const void* a, const void* b);

    JSScript* script;
    CFGBlock* current;
    jsbytecode* pc;
    GSNCache gsn;
    TempAllocator& alloc_;
    CFGBlockVector blocks_;

  public:
    ControlFlowGenerator(const ControlFlowGenerator&) = delete;
    void operator=(const ControlFlowGenerator&) = delete;

    TempAllocator& alloc() {
        return alloc_;
    }

    enum class ControlStatus {
        Error,
        Abort,
        Ended,        // There is no continuation/join point.
        Joined,       // Created a join node.
        Jumped,       // Parsing another branch at the same level.
        None          // No control flow.
    };

    struct DeferredEdge : public TempObject
    {
        CFGBlock* block;
        DeferredEdge* next;

        DeferredEdge(CFGBlock* block, DeferredEdge* next)
          : block(block), next(next)
        { }
    };

    struct ControlFlowInfo {
        // Entry in the cfgStack.
        uint32_t cfgEntry;

        // Label that continues go to.
        jsbytecode* continuepc;

        ControlFlowInfo(uint32_t cfgEntry, jsbytecode* continuepc)
          : cfgEntry(cfgEntry),
            continuepc(continuepc)
        { }
    };


    // To avoid recursion, the bytecode analyzer uses a stack where each entry
    // is a small state machine. As we encounter branches or jumps in the
    // bytecode, we push information about the edges on the stack so that the
    // CFG can be built in a tree-like fashion.
    struct CFGState {
        enum State {
            IF_TRUE,            // if() { }, no else.
            IF_TRUE_EMPTY_ELSE, // if() { }, empty else
            IF_ELSE_TRUE,       // if() { X } else { }
            IF_ELSE_FALSE,      // if() { } else { X }
            DO_WHILE_LOOP_BODY, // do { x } while ()
            DO_WHILE_LOOP_COND, // do { } while (x)
            WHILE_LOOP_COND,    // while (x) { }
            WHILE_LOOP_BODY,    // while () { x }
            FOR_LOOP_COND,      // for (; x;) { }
            FOR_LOOP_BODY,      // for (; ;) { x }
            FOR_LOOP_UPDATE,    // for (; ; x) { }
            TABLE_SWITCH,       // switch() { x }
            COND_SWITCH_CASE,   // switch() { case X: ... }
            COND_SWITCH_BODY,   // switch() { case ...: X }
            AND_OR,             // && x, || x
            LABEL,              // label: x
            TRY                 // try { x } catch(e) { }
        };

        State state;            // Current state of this control structure.
        jsbytecode* stopAt;     // Bytecode at which to stop the processing loop.

        // For if structures, this contains branch information.
        union {
            struct {
                CFGBlock* ifFalse;
                jsbytecode* falseEnd;
                CFGBlock* ifTrue;    // Set when the end of the true path is reached.
                CFGTest* test;
            } branch;
            struct {
                // Common entry point.
                CFGBlock* entry;

                // Position of where the loop body starts and ends.
                jsbytecode* bodyStart;
                jsbytecode* bodyEnd;

                // pc immediately after the loop exits.
                jsbytecode* exitpc;

                // Common exit point. Created lazily, so it may be nullptr.
                CFGBlock* successor;

                // Deferred break and continue targets.
                DeferredEdge* breaks;
                DeferredEdge* continues;

                // Initial state, in case loop processing is restarted.
                State initialState;
                jsbytecode* initialPc;
                jsbytecode* initialStopAt;
                jsbytecode* loopHead;

                // For-loops only.
                jsbytecode* condpc;
                jsbytecode* updatepc;
                jsbytecode* updateEnd;
            } loop;
            struct {
                // Vector of body blocks to process after the cases.
                FixedList<CFGBlock*>* bodies;

                // When processing case statements, this counter points at the
                // last uninitialized body.  When processing bodies, this
                // counter targets the next body to process.
                uint32_t currentIdx;

                // Remember the block index of the default case.
                jsbytecode* defaultTarget;
                uint32_t defaultIdx;

                // Block immediately after the switch.
                jsbytecode* exitpc;
                DeferredEdge* breaks;
            } switch_;
            struct {
                DeferredEdge* breaks;
            } label;
            struct {
                CFGBlock* successor;
            } try_;
        };

        inline bool isLoop() const {
            switch (state) {
              case DO_WHILE_LOOP_COND:
              case DO_WHILE_LOOP_BODY:
              case WHILE_LOOP_COND:
              case WHILE_LOOP_BODY:
              case FOR_LOOP_COND:
              case FOR_LOOP_BODY:
              case FOR_LOOP_UPDATE:
                return true;
              default:
                return false;
            }
        }

        static CFGState If(jsbytecode* join, CFGTest* test);
        static CFGState IfElse(jsbytecode* trueEnd, jsbytecode* falseEnd, CFGTest* test);
        static CFGState AndOr(jsbytecode* join, CFGBlock* lhs);
        static CFGState TableSwitch(TempAllocator& alloc, jsbytecode* exitpc);
        static CFGState CondSwitch(TempAllocator& alloc, jsbytecode* exitpc,
                                   jsbytecode* defaultTarget);
        static CFGState Label(jsbytecode* exitpc);
        static CFGState Try(jsbytecode* exitpc, CFGBlock* successor);
    };

    Vector<CFGState, 8, JitAllocPolicy> cfgStack_;
    Vector<ControlFlowInfo, 4, JitAllocPolicy> loops_;
    Vector<ControlFlowInfo, 0, JitAllocPolicy> switches_;
    Vector<ControlFlowInfo, 2, JitAllocPolicy> labels_;
    bool aborted_;
    bool checkedTryFinally_;

  public:
    ControlFlowGenerator(TempAllocator& alloc, JSScript* script);

    MOZ_MUST_USE bool traverseBytecode();
    MOZ_MUST_USE bool addBlock(CFGBlock* block);
    ControlFlowGraph* getGraph(TempAllocator& alloc) {
        ControlFlowGraph* cfg = ControlFlowGraph::New(alloc);
        if (!cfg)
            return nullptr;
        if (!cfg->init(alloc, blocks_))
            return nullptr;
        return cfg;
    }

    bool aborted() const {
        return aborted_;
    }

  private:
    void popCfgStack();
    MOZ_MUST_USE bool processDeferredContinues(CFGState& state);
    ControlStatus processControlEnd();
    ControlStatus processCfgStack();
    ControlStatus processCfgEntry(CFGState& state);
    ControlStatus processIfStart(JSOp op);
    ControlStatus processIfEnd(CFGState& state);
    ControlStatus processIfElseTrueEnd(CFGState& state);
    ControlStatus processIfElseFalseEnd(CFGState& state);
    ControlStatus processDoWhileLoop(JSOp op, jssrcnote* sn);
    ControlStatus processDoWhileBodyEnd(CFGState& state);
    ControlStatus processDoWhileCondEnd(CFGState& state);
    ControlStatus processWhileCondEnd(CFGState& state);
    ControlStatus processWhileBodyEnd(CFGState& state);
    ControlStatus processForLoop(JSOp op, jssrcnote* sn);
    ControlStatus processForCondEnd(CFGState& state);
    ControlStatus processForBodyEnd(CFGState& state);
    ControlStatus processForUpdateEnd(CFGState& state);
    ControlStatus processWhileOrForInLoop(jssrcnote* sn);
    ControlStatus processNextTableSwitchCase(CFGState& state);
    ControlStatus processCondSwitch();
    ControlStatus processCondSwitchCase(CFGState& state);
    ControlStatus processCondSwitchDefault(CFGState& state);
    ControlStatus processCondSwitchBody(CFGState& state);
    ControlStatus processSwitchBreak(JSOp op);
    ControlStatus processSwitchEnd(DeferredEdge* breaks, jsbytecode* exitpc);
    ControlStatus processTry();
    ControlStatus processTryEnd(CFGState& state);
    ControlStatus processThrow();
    ControlStatus processTableSwitch(JSOp op, jssrcnote* sn);
    ControlStatus processContinue(JSOp op);
    ControlStatus processBreak(JSOp op, jssrcnote* sn);
    ControlStatus processReturn(JSOp op);
    ControlStatus maybeLoop(JSOp op, jssrcnote* sn);
    ControlStatus snoopControlFlow(JSOp op);
    ControlStatus processBrokenLoop(CFGState& state);
    ControlStatus finishLoop(CFGState& state, CFGBlock* successor);
    ControlStatus processAndOr(JSOp op);
    ControlStatus processAndOrEnd(CFGState& state);
    ControlStatus processLabel();
    ControlStatus processLabelEnd(CFGState& state);

    MOZ_MUST_USE bool pushLoop(CFGState::State state, jsbytecode* stopAt, CFGBlock* entry,
                               jsbytecode* loopHead, jsbytecode* initialPc,
                               jsbytecode* bodyStart, jsbytecode* bodyEnd,
                               jsbytecode* exitpc, jsbytecode* continuepc);
    void endCurrentBlock(CFGControlInstruction* ins);
    CFGBlock* createBreakCatchBlock(DeferredEdge* edge, jsbytecode* pc);
};

} // namespace jit
} // namespace js

#endif /* jit_IonControlFlow_h */
