/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 * 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 js_ProfilingFrameIterator_h
#define js_ProfilingFrameIterator_h

#include "mozilla/Attributes.h"
#include "mozilla/Maybe.h"

#include "js/GCAPI.h"
#include "js/TypeDecls.h"
#include "js/Utility.h"

namespace js {
    class Activation;
    namespace jit {
        class JitActivation;
        class JSJitProfilingFrameIterator;
        class JitcodeGlobalEntry;
    } // namespace jit
    namespace wasm {
        class ProfilingFrameIterator;
    } // namespace wasm
} // namespace js

namespace JS {

struct ForEachTrackedOptimizationAttemptOp;
struct ForEachTrackedOptimizationTypeInfoOp;

// This iterator can be used to walk the stack of a thread suspended at an
// arbitrary pc. To provide accurate results, profiling must have been enabled
// (via EnableRuntimeProfilingStack) before executing the callstack being
// unwound.
//
// Note that the caller must not do anything that could cause GC to happen while
// the iterator is alive, since this could invalidate Ion code and cause its
// contents to become out of date.
class MOZ_NON_PARAM JS_PUBLIC_API(ProfilingFrameIterator)
{
  public:
    enum class Kind : bool {
        JSJit,
        Wasm
    };

  private:
    JSContext* cx_;
    mozilla::Maybe<uint64_t> samplePositionInProfilerBuffer_;
    js::Activation* activation_;
    Kind kind_;

    static const unsigned StorageSpace = 8 * sizeof(void*);
    alignas(void*) unsigned char storage_[StorageSpace];

    void* storage() { return storage_; }
    const void* storage() const { return storage_; }

    js::wasm::ProfilingFrameIterator& wasmIter() {
        MOZ_ASSERT(!done());
        MOZ_ASSERT(isWasm());
        return *static_cast<js::wasm::ProfilingFrameIterator*>(storage());
    }
    const js::wasm::ProfilingFrameIterator& wasmIter() const {
        MOZ_ASSERT(!done());
        MOZ_ASSERT(isWasm());
        return *static_cast<const js::wasm::ProfilingFrameIterator*>(storage());
    }

    js::jit::JSJitProfilingFrameIterator& jsJitIter() {
        MOZ_ASSERT(!done());
        MOZ_ASSERT(isJSJit());
        return *static_cast<js::jit::JSJitProfilingFrameIterator*>(storage());
    }

    const js::jit::JSJitProfilingFrameIterator& jsJitIter() const {
        MOZ_ASSERT(!done());
        MOZ_ASSERT(isJSJit());
        return *static_cast<const js::jit::JSJitProfilingFrameIterator*>(storage());
    }

    void settleFrames();
    void settle();

  public:
    struct RegisterState
    {
        RegisterState() : pc(nullptr), sp(nullptr), fp(nullptr), lr(nullptr) {}
        void* pc;
        void* sp;
        void* fp;
        void* lr;
    };

    ProfilingFrameIterator(JSContext* cx, const RegisterState& state,
                           const mozilla::Maybe<uint64_t>& samplePositionInProfilerBuffer = mozilla::Nothing());
    ~ProfilingFrameIterator();
    void operator++();
    bool done() const { return !activation_; }

    // Assuming the stack grows down (we do), the return value:
    //  - always points into the stack
    //  - is weakly monotonically increasing (may be equal for successive frames)
    //  - will compare greater than newer native and psuedo-stack frame addresses
    //    and less than older native and psuedo-stack frame addresses
    void* stackAddress() const;

    enum FrameKind
    {
      Frame_Baseline,
      Frame_Ion,
      Frame_Wasm
    };

    struct Frame
    {
        FrameKind kind;
        void* stackAddress;
        void* returnAddress;
        void* activation;
        void* endStackAddress;
        const char* label;
    } JS_HAZ_GC_INVALIDATED;

    bool isWasm() const;
    bool isJSJit() const;

    uint32_t extractStack(Frame* frames, uint32_t offset, uint32_t end) const;

