ProfilingFrameIterator.h (10208B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef js_ProfilingFrameIterator_h 8 #define js_ProfilingFrameIterator_h 9 10 #include "mozilla/Assertions.h" 11 #include "mozilla/Attributes.h" 12 #include "mozilla/Maybe.h" 13 14 #include "jstypes.h" 15 16 #include "js/GCAnnotations.h" 17 #include "js/ProfilingCategory.h" 18 #include "js/TypeDecls.h" 19 20 namespace js { 21 class Activation; 22 namespace jit { 23 class JitActivation; 24 class JSJitProfilingFrameIterator; 25 class JitcodeGlobalEntry; 26 27 // Information about a single frame in a JIT call stack, returned by 28 // JitcodeGlobalEntry::callStackAtAddr. 29 struct CallStackFrameInfo { 30 // The function name or label for this frame. 31 const char* label; 32 // The script source ID for this frame. Used to identify which script source 33 // this frame belongs to. 34 uint32_t sourceId; 35 }; 36 37 } // namespace jit 38 namespace wasm { 39 class ProfilingFrameIterator; 40 } // namespace wasm 41 } // namespace js 42 43 namespace JS { 44 45 // This iterator can be used to walk the stack of a thread suspended at an 46 // arbitrary pc. To provide accurate results, profiling must have been enabled 47 // (via EnableRuntimeProfilingStack) before executing the callstack being 48 // unwound. 49 // 50 // Note that the caller must not do anything that could cause GC to happen while 51 // the iterator is alive, since this could invalidate Ion code and cause its 52 // contents to become out of date. 53 class MOZ_NON_PARAM JS_PUBLIC_API ProfilingFrameIterator { 54 public: 55 enum class Kind : bool { JSJit, Wasm }; 56 57 private: 58 JSContext* cx_; 59 mozilla::Maybe<uint64_t> samplePositionInProfilerBuffer_; 60 js::Activation* activation_; 61 // For each JitActivation, this records the lowest (most recent) stack 62 // address. This will usually be either the exitFP of the activation or the 63 // frame or stack pointer of currently executing JIT/Wasm code. The Gecko 64 // profiler uses this to skip native frames between the activation and 65 // endStackAddress_. 66 void* endStackAddress_ = nullptr; 67 Kind kind_; 68 69 static const unsigned StorageSpace = 9 * sizeof(void*); 70 alignas(void*) unsigned char storage_[StorageSpace]; 71 72 void* storage() { return storage_; } 73 const void* storage() const { return storage_; } 74 75 js::wasm::ProfilingFrameIterator& wasmIter() { 76 MOZ_ASSERT(!done()); 77 MOZ_ASSERT(isWasm()); 78 return *static_cast<js::wasm::ProfilingFrameIterator*>(storage()); 79 } 80 const js::wasm::ProfilingFrameIterator& wasmIter() const { 81 MOZ_ASSERT(!done()); 82 MOZ_ASSERT(isWasm()); 83 return *static_cast<const js::wasm::ProfilingFrameIterator*>(storage()); 84 } 85 86 js::jit::JSJitProfilingFrameIterator& jsJitIter() { 87 MOZ_ASSERT(!done()); 88 MOZ_ASSERT(isJSJit()); 89 return *static_cast<js::jit::JSJitProfilingFrameIterator*>(storage()); 90 } 91 92 const js::jit::JSJitProfilingFrameIterator& jsJitIter() const { 93 MOZ_ASSERT(!done()); 94 MOZ_ASSERT(isJSJit()); 95 return *static_cast<const js::jit::JSJitProfilingFrameIterator*>(storage()); 96 } 97 98 void maybeSetEndStackAddress(void* addr) { 99 // If endStackAddress_ has already been set, don't change it because we 100 // want this to correspond to the most recent frame. 101 if (!endStackAddress_) { 102 endStackAddress_ = addr; 103 } 104 } 105 106 void settleFrames(); 107 void settle(); 108 109 public: 110 struct RegisterState { 111 RegisterState() 112 : pc(nullptr), 113 sp(nullptr), 114 fp(nullptr), 115 unused1(nullptr), 116 unused2(nullptr) {} 117 void* pc; 118 void* sp; 119 void* fp; 120 union { 121 // Value of the LR register on ARM platforms. 122 void* lr; 123 // The return address during a tail call operation. 124 // Note that for ARM is still the value of LR register. 125 void* tempRA; 126 // Undefined on non-ARM plaforms outside tail calls operations. 127 void* unused1; 128 }; 129 union { 130 // The FP reference during a tail call operation. 131 void* tempFP; 132 // Undefined outside tail calls operations. 133 void* unused2; 134 }; 135 }; 136 137 ProfilingFrameIterator( 138 JSContext* cx, const RegisterState& state, 139 const mozilla::Maybe<uint64_t>& samplePositionInProfilerBuffer = 140 mozilla::Nothing()); 141 ~ProfilingFrameIterator(); 142 void operator++(); 143 bool done() const { return !activation_; } 144 145 // Assuming the stack grows down (we do), the return value: 146 // - always points into the stack 147 // - is weakly monotonically increasing (may be equal for successive frames) 148 // - will compare greater than newer native and psuedo-stack frame addresses 149 // and less than older native and psuedo-stack frame addresses 150 // The exception is at the point of stack switching between the main stack 151 // and a suspendable one (see WebAssembly JS Promise Integration proposal). 152 void* stackAddress() const; 153 154 enum FrameKind { 155 Frame_BaselineInterpreter, 156 Frame_Baseline, 157 Frame_Ion, 158 Frame_WasmBaseline, 159 Frame_WasmIon, 160 Frame_WasmOther, 161 }; 162 163 struct Frame { 164 FrameKind kind; 165 void* stackAddress; 166 union { 167 void* returnAddress_; 168 jsbytecode* interpreterPC_; 169 }; 170 void* activation; 171 void* endStackAddress; 172 const char* label; 173 JSScript* interpreterScript; 174 uint64_t realmID; 175 uint32_t sourceId; 176 177 public: 178 void* returnAddress() const { 179 MOZ_ASSERT(kind != Frame_BaselineInterpreter); 180 return returnAddress_; 181 } 182 jsbytecode* interpreterPC() const { 183 MOZ_ASSERT(kind == Frame_BaselineInterpreter); 184 return interpreterPC_; 185 } 186 ProfilingCategoryPair profilingCategory() const { 187 switch (kind) { 188 case FrameKind::Frame_BaselineInterpreter: 189 return JS::ProfilingCategoryPair::JS_BaselineInterpret; 190 case FrameKind::Frame_Baseline: 191 return JS::ProfilingCategoryPair::JS_Baseline; 192 case FrameKind::Frame_Ion: 193 return JS::ProfilingCategoryPair::JS_IonMonkey; 194 case FrameKind::Frame_WasmBaseline: 195 return JS::ProfilingCategoryPair::JS_WasmBaseline; 196 case FrameKind::Frame_WasmIon: 197 return JS::ProfilingCategoryPair::JS_WasmIon; 198 case FrameKind::Frame_WasmOther: 199 return JS::ProfilingCategoryPair::JS_WasmOther; 200 } 201 MOZ_CRASH(); 202 } 203 } JS_HAZ_GC_INVALIDATED; 204 205 bool isWasm() const; 206 bool isJSJit() const; 207 208 uint32_t extractStack(Frame* frames, uint32_t offset, uint32_t end) const; 209 210 mozilla::Maybe<Frame> getPhysicalFrameWithoutLabel() const; 211 212 // Return the registers from the native caller frame. 213 // Nothing{} if this iterator is NOT pointing at a native-to-JIT entry frame, 214 // or if the information is not accessible/implemented on this platform. 215 mozilla::Maybe<RegisterState> getCppEntryRegisters() const; 216 217 private: 218 mozilla::Maybe<Frame> getPhysicalFrameAndEntry( 219 const js::jit::JitcodeGlobalEntry** entry) const; 220 221 void iteratorConstruct(const RegisterState& state); 222 void iteratorConstruct(); 223 void iteratorDestroy(); 224 bool iteratorDone(); 225 } JS_HAZ_GC_INVALIDATED; 226 227 JS_PUBLIC_API bool IsProfilingEnabledForContext(JSContext* cx); 228 229 /** 230 * After each sample run, this method should be called with the current buffer 231 * position at which the buffer contents start. This will update the 232 * corresponding field on the JSRuntime. 233 * 234 * See the field |profilerSampleBufferRangeStart| on JSRuntime for documentation 235 * about what this value is used for. 236 */ 237 JS_PUBLIC_API void SetJSContextProfilerSampleBufferRangeStart( 238 JSContext* cx, uint64_t rangeStart); 239 240 class ProfiledFrameRange; 241 242 // A handle to the underlying JitcodeGlobalEntry, so as to avoid repeated 243 // lookups on JitcodeGlobalTable. 244 class MOZ_STACK_CLASS ProfiledFrameHandle { 245 friend class ProfiledFrameRange; 246 247 JSRuntime* rt_; 248 js::jit::JitcodeGlobalEntry& entry_; 249 void* addr_; 250 void* canonicalAddr_; 251 const char* label_; 252 uint32_t sourceId_; 253 uint32_t depth_; 254 255 ProfiledFrameHandle(JSRuntime* rt, js::jit::JitcodeGlobalEntry& entry, 256 void* addr, const char* label, uint32_t sourceId, 257 uint32_t depth); 258 259 public: 260 const char* label() const { return label_; } 261 uint32_t depth() const { return depth_; } 262 void* canonicalAddress() const { return canonicalAddr_; } 263 264 JS_PUBLIC_API ProfilingFrameIterator::FrameKind frameKind() const; 265 266 JS_PUBLIC_API uint64_t realmID() const; 267 268 JS_PUBLIC_API uint32_t sourceId() const; 269 }; 270 271 class ProfiledFrameRange { 272 public: 273 class Iter final { 274 public: 275 Iter(const ProfiledFrameRange& range, uint32_t index) 276 : range_(range), index_(index) {} 277 278 JS_PUBLIC_API ProfiledFrameHandle operator*() const; 279 280 // Provide the bare minimum of iterator methods that are needed for 281 // C++ ranged for loops. 282 Iter& operator++() { 283 ++index_; 284 return *this; 285 } 286 bool operator==(const Iter& rhs) const { return index_ == rhs.index_; } 287 bool operator!=(const Iter& rhs) const { return !(*this == rhs); } 288 289 private: 290 const ProfiledFrameRange& range_; 291 uint32_t index_; 292 }; 293 294 Iter begin() const { return Iter(*this, 0); } 295 Iter end() const { return Iter(*this, depth_); } 296 297 private: 298 friend JS_PUBLIC_API ProfiledFrameRange GetProfiledFrames(JSContext* cx, 299 void* addr); 300 301 ProfiledFrameRange(JSRuntime* rt, void* addr, 302 js::jit::JitcodeGlobalEntry* entry) 303 : rt_(rt), addr_(addr), entry_(entry), depth_(0) {} 304 305 JSRuntime* rt_; 306 void* addr_; 307 js::jit::JitcodeGlobalEntry* entry_; 308 // Maximum inlining depth. This must match InlineScriptTree::MaxDepth. 309 // We can't use InlineScriptTree::MaxDepth directly here because this is a 310 // public header and InlineScriptTree.h is private. 311 static constexpr uint32_t MaxInliningDepth = 8; 312 js::jit::CallStackFrameInfo frames_[MaxInliningDepth]; 313 uint32_t depth_; 314 }; 315 316 // Returns a range that can be iterated over using C++ ranged for loops. 317 JS_PUBLIC_API ProfiledFrameRange GetProfiledFrames(JSContext* cx, void* addr); 318 319 } // namespace JS 320 321 #endif /* js_ProfilingFrameIterator_h */