DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Implementation

Mercurial (b6d82b1a6b02)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
/* -*- 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 vm_GeckoProfiler_h
#define vm_GeckoProfiler_h

#include "mozilla/DebugOnly.h"
#include "mozilla/GuardObjects.h"

#include <stddef.h>

#include "js/ProfilingStack.h"
#include "threading/ProtectedData.h"
#include "vm/JSScript.h"
#include "vm/MutexIDs.h"

/*
 * Gecko Profiler integration with the JS Engine
 * https://developer.mozilla.org/en/Performance/Profiling_with_the_Built-in_Profiler
 *
 * The Gecko Profiler (found in tools/profiler) is an implementation of a
 * profiler which has the ability to walk the C++ stack as well as use
 * instrumentation to gather information. When dealing with JS, however, the
 * profiler needs integration with the engine because otherwise it is very
 * difficult to figure out what javascript is executing.
 *
 * The current method of integration with the profiler is a form of
 * instrumentation: every time a JS function is entered, a bit of information
 * is pushed onto a stack that the profiler owns and maintains. This
 * information is then popped at the end of the JS function. The profiler
 * informs the JS engine of this stack at runtime, and it can by turned on/off
 * dynamically. Each stack frame has type ProfilingStackFrame.
 *
 * Throughout execution, the size of the stack recorded in memory may exceed the
 * maximum. The JS engine will not write any information past the maximum limit,
 * but it will still maintain the size of the stack. Profiler code is aware of
 * this and iterates the stack accordingly.
 *
 * There is some information pushed on the profiler stack for every JS function
 * that is entered. First is a char* label with a description of what function
 * was entered. Currently this string is of the form "function (file:line)" if
 * there's a function name, or just "file:line" if there's no function name
 * available. The other bit of information is the relevant C++ (native) stack
 * pointer. This stack pointer is what enables the interleaving of the C++ and
 * the JS stack. Finally, throughout execution of the function, some extra
 * information may be updated on the ProfilingStackFrame structure.
 *
 * = Profile Strings
 *
 * The profile strings' allocations and deallocation must be carefully
 * maintained, and ideally at a very low overhead cost. For this reason, the JS
 * engine maintains a mapping of all known profile strings. These strings are
 * keyed in lookup by a JSScript*, but are serialized with a JSFunction*,
 * JSScript* pair. A JSScript will destroy its corresponding profile string when
 * the script is finalized.
 *
 * For this reason, a char* pointer pushed on the profiler stack is valid only
 * while it is on the profiler stack. The profiler uses sampling to read off
 * information from this instrumented stack, and it therefore copies the string
 * byte for byte when a JS function is encountered during sampling.
 *
 * = Native Stack Pointer
 *
 * The actual value pushed as the native pointer is nullptr for most JS
 * functions. The reason for this is that there's actually very little
 * correlation between the JS stack and the C++ stack because many JS functions
 * all run in the same C++ frame, or can even go backwards in C++ when going
 * from the JIT back to the interpreter.
 *
 * To alleviate this problem, all JS functions push nullptr as their "native
 * stack pointer" to indicate that it's a JS function call. The function
 * RunScript(), however, pushes an actual C++ stack pointer onto the profiler
 * stack. This way when interleaving C++ and JS, if the Gecko Profiler sees a
 * nullptr native stack pointer on the profiler stack, it looks backwards for
 * the first non-nullptr pointer and uses that for all subsequent nullptr
 * native stack pointers.
 *
 * = Line Numbers
 *
 * One goal of sampling is to get both a backtrace of the JS stack, but also
 * know where within each function on the stack execution currently is. For
 * this, each ProfilingStackFrame has a 'pc' field to tell where its execution
 * currently is. This field is updated whenever a call is made to another JS
 * function, and for the JIT it is also updated whenever the JIT is left.
 *
 * This field is in a union with a uint32_t 'line' so that C++ can make use of
 * the field as well. It was observed that tracking 'line' via PCToLineNumber in
 * JS was far too expensive, so that is why the pc instead of the translated
 * line number is stored.
 *
 * As an invariant, if the pc is nullptr, then the JIT is currently executing
 * generated code. Otherwise execution is in another JS function or in C++. With
 * this in place, only the top frame of the stack can ever have nullptr as its
 * pc. Additionally with this invariant, it is possible to maintain mappings of
 * JIT code to pc which can be accessed safely because they will only be
 * accessed from a signal handler when the JIT code is executing.
 */