    mozilla::Maybe<Frame> getPhysicalFrameWithoutLabel() const;

  private:
    mozilla::Maybe<Frame> getPhysicalFrameAndEntry(js::jit::JitcodeGlobalEntry* entry) const;

    void iteratorConstruct(const RegisterState& state);
    void iteratorConstruct();
    void iteratorDestroy();
    bool iteratorDone();
} JS_HAZ_GC_INVALIDATED;

JS_FRIEND_API(bool)
IsProfilingEnabledForContext(JSContext* cx);

/**
 * After each sample run, this method should be called with the current buffer
 * position at which the buffer contents start. This will update the
 * corresponding field on the JSRuntime.
 *
 * See the field |profilerSampleBufferRangeStart| on JSRuntime for documentation
 * about what this value is used for.
 */
JS_FRIEND_API(void)
SetJSContextProfilerSampleBufferRangeStart(JSContext* cx, uint64_t rangeStart);

class ProfiledFrameRange;

// A handle to the underlying JitcodeGlobalEntry, so as to avoid repeated
// lookups on JitcodeGlobalTable.
class MOZ_STACK_CLASS ProfiledFrameHandle
{
    friend class ProfiledFrameRange;

    JSRuntime* rt_;
    js::jit::JitcodeGlobalEntry& entry_;
    void* addr_;
    void* canonicalAddr_;
    const char* label_;
    uint32_t depth_;
    mozilla::Maybe<uint8_t> optsIndex_;

    ProfiledFrameHandle(JSRuntime* rt, js::jit::JitcodeGlobalEntry& entry,
                        void* addr, const char* label, uint32_t depth);

    void updateHasTrackedOptimizations();

public:
    const char* label() const { return label_; }
    uint32_t depth() const { return depth_; }
    bool hasTrackedOptimizations() const { return optsIndex_.isSome(); }
    void* canonicalAddress() const { return canonicalAddr_; }

    JS_PUBLIC_API(ProfilingFrameIterator::FrameKind) frameKind() const;
    JS_PUBLIC_API(void) forEachOptimizationAttempt(ForEachTrackedOptimizationAttemptOp& op,
                                                    JSScript** scriptOut,
                                                    jsbytecode** pcOut) const;

    JS_PUBLIC_API(void)
    forEachOptimizationTypeInfo(ForEachTrackedOptimizationTypeInfoOp& op) const;
};

class ProfiledFrameRange
{
public:
    class Iter final
    {
    public:
        Iter(const ProfiledFrameRange& range, uint32_t index)
          : range_(range)
          , index_(index)
        {}

        JS_PUBLIC_API(ProfiledFrameHandle) operator*() const;

        // Provide the bare minimum of iterator methods that are needed for
        // C++ ranged for loops.
        Iter& operator++() { ++index_; return *this; }
        bool operator==(const Iter& rhs) { return index_ == rhs.index_; }
        bool operator!=(const Iter& rhs) { return !(*this == rhs); }

    private:
        const ProfiledFrameRange& range_;
        uint32_t index_;
    };

    Iter begin() const { return Iter(*this, 0); }
    Iter end() const { return Iter(*this, depth_); }

private:
    friend JS_PUBLIC_API(ProfiledFrameRange) GetProfiledFrames(JSContext* cx,
                                                               void* addr);

    ProfiledFrameRange(JSRuntime* rt, void* addr, js::jit::JitcodeGlobalEntry* entry)
      : rt_(rt)
      , addr_(addr)
      , entry_(entry)
      , depth_(0)
    {}

    JSRuntime* rt_;
    void* addr_;
    js::jit::JitcodeGlobalEntry* entry_;
    // Assume maximum inlining depth is <64
    const char* labels_[64];
    uint32_t depth_;
};

// Returns a range that can be iterated over using C++ ranged for loops.
JS_PUBLIC_API(ProfiledFrameRange)
GetProfiledFrames(JSContext* cx, void* addr);

} // namespace JS

#endif  /* js_ProfilingFrameIterator_h */
