diff --git a/JSTests/stress/sampling-profiler-inlined-top-frame.js b/JSTests/stress/sampling-profiler-inlined-top-frame.js new file mode 100644 index 0000000000000..7c3dafbcbcc96 --- /dev/null +++ b/JSTests/stress/sampling-profiler-inlined-top-frame.js @@ -0,0 +1,44 @@ +if (platformSupportsSamplingProfiler()) { + load("./sampling-profiler/samplingProfiler.js", "caller relative"); + + // Long enough for the call to cost next to nothing beside it, whichever tiers the two functions are in. + function inner(x) { + let sum = 0; + for (let i = 0; i < 1000; i++) + sum += (x ^ i) * 3; + return sum; + } + + function outer(count) { + let total = 0; + for (let i = 0; i < count; i++) + total += inner(i); + return total; + } + noInline(outer); + + // In the top tiers inner() is inlined, and only the PC tells the two apart. What is sampled on the way there does not count. + for (let i = 0; i < testLoopCount; i++) + outer(2); + samplingProfilerStackTraces(); + + const timeToFail = 50000; + const startTime = Date.now(); + let innerSamples = 0; + let outerSamples = 0; + while (innerSamples + outerSamples < 40) { + if (Date.now() - startTime > timeToFail) + throw new Error(`after ${timeToFail} ms inner() has ${innerSamples} samples and outer() has ${outerSamples}`); + outer(100); + for (const trace of samplingProfilerStackTraces().traces) { + const name = trace.frames[0].name; + if (name === "inner") + innerSamples++; + else if (name === "outer") + outerSamples++; + } + } + + if (innerSamples <= outerSamples) + throw new Error(`inner() has ${innerSamples} samples and outer() has ${outerSamples}`); +} diff --git a/JSTests/stress/sampling-profiler-start-during-ftl-compilation.js b/JSTests/stress/sampling-profiler-start-during-ftl-compilation.js new file mode 100644 index 0000000000000..20da47488fc3d --- /dev/null +++ b/JSTests/stress/sampling-profiler-start-during-ftl-compilation.js @@ -0,0 +1,16 @@ +//@ runFTLEager + +// Only means something with concurrent compilation and low thresholds. There are enough functions to keep the +// FTL threads busy at the moment the profiler starts, and enough calls after it for them to finish. +if (platformSupportsSamplingProfiler()) { + const functions = []; + for (let k = 0; k < 300; k++) + functions.push(new Function("count", `let sum = ${k}; for (let i = 0; i < count; i++) { sum += (i ^ ${k}) * 3; sum = (sum % 1000003) | 0; } return sum;`)); + + const startAt = testLoopCount >> 2; + for (let i = 0; i < testLoopCount; i++) { + if (i === startAt) + startSamplingProfiler(); + functions[i % functions.length](40); + } +} diff --git a/Source/JavaScriptCore/ftl/FTLCompile.cpp b/Source/JavaScriptCore/ftl/FTLCompile.cpp index dfc882e7ba7f2..9890b213f252e 100644 --- a/Source/JavaScriptCore/ftl/FTLCompile.cpp +++ b/Source/JavaScriptCore/ftl/FTLCompile.cpp @@ -320,7 +320,8 @@ void compile(State& state, Safepoint::Result& safepointResult) collectIRDumpDebugInfo(state, graph, codeBlock); collectSourceCodeDumpDebugInfo(state, codeBlock); - if (vm.shouldBuilderPCToCodeOriginMapping()) { + // The flag can turn on after FTL::State read it; B3 then kept no origins to build the map from. + if (state.proc->needsPCToOriginMap() && vm.shouldBuilderPCToCodeOriginMapping()) { B3::PCToOriginMap originMap = state.proc->releasePCToOriginMap(); state.jitCode->common.m_pcToCodeOriginMap = makeUnique(PCToCodeOriginMapBuilder(PCToCodeOriginMapBuilder::JSCodeOriginMap, vm, WTF::move(originMap)), *state.b3CodeLinkBuffer); } diff --git a/Source/JavaScriptCore/runtime/VM.cpp b/Source/JavaScriptCore/runtime/VM.cpp index 8713c8fcf8190..5da750fffaff9 100644 --- a/Source/JavaScriptCore/runtime/VM.cpp +++ b/Source/JavaScriptCore/runtime/VM.cpp @@ -524,7 +524,6 @@ WTF_ALLOW_UNSAFE_BUFFER_USAGE_END enableControlFlowProfiler(); #if ENABLE(SAMPLING_PROFILER) if (Options::useSamplingProfiler()) { - setShouldBuildPCToCodeOriginMapping(); Ref stopwatch = Stopwatch::create(); stopwatch->start(); ensureSamplingProfiler(WTF::move(stopwatch)); @@ -807,6 +806,8 @@ RefPtr VM::tryCreate(HeapType heapType, WTF::RunLoop* runLoop) SamplingProfiler& VM::ensureSamplingProfiler(Ref&& stopwatch) { if (!m_samplingProfiler) { + // The profiler reads these maps to place the top frame's PC, inlined functions included. + setShouldBuildPCToCodeOriginMapping(); lazyInitialize(m_samplingProfiler, adoptRef(*new SamplingProfiler(*this, WTF::move(stopwatch)))); requestEntryScopeService(EntryScopeService::SamplingProfiler); } diff --git a/Source/JavaScriptCore/runtime/VM.h b/Source/JavaScriptCore/runtime/VM.h index 58ef1f582d328..f36988db615dd 100644 --- a/Source/JavaScriptCore/runtime/VM.h +++ b/Source/JavaScriptCore/runtime/VM.h @@ -1182,8 +1182,8 @@ class VM : public ThreadSafeRefCountedWithSuppressingSaferCPPChecking { void setAllowRedeclaringSymbols(bool allowRedeclaringSymbols) { m_allowRedeclaringSymbols = allowRedeclaringSymbols; } ALWAYS_INLINE bool allowRedeclaringSymbols() const { return m_allowRedeclaringSymbols; } - void setShouldBuildPCToCodeOriginMapping() { m_shouldBuildPCToCodeOriginMapping = true; } - bool shouldBuilderPCToCodeOriginMapping() const { return m_shouldBuildPCToCodeOriginMapping; } + void setShouldBuildPCToCodeOriginMapping() { m_shouldBuildPCToCodeOriginMapping.store(true, std::memory_order_relaxed); } + bool shouldBuilderPCToCodeOriginMapping() const { return m_shouldBuildPCToCodeOriginMapping.load(std::memory_order_relaxed); } BytecodeIntrinsicRegistry& bytecodeIntrinsicRegistry() { return *m_bytecodeIntrinsicRegistry; } @@ -1405,7 +1405,7 @@ class VM : public ThreadSafeRefCountedWithSuppressingSaferCPPChecking { bool m_failNextNewCodeBlock { false }; bool m_globalConstRedeclarationShouldThrow { true }; bool m_allowRedeclaringSymbols { false }; - bool m_shouldBuildPCToCodeOriginMapping { false }; + std::atomic m_shouldBuildPCToCodeOriginMapping { false }; // Only ever turned on: the compiler threads read it, FTL more than once in a compilation. DeletePropertyMode m_deletePropertyMode { DeletePropertyMode::Default }; HeapAnalyzer* m_activeHeapAnalyzer { nullptr }; std::unique_ptr m_codeCache;