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

#include "frontend/ParserAtom.h"
#ifdef DEBUG
#  include "js/friend/DumpFunctions.h"  // js::DumpObject, js::DumpValue
#endif
#include "js/PropertySpec.h"
#include "vm/AsyncFunction.h"
#include "vm/AsyncIteration.h"
#include "vm/FunctionFlags.h"  // js::FunctionFlags
#include "vm/GlobalObject.h"
#include "vm/JSObject.h"
#include "vm/PlainObject.h"  // js::PlainObject

#include "debugger/DebugAPI-inl.h"
#include "vm/Stack-inl.h"

using namespace js;

AbstractGeneratorObject* AbstractGeneratorObject::create(
    JSContext* cx, HandleFunction callee, HandleScript script,
    HandleObject environmentChain, Handle<ArgumentsObject*> argsObject) {
  Rooted<AbstractGeneratorObject*> genObj(cx);
  if (!callee->isAsync()) {
    genObj = GeneratorObject::create(cx, callee);
  } else if (callee->isGenerator()) {
    genObj = AsyncGeneratorObject::create(cx, callee);
  } else {
    genObj = AsyncFunctionGeneratorObject::create(cx, callee);
  }
  if (!genObj) {
    return nullptr;
  }

  genObj->setCallee(*callee);
  genObj->setEnvironmentChain(*environmentChain);
  if (argsObject) {
    genObj->setArgsObj(*argsObject.get());
  }

  ArrayObject* stack = NewDenseFullyAllocatedArray(cx, script->nslots());
  if (!stack) {
    return nullptr;
  }

  genObj->setStackStorage(*stack);

  // Note: This assumes that a Warp frame cannot be the target of
  //       the debugger, as we do not call OnNewGenerator.
  return genObj;
}

JSObject* AbstractGeneratorObject::createFromFrame(JSContext* cx,
                                                   AbstractFramePtr frame) {
  MOZ_ASSERT(frame.isGeneratorFrame());
  MOZ_ASSERT(!frame.isConstructing());

  if (frame.isModuleFrame()) {
    return createModuleGenerator(cx, frame);
  }

  RootedFunction fun(cx, frame.callee());
  Rooted<ArgumentsObject*> maybeArgs(
      cx, frame.script()->needsArgsObj() ? &frame.argsObj() : nullptr);
  RootedObject environmentChain(cx, frame.environmentChain());

  RootedScript script(cx, frame.script());
  Rooted<AbstractGeneratorObject*> genObj(
      cx, AbstractGeneratorObject::create(cx, fun, script, environmentChain,
                                          maybeArgs));
  if (!genObj) {
    return nullptr;
  }

  if (!DebugAPI::onNewGenerator(cx, frame, genObj)) {
    return nullptr;
  }

  return genObj;
}

JSObject* AbstractGeneratorObject::createModuleGenerator(
    JSContext* cx, AbstractFramePtr frame) {
  Rooted<ModuleObject*> module(cx, frame.script()->module());
  Rooted<AbstractGeneratorObject*> genObj(cx);
  genObj = AsyncFunctionGeneratorObject::create(cx, module);
  if (!genObj) {
    return nullptr;
  }

  // Create a handler function to wrap the module's script. This way
  // we can access it later and restore the state.
  Handle<PropertyName*> funName = cx->names().empty_;
  RootedFunction handlerFun(
      cx, NewFunctionWithProto(cx, nullptr, 0,
                               FunctionFlags::INTERPRETED_GENERATOR_OR_ASYNC,
                               nullptr, funName, nullptr,
                               gc::AllocKind::FUNCTION, GenericObject));
  if (!handlerFun) {
    return nullptr;
  }
  handlerFun->initScript(module->script());

  genObj->setCallee(*handlerFun);
  genObj->setEnvironmentChain(*frame.environmentChain());

  ArrayObject* stack =
      NewDenseFullyAllocatedArray(cx, module->script()->nslots());
  if (!stack) {
    return nullptr;
  }

  genObj->setStackStorage(*stack);

  if (!DebugAPI::onNewGenerator(cx, frame, genObj)) {
    return nullptr;
  }

  return genObj;
}

