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

#include "frontend/BytecodeEmitter.h"  // BytecodeEmitter
#include "frontend/IfEmitter.h"        // InternalIfEmitter
#include "vm/CompletionKind.h"         // CompletionKind
#include "vm/Opcodes.h"                // JSOp

using namespace js;
using namespace js::frontend;

ForOfLoopControl::ForOfLoopControl(BytecodeEmitter* bce, int32_t iterDepth,
                                   SelfHostedIter selfHostedIter,
                                   IteratorKind iterKind)
    : LoopControl(bce, StatementKind::ForOfLoop),
      iterDepth_(iterDepth),
      numYieldsAtBeginCodeNeedingIterClose_(UINT32_MAX),
      selfHostedIter_(selfHostedIter),
      iterKind_(iterKind) {}

bool ForOfLoopControl::emitBeginCodeNeedingIteratorClose(BytecodeEmitter* bce) {
  tryCatch_.emplace(bce, TryEmitter::Kind::TryCatch,
                    TryEmitter::ControlKind::NonSyntactic);

  if (!tryCatch_->emitTry()) {
    return false;
  }

  MOZ_ASSERT(numYieldsAtBeginCodeNeedingIterClose_ == UINT32_MAX);
  numYieldsAtBeginCodeNeedingIterClose_ = bce->bytecodeSection().numYields();

  return true;
}

bool ForOfLoopControl::emitEndCodeNeedingIteratorClose(BytecodeEmitter* bce) {
  if (!tryCatch_->emitCatch()) {
    //              [stack] ITER ... EXCEPTION
    return false;
  }

  unsigned slotFromTop = bce->bytecodeSection().stackDepth() - iterDepth_;
  if (!bce->emitDupAt(slotFromTop)) {
    //              [stack] ITER ... EXCEPTION ITER
    return false;
  }
  if (!emitIteratorCloseInInnermostScopeWithTryNote(bce,
                                                    CompletionKind::Throw)) {
    return false;  // ITER ... EXCEPTION
  }

  if (!bce->emit1(JSOp::Throw)) {
    //              [stack] ITER ...
    return false;
  }

  // If any yields were emitted, then this for-of loop is inside a star
  // generator and must handle the case of Generator.return. Like in
  // yield*, it is handled with a finally block. If the generator is
  // closing, then the exception/resumeindex value (second value on
  // the stack) will be a magic JS_GENERATOR_CLOSING value.
  // TODO: Refactor this to eliminate the swaps.
  uint32_t numYieldsEmitted = bce->bytecodeSection().numYields();
  if (numYieldsEmitted > numYieldsAtBeginCodeNeedingIterClose_) {
    if (!tryCatch_->emitFinally()) {
      return false;
    }
    //              [stack] ITER ... FVALUE FTYPE
    InternalIfEmitter ifGeneratorClosing(bce);
    if (!bce->emit1(JSOp::Swap)) {
      //            [stack] ITER ... FTYPE FVALUE
      return false;
    }
    if (!bce->emit1(JSOp::IsGenClosing)) {
      //            [stack] ITER ... FTYPE FVALUE CLOSING
      return false;
    }
    if (!ifGeneratorClosing.emitThen()) {
      //            [stack] ITER ... FTYPE FVALUE
      return false;
    }
    if (!bce->emitDupAt(slotFromTop + 1)) {
      //            [stack] ITER ... FTYPE FVALUE ITER
      return false;
    }
    if (!emitIteratorCloseInInnermostScopeWithTryNote(bce,
                                                      CompletionKind::Normal)) {
      //            [stack] ITER ... FTYPE FVALUE
      return false;
    }
    if (!ifGeneratorClosing.emitEnd()) {
      //            [stack] ITER ... FTYPE FVALUE
      return false;
    }
    if (!bce->emit1(JSOp::Swap)) {
      //            [stack] ITER ... FVALUE FTYPE
      return false;
    }
  }

  if (!tryCatch_->emitEnd()) {
    return false;
  }

  tryCatch_.reset();
  numYieldsAtBeginCodeNeedingIterClose_ = UINT32_MAX;

  return true;
}

bool ForOfLoopControl::emitIteratorCloseInInnermostScopeWithTryNote(
    BytecodeEmitter* bce,
    CompletionKind completionKind /* = CompletionKind::Normal */) {
  BytecodeOffset start = bce->bytecodeSection().offset();
  if (!emitIteratorCloseInScope(bce, *bce->innermostEmitterScope(),
                                completionKind)) {
    return false;
  }
  BytecodeOffset end = bce->bytecodeSection().offset();
  return bce->addTryNote(TryNoteKind::ForOfIterClose, 0, start, end);
}

bool ForOfLoopControl::emitIteratorCloseInScope(
    BytecodeEmitter* bce, EmitterScope& currentScope,
    CompletionKind completionKind /* = CompletionKind::Normal */) {
  return bce->emitIteratorCloseInScope(currentScope, iterKind_, completionKind,
                                       selfHostedIter_);
}

// Since we're in the middle of emitting code that will leave
// |bce->innermostEmitterScope()|, passing the innermost emitter scope to
// emitIteratorCloseInScope and looking up .generator there would be very,
// very wrong.  We'd find .generator in the function environment, and we'd
// compute a NameLocation with the correct slot, but we'd compute a
// hop-count to the function environment that was too big.  At runtime we'd
// either crash, or we'd find a user-controllable value in that slot, and
// Very Bad Things would ensue as we reinterpreted that value as an
// iterator.
bool ForOfLoopControl::emitPrepareForNonLocalJumpFromScope(
    BytecodeEmitter* bce, EmitterScope& currentScope, bool isTarget,
    BytecodeOffset* tryNoteStart) {
  // Pop unnecessary value from the stack.  Effectively this means
  // leaving try-catch block.  However, the performing IteratorClose can
  // reach the depth for try-catch, and effectively re-enter the
  // try-catch block.
  if (!bce->emit1(JSOp::Pop)) {
    //              [stack] NEXT ITER
    return false;
  }

  // Pop the iterator's next method.
  if (!bce->emit1(JSOp::Swap)) {
    //              [stack] ITER NEXT
    return false;
  }
  if (!bce->emit1(JSOp::Pop)) {
    //              [stack] ITER
    return false;
  }

  if (!bce->emit1(JSOp::Dup)) {
    //              [stack] ITER ITER
    return false;
  }

  *tryNoteStart = bce->bytecodeSection().offset();
  if (!emitIteratorCloseInScope(bce, currentScope, CompletionKind::Normal)) {
    //              [stack] ITER
    return false;
  }

  if (isTarget) {
    // At the level of the target block, there's bytecode after the
    // loop that will pop the next method, the iterator, and the
    // value, so push two undefineds to balance the stack.
    if (!bce->emit1(JSOp::Undefined)) {
      //            [stack] ITER UNDEF
      return false;
    }
    if (!bce->emit1(JSOp::Undefined)) {
      //            [stack] ITER UNDEF UNDEF
      return false;
    }
  } else {
    if (!bce->emit1(JSOp::Pop)) {
      //            [stack]
      return false;
    }
  }

  return true;
}
