/* -*- 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/. */ #ifndef js_ProfilingFrameIterator_h #define js_ProfilingFrameIterator_h #include "mozilla/Assertions.h" #include "mozilla/Attributes.h" #include "mozilla/Maybe.h" #include "jstypes.h" #include "js/GCAnnotations.h" #include "js/ProfilingCategory.h" #include "js/TypeDecls.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 { // 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 samplePositionInProfilerBuffer_; js::Activation* activation_; // For each JitActivation, this records the lowest (most recent) stack // address. This will usually be either the exitFP of the activation or the // frame or stack pointer of currently executing JIT/Wasm code. The Gecko // profiler uses this to skip native frames between the activation and // endStackAddress_. void* endStackAddress_ = nullptr; 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(storage()); } const js::wasm::ProfilingFrameIterator& wasmIter() const { MOZ_ASSERT(!done()); MOZ_ASSERT(isWasm()); return *static_cast(storage()); } js::jit::JSJitProfilingFrameIterator& jsJitIter() { MOZ_ASSERT(!done()); MOZ_ASSERT(isJSJit()); return *static_cast(storage()); } const js::jit::JSJitProfilingFrameIterator& jsJitIter() const { MOZ_ASSERT(!done()); MOZ_ASSERT(isJSJit()); return *static_cast(storage()); } void maybeSetEndStackAddress(void* addr) { // If endStackAddress_ has already been set, don't change it because we // want this to correspond to the most recent frame. if (!endStackAddress_) { endStackAddress_ = addr; } } void settleFrames(); void settle(); public: struct RegisterState { RegisterState() : pc(nullptr), sp(nullptr), fp(nullptr), unused1(nullptr), unused2(nullptr) {} void* pc; void* sp; void* fp; union { // Value of the LR register on ARM platforms. void* lr; // The return address during a tail call operation. // Note that for ARM is still the value of LR register. void* tempRA; // Undefined on non-ARM plaforms outside tail calls operations. void* unused1; }; union { // The FP reference during a tail call operation. void* tempFP; // Undefined outside tail calls operations. void* unused2; }; }; ProfilingFrameIterator( JSContext* cx, const RegisterState& state, const mozilla::Maybe& 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 // The exception is at the point of stack switching between the main stack // and a suspendable one (see WebAssembly JS Promise Integration proposal). void* stackAddress() const; enum FrameKind { Frame_BaselineInterpreter, Frame_Baseline, Frame_Ion, Frame_WasmBaseline, Frame_WasmIon, Frame_WasmOther, }; struct Frame { FrameKind kind; void* stackAddress; union { void* returnAddress_; jsbytecode* interpreterPC_; }; void* activation; void* endStackAddress; const char* label; JSScript* interpreterScript; uint64_t realmID; public: void* returnAddress() const { MOZ_ASSERT(kind != Frame_BaselineInterpreter); return returnAddress_; } jsbytecode* interpreterPC() const { MOZ_ASSERT(kind == Frame_BaselineInterpreter); return interpreterPC_; } ProfilingCategoryPair profilingCategory() const { switch (kind) { case FrameKind::Frame_BaselineInterpreter: return JS::ProfilingCategoryPair::JS_BaselineInterpret; case FrameKind::Frame_Baseline: return JS::ProfilingCategoryPair::JS_Baseline; case FrameKind::Frame_Ion: return JS::ProfilingCategoryPair::JS_IonMonkey; case FrameKind::Frame_WasmBaseline: return JS::ProfilingCategoryPair::JS_WasmBaseline; case FrameKind::Frame_WasmIon: return JS::ProfilingCategoryPair::JS_WasmIon; case FrameKind::Frame_WasmOther: return JS::ProfilingCategoryPair::JS_WasmOther; } MOZ_CRASH(); } } JS_HAZ_GC_INVALIDATED; bool isWasm() const; bool isJSJit() const; uint32_t extractStack(Frame* frames, uint32_t offset, uint32_t end) const; mozilla::Maybe getPhysicalFrameWithoutLabel() const; // Return the registers from the native caller frame. // Nothing{} if this iterator is NOT pointing at a native-to-JIT entry frame, // or if the information is not accessible/implemented on this platform. mozilla::Maybe getCppEntryRegisters() const; private: mozilla::Maybe getPhysicalFrameAndEntry( const js::jit::JitcodeGlobalEntry** entry) const; void iteratorConstruct(const RegisterState& state); void iteratorConstruct(); void iteratorDestroy(); bool iteratorDone(); } JS_HAZ_GC_INVALIDATED; JS_PUBLIC_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_PUBLIC_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_; ProfiledFrameHandle(JSRuntime* rt, js::jit::JitcodeGlobalEntry& entry, void* addr, const char* label, uint32_t depth); public: const char* label() const { return label_; } uint32_t depth() const { return depth_; } void* canonicalAddress() const { return canonicalAddr_; } JS_PUBLIC_API ProfilingFrameIterator::FrameKind frameKind() const; JS_PUBLIC_API uint64_t realmID() 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) const { return index_ == rhs.index_; } bool operator!=(const Iter& rhs) const { 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 */