void AbstractGeneratorObject::trace(JSTracer* trc) {
  DebugAPI::traceGeneratorFrame(trc, this);
}

bool AbstractGeneratorObject::suspend(JSContext* cx, HandleObject obj,
                                      AbstractFramePtr frame,
                                      const jsbytecode* pc, unsigned nvalues) {
  MOZ_ASSERT(JSOp(*pc) == JSOp::InitialYield || JSOp(*pc) == JSOp::Yield ||
             JSOp(*pc) == JSOp::Await);

  auto genObj = obj.as<AbstractGeneratorObject>();
  MOZ_ASSERT(!genObj->hasStackStorage() || genObj->isStackStorageEmpty());
  MOZ_ASSERT_IF(JSOp(*pc) == JSOp::Await, genObj->callee().isAsync());
  MOZ_ASSERT_IF(JSOp(*pc) == JSOp::Yield, genObj->callee().isGenerator());

  if (nvalues > 0) {
    ArrayObject* stack = nullptr;
    MOZ_ASSERT(genObj->hasStackStorage());
    stack = &genObj->stackStorage();
    MOZ_ASSERT(stack->getDenseCapacity() >= nvalues);
    if (!frame.saveGeneratorSlots(cx, nvalues, stack)) {
      return false;
    }
  }

  genObj->setResumeIndex(pc);
  genObj->setEnvironmentChain(*frame.environmentChain());
  return true;
}

#ifdef DEBUG
void AbstractGeneratorObject::dump() const {
  fprintf(stderr, "(AbstractGeneratorObject*) %p {\n", (void*)this);
  fprintf(stderr, "  callee: (JSFunction*) %p,\n", (void*)&callee());
  fprintf(stderr, "  environmentChain: (JSObject*) %p,\n",
          (void*)&environmentChain());
  if (hasArgsObj()) {
    fprintf(stderr, "  argsObj: Some((ArgumentsObject*) %p),\n",
            (void*)&argsObj());
  } else {
    fprintf(stderr, "  argsObj: None,\n");
  }
  if (hasStackStorage()) {
    fprintf(stderr, "  stackStorage: Some(ArrayObject {\n");
    ArrayObject& stack = stackStorage();
    uint32_t denseLen = uint32_t(stack.getDenseInitializedLength());
    fprintf(stderr, "    denseInitializedLength: %u\n,", denseLen);
    uint32_t len = stack.length();
    fprintf(stderr, "    length: %u\n,", len);
    fprintf(stderr, "    data: [\n");
    const Value* elements = getDenseElements();
    for (uint32_t i = 0; i < std::max(len, denseLen); i++) {
      fprintf(stderr, "      [%u]: ", i);
      js::DumpValue(elements[i]);
    }
    fprintf(stderr, "    ],\n");
    fprintf(stderr, "  }),\n");
  } else {
    fprintf(stderr, "  stackStorage: None\n");
  }
  if (isSuspended()) {
    fprintf(stderr, "  resumeIndex: Some(%u),\n", resumeIndex());
  } else {
    fprintf(stderr, "  resumeIndex: None, /* (not suspended) */\n");
  }
  fprintf(stderr, "}\n");
}
#endif

void AbstractGeneratorObject::finalSuspend(JSContext* cx, HandleObject obj) {
  auto* genObj = &obj->as<AbstractGeneratorObject>();
  MOZ_ASSERT(genObj->isRunning());
  genObj->setClosed(cx);
}

static AbstractGeneratorObject* GetGeneratorObjectForCall(JSContext* cx,
                                                          CallObject& callObj) {
  // The ".generator" binding is always present and always "aliased".
  mozilla::Maybe<PropertyInfo> prop =
      callObj.lookup(cx, cx->names().dot_generator_);
  if (prop.isNothing()) {
    return nullptr;
  }
  Value genValue = callObj.getSlot(prop->slot());

  // If the `Generator; SetAliasedVar ".generator"; InitialYield` bytecode
  // sequence has not run yet, genValue is undefined.
  return genValue.isObject()
             ? &genValue.toObject().as<AbstractGeneratorObject>()
             : nullptr;
}

