Commit f514cc96 authored by Alexei Filippov's avatar Alexei Filippov Committed by Commit Bot

[cpu-profiler] Introduce NativeFrame type.

The new frame type is inteneded to represent native C++ stack frames.
JS code may sometimes make calls to helper native functions that do not
provide any special stack layout besides the return address and frame pointer.

Currently the stack iterator bails out when it sees an unknown frame.
The patch allows the iterator to unwind stacks having such frames.

BUG=chromium:768540

Change-Id: I9c273c7015695a6733c0a0c52b522fca7b25de0d
Reviewed-on: https://chromium-review.googlesource.com/794991
Commit-Queue: Alexei Filippov <alph@chromium.org>
Reviewed-by: 's avatarMichael Starzinger <mstarzinger@chromium.org>
Cr-Commit-Position: refs/heads/master@{#50058}
parent 3b59a795
......@@ -50,18 +50,22 @@ inline Address* StackFrame::ResolveReturnAddressLocation(Address* pc_address) {
}
}
inline NativeFrame::NativeFrame(StackFrameIteratorBase* iterator)
: StackFrame(iterator) {}
inline EntryFrame::EntryFrame(StackFrameIteratorBase* iterator)
: StackFrame(iterator) {
inline Address NativeFrame::GetCallerStackPointer() const {
return fp() + CommonFrameConstants::kCallerSPOffset;
}
inline EntryFrame::EntryFrame(StackFrameIteratorBase* iterator)
: StackFrame(iterator) {}
inline ConstructEntryFrame::ConstructEntryFrame(
StackFrameIteratorBase* iterator)
: EntryFrame(iterator) {}
inline ExitFrame::ExitFrame(StackFrameIteratorBase* iterator)
: StackFrame(iterator) {
}
: StackFrame(iterator) {}
inline BuiltinExitFrame::BuiltinExitFrame(StackFrameIteratorBase* iterator)
: ExitFrame(iterator) {}
......
......@@ -425,7 +425,7 @@ StackFrame::Type StackFrame::ComputeType(const StackFrameIteratorBase* iterator,
Memory::Object_at(state->fp + StandardFrameConstants::kFunctionOffset);
if (!StackFrame::IsTypeMarker(marker)) {
if (maybe_function->IsSmi()) {
return NONE;
return NATIVE;
} else if (IsInterpreterFramePc(iterator->isolate(),
*(state->pc_address))) {
return INTERPRETED;
......@@ -492,7 +492,7 @@ StackFrame::Type StackFrame::ComputeType(const StackFrameIteratorBase* iterator,
break;
}
} else {
return NONE;
return NATIVE;
}
}
}
......@@ -520,7 +520,7 @@ StackFrame::Type StackFrame::ComputeType(const StackFrameIteratorBase* iterator,
// interpreted frames, should never have a StackFrame::Type
// marker. If we find one, we're likely being called from the
// profiler in a bogus stack frame.
return NONE;
return NATIVE;
}
}
......@@ -542,6 +542,14 @@ Address StackFrame::UnpaddedFP() const {
return fp();
}
void NativeFrame::ComputeCallerState(State* state) const {
state->sp = caller_sp();
state->fp = Memory::Address_at(fp() + CommonFrameConstants::kCallerFPOffset);
state->pc_address = ResolveReturnAddressLocation(
reinterpret_cast<Address*>(fp() + CommonFrameConstants::kCallerPCOffset));
state->callee_pc_address = nullptr;
state->constant_pool_address = nullptr;
}
Code* EntryFrame::unchecked_code() const {
return isolate()->heap()->js_entry_code();
......@@ -841,6 +849,7 @@ void StandardFrame::IterateCompiledFrame(RootVisitor* v) const {
// in the place on the stack that one finds the frame type.
UNREACHABLE();
break;
case NATIVE:
case NONE:
case NUMBER_OF_TYPES:
case MANUAL:
......
......@@ -104,7 +104,8 @@ class StackHandler BASE_EMBEDDED {
V(CONSTRUCT, ConstructFrame) \
V(ARGUMENTS_ADAPTOR, ArgumentsAdaptorFrame) \
V(BUILTIN, BuiltinFrame) \
V(BUILTIN_EXIT, BuiltinExitFrame)
V(BUILTIN_EXIT, BuiltinExitFrame) \
V(NATIVE, NativeFrame)
// Abstract base class for all stack frames.
class StackFrame BASE_EMBEDDED {
......@@ -180,8 +181,7 @@ class StackFrame BASE_EMBEDDED {
// and should be converted back to a stack frame type using MarkerToType.
// Otherwise, the value is a tagged function pointer.
static bool IsTypeMarker(intptr_t function_or_marker) {
bool is_marker = ((function_or_marker & kSmiTagMask) == kSmiTag);
return is_marker;
return (function_or_marker & kSmiTagMask) == kSmiTag;
}
// Copy constructor; it breaks the connection to host iterator
......@@ -328,6 +328,25 @@ class StackFrame BASE_EMBEDDED {
friend class SafeStackFrameIterator;
};
class NativeFrame : public StackFrame {
public:
Type type() const override { return NATIVE; }
Code* unchecked_code() const override { return nullptr; }
// Garbage collection support.
void Iterate(RootVisitor* v) const override {}
protected:
inline explicit NativeFrame(StackFrameIteratorBase* iterator);
Address GetCallerStackPointer() const override;
private:
void ComputeCallerState(State* state) const override;
friend class StackFrameIteratorBase;
};
// Entry frames are used to enter JavaScript execution from C.
class EntryFrame: public StackFrame {
......
......@@ -476,6 +476,13 @@ v8::CpuProfile* ProfilerHelper::Run(v8::Local<v8::Function> function,
return profile;
}
static unsigned TotalHitCount(const v8::CpuProfileNode* node) {
unsigned hit_count = node->GetHitCount();
for (int i = 0, count = node->GetChildrenCount(); i < count; ++i)
hit_count += TotalHitCount(node->GetChild(i));
return hit_count;
}
static const v8::CpuProfileNode* FindChild(v8::Local<v8::Context> context,
const v8::CpuProfileNode* node,
const char* name) {
......@@ -490,6 +497,16 @@ static const v8::CpuProfileNode* FindChild(v8::Local<v8::Context> context,
return nullptr;
}
static const v8::CpuProfileNode* FindChild(const v8::CpuProfileNode* node,
const char* name) {
for (int i = 0, count = node->GetChildrenCount(); i < count; ++i) {
const v8::CpuProfileNode* child = node->GetChild(i);
if (strcmp(child->GetFunctionNameStr(), name) == 0) {
return child;
}
}
return nullptr;
}
static const v8::CpuProfileNode* GetChild(v8::Local<v8::Context> context,
const v8::CpuProfileNode* node,
......@@ -2295,6 +2312,53 @@ TEST(CodeEntriesMemoryLeak) {
CHECK_GE(10000ul, profiler_listener->entries_count_for_test());
}
TEST(NativeFrameStackTrace) {
// A test for issue https://crbug.com/768540
// When a sample lands in a native function which has not EXIT frame
// stack frame iterator used to bail out and produce an empty stack trace.
// The source code below makes v8 call the
// v8::internal::StringTable::LookupStringIfExists_NoAllocate native function
// without producing an EXIT frame.
v8::HandleScope scope(CcTest::isolate());
v8::Local<v8::Context> env = CcTest::NewContext(PROFILER_EXTENSION);
v8::Context::Scope context_scope(env);
const char* source = R"(
function jsFunction() {
var s = {};
for (var i = 0; i < 1e4; ++i) {
for (var j = 0; j < 100; j++) {
s['item' + j] = 'alph';
}
}
})";
CompileRun(source);
v8::Local<v8::Function> function = GetFunction(env, "jsFunction");
ProfilerHelper helper(env);
v8::CpuProfile* profile = helper.Run(function, nullptr, 0, 100, 0, true);
// Count the fraction of samples landing in 'jsFunction' (valid stack)
// vs '(program)' (no stack captured).
const v8::CpuProfileNode* root = profile->GetTopDownRoot();
const v8::CpuProfileNode* js_function = FindChild(root, "jsFunction");
const v8::CpuProfileNode* program = FindChild(root, "(program)");
if (program) {
unsigned js_function_samples = TotalHitCount(js_function);
unsigned program_samples = TotalHitCount(program);
double valid_samples_ratio =
1. * js_function_samples / (js_function_samples + program_samples);
i::PrintF("Ratio: %f\n", valid_samples_ratio);
// TODO(alph): Investigate other causes of dropped frames. The ratio
// should be close to 99%.
CHECK_GE(valid_samples_ratio, 0.3);
}
profile->Delete();
}
} // namespace test_cpu_profiler
} // namespace internal
} // namespace v8
......@@ -351,6 +351,7 @@ FRAME_MARKERS = (
"ARGUMENTS_ADAPTOR",
"BUILTIN",
"BUILTIN_EXIT",
"NATIVE",
)
# This set of constants is generated from a shipping build.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment