ba0f59d1bc
The optimizing compiler currently recognizes a certain frequent native methods like array length or string length and provides an inlined implementation. Inlining does currently not work for polymorphic call sites of these methods. This CL enables also polymorphic inlining in the case of .length getters for arrays and strings. 1. The method is recognized at flow graph build time. The builder creates the body of the method for both compilers (non-optimizing and optimizing). Native methods that are not recognized, are translated as before using a NativeCall IL instruction. 2. The flow graph inliner handles recognized methods in the same manner as normal methods. Until now intrinsic and recognized method could not be inlined. This CL enables it. 3. There is no need for an intrinsic assembly implementation because recognized methods have an IL implementation that does not call into the C++ runtime. I left the intrinsics in for now, but they can be removed if there is not noticable performance benefit anymore. 4. The inlining heuristics are tweaked in a way that enables more aggressive inlining of recognized methods: +1 level of inlining depths, call sites of recognized methods are not counted in the inlining heuristic. R=kmillikin@google.com, srdjan@google.com Review URL: https://codereview.chromium.org//22839003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@26429 260f80e4-7a28-3924-810f-c04153c831b5
929 lines
35 KiB
C++
929 lines
35 KiB
C++
// Copyright (c) 2012, 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.h"
|
|
|
|
#include "vm/assembler.h"
|
|
|
|
#include "vm/ast_printer.h"
|
|
#include "vm/code_generator.h"
|
|
#include "vm/code_patcher.h"
|
|
#include "vm/dart_entry.h"
|
|
#include "vm/debugger.h"
|
|
#include "vm/deopt_instructions.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/il_printer.h"
|
|
#include "vm/longjump.h"
|
|
#include "vm/object.h"
|
|
#include "vm/object_store.h"
|
|
#include "vm/os.h"
|
|
#include "vm/parser.h"
|
|
#include "vm/scanner.h"
|
|
#include "vm/symbols.h"
|
|
#include "vm/timer.h"
|
|
|
|
namespace dart {
|
|
|
|
DEFINE_FLAG(bool, disassemble, false, "Disassemble dart code.");
|
|
DEFINE_FLAG(bool, disassemble_optimized, false, "Disassemble optimized code.");
|
|
DEFINE_FLAG(bool, trace_bailout, false, "Print bailout from ssa compiler.");
|
|
DEFINE_FLAG(bool, trace_compiler, false, "Trace compiler operations.");
|
|
DEFINE_FLAG(bool, constant_propagation, true,
|
|
"Do conditional constant propagation/unreachable code elimination.");
|
|
DEFINE_FLAG(bool, common_subexpression_elimination, true,
|
|
"Do common subexpression elimination.");
|
|
DEFINE_FLAG(bool, loop_invariant_code_motion, true,
|
|
"Do loop invariant code motion.");
|
|
DEFINE_FLAG(bool, propagate_types, true, "Do static type propagation.");
|
|
DEFINE_FLAG(bool, allocation_sinking, true,
|
|
"attempt to sink temporary allocations to side exits");
|
|
DEFINE_FLAG(int, deoptimization_counter_threshold, 16,
|
|
"How many times we allow deoptimization before we disallow optimization.");
|
|
DEFINE_FLAG(int, deoptimization_counter_licm_threshold, 8,
|
|
"How many times we allow deoptimization before we disable LICM.");
|
|
DEFINE_FLAG(bool, use_inlining, true, "Enable call-site inlining");
|
|
DEFINE_FLAG(bool, range_analysis, true, "Enable range analysis");
|
|
DEFINE_FLAG(bool, verify_compiler, false,
|
|
"Enable compiler verification assertions");
|
|
DECLARE_FLAG(bool, print_flow_graph);
|
|
DECLARE_FLAG(bool, print_flow_graph_optimized);
|
|
DECLARE_FLAG(bool, trace_failed_optimization_attempts);
|
|
|
|
// Compile a function. Should call only if the function has not been compiled.
|
|
// Arg0: function object.
|
|
DEFINE_RUNTIME_ENTRY(CompileFunction, 1) {
|
|
ASSERT(arguments.ArgCount() == kCompileFunctionRuntimeEntry.argument_count());
|
|
const Function& function = Function::CheckedHandle(arguments.ArgAt(0));
|
|
ASSERT(!function.HasCode());
|
|
const Error& error = Error::Handle(Compiler::CompileFunction(function));
|
|
if (!error.IsNull()) {
|
|
Exceptions::PropagateError(error);
|
|
}
|
|
}
|
|
|
|
|
|
RawError* Compiler::Compile(const Library& library, const Script& script) {
|
|
Isolate* isolate = Isolate::Current();
|
|
StackZone zone(isolate);
|
|
LongJump* base = isolate->long_jump_base();
|
|
LongJump jump;
|
|
isolate->set_long_jump_base(&jump);
|
|
if (setjmp(*jump.Set()) == 0) {
|
|
if (FLAG_trace_compiler) {
|
|
const String& script_url = String::Handle(script.url());
|
|
// TODO(iposva): Extract script kind.
|
|
OS::Print("Compiling %s '%s'\n", "", script_url.ToCString());
|
|
}
|
|
const String& library_key = String::Handle(library.private_key());
|
|
script.Tokenize(library_key);
|
|
Parser::ParseCompilationUnit(library, script);
|
|
isolate->set_long_jump_base(base);
|
|
return Error::null();
|
|
} else {
|
|
Error& error = Error::Handle();
|
|
error = isolate->object_store()->sticky_error();
|
|
isolate->object_store()->clear_sticky_error();
|
|
isolate->set_long_jump_base(base);
|
|
return error.raw();
|
|
}
|
|
UNREACHABLE();
|
|
return Error::null();
|
|
}
|
|
|
|
|
|
static void AddRelatedClassesToList(const Class& cls,
|
|
const GrowableObjectArray& parse_list,
|
|
const GrowableObjectArray& patch_list) {
|
|
Isolate* isolate = Isolate::Current();
|
|
Class& parse_class = Class::Handle(isolate);
|
|
Type& interface_type = Type::Handle(isolate);
|
|
Array& interfaces = Array::Handle(isolate);
|
|
|
|
// Add all the interfaces implemented by the class that have not been
|
|
// already parsed to the parse list. Mark the interface as parsed so that
|
|
// we don't recursively add it back into the list.
|
|
interfaces ^= cls.interfaces();
|
|
for (intptr_t i = 0; i < interfaces.Length(); i++) {
|
|
interface_type ^= interfaces.At(i);
|
|
parse_class ^= interface_type.type_class();
|
|
if (!parse_class.is_finalized() && !parse_class.is_marked_for_parsing()) {
|
|
parse_list.Add(parse_class);
|
|
parse_class.set_is_marked_for_parsing();
|
|
}
|
|
}
|
|
|
|
// Walk up the super_class chain and add these classes to the list if they
|
|
// have not been already parsed to the parse list. Mark the class as parsed
|
|
// so that we don't recursively add it back into the list.
|
|
parse_class ^= cls.SuperClass();
|
|
while (!parse_class.IsNull()) {
|
|
if (!parse_class.is_finalized() && !parse_class.is_marked_for_parsing()) {
|
|
parse_list.Add(parse_class);
|
|
parse_class.set_is_marked_for_parsing();
|
|
}
|
|
parse_class ^= parse_class.SuperClass();
|
|
}
|
|
|
|
// Add patch classes if they exist to the parse list if they have not already
|
|
// been parsed and patched. Mark the class as parsed so that we don't
|
|
// recursively add it back into the list.
|
|
parse_class ^= cls.patch_class();
|
|
if (!parse_class.IsNull()) {
|
|
if (!parse_class.is_finalized() && !parse_class.is_marked_for_parsing()) {
|
|
patch_list.Add(parse_class);
|
|
parse_class.set_is_marked_for_parsing();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
RawError* Compiler::CompileClass(const Class& cls) {
|
|
// If class is a top level class it is already parsed.
|
|
if (cls.IsTopLevel()) {
|
|
return Error::null();
|
|
}
|
|
// If the class is already marked for parsing return immediately.
|
|
if (cls.is_marked_for_parsing()) {
|
|
return Error::null();
|
|
}
|
|
// Parse the class and all the interfaces it implements and super classes.
|
|
Isolate* isolate = Isolate::Current();
|
|
StackZone zone(isolate);
|
|
LongJump* base = isolate->long_jump_base();
|
|
LongJump jump;
|
|
isolate->set_long_jump_base(&jump);
|
|
if (setjmp(*jump.Set()) == 0) {
|
|
if (FLAG_trace_compiler) {
|
|
OS::Print("Compiling Class %s '%s'\n", "", cls.ToCString());
|
|
}
|
|
|
|
Class& parse_class = Class::Handle();
|
|
const GrowableObjectArray& parse_list =
|
|
GrowableObjectArray::Handle(GrowableObjectArray::New(4));
|
|
const GrowableObjectArray& patch_list =
|
|
GrowableObjectArray::Handle(GrowableObjectArray::New(4));
|
|
|
|
// Add the primary class which needs to be parsed to the parse list.
|
|
// Mark the class as parsed so that we don't recursively add the same
|
|
// class back into the list.
|
|
parse_list.Add(cls);
|
|
cls.set_is_marked_for_parsing();
|
|
|
|
// Add all super classes, interface classes and patch class if one
|
|
// exists to the corresponding lists.
|
|
// NOTE: The parse_list array keeps growing as more classes are added
|
|
// to it by AddRelatedClassesToList. It is not OK to hoist
|
|
// parse_list.Length() into a local variable and iterate using the local
|
|
// variable.
|
|
for (intptr_t i = 0; i < parse_list.Length(); i++) {
|
|
parse_class ^= parse_list.At(i);
|
|
AddRelatedClassesToList(parse_class, parse_list, patch_list);
|
|
}
|
|
|
|
// Parse all the classes that have been added above.
|
|
for (intptr_t i = (parse_list.Length() - 1); i >=0 ; i--) {
|
|
parse_class ^= parse_list.At(i);
|
|
ASSERT(!parse_class.IsNull());
|
|
Parser::ParseClass(parse_class);
|
|
}
|
|
|
|
// Parse all the patch classes that have been added above.
|
|
for (intptr_t i = 0; i < patch_list.Length(); i++) {
|
|
parse_class ^= patch_list.At(i);
|
|
ASSERT(!parse_class.IsNull());
|
|
Parser::ParseClass(parse_class);
|
|
}
|
|
|
|
// Finalize these classes.
|
|
for (intptr_t i = (parse_list.Length() - 1); i >=0 ; i--) {
|
|
parse_class ^= parse_list.At(i);
|
|
ASSERT(!parse_class.IsNull());
|
|
ClassFinalizer::FinalizeClass(parse_class);
|
|
parse_class.reset_is_marked_for_parsing();
|
|
}
|
|
|
|
isolate->set_long_jump_base(base);
|
|
return Error::null();
|
|
} else {
|
|
Error& error = Error::Handle();
|
|
error = isolate->object_store()->sticky_error();
|
|
isolate->object_store()->clear_sticky_error();
|
|
isolate->set_long_jump_base(base);
|
|
return error.raw();
|
|
}
|
|
UNREACHABLE();
|
|
return Error::null();
|
|
}
|
|
|
|
|
|
static void InstallUnoptimizedCode(const Function& function) {
|
|
// Disable optimized code.
|
|
ASSERT(function.HasOptimizedCode());
|
|
if (FLAG_trace_compiler) {
|
|
OS::Print("--> patching entry %#" Px "\n",
|
|
Code::Handle(function.CurrentCode()).EntryPoint());
|
|
}
|
|
function.SwitchToUnoptimizedCode();
|
|
if (FLAG_trace_compiler) {
|
|
OS::Print("--> restoring entry at %#" Px "\n",
|
|
Code::Handle(function.unoptimized_code()).EntryPoint());
|
|
}
|
|
}
|
|
|
|
|
|
// Return false if bailed out.
|
|
static bool CompileParsedFunctionHelper(ParsedFunction* parsed_function,
|
|
bool optimized,
|
|
intptr_t osr_id) {
|
|
const Function& function = parsed_function->function();
|
|
if (optimized && !function.is_optimizable()) {
|
|
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();
|
|
isolate->set_deopt_id(0);
|
|
LongJump* old_base = isolate->long_jump_base();
|
|
LongJump bailout_jump;
|
|
isolate->set_long_jump_base(&bailout_jump);
|
|
if (setjmp(*bailout_jump.Set()) == 0) {
|
|
FlowGraph* flow_graph = NULL;
|
|
// TimerScope needs an isolate to be properly terminated in case of a
|
|
// LongJump.
|
|
{
|
|
TimerScope timer(FLAG_compiler_stats,
|
|
&CompilerStats::graphbuilder_timer,
|
|
isolate);
|
|
Array& ic_data_array = Array::Handle();
|
|
if (optimized) {
|
|
ASSERT(function.HasCode());
|
|
// Extract type feedback before the graph is built, as the graph
|
|
// builder uses it to attach it to nodes.
|
|
ASSERT(function.deoptimization_counter() <
|
|
FLAG_deoptimization_counter_threshold);
|
|
const Code& unoptimized_code =
|
|
Code::Handle(function.unoptimized_code());
|
|
ic_data_array = unoptimized_code.ExtractTypeFeedbackArray();
|
|
}
|
|
|
|
// Build the flow graph.
|
|
FlowGraphBuilder builder(parsed_function,
|
|
ic_data_array,
|
|
NULL, // NULL = not inlining.
|
|
osr_id);
|
|
flow_graph = builder.BuildGraph();
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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.
|
|
GrowableArray<const Field*> guarded_fields(10);
|
|
if (optimized) {
|
|
TimerScope timer(FLAG_compiler_stats,
|
|
&CompilerStats::graphoptimizer_timer,
|
|
isolate);
|
|
|
|
FlowGraphOptimizer optimizer(flow_graph, &guarded_fields);
|
|
optimizer.ApplyICData();
|
|
DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
|
|
|
// Optimize (a << b) & c patterns. Must occur before
|
|
// 'SelectRepresentations' which inserts conversion nodes.
|
|
// TODO(srdjan): Moved before inlining until environment use list can
|
|
// be used to detect when shift-left is outside the scope of bit-and.
|
|
optimizer.TryOptimizeLeftShiftWithBitAndPattern();
|
|
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.
|
|
if (FLAG_propagate_types) {
|
|
FlowGraphTypePropagator propagator(flow_graph);
|
|
propagator.Propagate();
|
|
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, &guarded_fields);
|
|
inliner.Inline();
|
|
// Use lists are maintained and validated by the inliner.
|
|
DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
|
}
|
|
|
|
// Propagate types and eliminate more type tests.
|
|
if (FLAG_propagate_types) {
|
|
FlowGraphTypePropagator propagator(flow_graph);
|
|
propagator.Propagate();
|
|
DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
|
}
|
|
|
|
// Use propagated class-ids to optimize further.
|
|
optimizer.ApplyClassIds();
|
|
DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
|
|
|
// Do optimizations that depend on the propagated type information.
|
|
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.
|
|
if (FLAG_propagate_types) {
|
|
// Recompute types after constant propagation to infer more precise
|
|
// types for uses that were previously reached by now eliminated phis.
|
|
FlowGraphTypePropagator propagator(flow_graph);
|
|
propagator.Propagate();
|
|
DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
|
}
|
|
|
|
// Unbox doubles. Performed after constant propagation to minimize
|
|
// interference from phis merging double values and tagged
|
|
// values comming 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());
|
|
}
|
|
}
|
|
if (FLAG_loop_invariant_code_motion &&
|
|
(function.deoptimization_counter() <
|
|
FLAG_deoptimization_counter_licm_threshold)) {
|
|
LICM licm(flow_graph);
|
|
licm.Optimize();
|
|
DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
|
}
|
|
flow_graph->RemoveRedefinitions();
|
|
|
|
if (FLAG_range_analysis) {
|
|
if (FLAG_propagate_types) {
|
|
// Propagate types after store-load-forwarding. Some phis may have
|
|
// become smi phis that can be processed by range analysis.
|
|
FlowGraphTypePropagator propagator(flow_graph);
|
|
propagator.Propagate();
|
|
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.InferSmiRanges();
|
|
DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
|
}
|
|
|
|
if (FLAG_constant_propagation) {
|
|
// Constant propagation can use information from range analysis to
|
|
// find unreachable branch targets.
|
|
ConstantPropagator::OptimizeBranches(flow_graph);
|
|
DEBUG_ASSERT(flow_graph->VerifyUseLists());
|
|
}
|
|
|
|
if (FLAG_propagate_types) {
|
|
// Recompute types after code movement was done to ensure correct
|
|
// reaching types for hoisted values.
|
|
FlowGraphTypePropagator propagator(flow_graph);
|
|
propagator.Propagate();
|
|
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();
|
|
|
|
// 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();
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
|
|
// Perform register allocation on the SSA graph.
|
|
FlowGraphAllocator allocator(*flow_graph);
|
|
allocator.AllocateRegisters();
|
|
|
|
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);
|
|
graph_compiler.FinalizeVarDescriptors(code);
|
|
graph_compiler.FinalizeExceptionHandlers(code);
|
|
graph_compiler.FinalizeComments(code);
|
|
graph_compiler.FinalizeStaticCallTargetsTable(code);
|
|
|
|
if (optimized) {
|
|
if (osr_id == Isolate::kNoDeoptId) {
|
|
CodePatcher::PatchEntry(Code::Handle(function.CurrentCode()));
|
|
if (FLAG_trace_compiler) {
|
|
OS::Print("--> patching entry %#" Px "\n",
|
|
Code::Handle(function.unoptimized_code()).EntryPoint());
|
|
}
|
|
}
|
|
function.SetCode(code);
|
|
|
|
for (intptr_t i = 0; i < guarded_fields.length(); i++) {
|
|
const Field& field = *guarded_fields[i];
|
|
field.RegisterDependentCode(code);
|
|
}
|
|
} else {
|
|
function.set_unoptimized_code(code);
|
|
function.SetCode(code);
|
|
ASSERT(CodePatcher::CodeIsPatchable(code));
|
|
}
|
|
}
|
|
is_compiled = true;
|
|
done = true;
|
|
} else {
|
|
// We bailed out.
|
|
|
|
if (isolate->object_store()->sticky_error() ==
|
|
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) {
|
|
const Error& bailout_error = Error::Handle(
|
|
isolate->object_store()->sticky_error());
|
|
OS::Print("%s\n", bailout_error.ToErrorCString());
|
|
}
|
|
done = true;
|
|
ASSERT(optimized);
|
|
}
|
|
|
|
isolate->object_store()->clear_sticky_error();
|
|
is_compiled = false;
|
|
}
|
|
// Reset global isolate state.
|
|
isolate->set_long_jump_base(old_base);
|
|
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",
|
|
optimized ? "optimized " : "",
|
|
function_fullname);
|
|
const Code& code = Code::Handle(function.CurrentCode());
|
|
code.Disassemble();
|
|
OS::Print("}\n");
|
|
|
|
OS::Print("Pointer offsets for function: {\n");
|
|
// Pointer offsets are stored in descending order.
|
|
for (intptr_t i = code.pointer_offsets_length() - 1; i >= 0; i--) {
|
|
const uword addr = code.GetPointerOffsetAt(i) + code.EntryPoint();
|
|
Object& obj = Object::Handle();
|
|
obj = *reinterpret_cast<RawObject**>(addr);
|
|
OS::Print(" %d : %#" Px " '%s'\n",
|
|
code.GetPointerOffsetAt(i), addr, obj.ToCString());
|
|
}
|
|
OS::Print("}\n");
|
|
|
|
OS::Print("PC Descriptors for function '%s' {\n", function_fullname);
|
|
PcDescriptors::PrintHeaderString();
|
|
const PcDescriptors& descriptors =
|
|
PcDescriptors::Handle(code.pc_descriptors());
|
|
OS::Print("%s}\n", descriptors.ToCString());
|
|
|
|
uword start = Instructions::Handle(code.instructions()).EntryPoint();
|
|
const Array& deopt_table = Array::Handle(code.deopt_info_array());
|
|
intptr_t deopt_table_length = DeoptTable::GetLength(deopt_table);
|
|
if (deopt_table_length > 0) {
|
|
OS::Print("DeoptInfo: {\n");
|
|
Smi& offset = Smi::Handle();
|
|
DeoptInfo& info = DeoptInfo::Handle();
|
|
Smi& reason = Smi::Handle();
|
|
for (intptr_t i = 0; i < deopt_table_length; ++i) {
|
|
DeoptTable::GetEntry(deopt_table, i, &offset, &info, &reason);
|
|
OS::Print("%4" Pd ": 0x%" Px " %s (%s)\n",
|
|
i,
|
|
start + offset.Value(),
|
|
info.ToCString(),
|
|
DeoptReasonToText(reason.Value()));
|
|
}
|
|
OS::Print("}\n");
|
|
}
|
|
|
|
const Array& object_table = Array::Handle(code.object_table());
|
|
if (object_table.Length() > 0) {
|
|
OS::Print("Object Table: {\n");
|
|
for (intptr_t i = 0; i < object_table.Length(); i++) {
|
|
OS::Print(" %" Pd ": %s\n", i,
|
|
Object::Handle(object_table.At(i)).ToCString());
|
|
}
|
|
OS::Print("}\n");
|
|
}
|
|
|
|
OS::Print("Stackmaps for function '%s' {\n", function_fullname);
|
|
if (code.stackmaps() != Array::null()) {
|
|
const Array& stackmap_table = Array::Handle(code.stackmaps());
|
|
Stackmap& map = Stackmap::Handle();
|
|
for (intptr_t i = 0; i < stackmap_table.Length(); ++i) {
|
|
map ^= stackmap_table.At(i);
|
|
OS::Print("%s\n", map.ToCString());
|
|
}
|
|
}
|
|
OS::Print("}\n");
|
|
|
|
OS::Print("Variable Descriptors for function '%s' {\n",
|
|
function_fullname);
|
|
const LocalVarDescriptors& var_descriptors =
|
|
LocalVarDescriptors::Handle(code.var_descriptors());
|
|
intptr_t var_desc_length =
|
|
var_descriptors.IsNull() ? 0 : var_descriptors.Length();
|
|
String& var_name = String::Handle();
|
|
for (intptr_t i = 0; i < var_desc_length; i++) {
|
|
var_name = var_descriptors.GetName(i);
|
|
RawLocalVarDescriptors::VarInfo var_info;
|
|
var_descriptors.GetInfo(i, &var_info);
|
|
if (var_info.kind == RawLocalVarDescriptors::kSavedEntryContext) {
|
|
OS::Print(" saved caller's CTX reg offset %" Pd "\n", var_info.index);
|
|
} else if (var_info.kind == RawLocalVarDescriptors::kSavedCurrentContext) {
|
|
OS::Print(" saved current CTX reg offset %" Pd "\n", var_info.index);
|
|
} else {
|
|
if (var_info.kind == RawLocalVarDescriptors::kContextLevel) {
|
|
OS::Print(" context level %" Pd " scope %d",
|
|
var_info.index, var_info.scope_id);
|
|
} else if (var_info.kind == RawLocalVarDescriptors::kStackVar) {
|
|
OS::Print(" stack var '%s' offset %" Pd "",
|
|
var_name.ToCString(), var_info.index);
|
|
} else {
|
|
ASSERT(var_info.kind == RawLocalVarDescriptors::kContextVar);
|
|
OS::Print(" context var '%s' level %d offset %" Pd "",
|
|
var_name.ToCString(), var_info.scope_id, var_info.index);
|
|
}
|
|
OS::Print(" (valid %" Pd "-%" Pd ")\n",
|
|
var_info.begin_pos, var_info.end_pos);
|
|
}
|
|
}
|
|
OS::Print("}\n");
|
|
|
|
OS::Print("Exception Handlers for function '%s' {\n", function_fullname);
|
|
const ExceptionHandlers& handlers =
|
|
ExceptionHandlers::Handle(code.exception_handlers());
|
|
OS::Print("%s}\n", handlers.ToCString());
|
|
|
|
{
|
|
OS::Print("Static call target functions {\n");
|
|
const Array& table = Array::Handle(code.static_calls_target_table());
|
|
Smi& offset = Smi::Handle();
|
|
Function& function = Function::Handle();
|
|
Code& code = Code::Handle();
|
|
for (intptr_t i = 0; i < table.Length();
|
|
i += Code::kSCallTableEntryLength) {
|
|
offset ^= table.At(i + Code::kSCallTableOffsetEntry);
|
|
function ^= table.At(i + Code::kSCallTableFunctionEntry);
|
|
code ^= table.At(i + Code::kSCallTableCodeEntry);
|
|
OS::Print(" 0x%" Px ": %s, %p\n",
|
|
start + offset.Value(),
|
|
function.ToFullyQualifiedCString(),
|
|
code.raw());
|
|
}
|
|
OS::Print("}\n");
|
|
}
|
|
}
|
|
|
|
|
|
static RawError* CompileFunctionHelper(const Function& function,
|
|
bool optimized,
|
|
intptr_t osr_id) {
|
|
Isolate* isolate = Isolate::Current();
|
|
StackZone zone(isolate);
|
|
LongJump* base = isolate->long_jump_base();
|
|
LongJump jump;
|
|
isolate->set_long_jump_base(&jump);
|
|
// Skips parsing if we need to only install unoptimized code.
|
|
if (!optimized && !Code::Handle(function.unoptimized_code()).IsNull()) {
|
|
InstallUnoptimizedCode(function);
|
|
isolate->set_long_jump_base(base);
|
|
return Error::null();
|
|
}
|
|
if (setjmp(*jump.Set()) == 0) {
|
|
TIMERSCOPE(time_compilation);
|
|
Timer per_compile_timer(FLAG_trace_compiler, "Compilation time");
|
|
per_compile_timer.Start();
|
|
ParsedFunction* parsed_function = new ParsedFunction(
|
|
Function::ZoneHandle(function.raw()));
|
|
if (FLAG_trace_compiler) {
|
|
OS::Print("Compiling %s%sfunction: '%s' @ token %" Pd ", size %" Pd "\n",
|
|
(osr_id == Isolate::kNoDeoptId ? "" : "osr "),
|
|
(optimized ? "optimized " : ""),
|
|
function.ToFullyQualifiedCString(),
|
|
function.token_pos(),
|
|
(function.end_token_pos() - function.token_pos()));
|
|
}
|
|
{
|
|
HANDLESCOPE(isolate);
|
|
Parser::ParseFunction(parsed_function);
|
|
parsed_function->AllocateVariables();
|
|
}
|
|
|
|
const bool success =
|
|
CompileParsedFunctionHelper(parsed_function, optimized, osr_id);
|
|
if (optimized && !success) {
|
|
// Optimizer bailed out. Disable optimizations and to never try again.
|
|
if (FLAG_trace_compiler) {
|
|
OS::Print("--> disabling optimizations for '%s'\n",
|
|
function.ToFullyQualifiedCString());
|
|
} else if (FLAG_trace_failed_optimization_attempts) {
|
|
OS::Print("Cannot optimize: %s\n", function.ToFullyQualifiedCString());
|
|
}
|
|
function.set_is_optimizable(false);
|
|
isolate->set_long_jump_base(base);
|
|
return Error::null();
|
|
}
|
|
|
|
ASSERT(success);
|
|
per_compile_timer.Stop();
|
|
|
|
if (FLAG_trace_compiler) {
|
|
OS::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());
|
|
}
|
|
|
|
isolate->debugger()->NotifyCompilation(function);
|
|
|
|
if (FLAG_disassemble) {
|
|
DisassembleCode(function, optimized);
|
|
} else if (FLAG_disassemble_optimized && optimized) {
|
|
// TODO(fschneider): Print unoptimized code along with the optimized code.
|
|
OS::Print("*** BEGIN CODE\n");
|
|
DisassembleCode(function, true);
|
|
OS::Print("*** END CODE\n");
|
|
}
|
|
|
|
isolate->set_long_jump_base(base);
|
|
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();
|
|
isolate->set_long_jump_base(base);
|
|
return error.raw();
|
|
}
|
|
UNREACHABLE();
|
|
return Error::null();
|
|
}
|
|
|
|
|
|
RawError* Compiler::CompileFunction(const Function& function) {
|
|
return CompileFunctionHelper(function, false, Isolate::kNoDeoptId);
|
|
}
|
|
|
|
|
|
RawError* Compiler::CompileOptimizedFunction(const Function& function,
|
|
intptr_t osr_id) {
|
|
return CompileFunctionHelper(function, true, osr_id);
|
|
}
|
|
|
|
|
|
RawError* Compiler::CompileParsedFunction(
|
|
ParsedFunction* parsed_function) {
|
|
Isolate* isolate = Isolate::Current();
|
|
LongJump* base = isolate->long_jump_base();
|
|
LongJump jump;
|
|
isolate->set_long_jump_base(&jump);
|
|
if (setjmp(*jump.Set()) == 0) {
|
|
// Non-optimized code generator.
|
|
CompileParsedFunctionHelper(parsed_function, false, Isolate::kNoDeoptId);
|
|
if (FLAG_disassemble) {
|
|
DisassembleCode(parsed_function->function(), false);
|
|
}
|
|
isolate->set_long_jump_base(base);
|
|
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();
|
|
isolate->set_long_jump_base(base);
|
|
return error.raw();
|
|
}
|
|
UNREACHABLE();
|
|
return Error::null();
|
|
}
|
|
|
|
|
|
RawError* Compiler::CompileAllFunctions(const Class& cls) {
|
|
Error& error = Error::Handle();
|
|
Array& functions = Array::Handle(cls.functions());
|
|
Function& func = Function::Handle();
|
|
// Class dynamic lives in the vm isolate. Its array fields cannot be set to
|
|
// an empty array.
|
|
if (functions.IsNull()) {
|
|
ASSERT(cls.IsDynamicClass());
|
|
return error.raw();
|
|
}
|
|
for (int i = 0; i < functions.Length(); i++) {
|
|
func ^= functions.At(i);
|
|
ASSERT(!func.IsNull());
|
|
if (!func.HasCode() &&
|
|
!func.is_abstract() &&
|
|
!func.IsRedirectingFactory()) {
|
|
error = CompileFunction(func);
|
|
if (!error.IsNull()) {
|
|
return error.raw();
|
|
}
|
|
}
|
|
}
|
|
return error.raw();
|
|
}
|
|
|
|
|
|
RawObject* Compiler::ExecuteOnce(SequenceNode* fragment) {
|
|
Isolate* isolate = Isolate::Current();
|
|
LongJump* base = isolate->long_jump_base();
|
|
LongJump jump;
|
|
isolate->set_long_jump_base(&jump);
|
|
if (setjmp(*jump.Set()) == 0) {
|
|
if (FLAG_trace_compiler) {
|
|
OS::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::kImplicitStaticFinalGetter,
|
|
true, // static function.
|
|
false, // not const function.
|
|
false, // not abstract
|
|
false, // not external.
|
|
Class::Handle(Type::Handle(Type::Function()).type_class()),
|
|
fragment->token_pos()));
|
|
|
|
func.set_result_type(Type::Handle(Type::DynamicType()));
|
|
func.set_num_fixed_parameters(0);
|
|
func.SetNumOptionalParameters(0, true);
|
|
// Manually generated AST, do not recompile.
|
|
func.set_is_optimizable(false);
|
|
|
|
// We compile the function here, even though InvokeStatic() below
|
|
// would compile func automatically. We are checking fewer invariants
|
|
// here.
|
|
ParsedFunction* parsed_function = new ParsedFunction(func);
|
|
parsed_function->SetNodeSequence(fragment);
|
|
parsed_function->set_default_parameter_values(Object::null_array());
|
|
parsed_function->EnsureExpressionTemp();
|
|
fragment->scope()->AddVariable(parsed_function->expression_temp_var());
|
|
parsed_function->AllocateVariables();
|
|
|
|
// Non-optimized code generator.
|
|
CompileParsedFunctionHelper(parsed_function, false, Isolate::kNoDeoptId);
|
|
|
|
const Object& result = Object::Handle(
|
|
DartEntry::InvokeFunction(func, Object::empty_array()));
|
|
isolate->set_long_jump_base(base);
|
|
return result.raw();
|
|
} else {
|
|
const Object& result =
|
|
Object::Handle(isolate->object_store()->sticky_error());
|
|
isolate->object_store()->clear_sticky_error();
|
|
isolate->set_long_jump_base(base);
|
|
return result.raw();
|
|
}
|
|
UNREACHABLE();
|
|
return Object::null();
|
|
}
|
|
|
|
} // namespace dart
|