Index: runtime/vm/precompiler.cc |
diff --git a/runtime/vm/precompiler.cc b/runtime/vm/precompiler.cc |
index b386e48598e7f1970d3405a34bd434b62ea4b360..ebaa9e2b2b2667080ac463bf48b971924c61e986 100644 |
--- a/runtime/vm/precompiler.cc |
+++ b/runtime/vm/precompiler.cc |
@@ -4,17 +4,41 @@ |
#include "vm/precompiler.h" |
+#include "vm/assembler.h" |
+#include "vm/ast_printer.h" |
+#include "vm/branch_optimizer.h" |
#include "vm/cha.h" |
+#include "vm/code_generator.h" |
#include "vm/code_patcher.h" |
#include "vm/compiler.h" |
+#include "vm/constant_propagator.h" |
+#include "vm/dart_entry.h" |
+#include "vm/disassembler.h" |
+#include "vm/exceptions.h" |
+#include "vm/flags.h" |
+#include "vm/flow_graph.h" |
+#include "vm/flow_graph_allocator.h" |
+#include "vm/flow_graph_builder.h" |
+#include "vm/flow_graph_compiler.h" |
+#include "vm/flow_graph_inliner.h" |
+#include "vm/flow_graph_optimizer.h" |
+#include "vm/flow_graph_type_propagator.h" |
#include "vm/hash_table.h" |
+#include "vm/il_printer.h" |
#include "vm/isolate.h" |
#include "vm/log.h" |
#include "vm/longjump.h" |
#include "vm/object.h" |
#include "vm/object_store.h" |
+#include "vm/os.h" |
+#include "vm/parser.h" |
+#include "vm/redundancy_elimination.h" |
+#include "vm/regexp_assembler.h" |
+#include "vm/regexp_parser.h" |
#include "vm/resolver.h" |
#include "vm/symbols.h" |
+#include "vm/tags.h" |
+#include "vm/timer.h" |
namespace dart { |
@@ -29,6 +53,59 @@ DEFINE_FLAG(bool, collect_dynamic_function_names, false, |
" identify unique targets"); |
DEFINE_FLAG(bool, print_unique_targets, false, "Print unique dynaic targets"); |
DEFINE_FLAG(bool, trace_precompiler, false, "Trace precompiler."); |
+DEFINE_FLAG(int, max_speculative_inlining_attempts, 1, |
+ "Max number of attempts with speculative inlining (precompilation only)"); |
+ |
+DECLARE_FLAG(bool, allocation_sinking); |
+DECLARE_FLAG(bool, common_subexpression_elimination); |
+DECLARE_FLAG(bool, constant_propagation); |
+DECLARE_FLAG(bool, disassemble); |
+DECLARE_FLAG(bool, disassemble_optimized); |
+DECLARE_FLAG(bool, loop_invariant_code_motion); |
+DECLARE_FLAG(bool, print_flow_graph); |
+DECLARE_FLAG(bool, print_flow_graph_optimized); |
+DECLARE_FLAG(bool, range_analysis); |
+DECLARE_FLAG(bool, trace_compiler); |
+DECLARE_FLAG(bool, trace_optimizing_compiler); |
+DECLARE_FLAG(bool, trace_bailout); |
+DECLARE_FLAG(bool, use_inlining); |
+DECLARE_FLAG(bool, verify_compiler); |
+DECLARE_FLAG(bool, precompilation); |
+DECLARE_FLAG(bool, huge_method_cutoff_in_code_size); |
+DECLARE_FLAG(bool, load_deferred_eagerly); |
+DECLARE_FLAG(bool, trace_failed_optimization_attempts); |
+DECLARE_FLAG(bool, trace_inlining_intervals); |
+DECLARE_FLAG(bool, trace_irregexp); |
+ |
+#ifdef DART_PRECOMPILER |
+ |
+class PrecompileParsedFunctionHelper : public ValueObject { |
+ public: |
+ PrecompileParsedFunctionHelper(ParsedFunction* parsed_function, |
+ bool optimized) |
+ : parsed_function_(parsed_function), |
+ optimized_(optimized), |
+ thread_(Thread::Current()) { |
+ } |
+ |
+ bool Compile(CompilationPipeline* pipeline); |
+ |
+ private: |
+ ParsedFunction* parsed_function() const { return parsed_function_; } |
+ bool optimized() const { return optimized_; } |
+ Thread* thread() const { return thread_; } |
+ Isolate* isolate() const { return thread_->isolate(); } |
+ |
+ void FinalizeCompilation(Assembler* assembler, |
+ FlowGraphCompiler* graph_compiler, |
+ FlowGraph* flow_graph); |
+ |
+ ParsedFunction* parsed_function_; |
+ const bool optimized_; |
+ Thread* const thread_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(PrecompileParsedFunctionHelper); |
+}; |
static void Jump(const Error& error) { |
@@ -407,7 +484,7 @@ void Precompiler::ProcessFunction(const Function& function) { |
ASSERT(!function.is_abstract()); |
ASSERT(!function.IsRedirectingFactory()); |
- error_ = Compiler::CompileFunction(thread_, function); |
+ error_ = CompileFunction(thread_, function); |
if (!error_.IsNull()) { |
Jump(error_); |
} |
@@ -591,7 +668,11 @@ void Precompiler::AddField(const Field& field) { |
} |
ASSERT(!Dart::IsRunningPrecompiledCode()); |
field.SetStaticValue(Instance::Handle(field.SavedInitialStaticValue())); |
- Compiler::CompileStaticInitializer(field); |
+ const Function& initializer = |
+ Function::Handle(CompileStaticInitializer(field)); |
+ if (!initializer.IsNull()) { |
+ field.SetPrecompiledInitializer(initializer); |
+ } |
} |
const Function& function = |
@@ -602,6 +683,127 @@ void Precompiler::AddField(const Field& field) { |
} |
+RawFunction* Precompiler::CompileStaticInitializer(const Field& field) { |
+ ASSERT(field.is_static()); |
+ if (field.HasPrecompiledInitializer()) { |
+ // TODO(rmacnak): Investigate why this happens for _enum_names. |
+ OS::Print("Warning: Ignoring repeated request for initializer for %s\n", |
+ field.ToCString()); |
+ return Function::null(); |
+ } |
+ Thread* thread = Thread::Current(); |
+ StackZone zone(thread); |
+ |
+ ParsedFunction* parsed_function = Parser::ParseStaticFieldInitializer(field); |
+ |
+ parsed_function->AllocateVariables(); |
+ // Non-optimized code generator. |
+ DartCompilationPipeline pipeline; |
+ PrecompileParsedFunctionHelper helper(parsed_function, |
+ /* optimized = */ false); |
+ helper.Compile(&pipeline); |
+ return parsed_function->function().raw(); |
+} |
+ |
+ |
+RawObject* Precompiler::EvaluateStaticInitializer(const Field& field) { |
+ ASSERT(field.is_static()); |
+ // The VM sets the field's value to transiton_sentinel prior to |
+ // evaluating the initializer value. |
+ ASSERT(field.StaticValue() == Object::transition_sentinel().raw()); |
+ LongJumpScope jump; |
+ if (setjmp(*jump.Set()) == 0) { |
+ // Under precompilation, the initializer may have already been compiled, in |
+ // which case use it. Under lazy compilation or early in precompilation, the |
+ // initializer has not yet been created, so create it now, but don't bother |
+ // remembering it because it won't be used again. |
+ Function& initializer = Function::Handle(); |
+ if (!field.HasPrecompiledInitializer()) { |
+ initializer = CompileStaticInitializer(field); |
+ Code::Handle(initializer.unoptimized_code()).set_var_descriptors( |
+ Object::empty_var_descriptors()); |
+ } else { |
+ initializer ^= field.PrecompiledInitializer(); |
+ } |
+ // Invoke the function to evaluate the expression. |
+ return DartEntry::InvokeFunction(initializer, Object::empty_array()); |
+ } else { |
+ Thread* const thread = Thread::Current(); |
+ StackZone zone(thread); |
+ const Error& error = |
+ Error::Handle(thread->zone(), thread->sticky_error()); |
+ thread->clear_sticky_error(); |
+ return error.raw(); |
+ } |
+ UNREACHABLE(); |
+ return Object::null(); |
+} |
+ |
+ |
+RawObject* Precompiler::ExecuteOnce(SequenceNode* fragment) { |
+ LongJumpScope jump; |
+ if (setjmp(*jump.Set()) == 0) { |
+ Thread* const thread = Thread::Current(); |
+ if (FLAG_trace_compiler) { |
+ THR_Print("compiling expression: "); |
+ AstPrinter::PrintNode(fragment); |
+ } |
+ |
+ // Create a dummy function object for the code generator. |
+ // The function needs to be associated with a named Class: the interface |
+ // Function fits the bill. |
+ const char* kEvalConst = "eval_const"; |
+ const Function& func = Function::ZoneHandle(Function::New( |
+ String::Handle(Symbols::New(kEvalConst)), |
+ RawFunction::kRegularFunction, |
+ true, // static function |
+ false, // not const function |
+ false, // not abstract |
+ false, // not external |
+ false, // not native |
+ Class::Handle(Type::Handle(Type::Function()).type_class()), |
+ fragment->token_pos())); |
+ |
+ func.set_result_type(Object::dynamic_type()); |
+ func.set_num_fixed_parameters(0); |
+ func.SetNumOptionalParameters(0, true); |
+ // Manually generated AST, do not recompile. |
+ func.SetIsOptimizable(false); |
+ func.set_is_debuggable(false); |
+ |
+ // We compile the function here, even though InvokeFunction() below |
+ // would compile func automatically. We are checking fewer invariants |
+ // here. |
+ ParsedFunction* parsed_function = new ParsedFunction(thread, func); |
+ parsed_function->SetNodeSequence(fragment); |
+ fragment->scope()->AddVariable(parsed_function->EnsureExpressionTemp()); |
+ fragment->scope()->AddVariable( |
+ parsed_function->current_context_var()); |
+ parsed_function->AllocateVariables(); |
+ |
+ // Non-optimized code generator. |
+ DartCompilationPipeline pipeline; |
+ PrecompileParsedFunctionHelper helper(parsed_function, |
+ /* optimized = */ false); |
+ helper.Compile(&pipeline); |
+ Code::Handle(func.unoptimized_code()).set_var_descriptors( |
+ Object::empty_var_descriptors()); |
+ |
+ const Object& result = PassiveObject::Handle( |
+ DartEntry::InvokeFunction(func, Object::empty_array())); |
+ return result.raw(); |
+ } else { |
+ Thread* const thread = Thread::Current(); |
+ const Object& result = |
+ PassiveObject::Handle(thread->sticky_error()); |
+ thread->clear_sticky_error(); |
+ return result.raw(); |
+ } |
+ UNREACHABLE(); |
+ return Object::null(); |
+} |
+ |
+ |
void Precompiler::AddFunction(const Function& function) { |
if (enqueued_functions_.Lookup(&function) != NULL) return; |
@@ -1349,4 +1551,649 @@ void Precompiler::ResetPrecompilerState() { |
} |
} |
+ |
+void PrecompileParsedFunctionHelper::FinalizeCompilation( |
+ Assembler* assembler, |
+ FlowGraphCompiler* graph_compiler, |
+ FlowGraph* flow_graph) { |
+ const Function& function = parsed_function()->function(); |
+ Zone* const zone = thread()->zone(); |
+ |
+ CSTAT_TIMER_SCOPE(thread(), codefinalizer_timer); |
+ // CreateDeoptInfo uses the object pool and needs to be done before |
+ // FinalizeCode. |
+ const Array& deopt_info_array = |
+ Array::Handle(zone, graph_compiler->CreateDeoptInfo(assembler)); |
+ INC_STAT(thread(), total_code_size, |
+ deopt_info_array.Length() * sizeof(uword)); |
+ // Allocates instruction object. Since this occurs only at safepoint, |
+ // there can be no concurrent access to the instruction page. |
+ const Code& code = Code::Handle( |
+ Code::FinalizeCode(function, assembler, optimized())); |
+ code.set_is_optimized(optimized()); |
+ code.set_owner(function); |
+ if (!function.IsOptimizable()) { |
+ // A function with huge unoptimized code can become non-optimizable |
+ // after generating unoptimized code. |
+ function.set_usage_counter(INT_MIN); |
+ } |
+ |
+ const Array& intervals = graph_compiler->inlined_code_intervals(); |
+ INC_STAT(thread(), total_code_size, |
+ intervals.Length() * sizeof(uword)); |
+ code.SetInlinedIntervals(intervals); |
+ |
+ const Array& inlined_id_array = |
+ Array::Handle(zone, graph_compiler->InliningIdToFunction()); |
+ INC_STAT(thread(), total_code_size, |
+ inlined_id_array.Length() * sizeof(uword)); |
+ code.SetInlinedIdToFunction(inlined_id_array); |
+ |
+ const Array& caller_inlining_id_map_array = |
+ Array::Handle(zone, graph_compiler->CallerInliningIdMap()); |
+ INC_STAT(thread(), total_code_size, |
+ caller_inlining_id_map_array.Length() * sizeof(uword)); |
+ code.SetInlinedCallerIdMap(caller_inlining_id_map_array); |
+ |
+ graph_compiler->FinalizePcDescriptors(code); |
+ code.set_deopt_info_array(deopt_info_array); |
+ |
+ graph_compiler->FinalizeStackmaps(code); |
+ graph_compiler->FinalizeVarDescriptors(code); |
+ graph_compiler->FinalizeExceptionHandlers(code); |
+ graph_compiler->FinalizeStaticCallTargetsTable(code); |
+ |
+ if (optimized()) { |
+ // Installs code while at safepoint. |
+ ASSERT(thread()->IsMutatorThread()); |
+ function.InstallOptimizedCode(code, /* is_osr = */ false); |
+ } else { // not optimized. |
+ function.set_unoptimized_code(code); |
+ function.AttachCode(code); |
+ } |
+ ASSERT(!parsed_function()->HasDeferredPrefixes()); |
+ ASSERT(FLAG_load_deferred_eagerly); |
+} |
+ |
+ |
+// Return false if bailed out. |
+// If optimized_result_code is not NULL then it is caller's responsibility |
+// to install code. |
+bool PrecompileParsedFunctionHelper::Compile(CompilationPipeline* pipeline) { |
+ ASSERT(FLAG_precompilation); |
+ const Function& function = parsed_function()->function(); |
+ if (optimized() && !function.IsOptimizable()) { |
+ return false; |
+ } |
+ bool is_compiled = false; |
+ Zone* const zone = thread()->zone(); |
+ TimelineStream* compiler_timeline = isolate()->GetCompilerStream(); |
+ CSTAT_TIMER_SCOPE(thread(), codegen_timer); |
+ HANDLESCOPE(thread()); |
+ |
+ // 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; |
+ volatile bool use_speculative_inlining = |
+ FLAG_max_speculative_inlining_attempts > 0; |
+ GrowableArray<intptr_t> inlining_black_list; |
+ |
+ while (!done) { |
+ const intptr_t prev_deopt_id = thread()->deopt_id(); |
+ thread()->set_deopt_id(0); |
+ LongJumpScope jump; |
+ const intptr_t val = setjmp(*jump.Set()); |
+ if (val == 0) { |
+ FlowGraph* flow_graph = NULL; |
+ |
+ // Class hierarchy analysis is registered with the isolate in the |
+ // constructor and unregisters itself upon destruction. |
+ CHA cha(thread()); |
+ |
+ // TimerScope needs an isolate to be properly terminated in case of a |
+ // LongJump. |
+ { |
+ CSTAT_TIMER_SCOPE(thread(), graphbuilder_timer); |
+ ZoneGrowableArray<const ICData*>* ic_data_array = |
+ new(zone) ZoneGrowableArray<const ICData*>(); |
+ TimelineDurationScope tds(thread(), |
+ compiler_timeline, |
+ "BuildFlowGraph"); |
+ flow_graph = pipeline->BuildFlowGraph(zone, |
+ parsed_function(), |
+ *ic_data_array, |
+ Compiler::kNoOSRDeoptId); |
+ } |
+ |
+ const bool print_flow_graph = |
+ (FLAG_print_flow_graph || |
+ (optimized() && FLAG_print_flow_graph_optimized)) && |
+ FlowGraphPrinter::ShouldPrint(function); |
+ |
+ if (print_flow_graph) { |
+ FlowGraphPrinter::PrintGraph("Before Optimizations", flow_graph); |
+ } |
+ |
+ if (optimized()) { |
+ TimelineDurationScope tds(thread(), |
+ compiler_timeline, |
+ "ComputeSSA"); |
+ CSTAT_TIMER_SCOPE(thread(), ssa_timer); |
+ // Transform to SSA (virtual register 0 and no inlining arguments). |
+ flow_graph->ComputeSSA(0, NULL); |
+ DEBUG_ASSERT(flow_graph->VerifyUseLists()); |
+ if (print_flow_graph) { |
+ FlowGraphPrinter::PrintGraph("After SSA", flow_graph); |
+ } |
+ } |
+ |
+ // Maps inline_id_to_function[inline_id] -> function. Top scope |
+ // function has inline_id 0. The map is populated by the inliner. |
+ GrowableArray<const Function*> inline_id_to_function; |
+ // For a given inlining-id(index) specifies the caller's inlining-id. |
+ GrowableArray<intptr_t> caller_inline_id; |
+ // 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()) { |
+ TimelineDurationScope tds(thread(), |
+ compiler_timeline, |
+ "OptimizationPasses"); |
+ inline_id_to_function.Add(&function); |
+ // Top scope function has no caller (-1). |
+ caller_inline_id.Add(-1); |
+ CSTAT_TIMER_SCOPE(thread(), graphoptimizer_timer); |
+ |
+ FlowGraphOptimizer optimizer(flow_graph, |
+ use_speculative_inlining, |
+ &inlining_black_list); |
+ optimizer.PopulateWithICData(); |
+ |
+ optimizer.ApplyClassIds(); |
+ DEBUG_ASSERT(flow_graph->VerifyUseLists()); |
+ |
+ FlowGraphTypePropagator::Propagate(flow_graph); |
+ DEBUG_ASSERT(flow_graph->VerifyUseLists()); |
+ |
+ 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()); |
+ |
+ FlowGraphInliner::SetInliningId(flow_graph, 0); |
+ |
+ // Inlining (mutates the flow graph) |
+ if (FLAG_use_inlining) { |
+ TimelineDurationScope tds2(thread(), |
+ compiler_timeline, |
+ "Inlining"); |
+ CSTAT_TIMER_SCOPE(thread(), 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, |
+ &inline_id_to_function, |
+ &caller_inline_id, |
+ use_speculative_inlining, |
+ &inlining_black_list); |
+ 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()); |
+ |
+ { |
+ TimelineDurationScope tds2(thread(), |
+ compiler_timeline, |
+ "ApplyClassIds"); |
+ // 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()); |
+ |
+ { |
+ TimelineDurationScope tds2(thread(), |
+ compiler_timeline, |
+ "BranchSimplifier"); |
+ BranchSimplifier::Simplify(flow_graph); |
+ DEBUG_ASSERT(flow_graph->VerifyUseLists()); |
+ |
+ IfConverter::Simplify(flow_graph); |
+ DEBUG_ASSERT(flow_graph->VerifyUseLists()); |
+ } |
+ |
+ if (FLAG_constant_propagation) { |
+ TimelineDurationScope tds2(thread(), |
+ compiler_timeline, |
+ "ConstantPropagation"); |
+ 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()); |
+ } |
+ |
+ // Optimistically convert loop phis that have a single non-smi input |
+ // coming from the loop pre-header into smi-phis. |
+ if (FLAG_loop_invariant_code_motion) { |
+ LICM licm(flow_graph); |
+ licm.OptimisticallySpecializeSmiPhis(); |
+ 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()); |
+ |
+ { |
+ TimelineDurationScope tds2(thread(), |
+ compiler_timeline, |
+ "SelectRepresentations"); |
+ // Where beneficial convert Smi operations into Int32 operations. |
+ // Only meanigful for 32bit platforms right now. |
+ optimizer.WidenSmiToInt32(); |
+ |
+ // 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()); |
+ } |
+ |
+ { |
+ TimelineDurationScope tds2(thread(), |
+ compiler_timeline, |
+ "CommonSubexpressionElinination"); |
+ 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()); |
+ optimizer.Canonicalize(); |
+ // 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. |
+ if (DominatorBasedCSE::Optimize(flow_graph)) { |
+ optimizer.Canonicalize(); |
+ } |
+ 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()); |
+ |
+ { |
+ TimelineDurationScope tds2(thread(), |
+ compiler_timeline, |
+ "DeadStoreElimination"); |
+ DeadStoreElimination::Optimize(flow_graph); |
+ } |
+ |
+ if (FLAG_range_analysis) { |
+ TimelineDurationScope tds2(thread(), |
+ compiler_timeline, |
+ "RangeAnalysis"); |
+ // 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) { |
+ TimelineDurationScope tds2(thread(), |
+ compiler_timeline, |
+ "ConstantPropagator::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); |
+ 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()); |
+ |
+ { |
+ TimelineDurationScope tds2(thread(), |
+ compiler_timeline, |
+ "TryCatchAnalyzer::Optimize"); |
+ // 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(); |
+ |
+ { |
+ TimelineDurationScope tds2(thread(), |
+ compiler_timeline, |
+ "EliminateDeadPhis"); |
+ 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)) { |
+ TimelineDurationScope tds2(thread(), |
+ compiler_timeline, |
+ "AllocationSinking::Optimize"); |
+ // TODO(fschneider): Support allocation sinking with try-catch. |
+ sinking = new AllocationSinking(flow_graph); |
+ sinking->Optimize(); |
+ } |
+ DEBUG_ASSERT(flow_graph->VerifyUseLists()); |
+ |
+ DeadCodeElimination::EliminateDeadPhis(flow_graph); |
+ DEBUG_ASSERT(flow_graph->VerifyUseLists()); |
+ |
+ FlowGraphTypePropagator::Propagate(flow_graph); |
+ DEBUG_ASSERT(flow_graph->VerifyUseLists()); |
+ |
+ { |
+ TimelineDurationScope tds2(thread(), |
+ compiler_timeline, |
+ "SelectRepresentations"); |
+ // 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) { |
+ TimelineDurationScope tds2( |
+ thread(), |
+ compiler_timeline, |
+ "AllocationSinking::DetachMaterializations"); |
+ // 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); |
+ |
+ { |
+ TimelineDurationScope tds2(thread(), |
+ compiler_timeline, |
+ "AllocateRegisters"); |
+ // Perform register allocation on the SSA graph. |
+ FlowGraphAllocator allocator(*flow_graph); |
+ allocator.AllocateRegisters(); |
+ } |
+ |
+ if (print_flow_graph) { |
+ FlowGraphPrinter::PrintGraph("After Optimizations", flow_graph); |
+ } |
+ } |
+ |
+ ASSERT(inline_id_to_function.length() == caller_inline_id.length()); |
+ Assembler assembler(use_far_branches); |
+ FlowGraphCompiler graph_compiler(&assembler, flow_graph, |
+ *parsed_function(), optimized(), |
+ inline_id_to_function, |
+ caller_inline_id); |
+ { |
+ CSTAT_TIMER_SCOPE(thread(), graphcompiler_timer); |
+ TimelineDurationScope tds(thread(), |
+ compiler_timeline, |
+ "CompileGraph"); |
+ graph_compiler.CompileGraph(); |
+ pipeline->FinalizeCompilation(); |
+ } |
+ { |
+ TimelineDurationScope tds(thread(), |
+ compiler_timeline, |
+ "FinalizeCompilation"); |
+ ASSERT(thread()->IsMutatorThread()); |
+ FinalizeCompilation(&assembler, &graph_compiler, flow_graph); |
+ } |
+ // Mark that this isolate now has compiled code. |
+ isolate()->set_has_compiled_code(true); |
+ // Exit the loop and the function with the correct result value. |
+ is_compiled = true; |
+ done = true; |
+ } else { |
+ // We bailed out or we encountered an error. |
+ const Error& error = Error::Handle(thread()->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 (error.raw() == Object::speculative_inlining_error().raw()) { |
+ // The return value of setjmp is the deopt id of the check instruction |
+ // that caused the bailout. |
+ done = false; |
+#if defined(DEBUG) |
+ ASSERT(use_speculative_inlining); |
+ for (intptr_t i = 0; i < inlining_black_list.length(); ++i) { |
+ ASSERT(inlining_black_list[i] != val); |
+ } |
+#endif |
+ inlining_black_list.Add(val); |
+ const intptr_t max_attempts = FLAG_max_speculative_inlining_attempts; |
+ if (inlining_black_list.length() >= max_attempts) { |
+ use_speculative_inlining = false; |
+ if (FLAG_trace_compiler || FLAG_trace_optimizing_compiler) { |
+ THR_Print("Disabled speculative inlining after %" Pd " attempts.\n", |
+ inlining_black_list.length()); |
+ } |
+ } |
+ } 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) { |
+ THR_Print("%s\n", error.ToErrorCString()); |
+ } |
+ done = true; |
+ } |
+ |
+ // Clear the error if it was not a real error, but just a bailout. |
+ if (error.IsLanguageError() && |
+ (LanguageError::Cast(error).kind() == Report::kBailout)) { |
+ thread()->clear_sticky_error(); |
+ } |
+ is_compiled = false; |
+ } |
+ // Reset global isolate state. |
+ thread()->set_deopt_id(prev_deopt_id); |
+ } |
+ return is_compiled; |
+} |
+ |
+ |
+static RawError* PrecompileFunctionHelper(CompilationPipeline* pipeline, |
+ const Function& function, |
+ bool optimized) { |
+ // Check that we optimize, except if the function is not optimizable. |
+ ASSERT(FLAG_precompilation); |
+ ASSERT(!function.IsOptimizable() || optimized); |
+ ASSERT(!function.HasCode()); |
+ LongJumpScope jump; |
+ if (setjmp(*jump.Set()) == 0) { |
+ Thread* const thread = Thread::Current(); |
+ StackZone stack_zone(thread); |
+ Zone* const zone = stack_zone.GetZone(); |
+ const bool trace_compiler = |
+ FLAG_trace_compiler || |
+ (FLAG_trace_optimizing_compiler && optimized); |
+ Timer per_compile_timer(trace_compiler, "Compilation time"); |
+ per_compile_timer.Start(); |
+ |
+ ParsedFunction* parsed_function = new(zone) ParsedFunction( |
+ thread, Function::ZoneHandle(zone, function.raw())); |
+ if (trace_compiler) { |
+ THR_Print( |
+ "Precompiling %sfunction: '%s' @ token %" Pd ", size %" Pd "\n", |
+ (optimized ? "optimized " : ""), |
+ function.ToFullyQualifiedCString(), |
+ function.token_pos().Pos(), |
+ (function.end_token_pos().Pos() - function.token_pos().Pos())); |
+ } |
+ INC_STAT(thread, num_functions_compiled, 1); |
+ if (optimized) { |
+ INC_STAT(thread, num_functions_optimized, 1); |
+ } |
+ { |
+ HANDLESCOPE(thread); |
+ const int64_t num_tokens_before = STAT_VALUE(thread, num_tokens_consumed); |
+ pipeline->ParseFunction(parsed_function); |
+ const int64_t num_tokens_after = STAT_VALUE(thread, num_tokens_consumed); |
+ INC_STAT(thread, |
+ num_func_tokens_compiled, |
+ num_tokens_after - num_tokens_before); |
+ } |
+ |
+ PrecompileParsedFunctionHelper helper(parsed_function, optimized); |
+ const bool success = helper.Compile(pipeline); |
+ if (!success) { |
+ // Encountered error. |
+ Error& error = Error::Handle(); |
+ // We got an error during compilation. |
+ error = thread->sticky_error(); |
+ thread->clear_sticky_error(); |
+ ASSERT(error.IsLanguageError() && |
+ LanguageError::Cast(error).kind() != Report::kBailout); |
+ return error.raw(); |
+ } |
+ |
+ per_compile_timer.Stop(); |
+ |
+ if (trace_compiler && success) { |
+ THR_Print("--> '%s' entry: %#" Px " size: %" Pd " time: %" Pd64 " us\n", |
+ function.ToFullyQualifiedCString(), |
+ Code::Handle(function.CurrentCode()).EntryPoint(), |
+ Code::Handle(function.CurrentCode()).Size(), |
+ per_compile_timer.TotalElapsedTime()); |
+ } |
+ |
+ if (FLAG_disassemble && FlowGraphPrinter::ShouldPrint(function)) { |
+ Disassembler::DisassembleCode(function, optimized); |
+ } else if (FLAG_disassemble_optimized && |
+ optimized && |
+ FlowGraphPrinter::ShouldPrint(function)) { |
+ // TODO(fschneider): Print unoptimized code along with the optimized code. |
+ THR_Print("*** BEGIN CODE\n"); |
+ Disassembler::DisassembleCode(function, true); |
+ THR_Print("*** END CODE\n"); |
+ } |
+ return Error::null(); |
+ } else { |
+ Thread* const thread = Thread::Current(); |
+ StackZone stack_zone(thread); |
+ Error& error = Error::Handle(); |
+ // We got an error during compilation. |
+ error = thread->sticky_error(); |
+ thread->clear_sticky_error(); |
+ // Precompilation may encounter compile-time errors. |
+ // Do not attempt to optimize functions that can cause errors. |
+ function.set_is_optimizable(false); |
+ return error.raw(); |
+ } |
+ UNREACHABLE(); |
+ return Error::null(); |
+} |
+ |
+ |
+RawError* Precompiler::CompileFunction(Thread* thread, |
+ const Function& function) { |
+ VMTagScope tagScope(thread, VMTag::kCompileUnoptimizedTagId); |
+ TIMELINE_FUNCTION_COMPILATION_DURATION(thread, "Function", function); |
+ |
+ CompilationPipeline* pipeline = |
+ CompilationPipeline::New(thread->zone(), function); |
+ |
+ ASSERT(FLAG_precompilation); |
+ const bool optimized = function.IsOptimizable(); // False for natives. |
+ return PrecompileFunctionHelper(pipeline, function, optimized); |
+} |
+ |
+#endif // DART_PRECOMPILER |
+ |
} // namespace dart |