AbstractGeneratorObject* js::GetGeneratorObjectForFrame(
    JSContext* cx, AbstractFramePtr frame) {
  cx->check(frame);
  MOZ_ASSERT(frame.isGeneratorFrame());

  if (frame.isModuleFrame()) {
    ModuleEnvironmentObject* moduleEnv =
        frame.script()->module()->environment();
    mozilla::Maybe<PropertyInfo> prop =
        moduleEnv->lookup(cx, cx->names().dot_generator_);
    Value genValue = moduleEnv->getSlot(prop->slot());
    return genValue.isObject()
               ? &genValue.toObject().as<AbstractGeneratorObject>()
               : nullptr;
  }
  if (!frame.hasInitialEnvironment()) {
    return nullptr;
  }

  return GetGeneratorObjectForCall(cx, frame.callObj());
}

AbstractGeneratorObject* js::GetGeneratorObjectForEnvironment(
    JSContext* cx, HandleObject env) {
  auto* call = CallObject::find(env);
  return call ? GetGeneratorObjectForCall(cx, *call) : nullptr;
}

bool js::GeneratorThrowOrReturn(JSContext* cx, AbstractFramePtr frame,
                                Handle<AbstractGeneratorObject*> genObj,
                                HandleValue arg,
                                GeneratorResumeKind resumeKind) {
  MOZ_ASSERT(genObj->isRunning());
  if (resumeKind == GeneratorResumeKind::Throw) {
    cx->setPendingException(arg, ShouldCaptureStack::Maybe);
  } else {
    MOZ_ASSERT(resumeKind == GeneratorResumeKind::Return);

    MOZ_ASSERT_IF(genObj->is<GeneratorObject>(), arg.isObject());
    frame.setReturnValue(arg);

    RootedValue closing(cx, MagicValue(JS_GENERATOR_CLOSING));
    cx->setPendingException(closing, nullptr);
  }
  return false;
}

bool AbstractGeneratorObject::resume(JSContext* cx,
                                     InterpreterActivation& activation,
                                     Handle<AbstractGeneratorObject*> genObj,
                                     HandleValue arg, HandleValue resumeKind) {
  MOZ_ASSERT(genObj->isSuspended());

  RootedFunction callee(cx, &genObj->callee());
  RootedObject envChain(cx, &genObj->environmentChain());
  if (!activation.resumeGeneratorFrame(callee, envChain)) {
    return false;
  }
  activation.regs().fp()->setResumedGenerator();

  if (genObj->hasArgsObj()) {
    activation.regs().fp()->initArgsObj(genObj->argsObj());
  }

  if (genObj->hasStackStorage() && !genObj->isStackStorageEmpty()) {
    JSScript* script = activation.regs().fp()->script();
    ArrayObject* storage = &genObj->stackStorage();
    uint32_t len = storage->getDenseInitializedLength();
    activation.regs().fp()->restoreGeneratorSlots(storage);
    activation.regs().sp += len - script->nfixed();
    storage->setDenseInitializedLength(0);
  }

  JSScript* script = callee->nonLazyScript();
  uint32_t offset = script->resumeOffsets()[genObj->resumeIndex()];
  activation.regs().pc = script->offsetToPC(offset);

  // Push arg, generator, resumeKind Values on the generator's stack.
  activation.regs().sp += 3;
  MOZ_ASSERT(activation.regs().spForStackDepth(activation.regs().stackDepth()));
  activation.regs().sp[-3] = arg;
  activation.regs().sp[-2] = ObjectValue(*genObj);
  activation.regs().sp[-1] = resumeKind;

  genObj->setRunning();
  return true;
}

GeneratorObject* GeneratorObject::create(JSContext* cx, HandleFunction fun) {
  MOZ_ASSERT(fun->isGenerator() && !fun->isAsync());

  // FIXME: This would be faster if we could avoid doing a lookup to get
  // the prototype for the instance.  Bug 906600.
  RootedValue pval(cx);
  if (!GetProperty(cx, fun, fun, cx->names().prototype, &pval)) {
    return nullptr;
  }
  RootedObject proto(cx, pval.isObject() ? &pval.toObject() : nullptr);
  if (!proto) {
    proto = GlobalObject::getOrCreateGeneratorObjectPrototype(cx, cx->global());
    if (!proto) {
      return nullptr;
    }
  }
  return NewObjectWithGivenProto<GeneratorObject>(cx, proto);
}

