/* 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/PortableBaselineInterpret.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);
#ifndef ENABLE_PORTABLE_BASELINE_INTERP
  MOZ_ASSERT(code != cx->runtime()->jitRuntime()->interpreterStub().value);
  MOZ_ASSERT(IsBaselineInterpreterEnabled());
#else
  MOZ_ASSERT(IsBaselineInterpreterEnabled() ||
             IsPortableBaselineInterpreterEnabled());
#endif

  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

  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();

    // Caller must construct |this| before invoking the function.
    MOZ_ASSERT_IF(constructing,
                  args.thisv().isObject() ||
                      args.thisv().isMagic(JS_UNINITIALIZED_LEXICAL));

    maxArgc = args.length();
    maxArgv = args.array();
    envChain = nullptr;
    calleeToken = CalleeToToken(&args.callee().as<JSFunction>(), constructing);
  } else {
    numActualArgs = 0;
    constructing = false;
    maxArgc = 0;
    maxArgv = nullptr;
    envChain = state.asExecute()->environmentChain();
    calleeToken = CalleeToToken(state.script());
  }

  RootedValue result(cx, Int32Value(numActualArgs));
  {
    AssertRealmUnchanged aru(cx);
    JitActivation activation(cx);

#ifndef ENABLE_PORTABLE_BASELINE_INTERP
    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());
#else  // !ENABLE_PORTABLE_BASELINE_INTERP
    (void)code;
#  ifdef DEBUG
    nogc.reset();
#  endif
    if (!pbl::PortablebaselineInterpreterStackCheck(cx, state, numActualArgs)) {
      return EnterJitStatus::NotEntered;
    }
    unsigned numFormals =
        state.isInvoke() ? state.script()->function()->nargs() : 0;
    if (!pbl::PortableBaselineTrampoline(cx, maxArgc, maxArgv, numFormals,
                                         calleeToken, envChain,
                                         result.address())) {
      return EnterJitStatus::Error;
    }
#endif  // ENABLE_PORTABLE_BASELINE_INTERP
  }

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

  // Release temporary buffer used for OSR into Ion.
  if (!IsPortableBaselineInterpreterEnabled()) {
    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()) {
    result = state.asInvoke()->args().thisv();
    MOZ_ASSERT(result.isObject());
  }

  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()
#ifdef ENABLE_PORTABLE_BASELINE_INTERP
      && !IsPortableBaselineInterpreterEnabled()
#endif
  ) {
    // 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->realm()->debuggerObservesNativeCall()) {
    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() && code) {
      break;
    }

    script->incWarmUpCounter();

#ifndef ENABLE_PORTABLE_BASELINE_INTERP
    // 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;
      }
    }

#else  // !ENABLE_PORTABLE_BASELINE_INTERP

    // Try to enter the Portable Baseline Interpreter.
    if (IsPortableBaselineInterpreterEnabled()) {
      jit::MethodStatus status =
          pbl::CanEnterPortableBaselineInterpreter(cx, state);
      if (status == jit::Method_Error) {
        return EnterJitStatus::Error;
      }
      if (status == jit::Method_Compiled) {
        code = script->jitCodeRaw();
        break;
      }
    }
#endif

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

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

  return EnterJit(cx, state, code);
}
