Skip to content

Commit 80702cb

Browse files
Fall back to inlining intervals to generate stack traces in --noopt. Inlined frames will lack line and column positions, but they will at least be expanded.
Fix stack traces when visible functions are inlined into an invisible function. Fix off-by-one expanding inlined functions in the profiler. BUG=http://dartbug.com/24783 [email protected] Review URL: https://codereview.chromium.org/1582683003 .
1 parent a3cf74f commit 80702cb

File tree

6 files changed

+223
-24
lines changed

6 files changed

+223
-24
lines changed

runtime/tests/vm/vm.status

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,8 @@ dart/byte_array_test: Crash # Incompatible flag --disable_alloc_stubs_after_gc
8686

8787
[ $noopt || $compiler == precompiler ]
8888
dart/redirection_type_shuffling_test: CompileTimeError # Imports dart:mirrors
89-
dart/inline_stack_frame_test: Fail # Issue 24783 - inlined frames missing
89+
dart/optimized_stacktrace_test: RuntimeError # Expects line and column numbers
9090

9191
[ $runtime == dart_precompiled ]
92-
dart/optimized_stacktrace_test: Fail
92+
dart/inline_stack_frame_test: Fail # Issue 24783 - inlined frames missing
9393
dart/data_uri_spawn_test: RuntimeError # Isolate.spawnUri

runtime/vm/object.cc

Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21600,6 +21600,23 @@ static intptr_t PrintOneStacktrace(Zone* zone,
2160021600
}
2160121601

2160221602

