Files
sdk/runtime/vm/compiler.cc
T
Regis Crelier 7f57ebcfa1 Remove signature classes from the VM.
They were used as the class of closure instances and as the type class of
function types.
All closure instances now have class _Closure and function types are represented
by a new class FunctionType extending AbstractType.
Fix issue 24567 and add regression test.

R=asiva@google.com, rmacnak@google.com

Review URL: https://codereview.chromium.org/1584223006 .
2016-01-19 16:32:59 -08:00

2052 lines
73 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/block_scheduler.h"
#include "vm/cha.h"
#include "vm/code_generator.h"
#include "vm/code_patcher.h"
#include "vm/constant_propagator.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/regexp_parser.h"
#include "vm/regexp_assembler.h"
#include "vm/scanner.h"
#include "vm/symbols.h"
#include "vm/tags.h"
#include "vm/thread_registry.h"
#include "vm/timer.h"
namespace dart {
DEFINE_FLAG(bool, allocation_sinking, true,
"Attempt to sink temporary allocations to side exits");
DEFINE_FLAG(bool, common_subexpression_elimination, true,
"Do common subexpression elimination.");
DEFINE_FLAG(bool, constant_propagation, true,
"Do conditional constant propagation/unreachable code elimination.");
DEFINE_FLAG(int, max_deoptimization_counter_threshold, 16,
"How many times we allow deoptimization before we disallow optimization.");
DEFINE_FLAG(bool, disassemble, false, "Disassemble dart code.");
DEFINE_FLAG(bool, disassemble_optimized, false, "Disassemble optimized code.");
DEFINE_FLAG(bool, loop_invariant_code_motion, true,
"Do loop invariant code motion.");
DEFINE_FLAG(bool, print_flow_graph, false, "Print the IR flow graph.");
DEFINE_FLAG(bool, print_flow_graph_optimized, false,
"Print the IR flow graph when optimizing.");
DEFINE_FLAG(bool, print_ic_data_map, false,
"Print the deopt-id to ICData map in optimizing compiler.");
DEFINE_FLAG(bool, range_analysis, true, "Enable range analysis");
DEFINE_FLAG(bool, reorder_basic_blocks, true, "Enable basic-block reordering.");
DEFINE_FLAG(bool, trace_compiler, false, "Trace compiler operations.");
DEFINE_FLAG(bool, trace_optimizing_compiler, false,
"Trace only optimizing compiler operations.");
DEFINE_FLAG(bool, trace_bailout, false, "Print bailout from ssa compiler.");
DEFINE_FLAG(bool, use_inlining, true, "Enable call-site inlining");
DEFINE_FLAG(bool, verify_compiler, false,
"Enable compiler verification assertions");
DEFINE_FLAG(int, max_speculative_inlining_attempts, 1,
"Max number of attempts with speculative inlining (precompilation only)");
DECLARE_FLAG(bool, background_compilation);
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);
bool Compiler::always_optimize_ = false;
bool Compiler::allow_recompilation_ = true;
#ifndef DART_PRECOMPILED_RUNTIME
// TODO(zerny): Factor out unoptimizing/optimizing pipelines and remove
// separate helpers functions & `optimizing` args.
class CompilationPipeline : public ZoneAllocated {
public:
static CompilationPipeline* New(Zone* zone, const Function& function);
virtual void ParseFunction(ParsedFunction* parsed_function) = 0;
virtual FlowGraph* BuildFlowGraph(
Zone* zone,
ParsedFunction* parsed_function,
const ZoneGrowableArray<const ICData*>& ic_data_array,
intptr_t osr_id) = 0;
virtual void FinalizeCompilation() = 0;
virtual ~CompilationPipeline() { }
};
class DartCompilationPipeline : public CompilationPipeline {
public:
virtual void ParseFunction(ParsedFunction* parsed_function) {
Parser::ParseFunction(parsed_function);
parsed_function->AllocateVariables();
}
virtual FlowGraph* BuildFlowGraph(
Zone* zone,
ParsedFunction* parsed_function,
const ZoneGrowableArray<const ICData*>& ic_data_array,
intptr_t osr_id) {
// Build the flow graph.
FlowGraphBuilder builder(*parsed_function,
ic_data_array,
NULL, // NULL = not inlining.
osr_id);
return builder.BuildGraph();
}
virtual void FinalizeCompilation() { }
};
class IrregexpCompilationPipeline : public CompilationPipeline {
public:
IrregexpCompilationPipeline() : backtrack_goto_(NULL) { }
virtual void ParseFunction(ParsedFunction* parsed_function) {
RegExpParser::ParseFunction(parsed_function);
// Variables are allocated after compilation.
}
virtual FlowGraph* BuildFlowGraph(
Zone* zone,
ParsedFunction* parsed_function,
const ZoneGrowableArray<const ICData*>& ic_data_array,
intptr_t osr_id) {
// Compile to the dart IR.
RegExpEngine::CompilationResult result =
RegExpEngine::CompileIR(parsed_function->regexp_compile_data(),
parsed_function,
ic_data_array);
backtrack_goto_ = result.backtrack_goto;
// Allocate variables now that we know the number of locals.
parsed_function->AllocateIrregexpVariables(result.num_stack_locals);
// Build the flow graph.
FlowGraphBuilder builder(*parsed_function,
ic_data_array,
NULL, // NULL = not inlining.
osr_id);
return new(zone) FlowGraph(*parsed_function,
result.graph_entry,
result.num_blocks);
}
virtual void FinalizeCompilation() {
backtrack_goto_->ComputeOffsetTable();
}
private:
IndirectGotoInstr* backtrack_goto_;
};
CompilationPipeline* CompilationPipeline::New(Zone* zone,
const Function& function) {
if (function.IsIrregexpFunction()) {
return new(zone) IrregexpCompilationPipeline();
} else {
return new(zone) DartCompilationPipeline();
}
}
// Compile a function. Should call only if the function has not been compiled.
// Arg0: function object.
DEFINE_RUNTIME_ENTRY(CompileFunction, 1) {
const Function& function = Function::CheckedHandle(arguments.ArgAt(0));
ASSERT(!function.HasCode());
const Error& error =
Error::Handle(Compiler::CompileFunction(thread, function));
if (!error.IsNull()) {
Exceptions::PropagateError(error);
}
}
bool Compiler::IsBackgroundCompilation() {
// For now: compilation in non mutator thread is the background compoilation.
return !Thread::Current()->IsMutatorThread();
}
RawError* Compiler::Compile(const Library& library, const Script& script) {
LongJumpScope jump;
if (setjmp(*jump.Set()) == 0) {
Thread* const thread = Thread::Current();
StackZone zone(thread);
if (FLAG_trace_compiler) {
const String& script_url = String::Handle(script.url());
// TODO(iposva): Extract script kind.
THR_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);
return Error::null();
} else {
Thread* const thread = Thread::Current();
Isolate* const isolate = thread->isolate();
StackZone zone(thread);
Error& error = Error::Handle();
error = isolate->object_store()->sticky_error();
isolate->object_store()->clear_sticky_error();
return error.raw();
}
UNREACHABLE();
return Error::null();
}
static void AddRelatedClassesToList(
const Class& cls,
GrowableHandlePtrArray<const Class>* parse_list,
GrowableHandlePtrArray<const Class>* patch_list) {
Zone* zone = Thread::Current()->zone();
Class& parse_class = Class::Handle(zone);
AbstractType& interface_type = Type::Handle(zone);
Array& interfaces = Array::Handle(zone);
// 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.GetPatchClass();
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) {
ASSERT(Thread::Current()->IsMutatorThread());
// 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();
}
// If the class is a typedef class there is no need to try and
// compile it. Just finalize it directly.
if (cls.IsTypedefClass()) {
#if defined(DEBUG)
const Class& closure_cls = Class::Handle(
Isolate::Current()->object_store()->closure_class());
ASSERT(closure_cls.is_finalized());
#endif
LongJumpScope jump;
if (setjmp(*jump.Set()) == 0) {
ClassFinalizer::FinalizeClass(cls);
return Error::null();
} else {
Thread* thread = Thread::Current();
Isolate* isolate = thread->isolate();
Error& error = Error::Handle(thread->zone());
error = isolate->object_store()->sticky_error();
isolate->object_store()->clear_sticky_error();
return error.raw();
}
}
Thread* const thread = Thread::Current();
Isolate* const isolate = thread->isolate();
StackZone zone(thread);
// We remember all the classes that are being compiled in these lists. This
// also allows us to reset the marked_for_parsing state in case we see an
// error.
VMTagScope tagScope(thread, VMTag::kCompileClassTagId);
TimelineDurationScope tds(thread,
thread->isolate()->GetCompilerStream(),
"CompileClass");
if (tds.enabled()) {
tds.SetNumArguments(1);
tds.CopyArgument(0, "class", cls.ToCString());
}
GrowableHandlePtrArray<const Class> parse_list(thread->zone(), 4);
GrowableHandlePtrArray<const Class> patch_list(thread->zone(), 4);
// Parse the class and all the interfaces it implements and super classes.
LongJumpScope jump;
if (setjmp(*jump.Set()) == 0) {
if (FLAG_trace_compiler) {
THR_Print("Compiling Class '%s'\n", cls.ToCString());
}
// 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++) {
AddRelatedClassesToList(parse_list.At(i), &parse_list, &patch_list);
}
// Parse all the classes that have been added above.
for (intptr_t i = (parse_list.length() - 1); i >=0 ; i--) {
const Class& 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++) {
const Class& 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--) {
const Class& parse_class = parse_list.At(i);
ASSERT(!parse_class.IsNull());
ClassFinalizer::FinalizeClass(parse_class);
parse_class.reset_is_marked_for_parsing();
}
for (intptr_t i = (patch_list.length() - 1); i >=0 ; i--) {
const Class& parse_class = patch_list.At(i);
ASSERT(!parse_class.IsNull());
ClassFinalizer::FinalizeClass(parse_class);
parse_class.reset_is_marked_for_parsing();
}
return Error::null();
} else {
// Reset the marked for parsing flags.
for (intptr_t i = 0; i < parse_list.length(); i++) {
const Class& parse_class = parse_list.At(i);
if (parse_class.is_marked_for_parsing()) {
parse_class.reset_is_marked_for_parsing();
}
}
for (intptr_t i = 0; i < patch_list.length(); i++) {
const Class& parse_class = patch_list.At(i);
if (parse_class.is_marked_for_parsing()) {
parse_class.reset_is_marked_for_parsing();
}
}
Error& error = Error::Handle(zone.GetZone());
error = isolate->object_store()->sticky_error();
isolate->object_store()->clear_sticky_error();
return error.raw();
}
UNREACHABLE();
return Error::null();
}
class CompileParsedFunctionHelper : public ValueObject {
public:
CompileParsedFunctionHelper(ParsedFunction* parsed_function,
bool optimized,
intptr_t osr_id)
: parsed_function_(parsed_function),
optimized_(optimized),
osr_id_(osr_id),
thread_(Thread::Current()),
cha_invalidation_gen_at_start_(isolate()->cha_invalidation_gen()),
field_invalidation_gen_at_start_(isolate()->field_invalidation_gen()),
prefix_invalidation_gen_at_start_(
isolate()->prefix_invalidation_gen()) {
}
bool Compile(CompilationPipeline* pipeline);
private:
ParsedFunction* parsed_function() const { return parsed_function_; }
bool optimized() const { return optimized_; }
intptr_t osr_id() const { return osr_id_; }
Thread* thread() const { return thread_; }
Isolate* isolate() const { return thread_->isolate(); }
uint32_t cha_invalidation_gen_at_start() const {
return cha_invalidation_gen_at_start_;
}
uint32_t field_invalidation_gen_at_start() const {
return field_invalidation_gen_at_start_;
}
uint32_t prefix_invalidation_gen_at_start() const {
return prefix_invalidation_gen_at_start_;
}
void FinalizeCompilation(Assembler* assembler,
FlowGraphCompiler* graph_compiler,
FlowGraph* flow_graph);
ParsedFunction* parsed_function_;
const bool optimized_;
const intptr_t osr_id_;
Thread* const thread_;
const uint32_t cha_invalidation_gen_at_start_;
const uint32_t field_invalidation_gen_at_start_;
const uint32_t prefix_invalidation_gen_at_start_;
DISALLOW_COPY_AND_ASSIGN(CompileParsedFunctionHelper);
};
void CompileParsedFunctionHelper::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.
if (thread()->IsMutatorThread()) {
const bool is_osr = osr_id() != Compiler::kNoOSRDeoptId;
function.InstallOptimizedCode(code, is_osr);
} else {
// Background compilation.
// Before installing code check generation counts if the code may
// have become invalid.
const bool trace_compiler =
FLAG_trace_compiler || FLAG_trace_optimizing_compiler;
bool code_is_valid = true;
if (!thread()->cha()->leaf_classes().is_empty()) {
if (cha_invalidation_gen_at_start() !=
isolate()->cha_invalidation_gen()) {
code_is_valid = false;
if (trace_compiler) {
THR_Print("--> FAIL: CHA invalidation.");
}
}
}
if (!flow_graph->guarded_fields()->is_empty()) {
if (field_invalidation_gen_at_start() !=
isolate()->field_invalidation_gen()) {
code_is_valid = false;
if (trace_compiler) {
THR_Print("--> FAIL: Field invalidation.");
}
}
}
if (parsed_function()->HasDeferredPrefixes()) {
if (prefix_invalidation_gen_at_start() !=
isolate()->prefix_invalidation_gen()) {
code_is_valid = false;
if (trace_compiler) {
THR_Print("--> FAIL: Prefix invalidation.");
}
}
}
if (code_is_valid) {
const bool is_osr = osr_id() != Compiler::kNoOSRDeoptId;
ASSERT(!is_osr); // OSR is compiled in background.
function.InstallOptimizedCode(code, is_osr);
}
if (function.usage_counter() < 0) {
// Reset to 0 so that it can be recompiled if needed.
function.set_usage_counter(0);
}
}
// Register code with the classes it depends on because of CHA and
// fields it depends on because of store guards, unless we cannot
// deopt.
if (Compiler::allow_recompilation()) {
// Deoptimize field dependent code first, before registering
// this yet uninstalled code as dependent on a field.
// TODO(srdjan): Debugging dart2js crashes;
// FlowGraphOptimizer::VisitStoreInstanceField populates
// deoptimize_dependent_code() list, currently disabled.
for (intptr_t i = 0;
i < flow_graph->deoptimize_dependent_code().length();
i++) {
const Field* field = flow_graph->deoptimize_dependent_code()[i];
field->DeoptimizeDependentCode();
}
for (intptr_t i = 0;
i < thread()->cha()->leaf_classes().length();
++i) {
thread()->cha()->leaf_classes()[i]->RegisterCHACode(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 (!Compiler::always_optimize() &&
(function.ic_data_array() == Array::null())) {
function.SaveICDataMap(
graph_compiler->deopt_id_to_ic_data(),
Array::Handle(zone, graph_compiler->edge_counters_array()));
}
function.set_unoptimized_code(code);
function.AttachCode(code);
}
if (parsed_function()->HasDeferredPrefixes()) {
ASSERT(!FLAG_load_deferred_eagerly);
ZoneGrowableArray<const LibraryPrefix*>* prefixes =
parsed_function()->deferred_prefixes();
for (intptr_t i = 0; i < prefixes->length(); i++) {
(*prefixes)[i]->RegisterDependentCode(code);
}
}
}
// Return false if bailed out.
// If optimized_result_code is not NULL then it is caller's responsibility
// to install code.
bool CompileParsedFunctionHelper::Compile(CompilationPipeline* pipeline) {
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*>();
if (optimized()) {
// Extract type feedback before the graph is built, as the graph
// builder uses it to attach it to nodes.
ASSERT(function.deoptimization_counter() <
FLAG_max_deoptimization_counter_threshold);
// 'Freeze' ICData in background compilation so that it does not
// change while compiling.
const bool clone_descriptors = Compiler::IsBackgroundCompilation();
function.RestoreICDataMap(ic_data_array, clone_descriptors);
if (FLAG_print_ic_data_map) {
for (intptr_t i = 0; i < ic_data_array->length(); i++) {
if ((*ic_data_array)[i] != NULL) {
THR_Print("%" Pd " ", i);
FlowGraphPrinter::PrintICData(*(*ic_data_array)[i]);
}
}
}
}
TimelineDurationScope tds(thread(),
compiler_timeline,
"BuildFlowGraph");
flow_graph = pipeline->BuildFlowGraph(zone,
parsed_function(),
*ic_data_array,
osr_id());
}
const bool print_flow_graph =
(FLAG_print_flow_graph ||
(optimized() && FLAG_print_flow_graph_optimized)) &&
FlowGraphPrinter::ShouldPrint(function);
if (print_flow_graph) {
if (osr_id() == Compiler::kNoOSRDeoptId) {
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) {
TimelineDurationScope tds(thread(),
compiler_timeline,
"BlockScheduler::AssignEdgeWeights");
block_scheduler.AssignEdgeWeights();
}
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);
if (Compiler::always_optimize()) {
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 (reorder_blocks) {
TimelineDurationScope tds(thread(),
compiler_timeline,
"BlockScheduler::ReorderBlocks");
block_scheduler.ReorderBlocks();
}
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");
if (thread()->IsMutatorThread()) {
FinalizeCompilation(&assembler, &graph_compiler, flow_graph);
} else {
// This part of compilation must be at a safepoint.
// Stop mutator thread before creating the instruction object and
// installing code.
// Mutator thread may not run code while we are creating the
// instruction object, since the creation of instruction object
// changes code page access permissions (makes them temporary not
// executable).
isolate()->thread_registry()->SafepointThreads();
{
// Do not Garbage collect during this stage and instead allow the
// heap to grow.
NoHeapGrowthControlScope no_growth_control;
FinalizeCompilation(&assembler, &graph_compiler, flow_graph);
}
isolate()->thread_registry()->ResumeAllThreads();
if (isolate()->heap()->NeedsGarbageCollection()) {
isolate()->heap()->CollectAllGarbage();
}
}
}
// 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(
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 (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(Compiler::always_optimize());
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)) {
isolate()->object_store()->clear_sticky_error();
}
is_compiled = false;
}
// Reset global isolate state.
thread()->set_deopt_id(prev_deopt_id);
}
return is_compiled;
}
static void DisassembleCode(const Function& function, bool optimized) {
const char* function_fullname = function.ToFullyQualifiedCString();
THR_Print("Code for %sfunction '%s' {\n",
optimized ? "optimized " : "",
function_fullname);
const Code& code = Code::Handle(function.CurrentCode());
code.Disassemble();
THR_Print("}\n");
THR_Print("Pointer offsets for function: {\n");
// Pointer offsets are stored in descending order.
Object& obj = Object::Handle();
for (intptr_t i = code.pointer_offsets_length() - 1; i >= 0; i--) {
const uword addr = code.GetPointerOffsetAt(i) + code.EntryPoint();
obj = *reinterpret_cast<RawObject**>(addr);
THR_Print(" %d : %#" Px " '%s'\n",
code.GetPointerOffsetAt(i), addr, obj.ToCString());
}
THR_Print("}\n");
THR_Print("PC Descriptors for function '%s' {\n", function_fullname);
PcDescriptors::PrintHeaderString();
const PcDescriptors& descriptors =
PcDescriptors::Handle(code.pc_descriptors());
THR_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) {
THR_Print("DeoptInfo: {\n");
Smi& offset = Smi::Handle();
TypedData& info = TypedData::Handle();
Smi& reason_and_flags = Smi::Handle();
for (intptr_t i = 0; i < deopt_table_length; ++i) {
DeoptTable::GetEntry(deopt_table, i, &offset, &info, &reason_and_flags);
const intptr_t reason =
DeoptTable::ReasonField::decode(reason_and_flags.Value());
ASSERT((0 <= reason) && (reason < ICData::kDeoptNumReasons));
THR_Print("%4" Pd ": 0x%" Px " %s (%s)\n",
i,
start + offset.Value(),
DeoptInfo::ToCString(deopt_table, info),
DeoptReasonToCString(
static_cast<ICData::DeoptReasonId>(reason)));
}
THR_Print("}\n");
}
const ObjectPool& object_pool = ObjectPool::Handle(code.GetObjectPool());
object_pool.DebugPrint();
THR_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);
THR_Print("%s\n", map.ToCString());
}
}
THR_Print("}\n");
THR_Print("Variable Descriptors for function '%s' {\n",
function_fullname);
const LocalVarDescriptors& var_descriptors =
LocalVarDescriptors::Handle(code.GetLocalVarDescriptors());
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);
const int8_t kind = var_info.kind();
if (kind == RawLocalVarDescriptors::kSavedCurrentContext) {
THR_Print(" saved current CTX reg offset %d\n", var_info.index());
} else {
if (kind == RawLocalVarDescriptors::kContextLevel) {
THR_Print(" context level %d scope %d", var_info.index(),
var_info.scope_id);
} else if (kind == RawLocalVarDescriptors::kStackVar) {
THR_Print(" stack var '%s' offset %d",
var_name.ToCString(), var_info.index());
} else {
ASSERT(kind == RawLocalVarDescriptors::kContextVar);
THR_Print(" context var '%s' level %d offset %d",
var_name.ToCString(), var_info.scope_id, var_info.index());
}
THR_Print(" (valid %d-%d)\n", var_info.begin_pos, var_info.end_pos);
}
}
THR_Print("}\n");
THR_Print("Exception Handlers for function '%s' {\n", function_fullname);
const ExceptionHandlers& handlers =
ExceptionHandlers::Handle(code.exception_handlers());
THR_Print("%s}\n", handlers.ToCString());
{
THR_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);
if (function.IsNull()) {
Class& cls = Class::Handle();
cls ^= code.owner();
if (cls.IsNull()) {
const String& code_name = String::Handle(code.Name());
THR_Print(" 0x%" Px ": %s, %p\n",
start + offset.Value(),
code_name.ToCString(),
code.raw());
} else {
THR_Print(" 0x%" Px ": allocation stub for %s, %p\n",
start + offset.Value(),
cls.ToCString(),
code.raw());
}
} else {
THR_Print(" 0x%" Px ": %s, %p\n",
start + offset.Value(),
function.ToFullyQualifiedCString(),
code.raw());
}
}
THR_Print("}\n");
}
if (optimized && FLAG_trace_inlining_intervals) {
code.DumpInlinedIntervals();
}
}
#if defined(DEBUG)
// Verifies that the inliner is always in the list of inlined functions.
// If this fails run with --trace-inlining-intervals to get more information.
static void CheckInliningIntervals(const Function& function) {
const Code& code = Code::Handle(function.CurrentCode());
const Array& intervals = Array::Handle(code.GetInlinedIntervals());
if (intervals.IsNull() || (intervals.Length() == 0)) return;
Smi& start = Smi::Handle();
GrowableArray<Function*> inlined_functions;
for (intptr_t i = 0; i < intervals.Length(); i += Code::kInlIntNumEntries) {
start ^= intervals.At(i + Code::kInlIntStart);
ASSERT(!start.IsNull());
if (start.IsNull()) continue;
code.GetInlinedFunctionsAt(start.Value(), &inlined_functions);
ASSERT(inlined_functions[inlined_functions.length() - 1]->raw() ==
function.raw());
}
}
#endif
static RawError* CompileFunctionHelper(CompilationPipeline* pipeline,
const Function& function,
bool optimized,
intptr_t osr_id) {
// Check that we optimize if 'Compiler::always_optimize()' is set to true,
// except if the function is marked as not optimizable.
ASSERT(!function.IsOptimizable() ||
!Compiler::always_optimize() || optimized);
ASSERT(Compiler::allow_recompilation() || !function.HasCode());
LongJumpScope jump;
if (setjmp(*jump.Set()) == 0) {
Thread* const thread = Thread::Current();
Isolate* const isolate = thread->isolate();
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("Compiling %s%sfunction: '%s' @ token %" Pd ", size %" Pd "\n",
(osr_id == Compiler::kNoOSRDeoptId ? "" : "osr "),
(optimized ? "optimized " : ""),
function.ToFullyQualifiedCString(),
function.token_pos(),
(function.end_token_pos() - function.token_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);
}
CompileParsedFunctionHelper helper(parsed_function, optimized, osr_id);
const bool success = helper.Compile(pipeline);
if (!success) {
if (optimized && !Compiler::always_optimize()) {
// Optimizer bailed out. Disable optimizations and never try again.
if (trace_compiler) {
THR_Print("--> disabling optimizations for '%s'\n",
function.ToFullyQualifiedCString());
} else if (FLAG_trace_failed_optimization_attempts) {
THR_Print("Cannot optimize: %s\n",
function.ToFullyQualifiedCString());
}
function.SetIsOptimizable(false);
return Error::null();
} else {
// Encountered error.
Error& error = Error::Handle();
// We got an error during compilation.
error = isolate->object_store()->sticky_error();
isolate->object_store()->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());
}
isolate->debugger()->NotifyCompilation(function);
if (FLAG_disassemble && FlowGraphPrinter::ShouldPrint(function)) {
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");
DisassembleCode(function, true);
THR_Print("*** END CODE\n");
}
#if defined(DEBUG)
CheckInliningIntervals(function);
#endif
return Error::null();
} else {
Thread* const thread = Thread::Current();
Isolate* const isolate = thread->isolate();
StackZone stack_zone(thread);
Error& error = Error::Handle();
// We got an error during compilation.
error = isolate->object_store()->sticky_error();
isolate->object_store()->clear_sticky_error();
// Unoptimized compilation or precompilation may encounter compile-time
// errors, but regular optimized compilation should not.
ASSERT(!optimized || Compiler::always_optimize());
// Do not attempt to optimize functions that can cause errors.
function.set_is_optimizable(false);
return error.raw();
}
UNREACHABLE();
return Error::null();
}
RawError* Compiler::CompileFunction(Thread* thread,
const Function& function) {
Isolate* isolate = thread->isolate();
VMTagScope tagScope(thread, VMTag::kCompileUnoptimizedTagId);
TIMELINE_FUNCTION_COMPILATION_DURATION(thread, "Function", function);
if (!isolate->compilation_allowed()) {
FATAL3("Precompilation missed function %s (%" Pd ", %s)\n",
function.ToLibNamePrefixedQualifiedCString(),
function.token_pos(),
Function::KindToCString(function.kind()));
}
CompilationPipeline* pipeline =
CompilationPipeline::New(thread->zone(), function);
const bool optimized =
Compiler::always_optimize() && function.IsOptimizable();
return CompileFunctionHelper(pipeline,
function,
optimized,
kNoOSRDeoptId);
}
RawError* Compiler::EnsureUnoptimizedCode(Thread* thread,
const Function& function) {
if (function.unoptimized_code() != Object::null()) {
return Error::null();
}
Code& original_code = Code::ZoneHandle(thread->zone());
if (function.HasCode()) {
original_code = function.CurrentCode();
}
CompilationPipeline* pipeline =
CompilationPipeline::New(thread->zone(), function);
const Error& error = Error::Handle(
CompileFunctionHelper(pipeline,
function,
false, /* not optimized */
kNoOSRDeoptId));
if (!error.IsNull()) {
return error.raw();
}
// Since CompileFunctionHelper replaces the current code, re-attach the
// the original code if the function was already compiled.
if (!original_code.IsNull() &&
(original_code.raw() != function.CurrentCode())) {
function.AttachCode(original_code);
}
ASSERT(function.unoptimized_code() != Object::null());
if (FLAG_trace_compiler) {
THR_Print("Ensure unoptimized code for %s\n", function.ToCString());
}
return Error::null();
}
RawError* Compiler::CompileOptimizedFunction(Thread* thread,
const Function& function,
intptr_t osr_id) {
VMTagScope tagScope(thread, VMTag::kCompileOptimizedTagId);
TIMELINE_FUNCTION_COMPILATION_DURATION(thread,
"OptimizedFunction", function);
// Optimization must happen in non-mutator/Dart thread if background
// compilation is on. OSR compilation still occurs in the main thread.
ASSERT((osr_id != kNoOSRDeoptId) || !FLAG_background_compilation ||
!thread->IsMutatorThread());
CompilationPipeline* pipeline =
CompilationPipeline::New(thread->zone(), function);
return CompileFunctionHelper(pipeline,
function,
true, /* optimized */
osr_id);
}
// This is only used from unit tests.
RawError* Compiler::CompileParsedFunction(
ParsedFunction* parsed_function) {
LongJumpScope jump;
if (setjmp(*jump.Set()) == 0) {
// Non-optimized code generator.
DartCompilationPipeline pipeline;
CompileParsedFunctionHelper helper(parsed_function, false, kNoOSRDeoptId);
helper.Compile(&pipeline);
if (FLAG_disassemble) {
DisassembleCode(parsed_function->function(), false);
}
return Error::null();
} else {
Isolate* const isolate = Isolate::Current();
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();
}
void Compiler::ComputeLocalVarDescriptors(const Code& code) {
ASSERT(!code.is_optimized());
const Function& function = Function::Handle(code.function());
ParsedFunction* parsed_function = new ParsedFunction(
Thread::Current(), Function::ZoneHandle(function.raw()));
LocalVarDescriptors& var_descs =
LocalVarDescriptors::Handle(code.var_descriptors());
ASSERT(var_descs.IsNull());
// IsIrregexpFunction have eager var descriptors generation.
ASSERT(!function.IsIrregexpFunction());
// Parser should not produce any errors, therefore no LongJumpScope needed.
Parser::ParseFunction(parsed_function);
parsed_function->AllocateVariables();
var_descs = parsed_function->node_sequence()->scope()->
GetVarDescriptors(function);
ASSERT(!var_descs.IsNull());
code.set_var_descriptors(var_descs);
}
RawError* Compiler::CompileAllFunctions(const Class& cls) {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
Error& error = Error::Handle(zone);
Array& functions = Array::Handle(zone, cls.functions());
Function& func = Function::Handle(zone);
// 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();
}
// Compile all the regular functions.
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(thread, func);
if (!error.IsNull()) {
return error.raw();
}
func.ClearICDataArray();
func.ClearCode();
}
}
return error.raw();
}
void Compiler::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;
}
Thread* thread = Thread::Current();
StackZone zone(thread);
ParsedFunction* parsed_function = Parser::ParseStaticFieldInitializer(field);
parsed_function->AllocateVariables();
// Non-optimized code generator.
DartCompilationPipeline pipeline;
CompileParsedFunctionHelper helper(parsed_function, false, kNoOSRDeoptId);
helper.Compile(&pipeline);
const Function& initializer = parsed_function->function();
field.SetPrecompiledInitializer(initializer);
}
RawObject* Compiler::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()) {
Thread* const thread = Thread::Current();
StackZone zone(thread);
ParsedFunction* parsed_function =
Parser::ParseStaticFieldInitializer(field);
parsed_function->AllocateVariables();
// Non-optimized code generator.
DartCompilationPipeline pipeline;
CompileParsedFunctionHelper helper(parsed_function, false, kNoOSRDeoptId);
helper.Compile(&pipeline);
initializer = parsed_function->function().raw();
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();
Isolate* const isolate = thread->isolate();
StackZone zone(thread);
const Error& error =
Error::Handle(thread->zone(), isolate->object_store()->sticky_error());
isolate->object_store()->clear_sticky_error();
return error.raw();
}
UNREACHABLE();
return Object::null();
}
RawObject* Compiler::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;
CompileParsedFunctionHelper helper(parsed_function, false, kNoOSRDeoptId);
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();
Isolate* const isolate = thread->isolate();
const Object& result =
PassiveObject::Handle(isolate->object_store()->sticky_error());
isolate->object_store()->clear_sticky_error();
return result.raw();
}
UNREACHABLE();
return Object::null();
}
// C-heap allocated background compilation queue element.
class QueueElement {
public:
explicit QueueElement(const Function& function)
: next_(NULL),
function_(function.raw()) {
ASSERT(Thread::Current()->IsMutatorThread());
}
~QueueElement() {
function_ = Function::null();
}
RawFunction* Function() const { return function_; }
void set_next(QueueElement* elem) { next_ = elem; }
QueueElement* next() const { return next_; }
RawObject* function() const { return function_; }
RawObject** function_ptr() {
return reinterpret_cast<RawObject**>(&function_);
}
private:
QueueElement* next_;
RawFunction* function_;
DISALLOW_COPY_AND_ASSIGN(QueueElement);
};
// Allocated in C-heap. Handles both input and output of background compilation.
// It implements a FIFO queue, using Peek, Add, Remove operations.
class BackgroundCompilationQueue {
public:
BackgroundCompilationQueue() : first_(NULL), last_(NULL) {}
~BackgroundCompilationQueue() {
while (!IsEmpty()) {
QueueElement* e = Remove();
delete e;
}
ASSERT((first_ == NULL) && (last_ == NULL));
}
void VisitObjectPointers(ObjectPointerVisitor* visitor) {
ASSERT(visitor != NULL);
QueueElement* p = first_;
while (p != NULL) {
visitor->VisitPointer(p->function_ptr());
p = p->next();
}
}
bool IsEmpty() const { return first_ == NULL; }
void Add(QueueElement* value) {
ASSERT(value != NULL);
if (first_ == NULL) {
first_ = value;
} else {
last_->set_next(value);
}
value->set_next(NULL);
last_ = value;
}
QueueElement* Peek() const {
return first_;
}
RawFunction* PeekFunction() const {
QueueElement* e = Peek();
if (e == NULL) {
return Function::null();
} else {
return e->Function();
}
}
QueueElement* Remove() {
ASSERT(first_ != NULL);
QueueElement* result = first_;
first_ = first_->next();
if (first_ == NULL) {
last_ = NULL;
}
return result;
}
bool ContainsObj(const Object& obj) const {
QueueElement* p = first_;
while (p != NULL) {
if (p->function() == obj.raw()) {
return true;
}
p = p->next();
}
return false;
}
private:
QueueElement* first_;
QueueElement* last_;
DISALLOW_COPY_AND_ASSIGN(BackgroundCompilationQueue);
};
BackgroundCompiler::BackgroundCompiler(Isolate* isolate)
: isolate_(isolate), running_(true), done_(new bool()),
queue_monitor_(new Monitor()), done_monitor_(new Monitor()),
function_queue_(new BackgroundCompilationQueue()) {
*done_ = false;
}
void BackgroundCompiler::Run() {
while (running_) {
// Maybe something is already in the queue, check first before waiting
// to be notified.
bool result = Thread::EnterIsolateAsHelper(isolate_);
ASSERT(result);
{
Thread* thread = Thread::Current();
StackZone stack_zone(thread);
Zone* zone = stack_zone.GetZone();
HANDLESCOPE(thread);
Function& function = Function::Handle(zone);
function = function_queue()->PeekFunction();
while (running_ && !function.IsNull()) {
const Error& error = Error::Handle(zone,
Compiler::CompileOptimizedFunction(thread,
function,
Compiler::kNoOSRDeoptId));
// TODO(srdjan): We do not expect errors while compiling optimized
// code, any errors should have been caught when compiling
// unoptimized code. Any issues while optimizing are flagged by
// making the result invalid.
ASSERT(error.IsNull());
QueueElement* qelem = function_queue()->Remove();
delete qelem;
function = function_queue()->PeekFunction();
}
}
Thread::ExitIsolateAsHelper();
{
// Wait to be notified when the work queue is not empty.
MonitorLocker ml(queue_monitor_);
while (function_queue()->IsEmpty() && running_) {
ml.Wait();
}
}
} // while running
{
// Notify that the thread is done.
MonitorLocker ml_done(done_monitor_);
*done_ = true;
ml_done.Notify();
}
}
void BackgroundCompiler::CompileOptimized(const Function& function) {
ASSERT(Thread::Current()->IsMutatorThread());
MonitorLocker ml(queue_monitor_);
if (function_queue()->ContainsObj(function)) {
return;
}
QueueElement* elem = new QueueElement(function);
function_queue()->Add(elem);
ml.Notify();
}
void BackgroundCompiler::VisitPointers(ObjectPointerVisitor* visitor) {
function_queue_->VisitObjectPointers(visitor);
}
void BackgroundCompiler::Stop(BackgroundCompiler* task) {
ASSERT(Isolate::Current()->background_compiler() == task);
ASSERT(task != NULL);
BackgroundCompilationQueue* function_queue = task->function_queue();
Monitor* queue_monitor = task->queue_monitor_;
Monitor* done_monitor = task->done_monitor_;
bool* task_done = task->done_;
// Wake up compiler task and stop it.
{
MonitorLocker ml(task->queue_monitor_);
task->running_ = false;
// 'task' will be deleted by thread pool.
task = NULL;
ml.Notify(); // Stop waiting for the queue.
}
{
MonitorLocker ml_done(done_monitor);
while (!(*task_done)) {
// In case that the compiler is waiting for safepoint.
Isolate::Current()->thread_registry()->CheckSafepoint();
ml_done.Wait(1);
}
}
delete task_done;
delete done_monitor;
delete queue_monitor;
delete function_queue;
Isolate::Current()->set_background_compiler(NULL);
}
void BackgroundCompiler::EnsureInit(Thread* thread) {
ASSERT(thread->IsMutatorThread());
// Finalize NoSuchMethodError, _Mint; occasionally needed in optimized
// compilation.
Class& cls = Class::Handle(thread->zone(),
Library::LookupCoreClass(Symbols::NoSuchMethodError()));
Error& error = Error::Handle(thread->zone(),
cls.EnsureIsFinalized(thread));
ASSERT(error.IsNull());
cls = Library::LookupCoreClass(Symbols::_Mint());
error = cls.EnsureIsFinalized(thread);
ASSERT(error.IsNull());
bool start_task = false;
Isolate* isolate = thread->isolate();
{
MutexLocker ml(isolate->mutex());
if (isolate->background_compiler() == NULL) {
BackgroundCompiler* task = new BackgroundCompiler(isolate);
isolate->set_background_compiler(task);
start_task = true;
}
}
if (start_task) {
Dart::thread_pool()->Run(isolate->background_compiler());
}
}
#else // DART_PRECOMPILED_RUNTIME
DEFINE_RUNTIME_ENTRY(CompileFunction, 1) {
UNREACHABLE();
}
bool Compiler::IsBackgroundCompilation() {
UNREACHABLE();
return false;
}
RawError* Compiler::Compile(const Library& library, const Script& script) {
UNREACHABLE();
return Error::null();
}
RawError* Compiler::CompileClass(const Class& cls) {
UNREACHABLE();
return Error::null();
}
RawError* Compiler::CompileFunction(Thread* thread,
const Function& function) {
UNREACHABLE();
return Error::null();
}
RawError* Compiler::EnsureUnoptimizedCode(Thread* thread,
const Function& function) {
UNREACHABLE();
return Error::null();
}
RawError* Compiler::CompileOptimizedFunction(Thread* thread,
const Function& function,
intptr_t osr_id) {
UNREACHABLE();
return Error::null();
}
RawError* Compiler::CompileParsedFunction(
ParsedFunction* parsed_function) {
UNREACHABLE();
return Error::null();
}
void Compiler::ComputeLocalVarDescriptors(const Code& code) {
UNREACHABLE();
}
RawError* Compiler::CompileAllFunctions(const Class& cls) {
UNREACHABLE();
return Error::null();
}
void Compiler::CompileStaticInitializer(const Field& field) {
UNREACHABLE();
}
RawObject* Compiler::EvaluateStaticInitializer(const Field& field) {
ASSERT(field.HasPrecompiledInitializer());
const Function& initializer =
Function::Handle(field.PrecompiledInitializer());
return DartEntry::InvokeFunction(initializer, Object::empty_array());
}
RawObject* Compiler::ExecuteOnce(SequenceNode* fragment) {
UNREACHABLE();
return Object::null();
}
void BackgroundCompiler::CompileOptimized(const Function& function) {
UNREACHABLE();
}
void BackgroundCompiler::VisitPointers(ObjectPointerVisitor* visitor) {
UNREACHABLE();
}
void BackgroundCompiler::Stop(BackgroundCompiler* task) {
UNREACHABLE();
}
void BackgroundCompiler::EnsureInit(Thread* thread) {
UNREACHABLE();
}
#endif // DART_PRECOMPILED_RUNTIME
} // namespace dart