Files
sdk/runtime/vm/compiler/compiler_pass.cc
T
Daco Harkes 5380fa5d40 [vm/ffi] Introduce CCallInstr and replace handle instructions
The CCallInstr takes unboxed word-sized integers and does a call to C
without any VM transitions.

This can be used for the following.
1) Leaf runtime entries. This CL removes the EnterHandleScope,
   ExitHandleScope, and AllocateHandle instructions.
2) Compilation of isolate independent code where all definitions in IL
   are unboxed integers.

Currently, the CCallInstr expects all it's arguments and the return type
to be word-sized.

This CL also:
- Adds loadImmediate and CCall with a register for each assembler.
- Adds a CSE pass to force-optimized so that LoadThreadInstr gets de-
  duplicated.

TEST=tests/ffi(_2)/*

Change-Id: I67c6aaa1b7d6aa7d60e274477686a54e5a331216
Cq-Include-Trybots: luci.dart.try:vm-kernel-linux-debug-simriscv64-try,vm-kernel-precomp-linux-debug-simriscv64-try,vm-kernel-nnbd-mac-debug-arm64-try,vm-kernel-nnbd-mac-debug-x64-try,vm-kernel-win-debug-ia32-try,vm-kernel-win-debug-x64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/246240
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
2022-05-30 20:58:03 +00:00

590 lines
20 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"
#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,
SpeculativeInliningPolicy* speculative_policy,
Precompiler* precompiler)
: thread(thread),
precompiler(precompiler),
inlining_depth(0),
sinking(NULL),
call_specializer(NULL),
speculative_policy(speculative_policy),
reorder_blocks(false),
sticky_flags(0),
flow_graph_(flow_graph) {
// Top scope function is at inlining id 0.
inline_id_to_function.Add(&flow_graph->parsed_function().function());
// Top scope function has no caller (-1).
caller_inline_id.Add(-1);
// We do not add a token position for the top scope function to
// |inline_id_to_token_pos| because it is not (currently) inlined into
// another graph at a given token position. A side effect of this is that
// the length of |inline_id_to_function| and |caller_inline_id| is always
// larger than the length of |inline_id_to_token_pos| by one.
}
CompilerPass* CompilerPass::passes_[CompilerPass::kNumPasses] = {NULL};
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);
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<uint8_t>(CompilerPass::kNumPasses);
ParseFilters(filter, flags);
return flags;
}
void CompilerPass::ParseFilters(const char* filter, uint8_t* pass_flags) {
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.
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 != NULL) {
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(), state->inline_id_to_function)
.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);
}
// Keep in sync with TestPipeline::RunForcedOptimizedAfterSSAPasses.
FlowGraph* CompilerPass::RunForceOptimizedPipeline(
PipelineMode mode,
CompilerPassState* pass_state) {
INVOKE_PASS(ComputeSSA);
INVOKE_PASS(SetOuterInliningId);
INVOKE_PASS(TypePropagation);
INVOKE_PASS(Canonicalize);
INVOKE_PASS(BranchSimplify);
INVOKE_PASS(IfConvert);
INVOKE_PASS(ConstantPropagation);
INVOKE_PASS(TypePropagation);
INVOKE_PASS(WidenSmiToInt32);
INVOKE_PASS(SelectRepresentations_Final);
INVOKE_PASS(CSE);
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);
INVOKE_PASS(EliminateWriteBarriers);
INVOKE_PASS(FinalizeGraph);
INVOKE_PASS(AllocateRegisters);
INVOKE_PASS(ReorderBlocks);
return pass_state->flow_graph();
}
FlowGraph* CompilerPass::RunPipeline(PipelineMode mode,
CompilerPassState* pass_state) {
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(WidenSmiToInt32);
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(TypePropagation);
INVOKE_PASS(SelectRepresentations_Final);
INVOKE_PASS(Canonicalize);
INVOKE_PASS(UseTableDispatch);
INVOKE_PASS(EliminateStackOverflowChecks);
INVOKE_PASS(Canonicalize);
INVOKE_PASS(AllocationSinking_DetachMaterializations);
INVOKE_PASS(EliminateWriteBarriers);
INVOKE_PASS(FinalizeGraph);
INVOKE_PASS(Canonicalize);
INVOKE_PASS(AllocateRegisters);
INVOKE_PASS(ReorderBlocks);
return pass_state->flow_graph();
}
FlowGraph* CompilerPass::RunPipelineWithPasses(
CompilerPassState* state,
std::initializer_list<CompilerPass::Id> passes) {
for (auto pass_id : passes) {
passes_[pass_id]->Run(state);
}
return state->flow_graph();
}
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(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, {
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=*/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->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, {
flow_graph->InsertPushArguments();
// 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) {
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(GenerateCode, { state->graph_compiler->CompileGraph(); });
} // namespace dart