tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

GeckoProfiler.cpp (25613B)


      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 #include "vm/GeckoProfiler-inl.h"
      8 
      9 #include "mozilla/DebugOnly.h"
     10 #include "mozilla/Sprintf.h"
     11 
     12 #include "gc/GC.h"
     13 #include "gc/PublicIterators.h"
     14 #include "jit/BaselineJIT.h"
     15 #include "jit/JitcodeMap.h"
     16 #include "jit/JitRuntime.h"
     17 #include "jit/JSJitFrameIter.h"
     18 #include "jit/PerfSpewer.h"
     19 #include "js/experimental/SourceHook.h"
     20 #include "vm/FrameIter.h"  // js::OnlyJSJitFrameIter
     21 #include "vm/JitActivation.h"
     22 #include "vm/JSScript.h"
     23 #include "vm/MutexIDs.h"
     24 
     25 #include "gc/Marking-inl.h"
     26 #include "jit/JSJitFrameIter-inl.h"
     27 
     28 using namespace js;
     29 using mozilla::Utf8Unit;
     30 
     31 GeckoProfilerThread::GeckoProfilerThread()
     32    : profilingStack_(nullptr), profilingStackIfEnabled_(nullptr) {}
     33 
     34 GeckoProfilerRuntime::GeckoProfilerRuntime(JSRuntime* rt)
     35    : rt(rt),
     36      scriptSources_(mutexid::GeckoProfilerScriptSources),
     37      slowAssertions(false),
     38      enabled_(false),
     39      eventMarker_(nullptr),
     40      intervalMarker_(nullptr),
     41      flowMarker_(nullptr),
     42      terminatingFlowMarker_(nullptr) {
     43  MOZ_ASSERT(rt != nullptr);
     44 }
     45 
     46 void GeckoProfilerThread::setProfilingStack(ProfilingStack* profilingStack,
     47                                            bool enabled) {
     48  profilingStack_ = profilingStack;
     49  profilingStackIfEnabled_ = enabled ? profilingStack : nullptr;
     50 }
     51 
     52 void GeckoProfilerRuntime::setEventMarker(void (*fn)(mozilla::MarkerCategory,
     53                                                     const char*,
     54                                                     const char*)) {
     55  eventMarker_ = fn;
     56 }
     57 
     58 void GeckoProfilerRuntime::setIntervalMarker(void (*fn)(
     59    mozilla::MarkerCategory, const char*, mozilla::TimeStamp, const char*)) {
     60  intervalMarker_ = fn;
     61 }
     62 
     63 void GeckoProfilerRuntime::setFlowMarker(void (*fn)(mozilla::MarkerCategory,
     64                                                    const char*, uint64_t)) {
     65  flowMarker_ = fn;
     66 }
     67 
     68 void GeckoProfilerRuntime::setTerminatingFlowMarker(
     69    void (*fn)(mozilla::MarkerCategory, const char*, uint64_t)) {
     70  terminatingFlowMarker_ = fn;
     71 }
     72 
     73 // Get a pointer to the top-most profiling frame, given the exit frame pointer.
     74 static jit::JitFrameLayout* GetTopProfilingJitFrame(jit::JitActivation* act) {
     75  // If there is no exit frame set, just return.
     76  if (!act->hasExitFP()) {
     77    return nullptr;
     78  }
     79 
     80  // Skip wasm frames that might be in the way.
     81  OnlyJSJitFrameIter iter(act);
     82  if (iter.done()) {
     83    return nullptr;
     84  }
     85 
     86  // Skip if the activation has no JS frames. This can happen if there's only a
     87  // TrampolineNative frame because these are skipped by the profiling frame
     88  // iterator.
     89  jit::JSJitProfilingFrameIterator jitIter(
     90      (jit::CommonFrameLayout*)iter.frame().fp());
     91  if (jitIter.done()) {
     92    return nullptr;
     93  }
     94 
     95  return jitIter.framePtr();
     96 }
     97 
     98 void GeckoProfilerRuntime::enable(bool enabled) {
     99  JSContext* cx = rt->mainContextFromAnyThread();
    100  MOZ_ASSERT(cx->geckoProfiler().infraInstalled());
    101 
    102  if (enabled_ == enabled) {
    103    return;
    104  }
    105 
    106  /*
    107   * Ensure all future generated code will be instrumented, or that all
    108   * currently instrumented code is discarded
    109   */
    110  ReleaseAllJITCode(rt->gcContext());
    111 
    112  // This function is called when the Gecko profiler makes a new Sampler
    113  // (and thus, a new circular buffer). Set all current entries in the
    114  // JitcodeGlobalTable as expired and reset the buffer range start.
    115  if (rt->hasJitRuntime() && rt->jitRuntime()->hasJitcodeGlobalTable()) {
    116    rt->jitRuntime()->getJitcodeGlobalTable()->setAllEntriesAsExpired();
    117  }
    118  rt->setProfilerSampleBufferRangeStart(0);
    119 
    120  // Ensure that lastProfilingFrame is null for the main thread.
    121  if (cx->jitActivation) {
    122    cx->jitActivation->setLastProfilingFrame(nullptr);
    123    cx->jitActivation->setLastProfilingCallSite(nullptr);
    124  }
    125 
    126  enabled_ = enabled;
    127 
    128  scriptSources_.writeLock()->clear();
    129 
    130  /* Toggle Gecko Profiler-related jumps on baseline jitcode.
    131   * The call to |ReleaseAllJITCode| above will release most baseline jitcode,
    132   * but not jitcode for scripts with active frames on the stack.  These scripts
    133   * need to have their profiler state toggled so they behave properly.
    134   */
    135  jit::ToggleBaselineProfiling(cx, enabled);
    136 
    137  // Update lastProfilingFrame to point to the top-most JS jit-frame currently
    138  // on stack.
    139  if (cx->jitActivation) {
    140    // Walk through all activations, and set their lastProfilingFrame
    141    // appropriately.
    142    if (enabled) {
    143      jit::JitActivation* jitActivation = cx->jitActivation;
    144      while (jitActivation) {
    145        auto* lastProfilingFrame = GetTopProfilingJitFrame(jitActivation);
    146        jitActivation->setLastProfilingFrame(lastProfilingFrame);
    147        jitActivation->setLastProfilingCallSite(nullptr);
    148        jitActivation = jitActivation->prevJitActivation();
    149      }
    150    } else {
    151      jit::JitActivation* jitActivation = cx->jitActivation;
    152      while (jitActivation) {
    153        jitActivation->setLastProfilingFrame(nullptr);
    154        jitActivation->setLastProfilingCallSite(nullptr);
    155        jitActivation = jitActivation->prevJitActivation();
    156      }
    157    }
    158  }
    159 
    160  // WebAssembly code does not need to be released, but profiling string
    161  // labels have to be generated so that they are available during async
    162  // profiling stack iteration.
    163  for (RealmsIter r(rt); !r.done(); r.next()) {
    164    r->wasm.ensureProfilingLabels(enabled);
    165  }
    166 
    167 #ifdef JS_STRUCTURED_SPEW
    168  // Enable the structured spewer if the environment variable is set.
    169  if (enabled) {
    170    cx->spewer().enableSpewing();
    171  } else {
    172    cx->spewer().disableSpewing();
    173  }
    174 #endif
    175 }
    176 
    177 /* Lookup the string for the function/script, creating one if necessary */
    178 const char* GeckoProfilerRuntime::profileString(JSContext* cx,
    179                                                BaseScript* script) {
    180  ProfileStringMap::AddPtr s = strings().lookupForAdd(script);
    181 
    182  if (!s) {
    183    UniqueChars str = allocProfileString(cx, script);
    184    if (!str) {
    185      return nullptr;
    186    }
    187    MOZ_ASSERT(script->hasBytecode());
    188    if (!strings().add(s, script, std::move(str))) {
    189      ReportOutOfMemory(cx);
    190      return nullptr;
    191    }
    192  }
    193 
    194  return s->value().get();
    195 }
    196 
    197 void GeckoProfilerRuntime::onScriptFinalized(BaseScript* script) {
    198  /*
    199   * This function is called whenever a script is destroyed, regardless of
    200   * whether profiling has been turned on, so don't invoke a function on an
    201   * invalid hash set. Also, even if profiling was enabled but then turned
    202   * off, we still want to remove the string, so no check of enabled() is
    203   * done.
    204   */
    205  if (ProfileStringMap::Ptr entry = strings().lookup(script)) {
    206    strings().remove(entry);
    207  }
    208 }
    209 
    210 void GeckoProfilerRuntime::markEvent(const char* event, const char* details,
    211                                     JS::ProfilingCategoryPair jsPair) {
    212  MOZ_ASSERT(enabled());
    213  if (eventMarker_) {
    214    JS::AutoSuppressGCAnalysis nogc;
    215    mozilla::MarkerCategory category(
    216        static_cast<mozilla::baseprofiler::ProfilingCategoryPair>(jsPair));
    217    eventMarker_(category, event, details);
    218  }
    219 }
    220 
    221 void GeckoProfilerRuntime::markInterval(const char* event,
    222                                        mozilla::TimeStamp start,
    223                                        const char* details,
    224                                        JS::ProfilingCategoryPair jsPair) {
    225  MOZ_ASSERT(enabled());
    226  if (intervalMarker_) {
    227    JS::AutoSuppressGCAnalysis nogc;
    228    mozilla::MarkerCategory category(
    229        static_cast<mozilla::baseprofiler::ProfilingCategoryPair>(jsPair));
    230    intervalMarker_(category, event, start, details);
    231  }
    232 }
    233 
    234 void GeckoProfilerRuntime::markFlow(const char* markerName, uint64_t flowId,
    235                                    JS::ProfilingCategoryPair jsPair) {
    236  MOZ_ASSERT(enabled());
    237  if (flowMarker_) {
    238    JS::AutoSuppressGCAnalysis nogc;
    239    mozilla::MarkerCategory category(
    240        static_cast<mozilla::baseprofiler::ProfilingCategoryPair>(jsPair));
    241    flowMarker_(category, markerName, flowId);
    242  }
    243 }
    244 
    245 void GeckoProfilerRuntime::markTerminatingFlow(
    246    const char* markerName, uint64_t flowId, JS::ProfilingCategoryPair jsPair) {
    247  MOZ_ASSERT(enabled());
    248  if (terminatingFlowMarker_) {
    249    JS::AutoSuppressGCAnalysis nogc;
    250    mozilla::MarkerCategory category(
    251        static_cast<mozilla::baseprofiler::ProfilingCategoryPair>(jsPair));
    252    terminatingFlowMarker_(category, markerName, flowId);
    253  }
    254 }
    255 
    256 bool GeckoProfilerThread::enter(JSContext* cx, JSScript* script) {
    257  const char* dynamicString =
    258      cx->runtime()->geckoProfiler().profileString(cx, script);
    259  if (dynamicString == nullptr) {
    260    return false;
    261  }
    262 
    263  if (!cx->runtime()->geckoProfiler().insertScriptSource(
    264          script->scriptSource())) {
    265    ReportOutOfMemory(cx);
    266    return false;
    267  }
    268 
    269 #ifdef DEBUG
    270  // In debug builds, assert the JS profiling stack frames already on the
    271  // stack have a non-null pc. Only look at the top frames to avoid quadratic
    272  // behavior.
    273  uint32_t sp = profilingStack_->stackPointer;
    274  if (sp > 0 && sp - 1 < profilingStack_->stackCapacity()) {
    275    size_t start = (sp > 4) ? sp - 4 : 0;
    276    for (size_t i = start; i < sp - 1; i++) {
    277      MOZ_ASSERT_IF(profilingStack_->frames[i].isJsFrame(),
    278                    profilingStack_->frames[i].pc());
    279    }
    280  }
    281 #endif
    282 
    283  profilingStack_->pushJsFrame(
    284      "", dynamicString, script, script->code(),
    285      script->realm()->creationOptions().profilerRealmID(),
    286      script->scriptSource()->id());
    287  return true;
    288 }
    289 
    290 void GeckoProfilerThread::exit(JSContext* cx, JSScript* script) {
    291  profilingStack_->pop();
    292 
    293 #ifdef DEBUG
    294  /* Sanity check to make sure push/pop balanced */
    295  uint32_t sp = profilingStack_->stackPointer;
    296  if (sp < profilingStack_->stackCapacity()) {
    297    JSRuntime* rt = script->runtimeFromMainThread();
    298    const char* dynamicString = rt->geckoProfiler().profileString(cx, script);
    299    /* Can't fail lookup because we should already be in the set */
    300    MOZ_ASSERT(dynamicString);
    301 
    302    // Bug 822041
    303    if (!profilingStack_->frames[sp].isJsFrame()) {
    304      fprintf(stderr, "--- ABOUT TO FAIL ASSERTION ---\n");
    305      fprintf(stderr, " frames=%p size=%u/%u\n", (void*)profilingStack_->frames,
    306              uint32_t(profilingStack_->stackPointer),
    307              profilingStack_->stackCapacity());
    308      for (int32_t i = sp; i >= 0; i--) {
    309        ProfilingStackFrame& frame = profilingStack_->frames[i];
    310        if (frame.isJsFrame()) {
    311          fprintf(stderr, "  [%d] JS %s\n", i, frame.dynamicString());
    312        } else {
    313          fprintf(stderr, "  [%d] Label %s\n", i, frame.dynamicString());
    314        }
    315      }
    316    }
    317 
    318    ProfilingStackFrame& frame = profilingStack_->frames[sp];
    319    MOZ_ASSERT(frame.isJsFrame());
    320    MOZ_ASSERT(frame.script() == script);
    321    MOZ_ASSERT(strcmp((const char*)frame.dynamicString(), dynamicString) == 0);
    322  }
    323 #endif
    324 }
    325 
    326 /*
    327 * Serializes the script/function pair into a "descriptive string" which is
    328 * allowed to fail. This function cannot trigger a GC because it could finalize
    329 * some scripts, resize the hash table of profile strings, and invalidate the
    330 * AddPtr held while invoking allocProfileString.
    331 */
    332 /* static */
    333 UniqueChars GeckoProfilerRuntime::allocProfileString(JSContext* cx,
    334                                                     BaseScript* script) {
    335  // Note: this profiler string is regexp-matched by
    336  // profiler code. Most recently at
    337  // https://github.com/firefox-devtools/profiler/blob/245b1a400c5c368ccc13641d0335398bafa0e870/src/profile-logic/process-profile.js#L520-L525
    338 
    339  // If the script has a function, try calculating its name.
    340  JSAtom* name = nullptr;
    341  size_t nameLength = 0;
    342  JSFunction* func = script->function();
    343  if (func && func->fullDisplayAtom()) {
    344    name = func->fullDisplayAtom();
    345    nameLength = JS::GetDeflatedUTF8StringLength(name);
    346  }
    347 
    348  // Calculate filename length. We cap this to a reasonable limit to avoid
    349  // performance impact of strlen/alloc/memcpy.
    350  constexpr size_t MaxFilenameLength = 200;
    351  const char* filenameStr = script->filename() ? script->filename() : "(null)";
    352  size_t filenameLength = js_strnlen(filenameStr, MaxFilenameLength);
    353 
    354  // Calculate line + column length.
    355  bool hasLineAndColumn = false;
    356  size_t lineAndColumnLength = 0;
    357  char lineAndColumnStr[30];
    358  if (name || script->isFunction() || script->isForEval()) {
    359    lineAndColumnLength =
    360        SprintfLiteral(lineAndColumnStr, "%u:%u", script->lineno(),
    361                       script->column().oneOriginValue());
    362    hasLineAndColumn = true;
    363  }
    364 
    365  // Full profile string for scripts with functions is:
    366  //      FuncName (FileName:Lineno:Column)
    367  // Full profile string for scripts without functions is:
    368  //      FileName:Lineno:Column
    369  // Full profile string for scripts without functions and without lines is:
    370  //      FileName
    371 
    372  // Calculate full string length.
    373  size_t fullLength = 0;
    374  if (name) {
    375    MOZ_ASSERT(hasLineAndColumn);
    376    fullLength = nameLength + 2 + filenameLength + 1 + lineAndColumnLength + 1;
    377  } else if (hasLineAndColumn) {
    378    fullLength = filenameLength + 1 + lineAndColumnLength;
    379  } else {
    380    fullLength = filenameLength;
    381  }
    382 
    383  // Allocate string.
    384  UniqueChars str(cx->pod_malloc<char>(fullLength + 1));
    385  if (!str) {
    386    return nullptr;
    387  }
    388 
    389  size_t cur = 0;
    390 
    391  // Fill string with function name if needed.
    392  if (name) {
    393    mozilla::DebugOnly<size_t> written = JS::DeflateStringToUTF8Buffer(
    394        name, mozilla::Span(str.get() + cur, nameLength));
    395    MOZ_ASSERT(written == nameLength);
    396    cur += nameLength;
    397    str[cur++] = ' ';
    398    str[cur++] = '(';
    399  }
    400 
    401  // Fill string with filename chars.
    402  memcpy(str.get() + cur, filenameStr, filenameLength);
    403  cur += filenameLength;
    404 
    405  // Fill line + column chars.
    406  if (hasLineAndColumn) {
    407    str[cur++] = ':';
    408    memcpy(str.get() + cur, lineAndColumnStr, lineAndColumnLength);
    409    cur += lineAndColumnLength;
    410  }
    411 
    412  // Terminal ')' if necessary.
    413  if (name) {
    414    str[cur++] = ')';
    415  }
    416 
    417  MOZ_ASSERT(cur == fullLength);
    418  str[cur] = 0;
    419 
    420  return str;
    421 }
    422 
    423 void GeckoProfilerThread::trace(JSTracer* trc) {
    424  if (profilingStack_) {
    425    size_t size = profilingStack_->stackSize();
    426    for (size_t i = 0; i < size; i++) {
    427      profilingStack_->frames[i].trace(trc);
    428    }
    429  }
    430 }
    431 
    432 void GeckoProfilerRuntime::fixupStringsMapAfterMovingGC() {
    433  for (ProfileStringMap::Enum e(strings()); !e.empty(); e.popFront()) {
    434    BaseScript* script = e.front().key();
    435    if (IsForwarded(script)) {
    436      script = Forwarded(script);
    437      e.rekeyFront(script);
    438    }
    439  }
    440 }
    441 
    442 #ifdef JSGC_HASH_TABLE_CHECKS
    443 void GeckoProfilerRuntime::checkStringsMapAfterMovingGC() {
    444  CheckTableAfterMovingGC(strings(), [](const auto& entry) {
    445    BaseScript* script = entry.key();
    446    CheckGCThingAfterMovingGC(script);
    447    return script;
    448  });
    449 }
    450 #endif
    451 
    452 // Get all script sources as a list of ProfilerJSSourceData.
    453 js::ProfilerJSSources GeckoProfilerRuntime::getProfilerScriptSources() {
    454  js::ProfilerJSSources result;
    455 
    456  auto guard = scriptSources_.readLock();
    457  for (auto iter = guard->iter(); !iter.done(); iter.next()) {
    458    const RefPtr<ScriptSource>& scriptSource = iter.get();
    459    MOZ_ASSERT(scriptSource);
    460 
    461    bool hasSourceText;
    462    bool retrievableSource;
    463    ScriptSource::getSourceProperties(scriptSource, &hasSourceText,
    464                                      &retrievableSource);
    465 
    466    uint32_t sourceId = scriptSource->id();
    467 
    468    // Get filename for all source types. Create single copy to be moved.
    469    const char* filename = scriptSource->filename();
    470    size_t filenameLen = 0;
    471    JS::UniqueChars filenameCopy;
    472    if (filename) {
    473      filenameLen = strlen(filename);
    474      filenameCopy.reset(static_cast<char*>(js_malloc(filenameLen + 1)));
    475      if (filenameCopy) {
    476        strcpy(filenameCopy.get(), filename);
    477      }
    478    }
    479 
    480    if (retrievableSource) {
    481      (void)result.append(ProfilerJSSourceData::CreateRetrievableFile(
    482          sourceId, std::move(filenameCopy), filenameLen));
    483      continue;
    484    }
    485 
    486    if (!hasSourceText) {
    487      (void)result.append(
    488          ProfilerJSSourceData(sourceId, std::move(filenameCopy), filenameLen));
    489      continue;
    490    }
    491 
    492    size_t sourceLength = scriptSource->length();
    493    if (sourceLength == 0) {
    494      (void)result.append(
    495          ProfilerJSSourceData(sourceId, JS::UniqueTwoByteChars(), 0,
    496                               std::move(filenameCopy), filenameLen));
    497      continue;
    498    }
    499 
    500    SubstringCharsResult sourceResult(JS::UniqueChars(nullptr));
    501    size_t charsLength = 0;
    502 
    503    if (scriptSource->shouldUnwrapEventHandlerBody()) {
    504      sourceResult = scriptSource->functionBodyStringChars(&charsLength);
    505 
    506      if (charsLength == 0) {
    507        (void)result.append(
    508            ProfilerJSSourceData(sourceId, JS::UniqueTwoByteChars(), 0,
    509                                 std::move(filenameCopy), filenameLen));
    510        continue;
    511      }
    512    } else {
    513      sourceResult = scriptSource->substringChars(0, sourceLength);
    514      charsLength = sourceLength;
    515    }
    516 
    517    // Convert SubstringCharsResult to ProfilerJSSourceData.
    518    // Note: The returned buffers are NOT null-terminated. The length is
    519    // tracked separately in charsLength and passed to ProfilerJSSourceData.
    520    if (sourceResult.is<JS::UniqueChars>()) {
    521      auto& utf8Chars = sourceResult.as<JS::UniqueChars>();
    522      if (!utf8Chars) {
    523        continue;
    524      }
    525      (void)result.append(
    526          ProfilerJSSourceData(sourceId, std::move(utf8Chars), charsLength,
    527                               std::move(filenameCopy), filenameLen));
    528    } else {
    529      auto& utf16Chars = sourceResult.as<JS::UniqueTwoByteChars>();
    530      if (!utf16Chars) {
    531        continue;
    532      }
    533      (void)result.append(
    534          ProfilerJSSourceData(sourceId, std::move(utf16Chars), charsLength,
    535                               std::move(filenameCopy), filenameLen));
    536    }
    537  }
    538 
    539  return result;
    540 }
    541 
    542 void ProfilingStackFrame::trace(JSTracer* trc) {
    543  if (isJsFrame()) {
    544    JSScript* s = rawScript();
    545    TraceNullableRoot(trc, &s, "ProfilingStackFrame script");
    546    spOrScript = s;
    547  }
    548 }
    549 
    550 GeckoProfilerBaselineOSRMarker::GeckoProfilerBaselineOSRMarker(
    551    JSContext* cx, bool hasProfilerFrame)
    552    : profiler(&cx->geckoProfiler()) {
    553  if (!hasProfilerFrame || !cx->runtime()->geckoProfiler().enabled()) {
    554    profiler = nullptr;
    555    return;
    556  }
    557 
    558  uint32_t sp = profiler->profilingStack_->stackPointer;
    559  if (sp >= profiler->profilingStack_->stackCapacity()) {
    560    profiler = nullptr;
    561    return;
    562  }
    563 
    564  spBefore_ = sp;
    565  if (sp == 0) {
    566    return;
    567  }
    568 
    569  ProfilingStackFrame& frame = profiler->profilingStack_->frames[sp - 1];
    570  MOZ_ASSERT(!frame.isOSRFrame());
    571  frame.setIsOSRFrame(true);
    572 }
    573 
    574 GeckoProfilerBaselineOSRMarker::~GeckoProfilerBaselineOSRMarker() {
    575  if (profiler == nullptr) {
    576    return;
    577  }
    578 
    579  uint32_t sp = profiler->stackPointer();
    580  MOZ_ASSERT(spBefore_ == sp);
    581  if (sp == 0) {
    582    return;
    583  }
    584 
    585  ProfilingStackFrame& frame = profiler->stack()[sp - 1];
    586  MOZ_ASSERT(frame.isOSRFrame());
    587  frame.setIsOSRFrame(false);
    588 }
    589 
    590 JS_PUBLIC_API JSScript* ProfilingStackFrame::script() const {
    591  MOZ_ASSERT(isJsFrame());
    592  auto* script = reinterpret_cast<JSScript*>(spOrScript.operator void*());
    593  if (!script) {
    594    return nullptr;
    595  }
    596 
    597  // If profiling is supressed then we can't trust the script pointers to be
    598  // valid as they could be in the process of being moved by a compacting GC
    599  // (although it's still OK to get the runtime from them).
    600  JSContext* cx = script->runtimeFromAnyThread()->mainContextFromAnyThread();
    601  if (!cx->isProfilerSamplingEnabled()) {
    602    return nullptr;
    603  }
    604 
    605  MOZ_ASSERT(!IsForwarded(script));
    606  return script;
    607 }
    608 
    609 JS_PUBLIC_API JSFunction* ProfilingStackFrame::function() const {
    610  JSScript* script = this->script();
    611  return script ? script->function() : nullptr;
    612 }
    613 
    614 JS_PUBLIC_API jsbytecode* ProfilingStackFrame::pc() const {
    615  MOZ_ASSERT(isJsFrame());
    616  if (pcOffsetIfJS_ == NullPCOffset) {
    617    return nullptr;
    618  }
    619 
    620  JSScript* script = this->script();
    621  return script ? script->offsetToPC(pcOffsetIfJS_) : nullptr;
    622 }
    623 
    624 /* static */
    625 int32_t ProfilingStackFrame::pcToOffset(JSScript* aScript, jsbytecode* aPc) {
    626  return aPc ? aScript->pcToOffset(aPc) : NullPCOffset;
    627 }
    628 
    629 void ProfilingStackFrame::setPC(jsbytecode* pc) {
    630  MOZ_ASSERT(isJsFrame());
    631  JSScript* script = this->script();
    632  MOZ_ASSERT(
    633      script);  // This should not be called while profiling is suppressed.
    634  pcOffsetIfJS_ = pcToOffset(script, pc);
    635 }
    636 
    637 JS_PUBLIC_API uint32_t ProfilingStackFrame::sourceId() const {
    638  return sourceId_;
    639 }
    640 
    641 JS_PUBLIC_API void js::SetContextProfilingStack(
    642    JSContext* cx, ProfilingStack* profilingStack) {
    643  cx->geckoProfiler().setProfilingStack(
    644      profilingStack, cx->runtime()->geckoProfiler().enabled());
    645 }
    646 
    647 JS_PUBLIC_API void js::EnableContextProfilingStack(JSContext* cx,
    648                                                   bool enabled) {
    649  cx->geckoProfiler().enable(enabled);
    650  cx->runtime()->geckoProfiler().enable(enabled);
    651 }
    652 
    653 JS_PUBLIC_API void js::RegisterContextProfilerMarkers(
    654    JSContext* cx,
    655    void (*eventMarker)(mozilla::MarkerCategory, const char*, const char*),
    656    void (*intervalMarker)(mozilla::MarkerCategory, const char*,
    657                           mozilla::TimeStamp, const char*),
    658    void (*flowMarker)(mozilla::MarkerCategory, const char*, uint64_t),
    659    void (*terminatingFlowMarker)(mozilla::MarkerCategory, const char*,
    660                                  uint64_t)) {
    661  MOZ_ASSERT(cx->runtime()->geckoProfiler().enabled());
    662  cx->runtime()->geckoProfiler().setEventMarker(eventMarker);
    663  cx->runtime()->geckoProfiler().setIntervalMarker(intervalMarker);
    664  cx->runtime()->geckoProfiler().setFlowMarker(flowMarker);
    665  cx->runtime()->geckoProfiler().setTerminatingFlowMarker(
    666      terminatingFlowMarker);
    667 }
    668 
    669 JS_PUBLIC_API js::ProfilerJSSources js::GetProfilerScriptSources(
    670    JSRuntime* rt) {
    671  return rt->geckoProfiler().getProfilerScriptSources();
    672 }
    673 
    674 JS_PUBLIC_API ProfilerJSSourceData
    675 js::RetrieveProfilerSourceContent(JSContext* cx, const char* filename) {
    676  MOZ_ASSERT(filename && strlen(filename));
    677  if (!cx) {
    678    return ProfilerJSSourceData();  // Return unavailable
    679  }
    680 
    681  // Check if source hook is available
    682  if (!cx->runtime()->sourceHook.ref()) {
    683    return ProfilerJSSourceData();  // Return unavailable
    684  }
    685 
    686  size_t sourceLength = 0;
    687  char* utf8Source = nullptr;
    688 
    689  bool loadSuccess = cx->runtime()->sourceHook->load(
    690      cx, filename, nullptr, &utf8Source, &sourceLength);
    691 
    692  if (!loadSuccess) {
    693    // Clear the pending exception that have been set by the source hook.
    694    JS_ClearPendingException(cx);
    695    return ProfilerJSSourceData();  // Return unavailable
    696  }
    697 
    698  if (utf8Source) {
    699    return ProfilerJSSourceData(JS::UniqueChars(utf8Source), sourceLength);
    700  }
    701 
    702  // Hook returned success but no source data. Return unavailable.
    703  return ProfilerJSSourceData();
    704 }
    705 
    706 AutoSuppressProfilerSampling::AutoSuppressProfilerSampling(JSContext* cx)
    707    : cx_(cx), previouslyEnabled_(cx->isProfilerSamplingEnabled()) {
    708  if (previouslyEnabled_) {
    709    cx_->disableProfilerSampling();
    710  }
    711 }
    712 
    713 AutoSuppressProfilerSampling::~AutoSuppressProfilerSampling() {
    714  if (previouslyEnabled_) {
    715    cx_->enableProfilerSampling();
    716  }
    717 }
    718 
    719 namespace JS {
    720 
    721 // clang-format off
    722 
    723 // ProfilingSubcategory_X:
    724 // One enum for each category X, listing that category's subcategories. This
    725 // allows the sProfilingCategoryInfo macro construction below to look up a
    726 // per-category index for a subcategory.
    727 #define SUBCATEGORY_ENUMS_BEGIN_CATEGORY(name, labelAsString, color) \
    728  enum class ProfilingSubcategory_##name : uint32_t {
    729 #define SUBCATEGORY_ENUMS_SUBCATEGORY(category, name, labelAsString) \
    730    name,
    731 #define SUBCATEGORY_ENUMS_END_CATEGORY \
    732  };
    733 MOZ_PROFILING_CATEGORY_LIST(SUBCATEGORY_ENUMS_BEGIN_CATEGORY,
    734                            SUBCATEGORY_ENUMS_SUBCATEGORY,
    735                            SUBCATEGORY_ENUMS_END_CATEGORY)
    736 #undef SUBCATEGORY_ENUMS_BEGIN_CATEGORY
    737 #undef SUBCATEGORY_ENUMS_SUBCATEGORY
    738 #undef SUBCATEGORY_ENUMS_END_CATEGORY
    739 
    740 // sProfilingCategoryPairInfo:
    741 // A list of ProfilingCategoryPairInfos with the same order as
    742 // ProfilingCategoryPair, which can be used to map a ProfilingCategoryPair to
    743 // its information.
    744 #define CATEGORY_INFO_BEGIN_CATEGORY(name, labelAsString, color)
    745 #define CATEGORY_INFO_SUBCATEGORY(category, name, labelAsString) \
    746  {ProfilingCategory::category,                                  \
    747   uint32_t(ProfilingSubcategory_##category::name), labelAsString},
    748 #define CATEGORY_INFO_END_CATEGORY
    749 const ProfilingCategoryPairInfo sProfilingCategoryPairInfo[] = {
    750  MOZ_PROFILING_CATEGORY_LIST(CATEGORY_INFO_BEGIN_CATEGORY,
    751                              CATEGORY_INFO_SUBCATEGORY,
    752                              CATEGORY_INFO_END_CATEGORY)
    753 };
    754 #undef CATEGORY_INFO_BEGIN_CATEGORY
    755 #undef CATEGORY_INFO_SUBCATEGORY
    756 #undef CATEGORY_INFO_END_CATEGORY
    757 
    758 // clang-format on
    759 
    760 JS_PUBLIC_API const ProfilingCategoryPairInfo& GetProfilingCategoryPairInfo(
    761    ProfilingCategoryPair aCategoryPair) {
    762  static_assert(
    763      std::size(sProfilingCategoryPairInfo) ==
    764          uint32_t(ProfilingCategoryPair::COUNT),
    765      "sProfilingCategoryPairInfo and ProfilingCategory need to have the "
    766      "same order and the same length");
    767 
    768  uint32_t categoryPairIndex = uint32_t(aCategoryPair);
    769  MOZ_RELEASE_ASSERT(categoryPairIndex <=
    770                     uint32_t(ProfilingCategoryPair::LAST));
    771  return sProfilingCategoryPairInfo[categoryPairIndex];
    772 }
    773 
    774 }  // namespace JS