const JSClass GeneratorObject::class_ = {
    "Generator",
    JSCLASS_HAS_RESERVED_SLOTS(GeneratorObject::RESERVED_SLOTS),
    &classOps_,
};

const JSClassOps GeneratorObject::classOps_ = {
    nullptr,                                   // addProperty
    nullptr,                                   // delProperty
    nullptr,                                   // enumerate
    nullptr,                                   // newEnumerate
    nullptr,                                   // resolve
    nullptr,                                   // mayResolve
    nullptr,                                   // finalize
    nullptr,                                   // call
    nullptr,                                   // construct
    CallTraceMethod<AbstractGeneratorObject>,  // trace
};

static const JSFunctionSpec generator_methods[] = {
    JS_SELF_HOSTED_FN("next", "GeneratorNext", 1, 0),
    JS_SELF_HOSTED_FN("throw", "GeneratorThrow", 1, 0),
    JS_SELF_HOSTED_FN("return", "GeneratorReturn", 1, 0),
    JS_FS_END,
};

JSObject* js::NewTenuredObjectWithFunctionPrototype(
    JSContext* cx, Handle<GlobalObject*> global) {
  RootedObject proto(cx, &cx->global()->getFunctionPrototype());
  return NewPlainObjectWithProto(cx, proto, TenuredObject);
}

static JSObject* CreateGeneratorFunction(JSContext* cx, JSProtoKey key) {
  RootedObject proto(cx, &cx->global()->getFunctionConstructor());
  Handle<PropertyName*> name = cx->names().GeneratorFunction;
  return NewFunctionWithProto(cx, Generator, 1, FunctionFlags::NATIVE_CTOR,
                              nullptr, name, proto, gc::AllocKind::FUNCTION,
                              TenuredObject);
}

static JSObject* CreateGeneratorFunctionPrototype(JSContext* cx,
                                                  JSProtoKey key) {
  return NewTenuredObjectWithFunctionPrototype(cx, cx->global());
}

static bool GeneratorFunctionClassFinish(JSContext* cx,
                                         HandleObject genFunction,
                                         HandleObject genFunctionProto) {
  Handle<GlobalObject*> global = cx->global();

  // Change the "constructor" property to non-writable before adding any other
  // properties, so it's still the last property and can be modified without a
  // dictionary-mode transition.
  MOZ_ASSERT(genFunctionProto->as<NativeObject>().getLastProperty().key() ==
             NameToId(cx->names().constructor));
  MOZ_ASSERT(!genFunctionProto->as<NativeObject>().inDictionaryMode());

  RootedValue genFunctionVal(cx, ObjectValue(*genFunction));
  if (!DefineDataProperty(cx, genFunctionProto, cx->names().constructor,
                          genFunctionVal, JSPROP_READONLY)) {
    return false;
  }
  MOZ_ASSERT(!genFunctionProto->as<NativeObject>().inDictionaryMode());

  RootedObject iteratorProto(
      cx, GlobalObject::getOrCreateIteratorPrototype(cx, global));
  if (!iteratorProto) {
    return false;
  }

  RootedObject genObjectProto(cx, GlobalObject::createBlankPrototypeInheriting(
                                      cx, &PlainObject::class_, iteratorProto));
  if (!genObjectProto) {
    return false;
  }
  if (!DefinePropertiesAndFunctions(cx, genObjectProto, nullptr,
                                    generator_methods) ||
      !DefineToStringTag(cx, genObjectProto, cx->names().Generator)) {
    return false;
  }

  if (!LinkConstructorAndPrototype(cx, genFunctionProto, genObjectProto,
                                   JSPROP_READONLY, JSPROP_READONLY) ||
      !DefineToStringTag(cx, genFunctionProto, cx->names().GeneratorFunction)) {
    return false;
  }

  global->setGeneratorObjectPrototype(genObjectProto);

  return true;
}