namespace js {

// The `ProfileStringMap` weakly holds its `JSScript*` keys and owns its string
// values. Entries are removed when the `JSScript` is finalized; see
// `GeckoProfiler::onScriptFinalized`.
using ProfileStringMap = HashMap<JSScript*, UniqueChars,
                                 DefaultHasher<JSScript*>, SystemAllocPolicy>;

class GeckoProfilerRuntime {
  JSRuntime* rt;
  MainThreadData<ProfileStringMap> strings_;
  bool slowAssertions;
  uint32_t enabled_;
  void (*eventMarker_)(const char*);

 public:
  explicit GeckoProfilerRuntime(JSRuntime* rt);

  /* management of whether instrumentation is on or off */
  bool enabled() { return enabled_; }
  void enable(bool enabled);
  void enableSlowAssertions(bool enabled) { slowAssertions = enabled; }
  bool slowAssertionsEnabled() { return slowAssertions; }

  void setEventMarker(void (*fn)(const char*));

  static UniqueChars allocProfileString(JSContext* cx, JSScript* script);
  const char* profileString(JSContext* cx, JSScript* script);

  void onScriptFinalized(JSScript* script);

  void markEvent(const char* event);

  ProfileStringMap& strings() { return strings_.ref(); }

  /* meant to be used for testing, not recommended to call in normal code */
  size_t stringsCount();
  void stringsReset();

  uint32_t* addressOfEnabled() { return &enabled_; }

  void fixupStringsMapAfterMovingGC();
#ifdef JSGC_HASH_TABLE_CHECKS
  void checkStringsMapAfterMovingGC();
#endif
};

inline size_t GeckoProfilerRuntime::stringsCount() { return strings().count(); }

inline void GeckoProfilerRuntime::stringsReset() { strings().clear(); }

/*
 * This class is used in RunScript() to push the marker onto the sampling stack
 * that we're about to enter JS function calls. This is the only time in which a
 * valid stack pointer is pushed to the sampling stack.
 */
class MOZ_RAII GeckoProfilerEntryMarker {
 public:
  explicit MOZ_ALWAYS_INLINE GeckoProfilerEntryMarker(
      JSContext* cx, JSScript* script MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
  MOZ_ALWAYS_INLINE ~GeckoProfilerEntryMarker();

 private:
  GeckoProfilerThread* profiler_;
#ifdef DEBUG
  uint32_t spBefore_;
#endif
  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};

/*
 * RAII class to automatically add Gecko Profiler profiling stack frames.
 *
 * NB: The `label` string must be statically allocated.
 */
class MOZ_NONHEAP_CLASS AutoGeckoProfilerEntry {
 public:
  explicit MOZ_ALWAYS_INLINE AutoGeckoProfilerEntry(
      JSContext* cx, const char* label,
      JS::ProfilingCategoryPair categoryPair = JS::ProfilingCategoryPair::JS,
      uint32_t flags = 0 MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
  MOZ_ALWAYS_INLINE ~AutoGeckoProfilerEntry();

 private:
  GeckoProfilerThread* profiler_;
#ifdef DEBUG
  uint32_t spBefore_;
#endif
  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};

/*
 * This class is used in the interpreter to bound regions where the baseline JIT
 * being entered via OSR.  It marks the current top profiling stack frame as
 * OSR-ed
 */
class MOZ_RAII GeckoProfilerBaselineOSRMarker {
 public:
  explicit GeckoProfilerBaselineOSRMarker(
      JSContext* cx, bool hasProfilerFrame MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
  ~GeckoProfilerBaselineOSRMarker();

 private:
  GeckoProfilerThread* profiler;
  mozilla::DebugOnly<uint32_t> spBefore_;
  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};

/*
 * This class manages the instrumentation portion of the profiling for JIT
 * code.
 *
 * The instrumentation tracks entry into functions, leaving those functions via
 * a function call, reentering the functions from a function call, and exiting
 * the functions from returning. This class also handles inline frames and
 * manages the instrumentation which needs to be attached to them as well.
 *
 * The basic methods which emit instrumentation are at the end of this class,
 * and the management functions are all described in the middle.
 */
template <class Assembler, class Register>
class GeckoProfilerInstrumentation {
  GeckoProfilerRuntime* profiler_;  // Instrumentation location management

 public:
  /*
   * Creates instrumentation which writes information out the the specified
   * profiler's stack and constituent fields.
   */
  explicit GeckoProfilerInstrumentation(GeckoProfilerRuntime* profiler)
      : profiler_(profiler) {}

  /* Small proxies around GeckoProfiler */
  bool enabled() { return profiler_ && profiler_->enabled(); }
  GeckoProfilerRuntime* profiler() {
    MOZ_ASSERT(enabled());
    return profiler_;
  }
  void disable() { profiler_ = nullptr; }
};

} /* namespace js */

#endif /* vm_GeckoProfiler_h */