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

#include "jit/BaselineJIT.h"
#include "jit/CalleeToken.h"
#include "jit/Ion.h"
#include "jit/JitCommon.h"
#include "jit/JitRuntime.h"
#include "js/friend/StackLimits.h"  // js::AutoCheckRecursionLimit
#include "vm/Interpreter.h"
#include "vm/JitActivation.h"
#include "vm/JSContext.h"
#include "vm/Realm.h"

#include "vm/Activation-inl.h"
#include "vm/JSScript-inl.h"

using namespace js;
using namespace js::jit;

static EnterJitStatus JS_HAZ_JSNATIVE_CALLER EnterJit(JSContext* cx,
                                                      RunState& state,
                                                      uint8_t* code) {
  // We don't want to call the interpreter stub here (because
  // C++ -> interpreterStub -> C++ is slower than staying in C++).
  MOZ_ASSERT(code);
  MOZ_ASSERT(code != cx->runtime()->jitRuntime()->interpreterStub().value);
  MOZ_ASSERT(IsBaselineInterpreterEnabled());

  AutoCheckRecursionLimit recursion(cx);
  if (!recursion.check(cx)) {
    return EnterJitStatus::Error;
  }

  // jit::Bailout(), jit::InvalidationBailout(), and jit::HandleException()
  // reset the counter to zero, so assert here it's also zero when we enter
  // JIT code.
  MOZ_ASSERT(!cx->isInUnsafeRegion());

#ifdef DEBUG
  // Assert we don't GC before entering JIT code. A GC could discard JIT code
  // or move the function stored in the CalleeToken (it won't be traced at
  // this point). We use Maybe<> here so we can call reset() to call the
  // AutoAssertNoGC destructor before we enter JIT code.
  mozilla::Maybe<JS::AutoAssertNoGC> nogc;
  nogc.emplace(cx);
#endif

  JSScript* script = state.script();
  size_t numActualArgs;
  bool constructing;
  size_t maxArgc;
  Value* maxArgv;
  JSObject* envChain;
  CalleeToken calleeToken;

  if (state.isInvoke()) {
    const CallArgs& args = state.asInvoke()->args();
    numActualArgs = args.length();

    if (TooManyActualArguments(numActualArgs)) {
      // Fall back to the C++ interpreter to avoid running out of stack space.
      return EnterJitStatus::NotEntered;
    }

    constructing = state.asInvoke()->constructing();
    maxArgc = args.length() + 1;
    maxArgv = args.array() - 1;  // -1 to include |this|
    envChain = nullptr;
    calleeToken = CalleeToToken(&args.callee().as<JSFunction>(), constructing);

    unsigned numFormals = script->function()->nargs();
    if (numFormals > numActualArgs) {
      code = cx->runtime()->jitRuntime()->getArgumentsRectifier().value;
    }
  } else {
    numActualArgs = 0;
    constructing = false;
    maxArgc = 0;
    maxArgv = nullptr;
    envChain = state.asExecute()->environmentChain();
    calleeToken = CalleeToToken(state.script());
  }

  // Caller must construct |this| before invoking the function.
  MOZ_ASSERT_IF(constructing, maxArgv[0].isObject() ||
                                  maxArgv[0].isMagic(JS_UNINITIALIZED_LEXICAL));

  RootedValue result(cx, Int32Value(numActualArgs));
  {
    AssertRealmUnchanged aru(cx);
    ActivationEntryMonitor entryMonitor(cx, calleeToken);
    JitActivation activation(cx);
    EnterJitCode enter = cx->runtime()->jitRuntime()->enterJit();

#ifdef DEBUG
    nogc.reset();
#endif
    CALL_GENERATED_CODE(enter, code, maxArgc, maxArgv, /* osrFrame = */ nullptr,
                        calleeToken, envChain, /* osrNumStackValues = */ 0,
                        result.address());
  }

  // Ensure the counter was reset to zero after exiting from JIT code.
  MOZ_ASSERT(!cx->isInUnsafeRegion());

  // Release temporary buffer used for OSR into Ion.
  cx->runtime()->jitRuntime()->freeIonOsrTempData();

  if (result.isMagic()) {
    MOZ_ASSERT(result.isMagic(JS_ION_ERROR));
    return EnterJitStatus::Error;
  }

  // Jit callers wrap primitive constructor return, except for derived
  // class constructors, which are forced to do it themselves.
  if (constructing && result.isPrimitive()) {
    MOZ_ASSERT(maxArgv[0].isObject());
    result = maxArgv[0];
  }

  state.setReturnValue(result);
  return EnterJitStatus::Ok;
}

// Call the per-script interpreter entry trampoline.
bool js::jit::EnterInterpreterEntryTrampoline(uint8_t* code, JSContext* cx,
                                              RunState* state) {
  using EnterTrampolineCodePtr = bool (*)(JSContext * cx, RunState*);
  auto funcPtr = JS_DATA_TO_FUNC_PTR(EnterTrampolineCodePtr, code);
  return CALL_GENERATED_2(funcPtr, cx, state);
}

EnterJitStatus js::jit::MaybeEnterJit(JSContext* cx, RunState& state) {
  if (!IsBaselineInterpreterEnabled()) {
    // All JITs are disabled.
    return EnterJitStatus::NotEntered;
  }

  // JITs do not respect the debugger's OnNativeCall hook, so JIT execution is
  // disabled if this hook might need to be called.
  if (cx->insideDebuggerEvaluationWithOnNativeCallHook) {
    return EnterJitStatus::NotEntered;
  }

  JSScript* script = state.script();

  uint8_t* code = script->jitCodeRaw();

#ifdef JS_CACHEIR_SPEW
  cx->spewer().enableSpewing();
#endif

  do {
    // Make sure we can enter Baseline Interpreter code. Note that the prologue
    // has warm-up checks to tier up if needed.
    if (script->hasJitScript()) {
      break;
    }

    script->incWarmUpCounter();

    // Try to Ion-compile.
    if (jit::IsIonEnabled(cx)) {
      jit::MethodStatus status = jit::CanEnterIon(cx, state);
      if (status == jit::Method_Error) {
        return EnterJitStatus::Error;
      }
      if (status == jit::Method_Compiled) {
        code = script->jitCodeRaw();
        break;
      }
    }

    // Try to Baseline-compile.
    if (jit::IsBaselineJitEnabled(cx)) {
      jit::MethodStatus status =
          jit::CanEnterBaselineMethod<BaselineTier::Compiler>(cx, state);
      if (status == jit::Method_Error) {
        return EnterJitStatus::Error;
      }
      if (status == jit::Method_Compiled) {
        code = script->jitCodeRaw();
        break;
      }
    }

    // Try to enter the Baseline Interpreter.
    if (IsBaselineInterpreterEnabled()) {
      jit::MethodStatus status =
          jit::CanEnterBaselineMethod<BaselineTier::Interpreter>(cx, state);
      if (status == jit::Method_Error) {
        return EnterJitStatus::Error;
      }
      if (status == jit::Method_Compiled) {
        code = script->jitCodeRaw();
        break;
      }
    }

    return EnterJitStatus::NotEntered;
  } while (false);

#ifdef JS_CACHEIR_SPEW
  cx->spewer().disableSpewing();
#endif

  return EnterJit(cx, state, code);
}
