// 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" #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/flow_graph_compiler.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" #include "vm/compiler/compiler_timings.h" #include "vm/compiler/write_barrier_elimination.h" #if defined(DART_PRECOMPILER) #include "vm/compiler/aot/aot_call_specializer.h" #include "vm/compiler/aot/precompiler.h" #endif #include "vm/thread.h" #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 { CompilerPassState::CompilerPassState(Thread* thread, FlowGraph* flow_graph, Precompiler* precompiler) : thread(thread), precompiler(precompiler), inlining_depth(0), sinking(nullptr), call_specializer(nullptr), sticky_flags(0), flow_graph_(flow_graph) {} CompilerPass* CompilerPass::passes_[CompilerPass::kNumPasses] = {nullptr}; uint8_t CompilerPass::flags_[CompilerPass::kNumPasses] = {0}; DEFINE_OPTION_HANDLER(CompilerPass::ParseFiltersFromFlag, 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); DEFINE_FLAG(bool, test_il_serialization, false, "Test IL serialization."); void CompilerPassState::set_flow_graph(FlowGraph* flow_graph) { flow_graph_ = flow_graph; if (call_specializer != nullptr) { call_specializer->set_flow_graph(flow_graph); } } 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::ParseFiltersFromFlag(const char* filter) { ParseFilters(filter, flags_); } uint8_t* CompilerPass::ParseFiltersFromPragma(const char* filter) { auto flags = ThreadState::Current()->zone()->Alloc(CompilerPass::kNumPasses); ParseFilters(filter, flags); return flags; } void CompilerPass::ParseFilters(const char* filter, uint8_t* pass_flags) { if (filter == nullptr || *filter == 0) { return; } if (strcmp(filter, "help") == 0) { OS::PrintErr("%s", kCompilerPassesUsage); for (intptr_t i = 0; i < kNumPasses; i++) { if (passes_[i] != nullptr) { OS::PrintErr(" %s\n", passes_[i]->name()); } } return; } // Clear all flags. memset(pass_flags, 0, CompilerPass::kNumPasses); 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; } ParseOneFilter(start, end, pass_flags); } } void CompilerPass::ParseOneFilter(const char* start, const char* end, uint8_t* pass_flags) { 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"); return; } 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 != nullptr) { pass_flags[pass->id()] |= 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++) { pass_flags[i] = kTraceAfter; } } } void CompilerPass::Run(CompilerPassState* state) const { if ((flags() & kDisabled) != 0) { 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); } CompilerState::Current().set_current_pass(this, state); PrintGraph(state, kTraceBefore, round); { TIMELINE_DURATION(thread, CompilerVerbose, name()); { COMPILER_TIMINGS_PASS_TIMER_SCOPE(thread, id()); repeat = DoBody(state); } thread->CheckForSafepoint(); } PrintGraph(state, kTraceAfter, round); #if defined(DEBUG) if (CompilerState::Current().is_optimizing()) { FlowGraphChecker(state->flow_graph()).Check(name()); } #endif CompilerState::Current().set_current_pass(nullptr, nullptr); } } void CompilerPass::PrintGraph(CompilerPassState* state, Flag mask, intptr_t round) const { FlowGraph* flow_graph = state->flow_graph(); const uint8_t* graph_flags = flow_graph->compiler_pass_filters(); const uint8_t current_flags = (graph_flags != nullptr ? graph_flags[id()] : flags()) | state->sticky_flags; 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); #if defined(DART_PRECOMPILER) #define INVOKE_PASS_AOT(Name) \ if (mode == kAOT) { \ INVOKE_PASS(Name); \ } #else #define INVOKE_PASS_AOT(Name) #endif 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); // Run constant propagation to make sure we specialize for // (optional) constant arguments passed into the inlined method. INVOKE_PASS(ConstantPropagation); // Constant propagation removes unreachable basic blocks and // may open more opportunities for call specialization. // Call specialization during inlining may cause more call // sites to be discovered and more functions inlined. INVOKE_PASS_AOT(ApplyClassIds); // Optimize (a << b) & c patterns, merge instructions. Must occur // before 'SelectRepresentations' which inserts conversion nodes. INVOKE_PASS(TryOptimizePatterns); } FlowGraph* CompilerPass::RunPipeline(PipelineMode mode, CompilerPassState* pass_state, bool compute_ssa) { if (compute_ssa) { INVOKE_PASS(ComputeSSA); } INVOKE_PASS_AOT(ApplyClassIds); INVOKE_PASS_AOT(TypePropagation); 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); // 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_AOT(ApplyICData); INVOKE_PASS_AOT(OptimizeTypedDataAccesses); INVOKE_PASS(SelectRepresentations); INVOKE_PASS(CSE); INVOKE_PASS(Canonicalize); 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); // Currently DCE assumes that EliminateEnvironments has already been run, // so it should not be lifted earlier than that pass. INVOKE_PASS(DCE); INVOKE_PASS(Canonicalize); INVOKE_PASS_AOT(DelayAllocations); // Repeat branches optimization after DCE, as it could make more // empty blocks. INVOKE_PASS(OptimizeBranches); INVOKE_PASS(AllocationSinking_Sink); INVOKE_PASS(EliminateDeadPhis); INVOKE_PASS(DCE); INVOKE_PASS(Canonicalize); INVOKE_PASS(TypePropagation); INVOKE_PASS(SelectRepresentations_Final); INVOKE_PASS(UseTableDispatch); INVOKE_PASS(EliminateStackOverflowChecks); INVOKE_PASS(Canonicalize); INVOKE_PASS(AllocationSinking_DetachMaterializations); INVOKE_PASS(EliminateWriteBarriers); // This must be done after all other possible intra-block code motion. INVOKE_PASS(LoweringAfterCodeMotionDisabled); INVOKE_PASS(FinalizeGraph); INVOKE_PASS(Canonicalize); INVOKE_PASS(ReorderBlocks); INVOKE_PASS(AllocateRegisters); INVOKE_PASS(TestILSerialization); // Must be last. return pass_state->flow_graph(); } FlowGraph* CompilerPass::RunPipelineWithPasses( CompilerPassState* state, std::initializer_list passes) { for (auto pass_id : passes) { passes_[pass_id]->Run(state); } return state->flow_graph(); } COMPILER_PASS(ComputeSSA, { // Transform to SSA (no inlining arguments). flow_graph->ComputeSSA(nullptr); }); COMPILER_PASS(ApplyICData, { state->call_specializer->ApplyICData(); }); COMPILER_PASS(TryOptimizePatterns, { flow_graph->TryOptimizePatterns(); }); COMPILER_PASS(SetOuterInliningId, { FlowGraphInliner::SetInliningIdAndTryIndex(flow_graph, 0, kInvalidTryIndex); }); COMPILER_PASS(Inlining, { FlowGraphInliner inliner(flow_graph, 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(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(SelectRepresentations_Final, { // Final selection of representations. After this pass // representations of inputs/outputs should match. flow_graph->SelectRepresentations(); flow_graph->disallow_unmatched_representations(); }); COMPILER_PASS(UseTableDispatch, { state->call_specializer->ReplaceInstanceCallsWithDispatchTableCalls(); }); COMPILER_PASS_REPEAT(CSE, { return DominatorBasedCSE::Optimize(flow_graph); }); COMPILER_PASS(LICM, { if (flow_graph->is_huge_method()) { return false; // Runs in quadratic time. } 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, { if (flow_graph->is_huge_method()) { return false; // Runs in quadratic time. } // 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=*/CompilerState::Current().is_aot()); }); COMPILER_PASS(EliminateEnvironments, { flow_graph->EliminateEnvironments(); }); COMPILER_PASS(EliminateDeadPhis, { DeadCodeElimination::EliminateDeadPhis(flow_graph); }); COMPILER_PASS(DCE, { DeadCodeElimination::EliminateDeadCode(flow_graph); }); COMPILER_PASS(DelayAllocations, { DelayAllocations::Optimize(flow_graph); }); COMPILER_PASS(AllocationSinking_Sink, { // TODO(vegorov): Support allocation sinking with try-catch. if (flow_graph->try_entries().is_empty()) { state->sinking = new AllocationSinking(flow_graph); state->sinking->Optimize(); } }); COMPILER_PASS(AllocationSinking_DetachMaterializations, { if (state->sinking != nullptr) { // 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, { flow_graph->InsertMoveArguments(); // 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, { flow_graph->set_max_argument_slot_count(0); // 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, { BlockScheduler::ReorderBlocks(flow_graph); }); COMPILER_PASS(EliminateWriteBarriers, { EliminateWriteBarriers(flow_graph); }); COMPILER_PASS(FinalizeGraph, { // At the end of the pipeline, force recomputing and caching graph // information (instruction and call site counts) for the (assumed) // non-specialized case with better values, for future inlining. intptr_t instruction_count = 0; intptr_t call_site_count = 0; FlowGraphInliner::CollectGraphInfo(flow_graph, /*constants_count*/ 0, /*force*/ true, &instruction_count, &call_site_count); flow_graph->function().set_inlining_depth(state->inlining_depth); // Remove redefinitions for the rest of the pipeline. flow_graph->RemoveRedefinitions(); }); COMPILER_PASS(TestILSerialization, { // This is the last compiler pass. // Test that round-trip IL serialization works before generating code. if (FLAG_test_il_serialization && CompilerState::Current().is_aot()) { Zone* zone = flow_graph->zone(); auto* detached_defs = new (zone) ZoneGrowableArray(zone, 0); flow_graph->CompactSSA(detached_defs); ZoneWriteStream write_stream(flow_graph->zone(), 1024); FlowGraphSerializer serializer(&write_stream); serializer.WriteFlowGraph(*flow_graph, *detached_defs); ReadStream read_stream(write_stream.buffer(), write_stream.bytes_written()); FlowGraphDeserializer deserializer(flow_graph->parsed_function(), &read_stream); state->set_flow_graph(deserializer.ReadFlowGraph()); } }); COMPILER_PASS(LoweringAfterCodeMotionDisabled, { flow_graph->ExtractNonInternalTypedDataPayloads(); flow_graph->AddAsanMsanInstrumentation(); flow_graph->AddTsanInstrumentation(); }); COMPILER_PASS(GenerateCode, { state->graph_compiler->CompileGraph(); }); } // namespace dart