Files
sdk/runtime/vm/compiler/compiler_pass.cc
T
Alexander Markov a386c07281 [vm/aot] Add extra call specialization pass
The extra call specialization pass after constant propagation is able to
replace more calls in AOT, as constant propagation removes unreachable
code and subsequent type propagation infers more accurate types.

Performance improvement (armv8):
DartMicroBench.IntPower3 196.4%
DartMicroBench.IntPower2 273.1%

Change-Id: I7076608f828484c4045e7a7b605ed8ff7981a868
Reviewed-on: https://dart-review.googlesource.com/75564
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
2018-09-19 20:49:28 +00:00

459 lines
15 KiB
C++

// Copyright (c) 2018, 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.
#include "vm/compiler/compiler_pass.h"
#ifndef DART_PRECOMPILED_RUNTIME
#include "vm/compiler/backend/block_scheduler.h"
#include "vm/compiler/backend/branch_optimizer.h"
#include "vm/compiler/backend/constant_propagator.h"
#include "vm/compiler/backend/il_printer.h"
#include "vm/compiler/backend/inliner.h"
#include "vm/compiler/backend/linearscan.h"
#include "vm/compiler/backend/range_analysis.h"
#include "vm/compiler/backend/redundancy_elimination.h"
#include "vm/compiler/backend/type_propagator.h"
#include "vm/compiler/call_specializer.h"
#if defined(DART_PRECOMPILER)
#include "vm/compiler/aot/aot_call_specializer.h"
#endif
#include "vm/timeline.h"
#define COMPILER_PASS_REPEAT(Name, Body) \
class CompilerPass_##Name : public CompilerPass { \
public: \
CompilerPass_##Name() : CompilerPass(k##Name, #Name) {} \
\
static bool Register() { return true; } \
\
protected: \
virtual bool DoBody(CompilerPassState* state) const { \
FlowGraph* flow_graph = state->flow_graph; \
USE(flow_graph); \
Body; \
} \
}; \
static CompilerPass_##Name compiler_pass_##Name;
#define COMPILER_PASS(Name, Body) \
COMPILER_PASS_REPEAT(Name, { \
Body; \
return false; \
})
namespace dart {
CompilerPass* CompilerPass::passes_[CompilerPass::kNumPasses] = {NULL};
DEFINE_OPTION_HANDLER(CompilerPass::ParseFilters,
compiler_passes,
"List of comma separated compilation passes flags. "
"Use -Name to disable a pass, Name to print IL after it. "
"Do --compiler-passes=help for more information.");
DECLARE_FLAG(bool, print_flow_graph);
DECLARE_FLAG(bool, print_flow_graph_optimized);
static const char* kCompilerPassesUsage =
"=== How to use --compiler-passes flag\n"
"\n"
"Pass the list of comma separated compiler pass filter flags.\n"
"\n"
"For the given pass Name the following flags are supported:\n"
"\n"
" -Name disable the pass\n"
" ]Name or Name print IL after the pass\n"
" [Name print IL before the pass\n"
" *Name print IL before and after the pass\n"
" * print IL after each pass.\n"
"\n"
" The flag can be followed by '+' which makes it sticky, e.g. Inlining+\n"
" would cause IL to be printed after all passes that follow inlining and\n"
" are not disabled.\n"
"\n"
"List of compiler passes:\n";
void CompilerPass::ParseFilters(const char* filter) {
if (filter == NULL || *filter == 0) {
return;
}
if (strcmp(filter, "help") == 0) {
OS::PrintErr("%s", kCompilerPassesUsage);
for (intptr_t i = 0; i < kNumPasses; i++) {
if (passes_[i] != NULL) {
OS::PrintErr(" %s\n", passes_[i]->name());
}
}
return;
}
// Clear all flags.
for (intptr_t i = 0; i < kNumPasses; i++) {
if (passes_[i] != NULL) {
passes_[i]->flags_ = 0;
}
}
for (const char *start = filter, *end = filter; *end != 0;
start = (end + 1)) {
// Search forward until the separator ',' or the end of filter is reached.
end = start;
while (*end != ',' && *end != '\0') {
end++;
}
if (start == end) {
OS::PrintErr("Ignoring empty compiler pass flag\n");
continue;
}
uint8_t flags = 0;
if (*start == '-') {
flags = kDisabled;
} else if (*start == ']') {
flags = kTraceAfter;
} else if (*start == '[') {
flags = kTraceBefore;
} else if (*start == '*') {
flags = kTraceBeforeOrAfter;
}
if (flags == 0) {
flags |= kTraceAfter;
} else {
start++; // Skip the modifier
}
size_t suffix = 0;
if (end[-1] == '+') {
if (start == (end - 1)) {
OS::PrintErr("Sticky modifier '+' should follow pass name\n");
continue;
}
flags |= kSticky;
suffix = 1;
}
size_t length = (end - start) - suffix;
if (length != 0) {
char* pass_name = Utils::StrNDup(start, length);
CompilerPass* pass = FindPassByName(pass_name);
if (pass != NULL) {
pass->flags_ |= flags;
} else {
OS::PrintErr("Unknown compiler pass: %s\n", pass_name);
}
free(pass_name);
} else if (flags == kTraceBeforeOrAfter) {
for (intptr_t i = 0; i < kNumPasses; i++) {
if (passes_[i] != NULL) {
passes_[i]->flags_ = kTraceAfter;
}
}
}
}
}
void CompilerPass::Run(CompilerPassState* state) const {
if (IsFlagSet(kDisabled)) {
return;
}
if ((flags() & kSticky) != 0) {
state->sticky_flags |= flags();
}
const intptr_t kMaxRounds = 2;
Thread* thread = state->thread;
bool repeat = true;
for (intptr_t round = 1; round <= kMaxRounds && repeat; round++) {
if (round > 1) {
Get(kCanonicalize)->Run(state);
}
PrintGraph(state, kTraceBefore, round);
{
NOT_IN_PRODUCT(
TimelineDurationScope tds2(thread, state->compiler_timeline, name()));
repeat = DoBody(state);
DEBUG_ASSERT(state->flow_graph->VerifyUseLists());
thread->CheckForSafepoint();
}
PrintGraph(state, kTraceAfter, round);
}
}
void CompilerPass::PrintGraph(CompilerPassState* state,
Flag mask,
intptr_t round) const {
const intptr_t current_flags = flags() | state->sticky_flags;
FlowGraph* flow_graph = state->flow_graph;
if ((FLAG_print_flow_graph || FLAG_print_flow_graph_optimized) &&
flow_graph->should_print() && ((current_flags & mask) != 0)) {
Zone* zone = state->thread->zone();
const char* when = mask == kTraceBefore ? "Before" : "After";
const char* phase =
round == 1
? zone->PrintToString("%s %s", when, name())
: zone->PrintToString("%s %s (round %" Pd ")", when, name(), round);
FlowGraphPrinter::PrintGraph(phase, flow_graph);
}
}
#define INVOKE_PASS(Name) \
CompilerPass::Get(CompilerPass::k##Name)->Run(pass_state);
void CompilerPass::RunPipeline(PipelineMode mode,
CompilerPassState* pass_state) {
INVOKE_PASS(ComputeSSA);
#if defined(DART_PRECOMPILER)
if (mode == kAOT) {
INVOKE_PASS(ApplyClassIds);
INVOKE_PASS(TypePropagation);
}
#endif
INVOKE_PASS(ApplyICData);
INVOKE_PASS(TryOptimizePatterns);
INVOKE_PASS(SetOuterInliningId);
INVOKE_PASS(TypePropagation);
INVOKE_PASS(ApplyClassIds);
INVOKE_PASS(Inlining);
INVOKE_PASS(TypePropagation);
INVOKE_PASS(ApplyClassIds);
INVOKE_PASS(TypePropagation);
INVOKE_PASS(ApplyICData);
INVOKE_PASS(Canonicalize);
INVOKE_PASS(BranchSimplify);
INVOKE_PASS(IfConvert);
INVOKE_PASS(Canonicalize);
INVOKE_PASS(ConstantPropagation);
INVOKE_PASS(OptimisticallySpecializeSmiPhis);
INVOKE_PASS(TypePropagation);
#if defined(DART_PRECOMPILER)
if (mode == kAOT) {
// The extra call specialization pass in AOT is able to specialize more
// calls after ConstantPropagation, which removes unreachable code, and
// TypePropagation, which can infer more accurate types after removing
// unreachable code.
INVOKE_PASS(ApplyICData);
}
#endif
INVOKE_PASS(WidenSmiToInt32);
INVOKE_PASS(SelectRepresentations);
INVOKE_PASS(CSE);
INVOKE_PASS(LICM);
INVOKE_PASS(TryOptimizePatterns);
INVOKE_PASS(DSE);
INVOKE_PASS(TypePropagation);
INVOKE_PASS(RangeAnalysis);
INVOKE_PASS(OptimizeBranches);
INVOKE_PASS(TypePropagation);
INVOKE_PASS(TryCatchOptimization);
INVOKE_PASS(EliminateEnvironments);
INVOKE_PASS(EliminateDeadPhis);
INVOKE_PASS(Canonicalize);
INVOKE_PASS(AllocationSinking_Sink);
INVOKE_PASS(EliminateDeadPhis);
INVOKE_PASS(TypePropagation);
INVOKE_PASS(SelectRepresentations);
INVOKE_PASS(Canonicalize);
INVOKE_PASS(EliminateStackOverflowChecks);
INVOKE_PASS(Canonicalize);
INVOKE_PASS(AllocationSinking_DetachMaterializations);
#if defined(DART_PRECOMPILER)
if (mode == kAOT) {
INVOKE_PASS(ReplaceArrayBoundChecksForAOT);
}
#endif
INVOKE_PASS(WriteBarrierElimination);
INVOKE_PASS(FinalizeGraph);
INVOKE_PASS(AllocateRegisters);
if (mode == kJIT) {
INVOKE_PASS(ReorderBlocks);
}
}
COMPILER_PASS(ComputeSSA, {
CSTAT_TIMER_SCOPE(state->thread, ssa_timer);
// Transform to SSA (virtual register 0 and no inlining arguments).
flow_graph->ComputeSSA(0, NULL);
});
COMPILER_PASS(ApplyICData, { state->call_specializer->ApplyICData(); });
COMPILER_PASS(TryOptimizePatterns, { flow_graph->TryOptimizePatterns(); });
COMPILER_PASS(SetOuterInliningId,
{ FlowGraphInliner::SetInliningId(flow_graph, 0); });
COMPILER_PASS(Inlining, {
CSTAT_TIMER_SCOPE(state->thread, graphinliner_timer);
FlowGraphInliner inliner(
flow_graph, &state->inline_id_to_function, &state->inline_id_to_token_pos,
&state->caller_inline_id, state->speculative_policy, state->precompiler);
state->inlining_depth = inliner.Inline();
});
COMPILER_PASS(TypePropagation,
{ FlowGraphTypePropagator::Propagate(flow_graph); });
COMPILER_PASS(ApplyClassIds, { state->call_specializer->ApplyClassIds(); });
COMPILER_PASS(EliminateStackOverflowChecks, {
if (!flow_graph->IsCompiledForOsr()) {
CheckStackOverflowElimination::EliminateStackOverflow(flow_graph);
}
});
COMPILER_PASS(Canonicalize, {
// Do optimizations that depend on the propagated type information.
if (flow_graph->Canonicalize()) {
flow_graph->Canonicalize();
}
});
COMPILER_PASS(BranchSimplify, { BranchSimplifier::Simplify(flow_graph); });
COMPILER_PASS(IfConvert, { IfConverter::Simplify(flow_graph); });
COMPILER_PASS_REPEAT(ConstantPropagation, {
ConstantPropagator::Optimize(flow_graph);
return true;
});
// Optimistically convert loop phis that have a single non-smi input
// coming from the loop pre-header into smi-phis.
COMPILER_PASS(OptimisticallySpecializeSmiPhis, {
LICM licm(flow_graph);
licm.OptimisticallySpecializeSmiPhis();
});
COMPILER_PASS(WidenSmiToInt32, {
// Where beneficial convert Smi operations into Int32 operations.
// Only meanigful for 32bit platforms right now.
flow_graph->WidenSmiToInt32();
});
COMPILER_PASS(SelectRepresentations, {
// Unbox doubles. Performed after constant propagation to minimize
// interference from phis merging double values and tagged
// values coming from dead paths.
flow_graph->SelectRepresentations();
});
COMPILER_PASS_REPEAT(CSE, { return DominatorBasedCSE::Optimize(flow_graph); });
COMPILER_PASS(LICM, {
flow_graph->RenameUsesDominatedByRedefinitions();
DEBUG_ASSERT(flow_graph->VerifyRedefinitions());
LICM licm(flow_graph);
licm.Optimize();
flow_graph->RemoveRedefinitions();
});
COMPILER_PASS(DSE, { DeadStoreElimination::Optimize(flow_graph); });
COMPILER_PASS(RangeAnalysis, {
// We have to perform range analysis after LICM because it
// optimistically moves CheckSmi through phis into loop preheaders
// making some phis smi.
RangeAnalysis range_analysis(flow_graph);
range_analysis.Analyze();
});
COMPILER_PASS(OptimizeBranches, {
// Constant propagation can use information from range analysis to
// find unreachable branch targets and eliminate branches that have
// the same true- and false-target.
ConstantPropagator::OptimizeBranches(flow_graph);
});
COMPILER_PASS(TryCatchOptimization,
{ TryCatchAnalyzer::Optimize(flow_graph); });
COMPILER_PASS(EliminateEnvironments, { flow_graph->EliminateEnvironments(); });
COMPILER_PASS(EliminateDeadPhis,
{ DeadCodeElimination::EliminateDeadPhis(flow_graph); });
COMPILER_PASS(AllocationSinking_Sink, {
// TODO(vegorov): Support allocation sinking with try-catch.
if (flow_graph->graph_entry()->catch_entries().is_empty()) {
state->sinking = new AllocationSinking(flow_graph);
state->sinking->Optimize();
}
});
COMPILER_PASS(AllocationSinking_DetachMaterializations, {
if (state->sinking != NULL) {
// Remove all MaterializeObject instructions inserted by allocation
// sinking from the flow graph and let them float on the side
// referenced only from environments. Register allocator will consider
// them as part of a deoptimization environment.
state->sinking->DetachMaterializations();
}
});
COMPILER_PASS(AllocateRegisters, {
// Perform register allocation on the SSA graph.
FlowGraphAllocator allocator(*flow_graph);
allocator.AllocateRegisters();
});
COMPILER_PASS(ReorderBlocks, {
if (state->reorder_blocks) {
state->block_scheduler->ReorderBlocks();
}
});
static void WriteBarrierElimination(FlowGraph* flow_graph) {
for (BlockIterator block_it = flow_graph->reverse_postorder_iterator();
!block_it.Done(); block_it.Advance()) {
BlockEntryInstr* block = block_it.Current();
Definition* last_allocated = nullptr;
for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) {
Instruction* current = it.Current();
if (StoreInstanceFieldInstr* instr = current->AsStoreInstanceField()) {
if (!current->CanTriggerGC()) {
if (instr->instance()->definition() == last_allocated) {
instr->set_emit_store_barrier(kNoStoreBarrier);
}
continue;
}
}
AllocationInstr* alloc = current->AsAllocation();
if (alloc != nullptr && alloc->WillAllocateNewOrRemembered()) {
last_allocated = alloc;
continue;
}
if (current->CanTriggerGC()) {
last_allocated = nullptr;
}
}
}
}
COMPILER_PASS(WriteBarrierElimination,
{ WriteBarrierElimination(flow_graph); });
COMPILER_PASS(FinalizeGraph, {
// Compute and store graph informations (call & instruction counts)
// to be later used by the inliner.
FlowGraphInliner::CollectGraphInfo(flow_graph, true);
flow_graph->function().set_inlining_depth(state->inlining_depth);
flow_graph->RemoveRedefinitions();
});
#if defined(DART_PRECOMPILER)
COMPILER_PASS(ReplaceArrayBoundChecksForAOT,
{ AotCallSpecializer::ReplaceArrayBoundChecks(flow_graph); })
#endif
} // namespace dart
#endif // DART_PRECOMPILED_RUNTIME