mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 08:20:31 +00:00
VM: Fix receiver type propagation in presence of try-catch.
With catch blocks appearing as additional function entry blocks, there can be phis for the receiver (parameter 0). This CL fixes the problem that the receiver type information was lost in the presence of try-catch. BUG= R=vegorov@google.com Review URL: https://codereview.chromium.org/1841073003 .
This commit is contained in:
parent
a7a7a5be55
commit
2783afd607
9 changed files with 168 additions and 84 deletions
|
@ -1311,40 +1311,6 @@ RawField* AotOptimizer::GetField(intptr_t class_id,
|
|||
}
|
||||
|
||||
|
||||
// Use CHA to determine if the call needs a class check: if the callee's
|
||||
// receiver is the same as the caller's receiver and there are no overriden
|
||||
// callee functions, then no class check is needed.
|
||||
bool AotOptimizer::InstanceCallNeedsClassCheck(
|
||||
InstanceCallInstr* call, RawFunction::Kind kind) const {
|
||||
if (!FLAG_use_cha_deopt && !isolate()->all_classes_finalized()) {
|
||||
// Even if class or function are private, lazy class finalization
|
||||
// may later add overriding methods.
|
||||
return true;
|
||||
}
|
||||
Definition* callee_receiver = call->ArgumentAt(0);
|
||||
ASSERT(callee_receiver != NULL);
|
||||
const Function& function = flow_graph_->function();
|
||||
if (function.IsDynamicFunction() &&
|
||||
callee_receiver->IsParameter() &&
|
||||
(callee_receiver->AsParameter()->index() == 0)) {
|
||||
const String& name = (kind == RawFunction::kMethodExtractor)
|
||||
? String::Handle(Z, Field::NameFromGetter(call->function_name()))
|
||||
: call->function_name();
|
||||
const Class& cls = Class::Handle(Z, function.Owner());
|
||||
if (!thread()->cha()->HasOverride(cls, name)) {
|
||||
if (FLAG_trace_cha) {
|
||||
THR_Print(" **(CHA) Instance call needs no check, "
|
||||
"no overrides of '%s' '%s'\n",
|
||||
name.ToCString(), cls.ToCString());
|
||||
}
|
||||
thread()->cha()->AddToLeafClasses(cls);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool AotOptimizer::InlineImplicitInstanceGetter(InstanceCallInstr* call) {
|
||||
ASSERT(call->HasICData());
|
||||
const ICData& ic_data = *call->ic_data();
|
||||
|
@ -1359,7 +1325,8 @@ bool AotOptimizer::InlineImplicitInstanceGetter(InstanceCallInstr* call) {
|
|||
Field::ZoneHandle(Z, GetField(class_ids[0], field_name));
|
||||
ASSERT(!field.IsNull());
|
||||
|
||||
if (InstanceCallNeedsClassCheck(call, RawFunction::kImplicitGetter)) {
|
||||
if (flow_graph()->InstanceCallNeedsClassCheck(
|
||||
call, RawFunction::kImplicitGetter)) {
|
||||
return false;
|
||||
}
|
||||
LoadFieldInstr* load = new(Z) LoadFieldInstr(
|
||||
|
@ -2470,7 +2437,8 @@ void AotOptimizer::VisitInstanceCall(InstanceCallInstr* instr) {
|
|||
if (has_one_target) {
|
||||
RawFunction::Kind function_kind =
|
||||
Function::Handle(Z, unary_checks.GetTargetAt(0)).kind();
|
||||
if (!InstanceCallNeedsClassCheck(instr, function_kind)) {
|
||||
if (!flow_graph()->InstanceCallNeedsClassCheck(
|
||||
instr, function_kind)) {
|
||||
PolymorphicInstanceCallInstr* call =
|
||||
new(Z) PolymorphicInstanceCallInstr(instr, unary_checks,
|
||||
/* with_checks = */ false);
|
||||
|
@ -2771,7 +2739,8 @@ bool AotOptimizer::TryInlineInstanceSetter(InstanceCallInstr* instr,
|
|||
Field::ZoneHandle(Z, GetField(class_id, field_name));
|
||||
ASSERT(!field.IsNull());
|
||||
|
||||
if (InstanceCallNeedsClassCheck(instr, RawFunction::kImplicitSetter)) {
|
||||
if (flow_graph()->InstanceCallNeedsClassCheck(
|
||||
instr, RawFunction::kImplicitSetter)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "vm/flow_graph.h"
|
||||
|
||||
#include "vm/bit_vector.h"
|
||||
#include "vm/cha.h"
|
||||
#include "vm/flow_graph_builder.h"
|
||||
#include "vm/flow_graph_compiler.h"
|
||||
#include "vm/flow_graph_range_analysis.h"
|
||||
|
@ -372,6 +373,75 @@ static void VerifyUseListsInInstruction(Instruction* instr) {
|
|||
}
|
||||
|
||||
|
||||
void FlowGraph::ComputeIsReceiverRecursive(PhiInstr* phi,
|
||||
BitVector* processed) const {
|
||||
if (phi->is_receiver() != PhiInstr::kUnknownReceiver) return;
|
||||
if (processed->Contains(phi->ssa_temp_index())) return;
|
||||
processed->Add(phi->ssa_temp_index());
|
||||
for (intptr_t i = 0; i < phi->InputCount(); ++i) {
|
||||
Definition* def = phi->InputAt(i)->definition();
|
||||
if (def->IsParameter() && (def->AsParameter()->index() == 0)) continue;
|
||||
if (!def->IsPhi()) {
|
||||
phi->set_is_receiver(PhiInstr::kNotReceiver);
|
||||
return;
|
||||
}
|
||||
ComputeIsReceiverRecursive(def->AsPhi(), processed);
|
||||
if (def->AsPhi()->is_receiver() == PhiInstr::kNotReceiver) {
|
||||
phi->set_is_receiver(PhiInstr::kNotReceiver);
|
||||
return;
|
||||
}
|
||||
}
|
||||
phi->set_is_receiver(PhiInstr::kReceiver);
|
||||
}
|
||||
|
||||
|
||||
bool FlowGraph::IsReceiver(Definition* def) const {
|
||||
if (def->IsParameter()) return (def->AsParameter()->index() == 0);
|
||||
if (!def->IsPhi()) return false;
|
||||
PhiInstr* phi = def->AsPhi();
|
||||
if (phi->is_receiver() != PhiInstr::kUnknownReceiver) {
|
||||
return (phi->is_receiver() == PhiInstr::kReceiver);
|
||||
}
|
||||
// Not known if this phi is the receiver yet. Compute it now.
|
||||
BitVector* processed =
|
||||
new(zone()) BitVector(zone(), max_virtual_register_number());
|
||||
ComputeIsReceiverRecursive(phi, processed);
|
||||
return (phi->is_receiver() == PhiInstr::kReceiver);
|
||||
}
|
||||
|
||||
|
||||
// Use CHA to determine if the call needs a class check: if the callee's
|
||||
// receiver is the same as the caller's receiver and there are no overriden
|
||||
// callee functions, then no class check is needed.
|
||||
bool FlowGraph::InstanceCallNeedsClassCheck(InstanceCallInstr* call,
|
||||
RawFunction::Kind kind) const {
|
||||
if (!FLAG_use_cha_deopt && !isolate()->all_classes_finalized()) {
|
||||
// Even if class or function are private, lazy class finalization
|
||||
// may later add overriding methods.
|
||||
return true;
|
||||
}
|
||||
Definition* callee_receiver = call->ArgumentAt(0);
|
||||
ASSERT(callee_receiver != NULL);
|
||||
if (function().IsDynamicFunction() && IsReceiver(callee_receiver)) {
|
||||
const String& name = (kind == RawFunction::kMethodExtractor)
|
||||
? String::Handle(zone(), Field::NameFromGetter(call->function_name()))
|
||||
: call->function_name();
|
||||
const Class& cls = Class::Handle(zone(), function().Owner());
|
||||
if (!thread()->cha()->HasOverride(cls, name)) {
|
||||
if (FLAG_trace_cha) {
|
||||
THR_Print(" **(CHA) Instance call needs no check, "
|
||||
"no overrides of '%s' '%s'\n",
|
||||
name.ToCString(), cls.ToCString());
|
||||
}
|
||||
thread()->cha()->AddToLeafClasses(cls);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool FlowGraph::VerifyUseLists() {
|
||||
// Verify the initial definitions.
|
||||
for (intptr_t i = 0; i < graph_entry_->initial_definitions()->length(); ++i) {
|
||||
|
|
|
@ -159,6 +159,9 @@ class FlowGraph : public ZoneAllocated {
|
|||
return current_ssa_temp_index();
|
||||
}
|
||||
|
||||
bool InstanceCallNeedsClassCheck(InstanceCallInstr* call,
|
||||
RawFunction::Kind kind) const;
|
||||
|
||||
Thread* thread() const { return thread_; }
|
||||
Zone* zone() const { return thread()->zone(); }
|
||||
Isolate* isolate() const { return thread()->isolate(); }
|
||||
|
@ -352,6 +355,9 @@ class FlowGraph : public ZoneAllocated {
|
|||
Value* use,
|
||||
bool is_environment_use);
|
||||
|
||||
bool IsReceiver(Definition* def) const;
|
||||
void ComputeIsReceiverRecursive(PhiInstr* phi, BitVector* processed) const;
|
||||
|
||||
Thread* thread_;
|
||||
|
||||
// DiscoverBlocks computes parent_ and assigned_vars_ which are then used
|
||||
|
|
|
@ -4336,6 +4336,7 @@ void EffectGraphVisitor::VisitTryCatchNode(TryCatchNode* node) {
|
|||
CatchBlockEntryInstr* catch_entry =
|
||||
new(Z) CatchBlockEntryInstr(owner()->AllocateBlockId(),
|
||||
catch_handler_index,
|
||||
owner()->graph_entry(),
|
||||
catch_block->handler_types(),
|
||||
try_handler_index,
|
||||
catch_block->exception_var(),
|
||||
|
@ -4381,6 +4382,7 @@ void EffectGraphVisitor::VisitTryCatchNode(TryCatchNode* node) {
|
|||
CatchBlockEntryInstr* finally_entry =
|
||||
new(Z) CatchBlockEntryInstr(owner()->AllocateBlockId(),
|
||||
original_handler_index,
|
||||
owner()->graph_entry(),
|
||||
types,
|
||||
catch_handler_index,
|
||||
catch_block->exception_var(),
|
||||
|
|
|
@ -140,6 +140,10 @@ class FlowGraphBuilder : public ValueObject {
|
|||
|
||||
void AddCatchEntry(CatchBlockEntryInstr* entry);
|
||||
|
||||
GraphEntryInstr* graph_entry() const {
|
||||
return graph_entry_;
|
||||
}
|
||||
|
||||
intptr_t num_copied_params() const {
|
||||
return num_copied_params_;
|
||||
}
|
||||
|
|
|
@ -697,12 +697,15 @@ CompileType ParameterInstr::ComputeType() const {
|
|||
// However there are parameters that are known to match their declared type:
|
||||
// for example receiver.
|
||||
GraphEntryInstr* graph_entry = block_->AsGraphEntry();
|
||||
// Parameters at catch blocks and OSR entries have type dynamic.
|
||||
if (graph_entry == NULL) {
|
||||
graph_entry = block_->AsCatchBlockEntry()->graph_entry();
|
||||
}
|
||||
// Parameters at OSR entries have type dynamic.
|
||||
//
|
||||
// TODO(kmillikin): Use the actual type of the parameter at OSR entry.
|
||||
// The code below is not safe for OSR because it doesn't necessarily use
|
||||
// the correct scope.
|
||||
if ((graph_entry == NULL) || graph_entry->IsCompiledForOsr()) {
|
||||
if (graph_entry->IsCompiledForOsr()) {
|
||||
return CompileType::Dynamic();
|
||||
}
|
||||
|
||||
|
@ -724,12 +727,11 @@ CompileType ParameterInstr::ComputeType() const {
|
|||
return CompileType::Dynamic();
|
||||
}
|
||||
|
||||
LocalScope* scope = graph_entry->parsed_function().node_sequence()->scope();
|
||||
const AbstractType& type = scope->VariableAt(index())->type();
|
||||
|
||||
// Parameter is the receiver.
|
||||
if ((index() == 0) &&
|
||||
(function.IsDynamicFunction() || function.IsGenerativeConstructor())) {
|
||||
LocalScope* scope = graph_entry->parsed_function().node_sequence()->scope();
|
||||
const AbstractType& type = scope->VariableAt(index())->type();
|
||||
if (type.IsObjectType() || type.IsNullType()) {
|
||||
// Receiver can be null.
|
||||
return CompileType::FromAbstractType(type, CompileType::kNullable);
|
||||
|
|
|
@ -1558,12 +1558,14 @@ class CatchBlockEntryInstr : public BlockEntryInstr {
|
|||
public:
|
||||
CatchBlockEntryInstr(intptr_t block_id,
|
||||
intptr_t try_index,
|
||||
GraphEntryInstr* graph_entry,
|
||||
const Array& handler_types,
|
||||
intptr_t catch_try_index,
|
||||
const LocalVariable& exception_var,
|
||||
const LocalVariable& stacktrace_var,
|
||||
bool needs_stacktrace)
|
||||
: BlockEntryInstr(block_id, try_index),
|
||||
graph_entry_(graph_entry),
|
||||
predecessor_(NULL),
|
||||
catch_handler_types_(Array::ZoneHandle(handler_types.raw())),
|
||||
catch_try_index_(catch_try_index),
|
||||
|
@ -1581,6 +1583,8 @@ class CatchBlockEntryInstr : public BlockEntryInstr {
|
|||
return predecessor_;
|
||||
}
|
||||
|
||||
GraphEntryInstr* graph_entry() const { return graph_entry_; }
|
||||
|
||||
const LocalVariable& exception_var() const { return exception_var_; }
|
||||
const LocalVariable& stacktrace_var() const { return stacktrace_var_; }
|
||||
|
||||
|
@ -1606,6 +1610,7 @@ class CatchBlockEntryInstr : public BlockEntryInstr {
|
|||
predecessor_ = predecessor;
|
||||
}
|
||||
|
||||
GraphEntryInstr* graph_entry_;
|
||||
BlockEntryInstr* predecessor_;
|
||||
const Array& catch_handler_types_;
|
||||
const intptr_t catch_try_index_;
|
||||
|
@ -1912,10 +1917,11 @@ class PhiInstr : public Definition {
|
|||
PhiInstr(JoinEntryInstr* block, intptr_t num_inputs)
|
||||
: block_(block),
|
||||
inputs_(num_inputs),
|
||||
is_alive_(false),
|
||||
representation_(kTagged),
|
||||
reaching_defs_(NULL),
|
||||
loop_variable_info_(NULL) {
|
||||
loop_variable_info_(NULL),
|
||||
is_alive_(false),
|
||||
is_receiver_(kUnknownReceiver) {
|
||||
for (intptr_t i = 0; i < num_inputs; ++i) {
|
||||
inputs_.Add(NULL);
|
||||
}
|
||||
|
@ -1985,6 +1991,21 @@ class PhiInstr : public Definition {
|
|||
|
||||
PRINT_TO_SUPPORT
|
||||
|
||||
enum ReceiverType {
|
||||
kUnknownReceiver = -1,
|
||||
kNotReceiver = 0,
|
||||
kReceiver = 1
|
||||
};
|
||||
|
||||
ReceiverType is_receiver() const {
|
||||
return static_cast<ReceiverType>(is_receiver_);
|
||||
}
|
||||
|
||||
void set_is_receiver(ReceiverType is_receiver) {
|
||||
ASSERT(is_receiver_ == kUnknownReceiver);
|
||||
is_receiver_ = is_receiver;
|
||||
}
|
||||
|
||||
private:
|
||||
// Direct access to inputs_ in order to resize it due to unreachable
|
||||
// predecessors.
|
||||
|
@ -1994,11 +2015,11 @@ class PhiInstr : public Definition {
|
|||
|
||||
JoinEntryInstr* block_;
|
||||
GrowableArray<Value*> inputs_;
|
||||
bool is_alive_;
|
||||
Representation representation_;
|
||||
|
||||
BitVector* reaching_defs_;
|
||||
InductionVariableInfo* loop_variable_info_;
|
||||
bool is_alive_;
|
||||
int8_t is_receiver_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(PhiInstr);
|
||||
};
|
||||
|
|
|
@ -1283,40 +1283,6 @@ RawField* JitOptimizer::GetField(intptr_t class_id,
|
|||
}
|
||||
|
||||
|
||||
// Use CHA to determine if the call needs a class check: if the callee's
|
||||
// receiver is the same as the caller's receiver and there are no overriden
|
||||
// callee functions, then no class check is needed.
|
||||
bool JitOptimizer::InstanceCallNeedsClassCheck(
|
||||
InstanceCallInstr* call, RawFunction::Kind kind) const {
|
||||
if (!FLAG_use_cha_deopt && !isolate()->all_classes_finalized()) {
|
||||
// Even if class or function are private, lazy class finalization
|
||||
// may later add overriding methods.
|
||||
return true;
|
||||
}
|
||||
Definition* callee_receiver = call->ArgumentAt(0);
|
||||
ASSERT(callee_receiver != NULL);
|
||||
const Function& function = flow_graph_->function();
|
||||
if (function.IsDynamicFunction() &&
|
||||
callee_receiver->IsParameter() &&
|
||||
(callee_receiver->AsParameter()->index() == 0)) {
|
||||
const String& name = (kind == RawFunction::kMethodExtractor)
|
||||
? String::Handle(Z, Field::NameFromGetter(call->function_name()))
|
||||
: call->function_name();
|
||||
const Class& cls = Class::Handle(Z, function.Owner());
|
||||
if (!thread()->cha()->HasOverride(cls, name)) {
|
||||
if (FLAG_trace_cha) {
|
||||
THR_Print(" **(CHA) Instance call needs no check, "
|
||||
"no overrides of '%s' '%s'\n",
|
||||
name.ToCString(), cls.ToCString());
|
||||
}
|
||||
thread()->cha()->AddToLeafClasses(cls);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool JitOptimizer::InlineImplicitInstanceGetter(InstanceCallInstr* call) {
|
||||
ASSERT(call->HasICData());
|
||||
const ICData& ic_data = *call->ic_data();
|
||||
|
@ -1331,7 +1297,8 @@ bool JitOptimizer::InlineImplicitInstanceGetter(InstanceCallInstr* call) {
|
|||
Field::ZoneHandle(Z, GetField(class_ids[0], field_name));
|
||||
ASSERT(!field.IsNull());
|
||||
|
||||
if (InstanceCallNeedsClassCheck(call, RawFunction::kImplicitGetter)) {
|
||||
if (flow_graph()->InstanceCallNeedsClassCheck(
|
||||
call, RawFunction::kImplicitGetter)) {
|
||||
AddReceiverCheck(call);
|
||||
}
|
||||
LoadFieldInstr* load = new(Z) LoadFieldInstr(
|
||||
|
@ -2727,7 +2694,8 @@ void JitOptimizer::VisitInstanceCall(InstanceCallInstr* instr) {
|
|||
? FLAG_max_equality_polymorphic_checks
|
||||
: FLAG_max_polymorphic_checks;
|
||||
if ((unary_checks.NumberOfChecks() > max_checks) &&
|
||||
InstanceCallNeedsClassCheck(instr, RawFunction::kRegularFunction)) {
|
||||
flow_graph()->InstanceCallNeedsClassCheck(
|
||||
instr, RawFunction::kRegularFunction)) {
|
||||
// Too many checks, it will be megamorphic which needs unary checks.
|
||||
instr->set_ic_data(&unary_checks);
|
||||
return;
|
||||
|
@ -2782,7 +2750,7 @@ void JitOptimizer::VisitInstanceCall(InstanceCallInstr* instr) {
|
|||
if (has_one_target) {
|
||||
RawFunction::Kind function_kind =
|
||||
Function::Handle(Z, unary_checks.GetTargetAt(0)).kind();
|
||||
if (!InstanceCallNeedsClassCheck(instr, function_kind)) {
|
||||
if (!flow_graph()->InstanceCallNeedsClassCheck(instr, function_kind)) {
|
||||
PolymorphicInstanceCallInstr* call =
|
||||
new(Z) PolymorphicInstanceCallInstr(instr, unary_checks,
|
||||
/* call_with_checks = */ false);
|
||||
|
@ -3114,7 +3082,8 @@ bool JitOptimizer::TryInlineInstanceSetter(InstanceCallInstr* instr,
|
|||
Field::ZoneHandle(Z, GetField(class_id, field_name));
|
||||
ASSERT(!field.IsNull());
|
||||
|
||||
if (InstanceCallNeedsClassCheck(instr, RawFunction::kImplicitSetter)) {
|
||||
if (flow_graph()->InstanceCallNeedsClassCheck(
|
||||
instr, RawFunction::kImplicitSetter)) {
|
||||
AddReceiverCheck(instr);
|
||||
}
|
||||
if (field.guarded_cid() != kDynamicCid) {
|
||||
|
|
41
tests/language/vm/optimized_try_catch_cha_test.dart
Normal file
41
tests/language/vm/optimized_try_catch_cha_test.dart
Normal file
|
@ -0,0 +1,41 @@
|
|||
// Copyright (c) 2016, 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.
|
||||
// VMOptions=--optimization_counter_threshold=100 --no-use-osr --no-background_compilation
|
||||
|
||||
// Test CHA-based optimizations in presence of try-catch.
|
||||
|
||||
import "package:expect/expect.dart";
|
||||
|
||||
bar(i) {
|
||||
if (i == 11) throw 123;
|
||||
}
|
||||
|
||||
class A {
|
||||
var f = 42;
|
||||
|
||||
foo(i) {
|
||||
do {
|
||||
try {
|
||||
bar(i);
|
||||
} catch (e, s) {
|
||||
Expect.equals(123, e);
|
||||
}
|
||||
} while (i < 0);
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
|
||||
}
|
||||
|
||||
main() {
|
||||
var result;
|
||||
for (var i = 0; i < 200; i++) {
|
||||
try {
|
||||
result = new B().foo(i);
|
||||
} catch (e) { }
|
||||
}
|
||||
Expect.equals(42, result);
|
||||
}
|
Loading…
Reference in a new issue