21603+
static intptr_t PrintOneStacktraceNoCode(Zone* zone,
21604+
GrowableArray<char*>* frame_strings,
21605+
const Function& function,
21606+
intptr_t frame_index) {
21607+
const Script& script = Script::Handle(zone, function.script());
21608+
const String& function_name =
21609+
String::Handle(zone, function.QualifiedUserVisibleName());
21610+
const String& url = String::Handle(zone, script.url());
21611+
char* chars = NULL;
21612+
chars = OS::SCreate(zone,
21613+
"#%-6" Pd " %s (%s)\n",
21614+
frame_index, function_name.ToCString(), url.ToCString());
21615+
frame_strings->Add(chars);
21616+
return strlen(chars);
21617+
}
21618+
21619+
2160321620
const char* Stacktrace::ToCStringInternal(intptr_t* frame_index,
2160421621
intptr_t max_frames) const {
2160521622
Zone* zone = Thread::Current()->zone();
@@ -21612,7 +21629,8 @@ const char* Stacktrace::ToCStringInternal(intptr_t* frame_index,
2161221629
for (intptr_t i = 0; (i < Length()) && (*frame_index < max_frames); i++) {
2161321630
function = FunctionAtFrame(i);
2161421631
if (function.IsNull()) {
21615-
// Check if null function object indicates a stack trace overflow.
21632+
// Check if null function object indicates a gap in a StackOverflow or
21633+
// OutOfMemory trace.
2161621634
if ((i < (Length() - 1)) &&
2161721635
(FunctionAtFrame(i + 1) != Function::null())) {
2161821636
const char* kTruncated = "...\n...\n";
@@ -21622,31 +21640,55 @@ const char* Stacktrace::ToCStringInternal(intptr_t* frame_index,
2162221640
frame_strings.Add(chars);
2162321641
total_len += truncated_len;
2162421642
}
21625-
} else if (function.is_visible() || FLAG_show_invisible_frames) {
21643+
} else {
2162621644
code = CodeAtFrame(i);
2162721645
ASSERT(function.raw() == code.function());
2162821646
uword pc = code.EntryPoint() + Smi::Value(PcOffsetAtFrame(i));
2162921647
if (code.is_optimized() && expand_inlined()) {
2163021648
// Traverse inlined frames.
21631-
for (InlinedFunctionsIterator it(code, pc);
21632-
!it.Done() && (*frame_index < max_frames); it.Advance()) {
21633-
function = it.function();
21634-
if (function.is_visible() || FLAG_show_invisible_frames) {
21635-
code = it.code();
21636-
ASSERT(function.raw() == code.function());
21637-
uword pc = it.pc();
21638-
ASSERT(pc != 0);
21639-
ASSERT(code.EntryPoint() <= pc);
21640-
ASSERT(pc < (code.EntryPoint() + code.Size()));
21641-
total_len += PrintOneStacktrace(
21642-
zone, &frame_strings, pc, function, code, *frame_index);
21643-
(*frame_index)++; // To account for inlined frames.
21649+
if (Compiler::allow_recompilation()) {
21650+
for (InlinedFunctionsIterator it(code, pc);
21651+
!it.Done() && (*frame_index < max_frames); it.Advance()) {
21652+
function = it.function();
21653+
if (function.is_visible() || FLAG_show_invisible_frames) {
21654+
code = it.code();
21655+
ASSERT(function.raw() == code.function());
21656+
uword pc = it.pc();
21657+
ASSERT(pc != 0);
21658+
ASSERT(code.EntryPoint() <= pc);
21659+
ASSERT(pc < (code.EntryPoint() + code.Size()));
21660+
total_len += PrintOneStacktrace(
21661+
zone, &frame_strings, pc, function, code, *frame_index);
21662+
(*frame_index)++; // To account for inlined frames.
21663+
}
21664+
}
21665+
} else {
21666+
// Precompilation: we don't have deopt info, so we don't know the
21667+
// source position of inlined functions, but we can still name them.
21668+
intptr_t offset = Smi::Value(PcOffsetAtFrame(i));
21669+
// The PC of frames below the top frame is a call's return address,
21670+
// which can belong to a different inlining interval than the call.
21671+
intptr_t effective_offset = offset - 1;
21672+
GrowableArray<Function*> inlined_functions;
21673+
code.GetInlinedFunctionsAt(effective_offset, &inlined_functions);
21674+
ASSERT(inlined_functions.length() >= 1); // At least the inliner.
21675+
for (intptr_t j = 0; j < inlined_functions.length(); j++) {
21676+
Function* inlined_function = inlined_functions[j];
21677+
ASSERT(inlined_function != NULL);
21678+
ASSERT(!inlined_function->IsNull());
21679+
if (inlined_function->is_visible() || FLAG_show_invisible_frames) {
21680+
total_len += PrintOneStacktraceNoCode(
21681+
zone, &frame_strings, *inlined_function, *frame_index);
21682+
(*frame_index)++;
21683+
}
2164421684
}
2164521685
}
2164621686
} else {
21647-
total_len += PrintOneStacktrace(
21648-
zone, &frame_strings, pc, function, code, *frame_index);
21649-
(*frame_index)++;
21687+
if (function.is_visible() || FLAG_show_invisible_frames) {
21688+
total_len += PrintOneStacktrace(
21689+
zone, &frame_strings, pc, function, code, *frame_index);
21690+
(*frame_index)++;
21691+
}
2165021692
}
2165121693
}
2165221694
}

runtime/vm/profiler_service.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1390,6 +1390,11 @@ class ProfileBuilder : public ValueObject {
13901390
GrowableArray<Function*> inlined_functions;
13911391
if (!code.IsNull()) {
13921392
intptr_t offset = pc - code.EntryPoint();
1393+
if (frame_index != 0) {
1394+
// The PC of frames below the top frame is a call's return address,
1395+
// which can belong to a different inlining interval than the call.
1396+
offset--;
1397+
}
13931398
code.GetInlinedFunctionsAt(offset, &inlined_functions);
13941399
}
13951400
if (code.IsNull() || (inlined_functions.length() == 0)) {

runtime/vm/profiler_test.cc

Lines changed: 154 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,22 @@ class DisableNativeProfileScope : public ValueObject {
3434
};
3535

3636

37+
class DisableBackgroundCompilationScope : public ValueObject {
38+
public:
39+
DisableBackgroundCompilationScope()
40+
: FLAG_background_compilation_(FLAG_background_compilation) {
41+
FLAG_background_compilation = false;
42+
}
43+
44+
~DisableBackgroundCompilationScope() {
45+
FLAG_background_compilation = FLAG_background_compilation_;
46+
}
47+
48+
private:
49+
const bool FLAG_background_compilation_;
50+
};
51+
52+
3753
// Temporarily adjust the maximum profile depth.
3854
class MaxProfileDepthScope : public ValueObject {
3955
public:
@@ -139,6 +155,7 @@ TEST_CASE(Profiler_AllocationSampleTest) {
139155
delete sample_buffer;
140156
}
141157

158+
142159
static RawClass* GetClass(const Library& lib, const char* name) {
143160
const Class& cls = Class::Handle(
144161
lib.LookupClassAllowPrivate(String::Handle(Symbols::New(name))));
@@ -147,6 +164,14 @@ static RawClass* GetClass(const Library& lib, const char* name) {
147164
}
148165

149166

167+
static RawFunction* GetFunction(const Library& lib, const char* name) {
168+
const Function& func = Function::Handle(
169+
lib.LookupFunctionAllowPrivate(String::Handle(Symbols::New(name))));
170+
EXPECT(!func.IsNull()); // No ambiguity error expected.
171+
return func.raw();
172+
}
173+
174+
150175
class AllocationFilter : public SampleFilter {
151176
public:
152177
AllocationFilter(Isolate* isolate,
@@ -1154,6 +1179,8 @@ TEST_CASE(Profiler_StringInterpolation) {
11541179

11551180
TEST_CASE(Profiler_FunctionInline) {
11561181
DisableNativeProfileScope dnps;
1182+
DisableBackgroundCompilationScope dbcs;
1183+
11571184
const char* kScript =
11581185
"class A {\n"
11591186
" var a;\n"
@@ -1180,8 +1207,6 @@ TEST_CASE(Profiler_FunctionInline) {
11801207
" B.boo(true);\n"
11811208
"}\n";
11821209

1183-
const bool old_flag = FLAG_background_compilation;
1184-
FLAG_background_compilation = false;
11851210
Dart_Handle lib = TestCase::LoadTestScript(kScript, NULL);
11861211
EXPECT_VALID(lib);
11871212
Library& root_library = Library::Handle();
@@ -1398,7 +1423,133 @@ TEST_CASE(Profiler_FunctionInline) {
13981423
EXPECT_STREQ("[Inline End]", walker.CurrentName());
13991424
EXPECT(!walker.Down());
14001425
}
1401-
FLAG_background_compilation = old_flag;
1426+
}
1427+
1428+
1429+
TEST_CASE(Profiler_InliningIntervalBoundry) {
1430+
// The PC of frames below the top frame is a call's return address,
1431+
// which can belong to a different inlining interval than the call.
1432+
// This test checks the profiler service takes this into account; see
1433+
// ProfileBuilder::ProcessFrame.
1434+
1435+
DisableNativeProfileScope dnps;
1436+
DisableBackgroundCompilationScope dbcs;
1437+
const char* kScript =
1438+
"class A {\n"
1439+
"}\n"
1440+
"bool alloc = false;"
1441+
"maybeAlloc() {\n"
1442+
" try {\n"
1443+
" if (alloc) new A();\n"
1444+
" } catch (e) {\n"
1445+
" }\n"
1446+
"}\n"
1447+
"right() => maybeAlloc();\n"
1448+
"doNothing() {\n"
1449+
" try {\n"
1450+
" } catch (e) {\n"
1451+
" }\n"
1452+
"}\n"
1453+
"wrong() => doNothing();\n"
1454+
"a() {\n"
1455+
" try {\n"
1456+
" right();\n"
1457+
" wrong();\n"
1458+
" } catch (e) {\n"
1459+
" }\n"
1460+
"}\n"
1461+
"mainNoAlloc() {\n"
1462+
" for (var i = 0; i < 20000; i++) {\n"
1463+
" a();\n"
1464+
" }\n"
1465+
"}\n"
1466+
"mainAlloc() {\n"
1467+
" alloc = true;\n"
1468+
" a();\n"
1469+
"}\n";
1470+
1471+
Dart_Handle lib = TestCase::LoadTestScript(kScript, NULL);
1472+
EXPECT_VALID(lib);
1473+
Library& root_library = Library::Handle();
1474+
root_library ^= Api::UnwrapHandle(lib);
1475+
1476+
const Class& class_a = Class::Handle(GetClass(root_library, "A"));
1477+
EXPECT(!class_a.IsNull());
1478+
1479+
// Compile and optimize.
1480+
Dart_Handle result = Dart_Invoke(lib, NewString("mainNoAlloc"), 0, NULL);
1481+
EXPECT_VALID(result);
1482+
result = Dart_Invoke(lib, NewString("mainAlloc"), 0, NULL);
1483+
EXPECT_VALID(result);
1484+
1485+
// At this point a should be optimized and have inlined both right and wrong,
1486+
// but not maybeAllocate or doNothing.
1487+
Function& func = Function::Handle();
1488+
func = GetFunction(root_library, "a");
1489+
EXPECT(!func.is_inlinable());
1490+
EXPECT(func.HasOptimizedCode());
1491+
func = GetFunction(root_library, "right");
1492+
EXPECT(func.is_inlinable());
1493+
func = GetFunction(root_library, "wrong");
1494+
EXPECT(func.is_inlinable());
1495+
func = GetFunction(root_library, "doNothing");
1496+
EXPECT(!func.is_inlinable());
1497+
func = GetFunction(root_library, "maybeAlloc");
1498+
EXPECT(!func.is_inlinable());
1499+
1500+
{
1501+
Thread* thread = Thread::Current();
1502+
Isolate* isolate = thread->isolate();
1503+
StackZone zone(thread);
1504+
HANDLESCOPE(thread);
1505+
Profile profile(isolate);
1506+
AllocationFilter filter(isolate, class_a.id());
1507+
profile.Build(thread, &filter, Profile::kNoTags);
1508+
// We should have no allocation samples.
1509+
EXPECT_EQ(0, profile.sample_count());
1510+
}
1511+
1512+
// Turn on allocation tracing for A.
1513+
class_a.SetTraceAllocation(true);
1514+
1515+
result = Dart_Invoke(lib, NewString("mainAlloc"), 0, NULL);
1516+
EXPECT_VALID(result);
1517+
1518+
{
1519+
Thread* thread = Thread::Current();
1520+
Isolate* isolate = thread->isolate();
1521+
StackZone zone(thread);
1522+
HANDLESCOPE(thread);
1523+
Profile profile(isolate);
1524+
AllocationFilter filter(isolate, class_a.id());
1525+
profile.Build(thread, &filter, Profile::kNoTags);
1526+
EXPECT_EQ(1, profile.sample_count());
1527+
ProfileTrieWalker walker(&profile);
1528+
1529+
// Inline expansion should show us the complete call chain:
1530+
walker.Reset(Profile::kExclusiveFunction);
1531+
EXPECT(walker.Down());
1532+
EXPECT_STREQ("maybeAlloc", walker.CurrentName());
1533+
EXPECT(walker.Down());
1534+
EXPECT_STREQ("right", walker.CurrentName());
1535+
EXPECT(walker.Down());
1536+
EXPECT_STREQ("a", walker.CurrentName());
1537+
EXPECT(walker.Down());
1538+
EXPECT_STREQ("mainAlloc", walker.CurrentName());
1539+
EXPECT(!walker.Down());
1540+
1541+
// Inline expansion should show us the complete call chain:
1542+
walker.Reset(Profile::kInclusiveFunction);
1543+
EXPECT(walker.Down());
1544+
EXPECT_STREQ("mainAlloc", walker.CurrentName());
1545+
EXPECT(walker.Down());
1546+
EXPECT_STREQ("a", walker.CurrentName());
1547+
EXPECT(walker.Down());
1548+
EXPECT_STREQ("right", walker.CurrentName());
1549+
EXPECT(walker.Down());
1550+
EXPECT_STREQ("maybeAlloc", walker.CurrentName());
1551+
EXPECT(!walker.Down());
1552+
}
14021553
}
14031554

14041555

tests/language/language.status

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ super_getter_setter_test: CompileTimeError
133133
vm/reflect_core_vm_test: CompileTimeError
134134
redirecting_factory_reflection_test: CompileTimeError
135135
deferred_constraints_constants_test: Skip # multitest gets confused
136+
vm/type_vm_test: RuntimeError # Expects line and column numbers
136137

137138
# Deferred loading happens eagerly
138139
regress_23408_test: RuntimeError
@@ -143,7 +144,6 @@ deferred_load_constants_test: Skip # multitest gets confused
143144
deopt_inlined_function_lazy_test: Pass, Crash # Incompatible flag: --deoptimize-alot
144145
tearoff_basic_test: RuntimeError, Crash # Conflicting flag.
145146
vm/type_cast_vm_test: RuntimeError # Line number mismatch.
146-
stack_trace_test: Fail # Issue 24783 - inlined frames missing
147147

148148
[ $runtime == dart_precompiled ]
149149
ct_const2_test: Pass, Crash # Incompatible flag --compile_all

tests/standalone/standalone.status

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ io/secure_socket_bad_data_test: RuntimeError # An error in a secure connection
222222
map_literal_oom_test: Pass, Crash # Issue 24678
223223
javascript*: SkipByDesign # JS overflow flag unsupported
224224
io/web_socket_test: Pass, RuntimeError # Issue 24674
225+
assert_test: RuntimeError # Expects line and column numbers
225226

226227
[ $runtime == dart_precompiled ]
227228
debugger/*: Skip

0 commit comments

Comments
 (0)