static void mergeStacksIntoProfile(ThreadProfile& aProfile, TickSample* aSample, NativeStack& aNativeStack) { PseudoStack* pseudoStack = aProfile.GetPseudoStack(); volatile StackEntry *pseudoFrames = pseudoStack->mStack; uint32_t pseudoCount = pseudoStack->stackSize(); // Make a copy of the JS stack into a JSFrame array. This is necessary since, // like the native stack, the JS stack is iterated youngest-to-oldest and we // need to iterate oldest-to-youngest when adding entries to aProfile. // Synchronous sampling reports an invalid buffer generation to // ProfilingFrameIterator to avoid incorrectly resetting the generation of // sampled JIT entries inside the JS engine. See note below concerning 'J' // entries. uint32_t startBufferGen; if (aSample->isSamplingCurrentThread) { startBufferGen = UINT32_MAX; } else { startBufferGen = aProfile.bufferGeneration(); } uint32_t jsCount = 0; #ifndef SPS_STANDALONE JS::ProfilingFrameIterator::Frame jsFrames[1000]; // Only walk jit stack if profiling frame iterator is turned on. if (pseudoStack->mRuntime && JS::IsProfilingEnabledForRuntime(pseudoStack->mRuntime)) { AutoWalkJSStack autoWalkJSStack; const uint32_t maxFrames = mozilla::ArrayLength(jsFrames); if (aSample && autoWalkJSStack.walkAllowed) { JS::ProfilingFrameIterator::RegisterState registerState; registerState.pc = aSample->pc; registerState.sp = aSample->sp; #ifdef ENABLE_ARM_LR_SAVING registerState.lr = aSample->lr; #endif JS::ProfilingFrameIterator jsIter(pseudoStack->mRuntime, registerState, startBufferGen); for (; jsCount < maxFrames && !jsIter.done(); ++jsIter) { // See note below regarding 'J' entries. if (aSample->isSamplingCurrentThread || jsIter.isAsmJS()) { uint32_t extracted = jsIter.extractStack(jsFrames, jsCount, maxFrames); jsCount += extracted; if (jsCount == maxFrames) break; } else { mozilla::Maybe<JS::ProfilingFrameIterator::Frame> frame = jsIter.getPhysicalFrameWithoutLabel(); if (frame.isSome()) jsFrames[jsCount++] = frame.value(); } } } } #endif // Start the sample with a root entry. aProfile.addTag(ProfileEntry('s', "(root)")); // While the pseudo-stack array is ordered oldest-to-youngest, the JS and // native arrays are ordered youngest-to-oldest. We must add frames to // aProfile oldest-to-youngest. Thus, iterate over the pseudo-stack forwards // and JS and native arrays backwards. Note: this means the terminating // condition jsIndex and nativeIndex is being < 0. uint32_t pseudoIndex = 0; int32_t jsIndex = jsCount - 1; int32_t nativeIndex = aNativeStack.count - 1; uint8_t *lastPseudoCppStackAddr = nullptr; // Iterate as long as there is at least one frame remaining. while (pseudoIndex != pseudoCount || jsIndex >= 0 || nativeIndex >= 0) { // There are 1 to 3 frames available. Find and add the oldest. uint8_t *pseudoStackAddr = nullptr; uint8_t *jsStackAddr = nullptr; uint8_t *nativeStackAddr = nullptr; if (pseudoIndex != pseudoCount) { volatile StackEntry &pseudoFrame = pseudoFrames[pseudoIndex]; if (pseudoFrame.isCpp()) lastPseudoCppStackAddr = (uint8_t *) pseudoFrame.stackAddress(); #ifndef SPS_STANDALONE // Skip any pseudo-stack JS frames which are marked isOSR // Pseudostack frames are marked isOSR when the JS interpreter // enters a jit frame on a loop edge (via on-stack-replacement, // or OSR). To avoid both the pseudoframe and jit frame being // recorded (and showing up twice), the interpreter marks the // interpreter pseudostack entry with the OSR flag to ensure that // it doesn't get counted. if (pseudoFrame.isJs() && pseudoFrame.isOSR()) { pseudoIndex++; continue; } #endif MOZ_ASSERT(lastPseudoCppStackAddr); pseudoStackAddr = lastPseudoCppStackAddr; } #ifndef SPS_STANDALONE if (jsIndex >= 0) jsStackAddr = (uint8_t *) jsFrames[jsIndex].stackAddress; #endif if (nativeIndex >= 0) nativeStackAddr = (uint8_t *) aNativeStack.sp_array[nativeIndex]; // If there's a native stack entry which has the same SP as a // pseudo stack entry, pretend we didn't see the native stack // entry. Ditto for a native stack entry which has the same SP as // a JS stack entry. In effect this means pseudo or JS entries // trump conflicting native entries. if (nativeStackAddr && (pseudoStackAddr == nativeStackAddr || jsStackAddr == nativeStackAddr)) { nativeStackAddr = nullptr; nativeIndex--; MOZ_ASSERT(pseudoStackAddr || jsStackAddr); } // Sanity checks. MOZ_ASSERT_IF(pseudoStackAddr, pseudoStackAddr != jsStackAddr && pseudoStackAddr != nativeStackAddr); MOZ_ASSERT_IF(jsStackAddr, jsStackAddr != pseudoStackAddr && jsStackAddr != nativeStackAddr); MOZ_ASSERT_IF(nativeStackAddr, nativeStackAddr != pseudoStackAddr && nativeStackAddr != jsStackAddr); // Check to see if pseudoStack frame is top-most. if (pseudoStackAddr > jsStackAddr && pseudoStackAddr > nativeStackAddr) { MOZ_ASSERT(pseudoIndex < pseudoCount); volatile StackEntry &pseudoFrame = pseudoFrames[pseudoIndex]; addPseudoEntry(pseudoFrame, aProfile, pseudoStack, nullptr); pseudoIndex++; continue; } #ifndef SPS_STANDALONE // Check to see if JS jit stack frame is top-most if (jsStackAddr > nativeStackAddr) { MOZ_ASSERT(jsIndex >= 0); const JS::ProfilingFrameIterator::Frame& jsFrame = jsFrames[jsIndex]; // Stringifying non-asm.js JIT frames is delayed until streaming // time. To re-lookup the entry in the JitcodeGlobalTable, we need to // store the JIT code address ('J') in the circular buffer. // // Note that we cannot do this when we are sychronously sampling the // current thread; that is, when called from profiler_get_backtrace. The // captured backtrace is usually externally stored for an indeterminate // amount of time, such as in nsRefreshDriver. Problematically, the // stored backtrace may be alive across a GC during which the profiler // itself is disabled. In that case, the JS engine is free to discard // its JIT code. This means that if we inserted such 'J' entries into // the buffer, nsRefreshDriver would now be holding on to a backtrace // with stale JIT code return addresses. if (aSample->isSamplingCurrentThread || jsFrame.kind == JS::ProfilingFrameIterator::Frame_AsmJS) { addDynamicTag(aProfile, 'c', jsFrame.label); } else { MOZ_ASSERT(jsFrame.kind == JS::ProfilingFrameIterator::Frame_Ion || jsFrame.kind == JS::ProfilingFrameIterator::Frame_Baseline); aProfile.addTag(ProfileEntry('J', jsFrames[jsIndex].returnAddress)); } jsIndex--; continue; } #endif // If we reach here, there must be a native stack entry and it must be the // greatest entry. if (nativeStackAddr) { MOZ_ASSERT(nativeIndex >= 0); aProfile .addTag(ProfileEntry('l', (void*)aNativeStack.pc_array[nativeIndex])); } if (nativeIndex >= 0) { nativeIndex--; } } #ifndef SPS_STANDALONE // Update the JS runtime with the current profile sample buffer generation. // // Do not do this for synchronous sampling, which create their own // ProfileBuffers. if (!aSample->isSamplingCurrentThread && pseudoStack->mRuntime) { MOZ_ASSERT(aProfile.bufferGeneration() >= startBufferGen); uint32_t lapCount = aProfile.bufferGeneration() - startBufferGen; JS::UpdateJSRuntimeProfilerSampleBufferGen(pseudoStack->mRuntime, aProfile.bufferGeneration(), lapCount); } #endif }
static void mergeStacksIntoProfile(ThreadProfile& aProfile, TickSample* aSample, NativeStack& aNativeStack) { PseudoStack* pseudoStack = aProfile.GetPseudoStack(); volatile StackEntry *pseudoFrames = pseudoStack->mStack; uint32_t pseudoCount = pseudoStack->stackSize(); // Make a copy of the JS stack into a JSFrame array. This is necessary since, // like the native stack, the JS stack is iterated youngest-to-oldest and we // need to iterate oldest-to-youngest when adding entries to aProfile. uint32_t startBufferGen = aProfile.bufferGeneration(); uint32_t jsCount = 0; JS::ProfilingFrameIterator::Frame jsFrames[1000]; // Only walk jit stack if profiling frame iterator is turned on. if (pseudoStack->mRuntime && JS::IsProfilingEnabledForRuntime(pseudoStack->mRuntime)) { AutoWalkJSStack autoWalkJSStack; const uint32_t maxFrames = mozilla::ArrayLength(jsFrames); if (aSample && autoWalkJSStack.walkAllowed) { JS::ProfilingFrameIterator::RegisterState registerState; registerState.pc = aSample->pc; registerState.sp = aSample->sp; #ifdef ENABLE_ARM_LR_SAVING registerState.lr = aSample->lr; #endif JS::ProfilingFrameIterator jsIter(pseudoStack->mRuntime, registerState, startBufferGen); for (; jsCount < maxFrames && !jsIter.done(); ++jsIter) { uint32_t extracted = jsIter.extractStack(jsFrames, jsCount, maxFrames); MOZ_ASSERT(extracted <= (maxFrames - jsCount)); jsCount += extracted; if (jsCount == maxFrames) break; } } } // Start the sample with a root entry. aProfile.addTag(ProfileEntry('s', "(root)")); // While the pseudo-stack array is ordered oldest-to-youngest, the JS and // native arrays are ordered youngest-to-oldest. We must add frames to // aProfile oldest-to-youngest. Thus, iterate over the pseudo-stack forwards // and JS and native arrays backwards. Note: this means the terminating // condition jsIndex and nativeIndex is being < 0. uint32_t pseudoIndex = 0; int32_t jsIndex = jsCount - 1; int32_t nativeIndex = aNativeStack.count - 1; uint8_t *lastPseudoCppStackAddr = nullptr; // Iterate as long as there is at least one frame remaining. while (pseudoIndex != pseudoCount || jsIndex >= 0 || nativeIndex >= 0) { // There are 1 to 3 frames available. Find and add the oldest. uint8_t *pseudoStackAddr = nullptr; uint8_t *jsStackAddr = nullptr; uint8_t *nativeStackAddr = nullptr; if (pseudoIndex != pseudoCount) { volatile StackEntry &pseudoFrame = pseudoFrames[pseudoIndex]; if (pseudoFrame.isCpp()) lastPseudoCppStackAddr = (uint8_t *) pseudoFrame.stackAddress(); // Skip any pseudo-stack JS frames which are marked isOSR // Pseudostack frames are marked isOSR when the JS interpreter // enters a jit frame on a loop edge (via on-stack-replacement, // or OSR). To avoid both the pseudoframe and jit frame being // recorded (and showing up twice), the interpreter marks the // interpreter pseudostack entry with the OSR flag to ensure that // it doesn't get counted. if (pseudoFrame.isJs() && pseudoFrame.isOSR()) { pseudoIndex++; continue; } MOZ_ASSERT(lastPseudoCppStackAddr); pseudoStackAddr = lastPseudoCppStackAddr; } if (jsIndex >= 0) jsStackAddr = (uint8_t *) jsFrames[jsIndex].stackAddress; if (nativeIndex >= 0) nativeStackAddr = (uint8_t *) aNativeStack.sp_array[nativeIndex]; // Sanity checks. MOZ_ASSERT_IF(pseudoStackAddr, pseudoStackAddr != jsStackAddr && pseudoStackAddr != nativeStackAddr); MOZ_ASSERT_IF(jsStackAddr, jsStackAddr != pseudoStackAddr && jsStackAddr != nativeStackAddr); MOZ_ASSERT_IF(nativeStackAddr, nativeStackAddr != pseudoStackAddr && nativeStackAddr != jsStackAddr); // Check to see if pseudoStack frame is top-most. if (pseudoStackAddr > jsStackAddr && pseudoStackAddr > nativeStackAddr) { MOZ_ASSERT(pseudoIndex < pseudoCount); volatile StackEntry &pseudoFrame = pseudoFrames[pseudoIndex]; addPseudoEntry(pseudoFrame, aProfile, pseudoStack, nullptr); pseudoIndex++; continue; } // Check to see if JS jit stack frame is top-most if (jsStackAddr > nativeStackAddr) { MOZ_ASSERT(jsIndex >= 0); addDynamicTag(aProfile, 'c', jsFrames[jsIndex].label); // Stringifying optimization information is delayed until streaming // time. To re-lookup the entry in the JitcodeGlobalTable, we need to // store the JIT code address ('J') in the circular buffer. if (jsFrames[jsIndex].hasTrackedOptimizations) { aProfile.addTag(ProfileEntry('J', jsFrames[jsIndex].returnAddress)); } jsIndex--; continue; } // If we reach here, there must be a native stack entry and it must be the // greatest entry. MOZ_ASSERT(nativeStackAddr); MOZ_ASSERT(nativeIndex >= 0); aProfile.addTag(ProfileEntry('l', (void*)aNativeStack.pc_array[nativeIndex])); nativeIndex--; } MOZ_ASSERT(aProfile.bufferGeneration() >= startBufferGen); uint32_t lapCount = aProfile.bufferGeneration() - startBufferGen; // Update the JS runtime with the current profile sample buffer generation. if (pseudoStack->mRuntime) { JS::UpdateJSRuntimeProfilerSampleBufferGen(pseudoStack->mRuntime, aProfile.bufferGeneration(), lapCount); } }