static const ClassSpec GeneratorFunctionClassSpec = {
    CreateGeneratorFunction,
    CreateGeneratorFunctionPrototype,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    GeneratorFunctionClassFinish,
    ClassSpec::DontDefineConstructor,
};

const JSClass js::GeneratorFunctionClass = {
    "GeneratorFunction",
    0,
    JS_NULL_CLASS_OPS,
    &GeneratorFunctionClassSpec,
};

const Value& AbstractGeneratorObject::getUnaliasedLocal(uint32_t slot) const {
  MOZ_ASSERT(isSuspended());
  MOZ_ASSERT(hasStackStorage());
  MOZ_ASSERT(slot < callee().nonLazyScript()->nfixed());
  return stackStorage().getDenseElement(slot);
}

void AbstractGeneratorObject::setUnaliasedLocal(uint32_t slot,
                                                const Value& value) {
  MOZ_ASSERT(isSuspended());
  MOZ_ASSERT(hasStackStorage());
  MOZ_ASSERT(slot < callee().nonLazyScript()->nfixed());
  return stackStorage().setDenseElement(slot, value);
}

void AbstractGeneratorObject::setClosed(JSContext* cx) {
  setFixedSlot(CALLEE_SLOT, NullValue());
  setFixedSlot(ENV_CHAIN_SLOT, NullValue());
  setFixedSlot(ARGS_OBJ_SLOT, NullValue());
  setFixedSlot(STACK_STORAGE_SLOT, NullValue());
  setFixedSlot(RESUME_INDEX_SLOT, NullValue());

  DebugAPI::onGeneratorClosed(cx, this);
}

bool AbstractGeneratorObject::isAfterYield() {
  return isAfterYieldOrAwait(JSOp::Yield);
}

bool AbstractGeneratorObject::isAfterAwait() {
  return isAfterYieldOrAwait(JSOp::Await);
}

bool AbstractGeneratorObject::isAfterYieldOrAwait(JSOp op) {
  if (isClosed() || isRunning()) {
    return false;
  }

  JSScript* script = callee().nonLazyScript();
  jsbytecode* code = script->code();
  uint32_t nextOffset = script->resumeOffsets()[resumeIndex()];
  if (JSOp(code[nextOffset]) != JSOp::AfterYield) {
    return false;
  }

  static_assert(JSOpLength_Yield == JSOpLength_InitialYield,
                "JSOp::Yield and JSOp::InitialYield must have the same length");
  static_assert(JSOpLength_Yield == JSOpLength_Await,
                "JSOp::Yield and JSOp::Await must have the same length");

  uint32_t offset = nextOffset - JSOpLength_Yield;
  JSOp prevOp = JSOp(code[offset]);
  MOZ_ASSERT(prevOp == JSOp::InitialYield || prevOp == JSOp::Yield ||
             prevOp == JSOp::Await);

  return prevOp == op;
}

template <>
bool JSObject::is<js::AbstractGeneratorObject>() const {
  return is<GeneratorObject>() || is<AsyncFunctionGeneratorObject>() ||
         is<AsyncGeneratorObject>();
}

GeneratorResumeKind js::ParserAtomToResumeKind(
    frontend::TaggedParserAtomIndex atom) {
  if (atom == frontend::TaggedParserAtomIndex::WellKnown::next()) {
    return GeneratorResumeKind::Next;
  }
  if (atom == frontend::TaggedParserAtomIndex::WellKnown::throw_()) {
    return GeneratorResumeKind::Throw;
  }
  MOZ_ASSERT(atom == frontend::TaggedParserAtomIndex::WellKnown::return_());
  return GeneratorResumeKind::Return;
}

JSAtom* js::ResumeKindToAtom(JSContext* cx, GeneratorResumeKind kind) {
  switch (kind) {
    case GeneratorResumeKind::Next:
      return cx->names().next;

    case GeneratorResumeKind::Throw:
      return cx->names().throw_;

    case GeneratorResumeKind::Return:
      return cx->names().return_;
  }
  MOZ_CRASH("Invalid resume kind");
}
