/* 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_RematerializedFrame_h
#define jit_RematerializedFrame_h

#include "mozilla/Assertions.h"

#include <algorithm>
#include <stddef.h>
#include <stdint.h>

#include "jstypes.h"

#include "jit/JitFrames.h"
#include "jit/ScriptFromCalleeToken.h"
#include "js/GCVector.h"
#include "js/TypeDecls.h"
#include "js/UniquePtr.h"
#include "js/Value.h"
#include "vm/JSFunction.h"
#include "vm/JSScript.h"
#include "vm/Stack.h"

class JS_PUBLIC_API JSTracer;

namespace js {

class ArgumentsObject;
class CallObject;

namespace jit {

class InlineFrameIterator;
struct MaybeReadFallback;

// RematerializedFrame: An optimized frame that has been rematerialized with
// values read out of Snapshots.
//
// If the Debugger API tries to inspect or modify an IonMonkey frame, much of
// the information it expects to find in a frame is missing: function calls may
// have been inlined, variables may have been optimized out, and so on. So when
// this happens, SpiderMonkey builds one or more Rematerialized frames from the
// IonMonkey frame, using the snapshot metadata built by Ion to reconstruct the
// missing parts. The Rematerialized frames are now the authority on the state
// of those frames, and the Ion frame is ignored: stack iterators ignore the Ion
// frame, producing the Rematerialized frames in their stead; and when control
// returns to the Ion frame, we pop it, rebuild Baseline frames from the
// Rematerialized frames, and resume execution in Baseline.
class RematerializedFrame {
  // See DebugScopes::updateLiveScopes.
  bool prevUpToDate_;

  // Propagated to the Baseline frame once this is popped.
  bool isDebuggee_;

  // Has an initial environment has been pushed on the environment chain for
  // function frames that need a CallObject or eval frames that need a
  // VarEnvironmentObject?
  bool hasInitialEnv_;

  // Is this frame constructing?
  bool isConstructing_;

  // If true, this frame has been on the stack when
  // |js::SavedStacks::saveCurrentStack| was called, and so there is a
  // |js::SavedFrame| object cached for this frame.
  bool hasCachedSavedFrame_;

  // The fp of the top frame associated with this possibly inlined frame.
  uint8_t* top_;

  // The bytecode at the time of rematerialization.
  jsbytecode* pc_;

  size_t frameNo_;
  unsigned numActualArgs_;

  JSScript* script_;
  JSObject* envChain_;
  JSFunction* callee_;
  ArgumentsObject* argsObj_;

  Value returnValue_;
  Value thisArgument_;
  Value slots_[1];

  RematerializedFrame(JSContext* cx, uint8_t* top, unsigned numActualArgs,
                      InlineFrameIterator& iter, MaybeReadFallback& fallback);

 public:
  static RematerializedFrame* New(JSContext* cx, uint8_t* top,
                                  InlineFrameIterator& iter,
                                  MaybeReadFallback& fallback);

  // RematerializedFrame are allocated on non-GC heap, so use GCVector and
  // UniquePtr to ensure they are traced and cleaned up correctly.
  using RematerializedFrameVector = GCVector<UniquePtr<RematerializedFrame>>;

  // Rematerialize all remaining frames pointed to by |iter| into |frames|
  // in older-to-younger order, e.g., frames[0] is the oldest frame.
  [[nodiscard]] static bool RematerializeInlineFrames(
      JSContext* cx, uint8_t* top, InlineFrameIterator& iter,
      MaybeReadFallback& fallback, RematerializedFrameVector& frames);

  bool prevUpToDate() const { return prevUpToDate_; }
  void setPrevUpToDate() { prevUpToDate_ = true; }
  void unsetPrevUpToDate() { prevUpToDate_ = false; }

  bool isDebuggee() const { return isDebuggee_; }
  void setIsDebuggee() { isDebuggee_ = true; }
  inline void unsetIsDebuggee();

  uint8_t* top() const { return top_; }
  JSScript* outerScript() const {
    JitFrameLayout* jsFrame = (JitFrameLayout*)top_;
    return ScriptFromCalleeToken(jsFrame->calleeToken());
  }
  jsbytecode* pc() const { return pc_; }
  size_t frameNo() const { return frameNo_; }
  bool inlined() const { return frameNo_ > 0; }

  JSObject* environmentChain() const { return envChain_; }

  template <typename SpecificEnvironment>
  void pushOnEnvironmentChain(SpecificEnvironment& env) {
    MOZ_ASSERT(*environmentChain() == env.enclosingEnvironment());
    envChain_ = &env;
    if (IsFrameInitialEnvironment(this, env)) {
      hasInitialEnv_ = true;
    }
  }

  template <typename SpecificEnvironment>
  void popOffEnvironmentChain() {
    MOZ_ASSERT(envChain_->is<SpecificEnvironment>());
    envChain_ = &envChain_->as<SpecificEnvironment>().enclosingEnvironment();
  }

  [[nodiscard]] bool initFunctionEnvironmentObjects(JSContext* cx);
  [[nodiscard]] bool pushVarEnvironment(JSContext* cx, Handle<Scope*> scope);

  bool hasInitialEnvironment() const { return hasInitialEnv_; }
  CallObject& callObj() const;

  bool hasArgsObj() const { return !!argsObj_; }
  ArgumentsObject& argsObj() const {
    MOZ_ASSERT(hasArgsObj());
    MOZ_ASSERT(script()->needsArgsObj());
    return *argsObj_;
  }

  bool isFunctionFrame() const { return script_->isFunction(); }
  bool isGlobalFrame() const { return script_->isGlobalCode(); }
  bool isModuleFrame() const { return script_->isModule(); }

  JSScript* script() const { return script_; }
  JSFunction* callee() const {
    MOZ_ASSERT(isFunctionFrame());
    MOZ_ASSERT(callee_);
    return callee_;
  }
  Value calleev() const { return ObjectValue(*callee()); }
  Value& thisArgument() { return thisArgument_; }

  bool isConstructing() const { return isConstructing_; }

  bool hasCachedSavedFrame() const { return hasCachedSavedFrame_; }

  void setHasCachedSavedFrame() { hasCachedSavedFrame_ = true; }

  void clearHasCachedSavedFrame() { hasCachedSavedFrame_ = false; }

  unsigned numFormalArgs() const {
    return isFunctionFrame() ? callee()->nargs() : 0;
  }
  unsigned numActualArgs() const { return numActualArgs_; }
  unsigned numArgSlots() const {
    return (std::max)(numFormalArgs(), numActualArgs());
  }

  Value* argv() { return slots_; }
  Value* locals() { return slots_ + numArgSlots(); }

  Value& unaliasedLocal(unsigned i) {
    MOZ_ASSERT(i < script()->nfixed());
    return locals()[i];
  }
  Value& unaliasedFormal(unsigned i,
                         MaybeCheckAliasing checkAliasing = CHECK_ALIASING) {
    MOZ_ASSERT(i < numFormalArgs());
    MOZ_ASSERT_IF(checkAliasing, !script()->argsObjAliasesFormals() &&
                                     !script()->formalIsAliased(i));
    return argv()[i];
  }
  Value& unaliasedActual(unsigned i,
                         MaybeCheckAliasing checkAliasing = CHECK_ALIASING) {
    MOZ_ASSERT(i < numActualArgs());
    MOZ_ASSERT_IF(checkAliasing, !script()->argsObjAliasesFormals());
    MOZ_ASSERT_IF(checkAliasing && i < numFormalArgs(),
                  !script()->formalIsAliased(i));
    return argv()[i];
  }

  void setReturnValue(const Value& value) { returnValue_ = value; }

  Value& returnValue() {
    MOZ_ASSERT(!script()->noScriptRval());
    return returnValue_;
  }

  void trace(JSTracer* trc);
  void dump();
};

}  // namespace jit
}  // namespace js

#endif  // jit_RematerializedFrame_h
