b0727bd661
Rationale: Refactored existing verify uses (with some overlap and some additional checks) into the graph checker. Also added more verification code and repaired some inconsistencies in IR found by new checker. However, some checks are disabled with a TODO, since the IR does not currently meet all the stricter assumptions. Fixes will follow. https://github.com/dart-lang/sdk/issues/36893 https://github.com/dart-lang/sdk/issues/36894 https://github.com/dart-lang/sdk/issues/36895 https://github.com/dart-lang/sdk/issues/36899 Change-Id: Ic0395208da38ecb6fc8ca2551efe819e6458a731 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/101922 Commit-Queue: Aart Bik <ajcbik@google.com> Reviewed-by: Alexander Markov <alexmarkov@google.com>
488 lines
16 KiB
C++
488 lines
16 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/flow_graph_checker.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);
|
|
{
|
|
TIMELINE_DURATION(thread, CompilerVerbose, name());
|
|
repeat = DoBody(state);
|
|
thread->CheckForSafepoint();
|
|
#if defined(DEBUG)
|
|
FlowGraphChecker(state->flow_graph).Check(name());
|
|
#endif
|
|
}
|
|
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::RunGraphIntrinsicPipeline(CompilerPassState* pass_state) {
|
|
INVOKE_PASS(AllocateRegistersForGraphIntrinsic);
|
|
}
|
|
|
|
void CompilerPass::RunInliningPipeline(PipelineMode mode,
|
|
CompilerPassState* pass_state) {
|
|
INVOKE_PASS(ApplyClassIds);
|
|
INVOKE_PASS(TypePropagation);
|
|
INVOKE_PASS(ApplyICData);
|
|
INVOKE_PASS(Canonicalize);
|
|
// Optimize (a << b) & c patterns, merge instructions. Must occur
|
|
// before 'SelectRepresentations' which inserts conversion nodes.
|
|
INVOKE_PASS(TryOptimizePatterns);
|
|
}
|
|
|
|
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);
|
|
}
|
|
if (mode == kAOT) {
|
|
INVOKE_PASS(OptimizeTypedDataAccesses);
|
|
}
|
|
#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);
|
|
INVOKE_PASS(WriteBarrierElimination);
|
|
INVOKE_PASS(FinalizeGraph);
|
|
INVOKE_PASS(AllocateRegisters);
|
|
INVOKE_PASS(ReorderBlocks);
|
|
}
|
|
|
|
void CompilerPass::RunPipelineWithPasses(
|
|
CompilerPassState* state,
|
|
std::initializer_list<CompilerPass::Id> passes) {
|
|
for (auto pass_id : passes) {
|
|
passes_[pass_id]->Run(state);
|
|
}
|
|
}
|
|
|
|
COMPILER_PASS(ComputeSSA, {
|
|
// 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, {
|
|
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(/*keep_checks*/ true);
|
|
});
|
|
|
|
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(OptimizeTypedDataAccesses,
|
|
{ TypedDataSpecializer::Optimize(flow_graph); });
|
|
|
|
COMPILER_PASS(TryCatchOptimization, {
|
|
OptimizeCatchEntryStates(flow_graph, /*is_aot=*/FLAG_precompiled_mode);
|
|
});
|
|
|
|
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, {
|
|
// Ensure loop hierarchy has been computed.
|
|
flow_graph->GetLoopHierarchy();
|
|
// Perform register allocation on the SSA graph.
|
|
FlowGraphAllocator allocator(*flow_graph);
|
|
allocator.AllocateRegisters();
|
|
});
|
|
|
|
COMPILER_PASS(AllocateRegistersForGraphIntrinsic, {
|
|
// Ensure loop hierarchy has been computed.
|
|
flow_graph->GetLoopHierarchy();
|
|
// Perform register allocation on the SSA graph.
|
|
FlowGraphAllocator allocator(*flow_graph, /*intrinsic_mode=*/true);
|
|
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 (!current->CanTriggerGC()) {
|
|
if (StoreInstanceFieldInstr* instr = current->AsStoreInstanceField()) {
|
|
if (instr->instance()->definition() == last_allocated) {
|
|
instr->set_emit_store_barrier(kNoStoreBarrier);
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (AllocationInstr* alloc = current->AsAllocation()) {
|
|
if (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();
|
|
});
|
|
|
|
} // namespace dart
|
|
|
|
#endif // DART_PRECOMPILED_RUNTIME
|