Files
sdk/runtime/vm/compiler/frontend/flow_graph_builder.cc
T
Aart Bik 598515f031 [vm/compiler] Fix another missing incremental DOM update
Rationale:
Very similar to what was recently fixed for true branch,
but this time along the false branch. Also resets the
DOM relation in the call block, since this only dominates
true and false afterwards.

Note:
This fix unblocks the improvements to the graph checker.

https://github.com/dart-lang/sdk/issues/35803
https://github.com/dart-lang/sdk/issues/35819

Change-Id: I594bc42cfb1032b71d2f06f2d23a9ac3f8cf346b
Reviewed-on: https://dart-review.googlesource.com/c/91689
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Aart Bik <ajcbik@google.com>
2019-02-01 00:29:32 +00:00

382 lines
15 KiB
C++

// Copyright (c) 2012, 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.
#if !defined(DART_PRECOMPILED_RUNTIME)
#include "vm/compiler/frontend/flow_graph_builder.h"
#include "vm/compiler/backend/branch_optimizer.h"
#include "vm/compiler/backend/flow_graph.h"
#include "vm/compiler/backend/il.h"
#include "vm/compiler/frontend/kernel_to_il.h"
#include "vm/object.h"
#include "vm/zone.h"
namespace dart {
// Quick access to the locally defined zone() method.
#define Z (zone())
// TODO(srdjan): Allow compiler to add constants as they are encountered in
// the compilation.
const double kCommonDoubleConstants[] = {
-1.0, -0.5, -0.1, 0.0, 0.1, 0.5, 1.0, 2.0, 4.0, 5.0, 10.0, 20.0, 30.0, 64.0,
255.0, NAN,
// From dart:math
2.718281828459045, 2.302585092994046, 0.6931471805599453,
1.4426950408889634, 0.4342944819032518, 3.1415926535897932,
0.7071067811865476, 1.4142135623730951};
uword FindDoubleConstant(double value) {
intptr_t len = sizeof(kCommonDoubleConstants) / sizeof(double); // NOLINT
for (intptr_t i = 0; i < len; i++) {
if (Utils::DoublesBitEqual(value, kCommonDoubleConstants[i])) {
return reinterpret_cast<uword>(&kCommonDoubleConstants[i]);
}
}
return 0;
}
void InlineExitCollector::PrepareGraphs(FlowGraph* callee_graph) {
ASSERT(callee_graph->graph_entry()->SuccessorCount() == 1);
ASSERT(callee_graph->max_block_id() > caller_graph_->max_block_id());
ASSERT(callee_graph->max_virtual_register_number() >
caller_graph_->max_virtual_register_number());
// Adjust the caller's maximum block id and current SSA temp index.
caller_graph_->set_max_block_id(callee_graph->max_block_id());
caller_graph_->set_current_ssa_temp_index(
callee_graph->max_virtual_register_number());
// Attach the outer environment on each instruction in the callee graph.
ASSERT(call_->env() != NULL);
// Scale the edge weights by the call count for the inlined function.
double scale_factor =
static_cast<double>(call_->CallCount()) /
static_cast<double>(caller_graph_->graph_entry()->entry_count());
for (BlockIterator block_it = callee_graph->postorder_iterator();
!block_it.Done(); block_it.Advance()) {
BlockEntryInstr* block = block_it.Current();
if (block->IsTargetEntry()) {
block->AsTargetEntry()->adjust_edge_weight(scale_factor);
}
Instruction* instr = block;
if (block->env() != NULL) {
call_->env()->DeepCopyToOuter(callee_graph->zone(), block);
}
for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) {
instr = it.Current();
// TODO(zerny): Avoid creating unnecessary environments. Note that some
// optimizations need deoptimization info for non-deoptable instructions,
// eg, LICM on GOTOs.
if (instr->env() != NULL) {
call_->env()->DeepCopyToOuter(callee_graph->zone(), instr);
}
}
if (instr->IsGoto()) {
instr->AsGoto()->adjust_edge_weight(scale_factor);
}
}
RemoveUnreachableExits(callee_graph);
}
void InlineExitCollector::AddExit(ReturnInstr* exit) {
Data data = {NULL, exit};
exits_.Add(data);
}
void InlineExitCollector::Union(const InlineExitCollector* other) {
// It doesn't make sense to combine different calls or calls from
// different graphs.
ASSERT(caller_graph_ == other->caller_graph_);
ASSERT(call_ == other->call_);
exits_.AddArray(other->exits_);
}
int InlineExitCollector::LowestBlockIdFirst(const Data* a, const Data* b) {
return (a->exit_block->block_id() - b->exit_block->block_id());
}
void InlineExitCollector::RemoveUnreachableExits(FlowGraph* callee_graph) {
const GrowableArray<BlockEntryInstr*>& postorder = callee_graph->postorder();
int j = 0;
for (int i = 0; i < exits_.length(); ++i) {
BlockEntryInstr* block = exits_[i].exit_return->GetBlock();
if ((block != NULL) && (0 <= block->postorder_number()) &&
(block->postorder_number() < postorder.length()) &&
(postorder[block->postorder_number()] == block)) {
if (i != j) {
exits_[j] = exits_[i];
}
j++;
}
}
exits_.TruncateTo(j);
}
void InlineExitCollector::SortExits() {
// Assign block entries here because we did not necessarily know them when
// the return exit was added to the array.
for (int i = 0; i < exits_.length(); ++i) {
exits_[i].exit_block = exits_[i].exit_return->GetBlock();
}
exits_.Sort(LowestBlockIdFirst);
}
Definition* InlineExitCollector::JoinReturns(BlockEntryInstr** exit_block,
Instruction** last_instruction,
intptr_t try_index) {
// First sort the list of exits by block id (caching return instruction
// block entries as a side effect).
SortExits();
intptr_t num_exits = exits_.length();
if (num_exits == 1) {
ReturnAt(0)->UnuseAllInputs();
*exit_block = ExitBlockAt(0);
*last_instruction = LastInstructionAt(0);
return call_->HasUses() ? ValueAt(0)->definition() : NULL;
} else {
ASSERT(num_exits > 1);
// Create a join of the returns.
intptr_t join_id = caller_graph_->max_block_id() + 1;
caller_graph_->set_max_block_id(join_id);
JoinEntryInstr* join = new (Z) JoinEntryInstr(
join_id, try_index, CompilerState::Current().GetNextDeoptId());
// The dominator set of the join is the intersection of the dominator
// sets of all the predecessors. If we keep the dominator sets ordered
// by height in the dominator tree, we can also get the immediate
// dominator of the join node from the intersection.
//
// block_dominators is the dominator set for each block, ordered from
// the immediate dominator to the root of the dominator tree. This is
// the order we collect them in (adding at the end).
//
// join_dominators is the join's dominators ordered from the root of the
// dominator tree to the immediate dominator. This order supports
// removing during intersection by truncating the list.
GrowableArray<BlockEntryInstr*> block_dominators;
GrowableArray<BlockEntryInstr*> join_dominators;
for (intptr_t i = 0; i < num_exits; ++i) {
// Add the control-flow edge.
GotoInstr* goto_instr =
new (Z) GotoInstr(join, CompilerState::Current().GetNextDeoptId());
goto_instr->InheritDeoptTarget(zone(), ReturnAt(i));
LastInstructionAt(i)->LinkTo(goto_instr);
ExitBlockAt(i)->set_last_instruction(LastInstructionAt(i)->next());
join->predecessors_.Add(ExitBlockAt(i));
// Collect the block's dominators.
block_dominators.Clear();
BlockEntryInstr* dominator = ExitBlockAt(i)->dominator();
while (dominator != NULL) {
block_dominators.Add(dominator);
dominator = dominator->dominator();
}
if (i == 0) {
// The initial dominator set is the first predecessor's dominator
// set. Reverse it.
for (intptr_t j = block_dominators.length() - 1; j >= 0; --j) {
join_dominators.Add(block_dominators[j]);
}
} else {
// Intersect the block's dominators with the join's dominators so far.
intptr_t last = block_dominators.length() - 1;
for (intptr_t j = 0; j < join_dominators.length(); ++j) {
intptr_t k = last - j; // Corresponding index in block_dominators.
if ((k < 0) || (join_dominators[j] != block_dominators[k])) {
// We either exhausted the dominators for this block before
// exhausting the current intersection, or else we found a block
// on the path from the root of the tree that is not in common.
// I.e., there cannot be an empty set of dominators.
ASSERT(j > 0);
join_dominators.TruncateTo(j);
break;
}
}
}
}
// The immediate dominator of the join is the last one in the ordered
// intersection.
join_dominators.Last()->AddDominatedBlock(join);
*exit_block = join;
*last_instruction = join;
// If the call has uses, create a phi of the returns.
if (call_->HasUses()) {
// Add a phi of the return values.
PhiInstr* phi = new (Z) PhiInstr(join, num_exits);
caller_graph_->AllocateSSAIndexes(phi);
phi->mark_alive();
for (intptr_t i = 0; i < num_exits; ++i) {
ReturnAt(i)->RemoveEnvironment();
phi->SetInputAt(i, ValueAt(i));
}
join->InsertPhi(phi);
join->InheritDeoptTargetAfter(caller_graph_, call_, phi);
return phi;
} else {
// In the case that the result is unused, remove the return value uses
// from their definition's use list.
for (intptr_t i = 0; i < num_exits; ++i) {
ReturnAt(i)->UnuseAllInputs();
}
join->InheritDeoptTargetAfter(caller_graph_, call_, NULL);
return NULL;
}
}
}
void InlineExitCollector::ReplaceCall(BlockEntryInstr* callee_entry) {
ASSERT(call_->previous() != NULL);
ASSERT(call_->next() != NULL);
BlockEntryInstr* call_block = call_->GetBlock();
// Insert the callee graph into the caller graph.
BlockEntryInstr* callee_exit = NULL;
Instruction* callee_last_instruction = NULL;
if (exits_.length() == 0) {
// Handle the case when there are no normal return exits from the callee
// (i.e. the callee unconditionally throws) by inserting an artificial
// branch (true === true).
// The true successor is the inlined body, the false successor
// goes to the rest of the caller graph. It is removed as unreachable code
// by the constant propagation.
TargetEntryInstr* false_block = new (Z) TargetEntryInstr(
caller_graph_->allocate_block_id(), call_block->try_index(),
CompilerState::Current().GetNextDeoptId());
false_block->InheritDeoptTargetAfter(caller_graph_, call_, NULL);
false_block->LinkTo(call_->next());
call_block->ReplaceAsPredecessorWith(false_block);
ConstantInstr* true_const = caller_graph_->GetConstant(Bool::True());
BranchInstr* branch = new (Z) BranchInstr(
new (Z) StrictCompareInstr(TokenPosition::kNoSource, Token::kEQ_STRICT,
new (Z) Value(true_const),
new (Z) Value(true_const), false,
CompilerState::Current().GetNextDeoptId()),
CompilerState::Current().GetNextDeoptId()); // No number check.
branch->InheritDeoptTarget(zone(), call_);
auto true_target = BranchSimplifier::ToTargetEntry(zone(), callee_entry);
callee_entry->ReplaceAsPredecessorWith(true_target);
*branch->true_successor_address() = true_target;
*branch->false_successor_address() = false_block;
call_->previous()->AppendInstruction(branch);
call_block->set_last_instruction(branch);
// Replace uses of the return value with null to maintain valid
// SSA form - even though the rest of the caller is unreachable.
call_->ReplaceUsesWith(caller_graph_->constant_null());
// Update dominator tree.
for (intptr_t i = 0, n = callee_entry->dominated_blocks().length(); i < n;
i++) {
BlockEntryInstr* block = callee_entry->dominated_blocks()[i];
true_target->AddDominatedBlock(block);
}
for (intptr_t i = 0, n = call_block->dominated_blocks().length(); i < n;
i++) {
BlockEntryInstr* block = call_block->dominated_blocks()[i];
false_block->AddDominatedBlock(block);
}
call_block->ClearDominatedBlocks();
call_block->AddDominatedBlock(true_target);
call_block->AddDominatedBlock(false_block);
} else {
Definition* callee_result = JoinReturns(
&callee_exit, &callee_last_instruction, call_block->try_index());
if (callee_result != NULL) {
call_->ReplaceUsesWith(callee_result);
}
if (callee_last_instruction == callee_entry) {
// There are no instructions in the inlined function (e.g., it might be
// a return of a parameter or a return of a constant defined in the
// initial definitions).
call_->previous()->LinkTo(call_->next());
} else {
call_->previous()->LinkTo(callee_entry->next());
callee_last_instruction->LinkTo(call_->next());
}
if (callee_exit != callee_entry) {
// In case of control flow, locally update the predecessors, phis and
// dominator tree.
//
// Pictorially, the graph structure is:
//
// Bc : call_block Bi : callee_entry
// before_call inlined_head
// call ... other blocks ...
// after_call Be : callee_exit
// inlined_foot
// And becomes:
//
// Bc : call_block
// before_call
// inlined_head
// ... other blocks ...
// Be : callee_exit
// inlined_foot
// after_call
//
// For successors of 'after_call', the call block (Bc) is replaced as a
// predecessor by the callee exit (Be).
call_block->ReplaceAsPredecessorWith(callee_exit);
// For successors of 'inlined_head', the callee entry (Bi) is replaced
// as a predecessor by the call block (Bc).
callee_entry->ReplaceAsPredecessorWith(call_block);
// The callee exit is now the immediate dominator of blocks whose
// immediate dominator was the call block.
ASSERT(callee_exit->dominated_blocks().is_empty());
for (intptr_t i = 0; i < call_block->dominated_blocks().length(); ++i) {
BlockEntryInstr* block = call_block->dominated_blocks()[i];
callee_exit->AddDominatedBlock(block);
}
// The call block is now the immediate dominator of blocks whose
// immediate dominator was the callee entry.
call_block->ClearDominatedBlocks();
for (intptr_t i = 0; i < callee_entry->dominated_blocks().length(); ++i) {
BlockEntryInstr* block = callee_entry->dominated_blocks()[i];
call_block->AddDominatedBlock(block);
}
}
// Callee entry in not in the graph anymore. Remove it from use lists.
callee_entry->UnuseAllInputs();
}
// Neither call nor the graph entry (if present) are in the
// graph at this point. Remove them from use lists.
if (callee_entry->PredecessorCount() > 0) {
callee_entry->PredecessorAt(0)->AsGraphEntry()->UnuseAllInputs();
}
call_->UnuseAllInputs();
}
bool SimpleInstanceOfType(const AbstractType& type) {
// Bail if the type is still uninstantiated at compile time.
if (!type.IsInstantiated()) return false;
// Bail if the type is a function or a Dart Function type.
if (type.IsFunctionType() || type.IsDartFunctionType()) return false;
ASSERT(type.HasTypeClass());
const Class& type_class = Class::Handle(type.type_class());
// Bail if the type has any type parameters.
if (type_class.IsGeneric()) return false;
// Finally a simple class for instance of checking.
return true;
}
} // namespace dart
#endif // !defined(DART_PRECOMPILED_RUNTIME)