| Index: runtime/vm/compiler.cc
|
| diff --git a/runtime/vm/compiler.cc b/runtime/vm/compiler.cc
|
| index 7d4387be499b25f58953b7c98a2bf09d71172901..e416af2034e787619e3ad606caa7b091fc7579db 100644
|
| --- a/runtime/vm/compiler.cc
|
| +++ b/runtime/vm/compiler.cc
|
| @@ -63,6 +63,7 @@ DEFINE_FLAG(bool, verify_compiler, false,
|
|
|
| DECLARE_FLAG(bool, trace_failed_optimization_attempts);
|
| DECLARE_FLAG(bool, trace_patching);
|
| +DECLARE_FLAG(bool, trace_irregexp);
|
|
|
| // Compile a function. Should call only if the function has not been compiled.
|
| // Arg0: function object.
|
| @@ -659,6 +660,366 @@ static bool CompileParsedFunctionHelper(ParsedFunction* parsed_function,
|
| }
|
|
|
|
|
| +// Return false if bailed out.
|
| +static bool CompileIrregexpFunctionHelper(ParsedFunction* parsed_function,
|
| + FlowGraph* flow_graph,
|
| + bool optimized,
|
| + intptr_t osr_id) {
|
| + const Function& function = parsed_function->function();
|
| + if (optimized && !function.IsOptimizable()) {
|
| + return false;
|
| + }
|
| + TimerScope timer(FLAG_compiler_stats, &CompilerStats::codegen_timer);
|
| + bool is_compiled = false;
|
| + Isolate* isolate = Isolate::Current();
|
| + HANDLESCOPE(isolate);
|
| +
|
| + // We may reattempt compilation if the function needs to be assembled using
|
| + // far branches on ARM and MIPS. In the else branch of the setjmp call,
|
| + // done is set to false, and use_far_branches is set to true if there is a
|
| + // longjmp from the ARM or MIPS assemblers. In all other paths through this
|
| + // while loop, done is set to true. use_far_branches is always false on ia32
|
| + // and x64.
|
| + bool done = false;
|
| + // volatile because the variable may be clobbered by a longjmp.
|
| + volatile bool use_far_branches = false;
|
| + while (!done) {
|
| + const intptr_t prev_deopt_id = isolate->deopt_id();
|
| + LongJumpScope jump;
|
| + if (setjmp(*jump.Set()) == 0) {
|
| + // The flow graph is passed in.
|
| +
|
| + if (FLAG_print_flow_graph ||
|
| + (optimized && FLAG_print_flow_graph_optimized)) {
|
| + if (osr_id == Isolate::kNoDeoptId) {
|
| + FlowGraphPrinter::PrintGraph("Before Optimizations", flow_graph);
|
| + } else {
|
| + FlowGraphPrinter::PrintGraph("For OSR", flow_graph);
|
| + }
|
| + }
|
| +
|
| + BlockScheduler block_scheduler(flow_graph);
|
| + const bool reorder_blocks =
|
| + FlowGraph::ShouldReorderBlocks(function, optimized);
|
| + if (reorder_blocks) {
|
| + block_scheduler.AssignEdgeWeights();
|
| + }
|
| +
|
| + if (optimized) {
|
| + TimerScope timer(FLAG_compiler_stats,
|
| + &CompilerStats::ssa_timer,
|
| + isolate);
|
| + // Transform to SSA (virtual register 0 and no inlining arguments).
|
| + flow_graph->ComputeSSA(0, NULL);
|
| + DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
| + if (FLAG_print_flow_graph || FLAG_print_flow_graph_optimized) {
|
| + FlowGraphPrinter::PrintGraph("After SSA", flow_graph);
|
| + }
|
| + }
|
| +
|
| + // Collect all instance fields that are loaded in the graph and
|
| + // have non-generic type feedback attached to them that can
|
| + // potentially affect optimizations.
|
| + if (optimized) {
|
| + TimerScope timer(FLAG_compiler_stats,
|
| + &CompilerStats::graphoptimizer_timer,
|
| + isolate);
|
| +
|
| + FlowGraphOptimizer optimizer(flow_graph);
|
| + optimizer.ApplyICData();
|
| + DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
| +
|
| + // Optimize (a << b) & c patterns, merge operations.
|
| + // Run early in order to have more opportunity to optimize left shifts.
|
| + optimizer.TryOptimizePatterns();
|
| + DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
| +
|
| + // Inlining (mutates the flow graph)
|
| + if (FLAG_use_inlining) {
|
| + TimerScope timer(FLAG_compiler_stats,
|
| + &CompilerStats::graphinliner_timer);
|
| + // Propagate types to create more inlining opportunities.
|
| + FlowGraphTypePropagator::Propagate(flow_graph);
|
| + DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
| +
|
| + // Use propagated class-ids to create more inlining opportunities.
|
| + optimizer.ApplyClassIds();
|
| + DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
| +
|
| + FlowGraphInliner inliner(flow_graph);
|
| + inliner.Inline();
|
| + // Use lists are maintained and validated by the inliner.
|
| + DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
| + }
|
| +
|
| + // Propagate types and eliminate more type tests.
|
| + FlowGraphTypePropagator::Propagate(flow_graph);
|
| + DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
| +
|
| + // Use propagated class-ids to optimize further.
|
| + optimizer.ApplyClassIds();
|
| + DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
| +
|
| + // Propagate types for potentially newly added instructions by
|
| + // ApplyClassIds(). Must occur before canonicalization.
|
| + FlowGraphTypePropagator::Propagate(flow_graph);
|
| + DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
| +
|
| + // Do optimizations that depend on the propagated type information.
|
| + if (optimizer.Canonicalize()) {
|
| + // Invoke Canonicalize twice in order to fully canonicalize patterns
|
| + // like "if (a & const == 0) { }".
|
| + optimizer.Canonicalize();
|
| + }
|
| + DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
| +
|
| + BranchSimplifier::Simplify(flow_graph);
|
| + DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
| +
|
| + IfConverter::Simplify(flow_graph);
|
| + DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
| +
|
| + if (FLAG_constant_propagation) {
|
| + ConstantPropagator::Optimize(flow_graph);
|
| + DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
| + // A canonicalization pass to remove e.g. smi checks on smi constants.
|
| + optimizer.Canonicalize();
|
| + DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
| + // Canonicalization introduced more opportunities for constant
|
| + // propagation.
|
| + ConstantPropagator::Optimize(flow_graph);
|
| + DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
| + }
|
| +
|
| + // Propagate types and eliminate even more type tests.
|
| + // Recompute types after constant propagation to infer more precise
|
| + // types for uses that were previously reached by now eliminated phis.
|
| + FlowGraphTypePropagator::Propagate(flow_graph);
|
| + DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
| +
|
| + // Unbox doubles. Performed after constant propagation to minimize
|
| + // interference from phis merging double values and tagged
|
| + // values coming from dead paths.
|
| + optimizer.SelectRepresentations();
|
| + DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
| +
|
| + if (FLAG_common_subexpression_elimination ||
|
| + FLAG_loop_invariant_code_motion) {
|
| + flow_graph->ComputeBlockEffects();
|
| + }
|
| +
|
| + if (FLAG_common_subexpression_elimination) {
|
| + if (DominatorBasedCSE::Optimize(flow_graph)) {
|
| + DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
| + // Do another round of CSE to take secondary effects into account:
|
| + // e.g. when eliminating dependent loads (a.x[0] + a.x[0])
|
| + // TODO(fschneider): Change to a one-pass optimization pass.
|
| + DominatorBasedCSE::Optimize(flow_graph);
|
| + DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
| + }
|
| + }
|
| +
|
| + // Run loop-invariant code motion right after load elimination since it
|
| + // depends on the numbering of loads from the previous load-elimination.
|
| + if (FLAG_loop_invariant_code_motion) {
|
| + LICM licm(flow_graph);
|
| + licm.Optimize();
|
| + DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
| + }
|
| + flow_graph->RemoveRedefinitions();
|
| +
|
| + // Optimize (a << b) & c patterns, merge operations.
|
| + // Run after CSE in order to have more opportunity to merge
|
| + // instructions that have same inputs.
|
| + optimizer.TryOptimizePatterns();
|
| + DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
| +
|
| + DeadStoreElimination::Optimize(flow_graph);
|
| +
|
| + if (FLAG_range_analysis) {
|
| + // Propagate types after store-load-forwarding. Some phis may have
|
| + // become smi phis that can be processed by range analysis.
|
| + FlowGraphTypePropagator::Propagate(flow_graph);
|
| + DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
| +
|
| + // We have to perform range analysis after LICM because it
|
| + // optimistically moves CheckSmi through phis into loop preheaders
|
| + // making some phis smi.
|
| + optimizer.InferIntRanges();
|
| + DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
| + }
|
| +
|
| + if (FLAG_constant_propagation) {
|
| + // 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);
|
| + DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
| + }
|
| +
|
| + // Recompute types after code movement was done to ensure correct
|
| + // reaching types for hoisted values.
|
| + FlowGraphTypePropagator::Propagate(flow_graph);
|
| + DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
| +
|
| + // Optimize try-blocks.
|
| + TryCatchAnalyzer::Optimize(flow_graph);
|
| +
|
| + // Detach environments from the instructions that can't deoptimize.
|
| + // Do it before we attempt to perform allocation sinking to minimize
|
| + // amount of materializations it has to perform.
|
| + optimizer.EliminateEnvironments();
|
| +
|
| + DeadCodeElimination::EliminateDeadPhis(flow_graph);
|
| + DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
| +
|
| + if (optimizer.Canonicalize()) {
|
| + optimizer.Canonicalize();
|
| + }
|
| +
|
| + // Attempt to sink allocations of temporary non-escaping objects to
|
| + // the deoptimization path.
|
| + AllocationSinking* sinking = NULL;
|
| + if (FLAG_allocation_sinking &&
|
| + (flow_graph->graph_entry()->SuccessorCount() == 1)) {
|
| + // TODO(fschneider): Support allocation sinking with try-catch.
|
| + sinking = new AllocationSinking(flow_graph);
|
| + sinking->Optimize();
|
| + }
|
| + DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
| +
|
| + // Ensure that all phis inserted by optimization passes have consistent
|
| + // representations.
|
| + optimizer.SelectRepresentations();
|
| +
|
| + if (optimizer.Canonicalize()) {
|
| + // To fully remove redundant boxing (e.g. BoxDouble used only in
|
| + // environments and UnboxDouble instructions) instruction we
|
| + // first need to replace all their uses and then fold them away.
|
| + // For now we just repeat Canonicalize twice to do that.
|
| + // TODO(vegorov): implement a separate representation folding pass.
|
| + optimizer.Canonicalize();
|
| + }
|
| + DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
| +
|
| + if (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.
|
| + sinking->DetachMaterializations();
|
| + }
|
| +
|
| + // Compute and store graph informations (call & instruction counts)
|
| + // to be later used by the inliner.
|
| + FlowGraphInliner::CollectGraphInfo(flow_graph, true);
|
| +
|
| + // Perform register allocation on the SSA graph.
|
| + FlowGraphAllocator allocator(*flow_graph);
|
| + allocator.AllocateRegisters();
|
| + if (reorder_blocks) block_scheduler.ReorderBlocks();
|
| +
|
| + if (FLAG_print_flow_graph || FLAG_print_flow_graph_optimized) {
|
| + FlowGraphPrinter::PrintGraph("After Optimizations", flow_graph);
|
| + }
|
| + }
|
| +
|
| + Assembler assembler(use_far_branches);
|
| + FlowGraphCompiler graph_compiler(&assembler, flow_graph, optimized);
|
| + {
|
| + TimerScope timer(FLAG_compiler_stats,
|
| + &CompilerStats::graphcompiler_timer,
|
| + isolate);
|
| + graph_compiler.CompileGraph();
|
| + }
|
| + {
|
| + TimerScope timer(FLAG_compiler_stats,
|
| + &CompilerStats::codefinalizer_timer,
|
| + isolate);
|
| + const Code& code = Code::Handle(
|
| + Code::FinalizeCode(function, &assembler, optimized));
|
| + code.set_is_optimized(optimized);
|
| + graph_compiler.FinalizePcDescriptors(code);
|
| + graph_compiler.FinalizeDeoptInfo(code);
|
| + graph_compiler.FinalizeStackmaps(code);
|
| + code.set_var_descriptors(LocalVarDescriptors::Handle()); // Empty.
|
| + graph_compiler.FinalizeExceptionHandlers(code);
|
| + graph_compiler.FinalizeStaticCallTargetsTable(code);
|
| +
|
| + if (optimized) {
|
| + if (osr_id == Isolate::kNoDeoptId) {
|
| + CodePatcher::PatchEntry(Code::Handle(function.CurrentCode()));
|
| + if (FLAG_trace_compiler || FLAG_trace_patching) {
|
| + if (FLAG_trace_compiler) {
|
| + OS::Print(" ");
|
| + }
|
| + OS::Print("Patch unoptimized '%s' entry point %#" Px "\n",
|
| + function.ToFullyQualifiedCString(),
|
| + Code::Handle(function.unoptimized_code()).EntryPoint());
|
| + }
|
| + }
|
| + function.AttachCode(code);
|
| +
|
| + for (intptr_t i = 0;
|
| + i < flow_graph->guarded_fields()->length();
|
| + i++) {
|
| + const Field* field = (*flow_graph->guarded_fields())[i];
|
| + field->RegisterDependentCode(code);
|
| + }
|
| + } else { // not optimized.
|
| + if (function.ic_data_array() == Array::null()) {
|
| + function.SaveICDataMap(graph_compiler.deopt_id_to_ic_data());
|
| + }
|
| + function.set_unoptimized_code(code);
|
| + function.AttachCode(code);
|
| + ASSERT(CodePatcher::CodeIsPatchable(code));
|
| + }
|
| + if (parsed_function->HasDeferredPrefixes()) {
|
| + ZoneGrowableArray<const LibraryPrefix*>* prefixes =
|
| + parsed_function->deferred_prefixes();
|
| + for (intptr_t i = 0; i < prefixes->length(); i++) {
|
| + prefixes->At(i)->RegisterDependentCode(code);
|
| + }
|
| + }
|
| + }
|
| + is_compiled = true;
|
| + done = true;
|
| + } else {
|
| + // We bailed out or we encountered an error.
|
| + const Error& error = Error::Handle(
|
| + isolate->object_store()->sticky_error());
|
| +
|
| + if (error.raw() == Object::branch_offset_error().raw()) {
|
| + // Compilation failed due to an out of range branch offset in the
|
| + // assembler. We try again (done = false) with far branches enabled.
|
| + done = false;
|
| + ASSERT(!use_far_branches);
|
| + use_far_branches = true;
|
| + } else {
|
| + // If the error isn't due to an out of range branch offset, we don't
|
| + // try again (done = true), and indicate that we did not finish
|
| + // compiling (is_compiled = false).
|
| + if (FLAG_trace_bailout) {
|
| + OS::Print("%s\n", error.ToErrorCString());
|
| + }
|
| + done = true;
|
| + ASSERT(optimized);
|
| + }
|
| +
|
| + // Clear the error if it was not a real error, but just a bailout.
|
| + if (error.IsLanguageError() &&
|
| + (LanguageError::Cast(error).kind() == Report::kBailout)) {
|
| + isolate->object_store()->clear_sticky_error();
|
| + }
|
| + is_compiled = false;
|
| + }
|
| + // Reset global isolate state.
|
| + isolate->set_deopt_id(prev_deopt_id);
|
| + }
|
| + return is_compiled;
|
| +}
|
| +
|
| +
|
| static void DisassembleCode(const Function& function, bool optimized) {
|
| const char* function_fullname = function.ToFullyQualifiedCString();
|
| OS::Print("Code for %sfunction '%s' {\n",
|
| @@ -904,6 +1265,36 @@ RawError* Compiler::CompileParsedFunction(
|
| }
|
|
|
|
|
| +// TODO(jgruber): Refactor into other functions.
|
| +RawError* Compiler::CompileIrregexpFunction(
|
| + ParsedFunction* parsed_function,
|
| + FlowGraph* flow_graph) {
|
| + Isolate* isolate = Isolate::Current();
|
| + LongJumpScope jump;
|
| + if (setjmp(*jump.Set()) == 0) {
|
| + // Disassemble the generated code if tracing the irregexp engine.
|
| + const bool stored_flag_disassemble = FLAG_disassemble;
|
| + FLAG_disassemble = FLAG_trace_irregexp;
|
| + // Non-optimized code generator.
|
| + CompileIrregexpFunctionHelper(parsed_function, flow_graph,
|
| + false, Isolate::kNoDeoptId);
|
| + if (FLAG_disassemble) {
|
| + DisassembleCode(parsed_function->function(), false);
|
| + }
|
| + FLAG_disassemble = stored_flag_disassemble;
|
| + return Error::null();
|
| + } else {
|
| + Error& error = Error::Handle();
|
| + // We got an error during compilation.
|
| + error = isolate->object_store()->sticky_error();
|
| + isolate->object_store()->clear_sticky_error();
|
| + return error.raw();
|
| + }
|
| + UNREACHABLE();
|
| + return Error::null();
|
| +}
|
| +
|
| +
|
| RawError* Compiler::CompileAllFunctions(const Class& cls) {
|
| Isolate* isolate = Isolate::Current();
|
| Error& error = Error::Handle(isolate);
|
|
|