[vm/kernel] record yield positions at streaming flow graph builder

Collect yield positions inside StreamFlowGraphBuilder. BuildGraph() when yield_positions() are required. All yield positions will be stored into script as a hashmap. Key of hashmap is the starting token position of function, value is an array containing all yield positions.

Bug: https://github.com/dart-lang/sdk/issues/37635
Change-Id: I6d301d1cb0f74c432f855c9dbc20ac2c13acb07f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/116743
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Régis Crelier <regis@google.com>
Commit-Queue: Zichang Guo <zichangguo@google.com>
This commit is contained in:
Zichang Guo
2019-09-12 16:37:16 +00:00
committed by commit-bot@chromium.org
parent c74e68e501
commit 59bcc4ea1e
13 changed files with 321 additions and 85 deletions
@@ -0,0 +1,212 @@
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:developer';
import 'package:observatory/service_io.dart';
import 'package:unittest/unittest.dart';
import 'service_test_common.dart';
import 'test_helper.dart';
const int LINE = 106;
const int LINE_A = 23;
const int LINE_B = 36;
const int LINE_C = 47;
const int LINE_D = 61;
const int LINE_E = 78;
const int LINE_F = 93;
// break statement
Stream<int> testBreak() async* {
for (int t = 0; t < 10; t++) {
try {
if (t == 1) break;
await throwException(); // LINE_A
} catch (e) {} finally {
yield t;
}
}
}
// return statement
Stream<int> testReturn() async* {
for (int t = 0; t < 10; t++) {
try {
yield t;
if (t == 1) return;
await throwException(); // LINE_B
} catch (e) {} finally {
yield t;
}
}
}
// Multiple functions
Stream<int> testMultipleFunctions() async* {
try {
yield 0;
await throwException(); // LINE_C
} catch (e) {} finally {
yield 1;
}
}
// continue statement
Stream<int> testContinueSwitch() async* {
int currentState = 0;
switch (currentState) {
case 0:
{
try {
if (currentState == 1) continue label;
await throwException(); // LINE_D
} catch (e) {} finally {
yield 0;
}
yield 1;
break;
}
label:
case 1:
break;
}
}
Stream<int> testNestFinally() async* {
int i = 0;
try {
if (i == 1) return;
await throwException(); //LINE_E
} catch (e) {} finally {
try {
yield i;
} finally {
yield 1;
}
yield 1;
}
}
Stream<int> testAsyncClosureInFinally() async* {
int i = 0;
try {
if (i == 1) return;
await throwException(); //LINE_F
} catch (e) {} finally {
inner() async {
await Future.delayed(Duration(milliseconds: 10));
}
await inner;
yield 1;
}
}
Future<void> throwException() async {
await Future.delayed(Duration(milliseconds: 10));
throw new Exception(""); // LINE
}
code() async {
await for (var x in testBreak()) {}
await for (var x in testReturn()) {}
await for (var x in testMultipleFunctions()) {}
await for (var x in testContinueSwitch()) {}
await for (var x in testNestFinally()) {}
await for (var x in testAsyncClosureInFinally()) {}
}
var tests = <IsolateTest>[
hasPausedAtStart,
setBreakpointAtLine(LINE),
resumeIsolate,
hasStoppedAtBreakpoint,
stoppedAtLine(LINE),
(Isolate isolate) async {
// test break statement
ServiceMap stack = await isolate.getStack();
expect(stack['awaiterFrames'], isNotNull);
expect(stack['awaiterFrames'].length, greaterThanOrEqualTo(2));
// Check second top frame contains correct line number
Script script = stack['awaiterFrames'][1].location.script;
expect(script.tokenToLine(stack['awaiterFrames'][1].location.tokenPos),
equals(LINE_A));
},
resumeIsolate,
hasStoppedAtBreakpoint,
stoppedAtLine(LINE),
(Isolate isolate) async {
// test return statement
ServiceMap stack = await isolate.getStack();
expect(stack['awaiterFrames'], isNotNull);
expect(stack['awaiterFrames'].length, greaterThanOrEqualTo(2));
// Check second top frame contains correct line number
Script script = stack['awaiterFrames'][1].location.script;
expect(script.tokenToLine(stack['awaiterFrames'][1].location.tokenPos),
equals(LINE_B));
},
resumeIsolate,
hasStoppedAtBreakpoint,
stoppedAtLine(LINE),
(Isolate isolate) async {
// test break statement
ServiceMap stack = await isolate.getStack();
expect(stack['awaiterFrames'], isNotNull);
expect(stack['awaiterFrames'].length, greaterThanOrEqualTo(2));
// Check second top frame contains correct line number
Script script = stack['awaiterFrames'][1].location.script;
expect(script.tokenToLine(stack['awaiterFrames'][1].location.tokenPos),
equals(LINE_C));
},
resumeIsolate,
hasStoppedAtBreakpoint,
stoppedAtLine(LINE),
(Isolate isolate) async {
// test break statement
ServiceMap stack = await isolate.getStack();
expect(stack['awaiterFrames'], isNotNull);
expect(stack['awaiterFrames'].length, greaterThanOrEqualTo(2));
// Check second top frame contains correct line number
Script script = stack['awaiterFrames'][1].location.script;
expect(script.tokenToLine(stack['awaiterFrames'][1].location.tokenPos),
equals(LINE_D));
},
resumeIsolate,
hasStoppedAtBreakpoint,
stoppedAtLine(LINE),
(Isolate isolate) async {
// test nested finally statement
ServiceMap stack = await isolate.getStack();
expect(stack['awaiterFrames'], isNotNull);
expect(stack['awaiterFrames'].length, greaterThanOrEqualTo(2));
// Check second top frame contains correct line number
Script script = stack['awaiterFrames'][1].location.script;
expect(script.tokenToLine(stack['awaiterFrames'][1].location.tokenPos),
equals(LINE_E));
},
resumeIsolate,
hasStoppedAtBreakpoint,
stoppedAtLine(LINE),
(Isolate isolate) async {
// test async closure within finally block
ServiceMap stack = await isolate.getStack();
expect(stack['awaiterFrames'], isNotNull);
expect(stack['awaiterFrames'].length, greaterThanOrEqualTo(2));
// Check second top frame contains correct line number
Script script = stack['awaiterFrames'][1].location.script;
expect(script.tokenToLine(stack['awaiterFrames'][1].location.tokenPos),
equals(LINE_F));
},
resumeIsolate,
hasStoppedAtExit
];
main(args) {
runIsolateTestsSynchronous(args, tests,
testeeConcurrent: code, pause_on_start: true, pause_on_exit: true);
}
@@ -4764,6 +4764,10 @@ Fragment StreamingFlowGraphBuilder::BuildYieldStatement() {
ASSERT(flags == kNativeYieldFlags); // Must have been desugared.
// Collect yield position
if (record_yield_positions_ != nullptr) {
record_yield_positions_->Add(Smi::Handle(Z, Smi::New(position.value())));
}
// Setup yield/continue point:
//
// ...
@@ -21,9 +21,11 @@ namespace kernel {
class StreamingFlowGraphBuilder : public KernelReaderHelper {
public:
StreamingFlowGraphBuilder(FlowGraphBuilder* flow_graph_builder,
const ExternalTypedData& data,
intptr_t data_program_offset)
StreamingFlowGraphBuilder(
FlowGraphBuilder* flow_graph_builder,
const ExternalTypedData& data,
intptr_t data_program_offset,
GrowableObjectArray* record_yield_positions = nullptr)
: KernelReaderHelper(
flow_graph_builder->zone_,
&flow_graph_builder->translation_helper_,
@@ -44,7 +46,8 @@ class StreamingFlowGraphBuilder : public KernelReaderHelper {
inferred_type_metadata_helper_(this),
procedure_attributes_metadata_helper_(this),
call_site_attributes_metadata_helper_(this, &type_translator_),
closure_owner_(Object::Handle(flow_graph_builder->zone_)) {}
closure_owner_(Object::Handle(flow_graph_builder->zone_)),
record_yield_positions_(record_yield_positions) {}
virtual ~StreamingFlowGraphBuilder() {}
@@ -363,6 +366,7 @@ class StreamingFlowGraphBuilder : public KernelReaderHelper {
ProcedureAttributesMetadataHelper procedure_attributes_metadata_helper_;
CallSiteAttributesMetadataHelper call_site_attributes_metadata_helper_;
Object& closure_owner_;
GrowableObjectArray* record_yield_positions_;
friend class KernelLoader;
+4 -2
View File
@@ -40,7 +40,8 @@ FlowGraphBuilder::FlowGraphBuilder(
bool optimizing,
intptr_t osr_id,
intptr_t first_block_id,
bool inlining_unchecked_entry)
bool inlining_unchecked_entry,
GrowableObjectArray* record_yield_positions)
: BaseFlowGraphBuilder(parsed_function,
first_block_id - 1,
osr_id,
@@ -68,6 +69,7 @@ FlowGraphBuilder::FlowGraphBuilder(
catch_block_(NULL) {
const Script& script =
Script::Handle(Z, parsed_function->function().script());
record_yield_positions_ = record_yield_positions;
H.InitFromScript(script);
}
@@ -643,7 +645,7 @@ FlowGraph* FlowGraphBuilder::BuildGraph() {
// TODO(alexmarkov): refactor this - StreamingFlowGraphBuilder should not be
// used for bytecode functions.
StreamingFlowGraphBuilder streaming_flow_graph_builder(
this, kernel_data, kernel_data_program_offset);
this, kernel_data, kernel_data_program_offset, record_yield_positions_);
return streaming_flow_graph_builder.BuildGraph();
}
+4 -1
View File
@@ -56,7 +56,8 @@ class FlowGraphBuilder : public BaseFlowGraphBuilder {
bool optimizing,
intptr_t osr_id,
intptr_t first_block_id = 1,
bool inlining_unchecked_entry = false);
bool inlining_unchecked_entry = false,
GrowableObjectArray* record_yield_position = nullptr);
virtual ~FlowGraphBuilder();
FlowGraph* BuildGraph();
@@ -377,6 +378,8 @@ class FlowGraphBuilder : public BaseFlowGraphBuilder {
ActiveClass active_class_;
GrowableObjectArray* record_yield_positions_;
friend class BreakableBlock;
friend class CatchBlock;
friend class ConstantEvaluator;
@@ -2475,8 +2475,7 @@ void KernelReaderHelper::SkipStatement() {
SkipStatement(); // read finalizer.
return;
case kYieldStatement: {
TokenPosition position = ReadPosition(); // read position.
RecordYieldPosition(position);
ReadPosition(); // read position.
ReadByte(); // read flags.
SkipExpression(); // read expression.
return;
@@ -1036,12 +1036,6 @@ class KernelReaderHelper {
USE(id);
}
virtual void RecordYieldPosition(TokenPosition position) {
// Do nothing by default.
// This is overridden in KernelTokenPositionCollector.
USE(position);
}
virtual void RecordTokenPosition(TokenPosition position) {
// Do nothing by default.
// This is overridden in KernelTokenPositionCollector.
+39
View File
@@ -1012,6 +1012,41 @@ void Compiler::ComputeLocalVarDescriptors(const Code& code) {
}
}
void Compiler::ComputeYieldPositions(const Function& function) {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
const Script& script = Script::Handle(zone, function.script());
Array& yield_position_map = Array::Handle(zone, script.yield_positions());
if (yield_position_map.IsNull()) {
yield_position_map = HashTables::New<UnorderedHashMap<SmiTraits>>(4);
}
UnorderedHashMap<SmiTraits> function_map(yield_position_map.raw());
Smi& key = Smi::Handle(zone, Smi::New(function.token_pos().value()));
if (function_map.ContainsKey(key)) {
ASSERT(function_map.Release().raw() == yield_position_map.raw());
return;
}
CompilerState state(thread);
LongJumpScope jump;
auto& array = GrowableObjectArray::Handle(zone, GrowableObjectArray::New());
if (setjmp(*jump.Set()) == 0) {
ParsedFunction* parsed_function =
new ParsedFunction(thread, Function::ZoneHandle(zone, function.raw()));
ZoneGrowableArray<const ICData*>* ic_data_array =
new ZoneGrowableArray<const ICData*>();
kernel::FlowGraphBuilder builder(parsed_function, ic_data_array, nullptr,
/* not inlining */ nullptr, false,
Compiler::kNoOSRDeoptId, 1, false, &array);
builder.BuildGraph();
}
function_map.UpdateOrInsert(key, array);
// Release and store back to script
yield_position_map = function_map.Release().raw();
script.set_yield_positions(yield_position_map);
}
RawError* Compiler::CompileAllFunctions(const Class& cls) {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
@@ -1412,6 +1447,10 @@ void Compiler::ComputeLocalVarDescriptors(const Code& code) {
UNREACHABLE();
}
void Compiler::ComputeYieldPositions(const Function& function) {
UNREACHABLE();
}
RawError* Compiler::CompileAllFunctions(const Class& cls) {
FATAL1("Attempt to compile class %s", cls.ToCString());
return Error::null();
+2
View File
@@ -108,6 +108,8 @@ class Compiler : public AllStatic {
// Generates local var descriptors and sets it in 'code'. Do not call if the
// local var descriptor already exists.
static void ComputeLocalVarDescriptors(const Code& code);
// Collect yield positions for function and store into its script object
static void ComputeYieldPositions(const Function& function);
// Eagerly compiles all functions in a class.
//
+14 -33
View File
@@ -1060,41 +1060,18 @@ void ActivationFrame::ExtractTokenPositionFromAsyncClosure() {
ASSERT(!IsInterpreted());
ASSERT(script.kind() == RawScript::kKernelTag);
const Array& await_to_token_map =
Array::Handle(zone, script.yield_positions());
if (await_to_token_map.IsNull()) {
// No mapping.
return;
}
const intptr_t await_jump_var = GetAwaitJumpVariable();
if (await_jump_var < 0) {
return;
}
intptr_t await_to_token_map_index = await_jump_var - 1;
// yield_positions returns all yield positions for the script (in sorted
// order).
// We thus need to offset the function start to get the actual index.
if (!function_.token_pos().IsReal()) {
return;
}
const intptr_t function_start = function_.token_pos().value();
for (intptr_t i = 0;
i < await_to_token_map.Length() &&
Smi::Value(reinterpret_cast<RawSmi*>(await_to_token_map.At(i))) <
function_start;
i++) {
await_to_token_map_index++;
}
if (await_to_token_map_index >= await_to_token_map.Length()) {
return;
}
const auto& array =
GrowableObjectArray::Handle(zone, script.GetYieldPositions(function_));
// await_jump_var is non zero means that array should not be empty
// index also fall into the correct range
ASSERT(array.Length() > 0 && await_to_token_map_index < array.Length());
const Object& token_pos =
Object::Handle(await_to_token_map.At(await_to_token_map_index));
if (token_pos.IsNull()) {
return;
}
Object::Handle(zone, array.At(await_to_token_map_index));
ASSERT(token_pos.IsSmi());
token_pos_ = TokenPosition(Smi::Cast(token_pos).Value());
token_pos_initialized_ = true;
@@ -4406,12 +4383,16 @@ bool Debugger::IsAtAsyncJump(ActivationFrame* top_frame) {
ASSERT(!top_frame->IsInterpreted());
const Script& script = Script::Handle(zone, top_frame->SourceScript());
ASSERT(script.kind() == RawScript::kKernelTag);
// Are we at a yield point (previous await)?
const Array& yields = Array::Handle(script.yield_positions());
const auto& yield_positions = GrowableObjectArray::Handle(
zone, script.GetYieldPositions(top_frame->function()));
// No yield statements
if (yield_positions.IsNull() || (yield_positions.Length() == 0)) {
return false;
}
intptr_t looking_for = top_frame->TokenPos().value();
Smi& value = Smi::Handle(zone);
for (int i = 0; i < yields.Length(); i++) {
value ^= yields.At(i);
for (int i = 0; i < yield_positions.Length(); i++) {
value ^= yield_positions.At(i);
if (value.Value() == looking_for) {
return true;
}
+12 -30
View File
@@ -8,6 +8,7 @@
#include "vm/compiler/frontend/bytecode_reader.h"
#include "vm/compiler/frontend/constant_evaluator.h"
#include "vm/compiler/frontend/kernel_translation_helper.h"
#include "vm/compiler/jit/compiler.h"
#include "vm/longjump.h"
#include "vm/object_store.h"
#include "vm/parser.h" // For Parser::kParameter* constants.
@@ -134,8 +135,7 @@ class KernelTokenPositionCollector : public KernelReaderHelper {
intptr_t data_program_offset,
intptr_t initial_script_index,
intptr_t record_for_script_id,
GrowableArray<intptr_t>* record_token_positions_into,
GrowableArray<intptr_t>* record_yield_positions_into)
GrowableArray<intptr_t>* record_token_positions_into)
: KernelReaderHelper(zone,
translation_helper,
script,
@@ -143,13 +143,11 @@ class KernelTokenPositionCollector : public KernelReaderHelper {
data_program_offset),
current_script_id_(initial_script_index),
record_for_script_id_(record_for_script_id),
record_token_positions_into_(record_token_positions_into),
record_yield_positions_into_(record_yield_positions_into) {}
record_token_positions_into_(record_token_positions_into) {}
void CollectTokenPositions(intptr_t kernel_offset);
void RecordTokenPosition(TokenPosition position) override;
void RecordYieldPosition(TokenPosition position) override;
void set_current_script_id(intptr_t id) override { current_script_id_ = id; }
@@ -157,7 +155,6 @@ class KernelTokenPositionCollector : public KernelReaderHelper {
intptr_t current_script_id_;
intptr_t record_for_script_id_;
GrowableArray<intptr_t>* record_token_positions_into_;
GrowableArray<intptr_t>* record_yield_positions_into_;
DISALLOW_COPY_AND_ASSIGN(KernelTokenPositionCollector);
};
@@ -195,13 +192,6 @@ void KernelTokenPositionCollector::RecordTokenPosition(TokenPosition position) {
}
}
void KernelTokenPositionCollector::RecordYieldPosition(TokenPosition position) {
if (record_for_script_id_ == current_script_id_ &&
record_yield_positions_into_ != NULL && position.IsReal()) {
record_yield_positions_into_->Add(position.value());
}
}
static int LowestFirst(const intptr_t* a, const intptr_t* b) {
return *a - *b;
}
@@ -246,8 +236,7 @@ static void CollectKernelDataTokenPositions(
intptr_t data_kernel_offset,
Zone* zone,
TranslationHelper* helper,
GrowableArray<intptr_t>* token_positions,
GrowableArray<intptr_t>* yield_positions) {
GrowableArray<intptr_t>* token_positions) {
if (kernel_data.IsNull()) {
return;
}
@@ -255,7 +244,7 @@ static void CollectKernelDataTokenPositions(
KernelTokenPositionCollector token_position_collector(
zone, helper, script, kernel_data, data_kernel_offset,
entry_script.kernel_script_index(), script.kernel_script_index(),
token_positions, yield_positions);
token_positions);
token_position_collector.CollectTokenPositions(kernel_offset);
}
@@ -333,7 +322,6 @@ void CollectTokenPositionsFor(const Script& interesting_script) {
helper.InitFromScript(interesting_script);
GrowableArray<intptr_t> token_positions(10);
GrowableArray<intptr_t> yield_positions(1);
Isolate* isolate = thread->isolate();
const GrowableObjectArray& libs =
@@ -396,7 +384,7 @@ void CollectTokenPositionsFor(const Script& interesting_script) {
data, interesting_script, entry_script,
temp_field.kernel_offset(),
temp_field.KernelDataProgramOffset(), zone, &helper,
&token_positions, &yield_positions);
&token_positions);
}
}
temp_array = klass.functions();
@@ -415,7 +403,7 @@ void CollectTokenPositionsFor(const Script& interesting_script) {
data, interesting_script, entry_script,
temp_function.kernel_offset(),
temp_function.KernelDataProgramOffset(), zone, &helper,
&token_positions, &yield_positions);
&token_positions);
}
}
} else {
@@ -432,10 +420,9 @@ void CollectTokenPositionsFor(const Script& interesting_script) {
if (entry_script.raw() != interesting_script.raw()) {
continue;
}
CollectKernelDataTokenPositions(data, interesting_script,
entry_script, class_offset,
library_kernel_offset, zone, &helper,
&token_positions, &yield_positions);
CollectKernelDataTokenPositions(
data, interesting_script, entry_script, class_offset,
library_kernel_offset, zone, &helper, &token_positions);
}
} else if (entry.IsFunction()) {
temp_function ^= entry.raw();
@@ -452,7 +439,7 @@ void CollectTokenPositionsFor(const Script& interesting_script) {
data, interesting_script, entry_script,
temp_function.kernel_offset(),
temp_function.KernelDataProgramOffset(), zone, &helper,
&token_positions, &yield_positions);
&token_positions);
}
} else if (entry.IsField()) {
const Field& field = Field::Cast(entry);
@@ -476,8 +463,7 @@ void CollectTokenPositionsFor(const Script& interesting_script) {
data = field.KernelData();
CollectKernelDataTokenPositions(
data, interesting_script, entry_script, field.kernel_offset(),
field.KernelDataProgramOffset(), zone, &helper, &token_positions,
&yield_positions);
field.KernelDataProgramOffset(), zone, &helper, &token_positions);
}
}
}
@@ -487,10 +473,6 @@ void CollectTokenPositionsFor(const Script& interesting_script) {
Array& array_object = Array::Handle(zone);
array_object = AsSortedDuplicateFreeArray(&token_positions);
script.set_debug_positions(array_object);
array_object = AsSortedDuplicateFreeArray(&yield_positions);
// Note that yield positions in members declared in bytecode are not collected
// here, but on demand in the debugger.
script.set_yield_positions(array_object);
}
class MetadataEvaluator : public KernelReaderHelper {
+19 -7
View File
@@ -9503,16 +9503,28 @@ void Script::set_yield_positions(const Array& value) const {
}
RawArray* Script::yield_positions() const {
#if !defined(DART_PRECOMPILED_RUNTIME)
Array& yields = Array::Handle(raw_ptr()->yield_positions_);
if (yields.IsNull() && kind() == RawScript::kKernelTag) {
// This is created lazily. Now we need it.
kernel::CollectTokenPositionsFor(*this);
}
#endif // !defined(DART_PRECOMPILED_RUNTIME)
return raw_ptr()->yield_positions_;
}
RawGrowableObjectArray* Script::GetYieldPositions(
const Function& function) const {
if (!function.IsAsyncClosure() && !function.IsAsyncGenClosure())
return GrowableObjectArray::null();
ASSERT(!function.is_declared_in_bytecode());
Compiler::ComputeYieldPositions(function);
UnorderedHashMap<SmiTraits> function_map(raw_ptr()->yield_positions_);
const auto& key = Smi::Handle(Smi::New(function.token_pos().value()));
intptr_t entry = function_map.FindKey(key);
GrowableObjectArray& array = GrowableObjectArray::Handle();
if (entry < 0) {
array ^= GrowableObjectArray::null();
} else {
array ^= function_map.GetPayload(entry, 0);
}
function_map.Release();
return array.raw();
}
RawTypedData* Script::line_starts() const {
return raw_ptr()->line_starts_;
}
+2
View File
@@ -3879,6 +3879,8 @@ class Script : public Object {
RawArray* yield_positions() const;
RawGrowableObjectArray* GetYieldPositions(const Function& function) const;
RawLibrary* FindLibrary() const;
RawString* GetLine(intptr_t line_number,
Heap::Space space = Heap::kNew) const;