| // Copyright (c) 2013, 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/globals.h" // Needed here to get TARGET_ARCH_XXX. |
| |
| #include "vm/flow_graph_compiler.h" |
| |
| #include "vm/bit_vector.h" |
| #include "vm/cha.h" |
| #include "vm/compiler.h" |
| #include "vm/dart_entry.h" |
| #include "vm/debugger.h" |
| #include "vm/deopt_instructions.h" |
| #include "vm/exceptions.h" |
| #include "vm/flow_graph_allocator.h" |
| #include "vm/il_printer.h" |
| #include "vm/intrinsifier.h" |
| #include "vm/locations.h" |
| #include "vm/log.h" |
| #include "vm/longjump.h" |
| #include "vm/object_store.h" |
| #include "vm/parser.h" |
| #include "vm/raw_object.h" |
| #include "vm/stack_frame.h" |
| #include "vm/stub_code.h" |
| #include "vm/symbols.h" |
| |
| namespace dart { |
| |
| DEFINE_FLAG(bool, allow_absolute_addresses, true, |
| "Allow embedding absolute addresses in generated code."); |
| DEFINE_FLAG(bool, always_megamorphic_calls, false, |
| "Instance call always as megamorphic."); |
| DEFINE_FLAG(bool, enable_simd_inline, true, |
| "Enable inlining of SIMD related method calls."); |
| DEFINE_FLAG(int, min_optimization_counter_threshold, 5000, |
| "The minimum invocation count for a function."); |
| DEFINE_FLAG(int, optimization_counter_scale, 2000, |
| "The scale of invocation count, by size of the function."); |
| DEFINE_FLAG(bool, polymorphic_with_deopt, true, |
| "Polymorphic calls can be generated so that failure either causes " |
| "deoptimization or falls through to a megamorphic call"); |
| DEFINE_FLAG(bool, source_lines, false, "Emit source line as assembly comment."); |
| DEFINE_FLAG(bool, trace_inlining_intervals, false, |
| "Inlining interval diagnostics"); |
| DEFINE_FLAG(bool, use_megamorphic_stub, true, "Out of line megamorphic lookup"); |
| |
| DECLARE_FLAG(bool, background_compilation); |
| DECLARE_FLAG(bool, code_comments); |
| DECLARE_FLAG(bool, deoptimize_alot); |
| DECLARE_FLAG(int, deoptimize_every); |
| DECLARE_FLAG(charp, deoptimize_filter); |
| DECLARE_FLAG(bool, disassemble); |
| DECLARE_FLAG(bool, disassemble_optimized); |
| DECLARE_FLAG(bool, emit_edge_counters); |
| DECLARE_FLAG(bool, fields_may_be_reset); |
| DECLARE_FLAG(bool, guess_other_cid); |
| DECLARE_FLAG(bool, ic_range_profiling); |
| DECLARE_FLAG(bool, intrinsify); |
| DECLARE_FLAG(bool, load_deferred_eagerly); |
| DECLARE_FLAG(int, optimization_counter_threshold); |
| DECLARE_FLAG(bool, propagate_ic_data); |
| DECLARE_FLAG(int, regexp_optimization_counter_threshold); |
| DECLARE_FLAG(int, reoptimization_counter_threshold); |
| DECLARE_FLAG(int, stacktrace_every); |
| DECLARE_FLAG(charp, stacktrace_filter); |
| DECLARE_FLAG(bool, support_debugger); |
| DECLARE_FLAG(bool, use_field_guards); |
| DECLARE_FLAG(bool, use_cha_deopt); |
| DECLARE_FLAG(bool, use_osr); |
| DECLARE_FLAG(bool, warn_on_javascript_compatibility); |
| DECLARE_FLAG(bool, precompile_collect_closures); |
| DECLARE_FLAG(bool, print_stop_message); |
| DECLARE_FLAG(bool, lazy_dispatchers); |
| DECLARE_FLAG(bool, interpret_irregexp); |
| DECLARE_FLAG(bool, enable_mirrors); |
| DECLARE_FLAG(bool, link_natives_lazily); |
| |
| static void PrecompilationModeHandler(bool value) { |
| if (value) { |
| #if defined(TARGET_ARCH_IA32) |
| FATAL("Precompilation not supported on IA32"); |
| #endif |
| |
| FLAG_always_megamorphic_calls = true; |
| FLAG_polymorphic_with_deopt = false; |
| FLAG_optimization_counter_threshold = -1; |
| FLAG_use_field_guards = false; |
| FLAG_use_osr = false; |
| FLAG_emit_edge_counters = false; |
| FLAG_support_debugger = false; |
| FLAG_ic_range_profiling = false; |
| FLAG_collect_code = false; |
| FLAG_load_deferred_eagerly = true; |
| FLAG_deoptimize_alot = false; // Used in some tests. |
| FLAG_deoptimize_every = 0; // Used in some tests. |
| FLAG_guess_other_cid = true; |
| Compiler::set_always_optimize(true); |
| // Triggers assert if we try to recompile (e.g., because of deferred |
| // loading, deoptimization, ...). Noopt mode simulates behavior |
| // of precompiled code, therefore do not allow recompilation. |
| Compiler::set_allow_recompilation(false); |
| // TODO(srdjan): Enable CHA deoptimization when eager class finalization is |
| // implemented, either with precompilation or as a special pass. |
| FLAG_use_cha_deopt = false; |
| // Calling the PrintStopMessage stub is not supported in precompiled code |
| // since it is done at places where no pool pointer is loaded. |
| FLAG_print_stop_message = false; |
| |
| FLAG_lazy_dispatchers = false; |
| FLAG_interpret_irregexp = true; |
| FLAG_enable_mirrors = false; |
| FLAG_precompile_collect_closures = true; |
| FLAG_link_natives_lazily = true; |
| FLAG_fields_may_be_reset = true; |
| FLAG_allow_absolute_addresses = false; |
| |
| // Background compilation relies on two-stage compilation pipeline, |
| // while precompilation has only one. |
| FLAG_background_compilation = false; |
| } |
| } |
| |
| |
| DEFINE_FLAG_HANDLER(PrecompilationModeHandler, |
| precompilation, |
| "Precompilation mode"); |
| |
| |
| // Assign locations to incoming arguments, i.e., values pushed above spill slots |
| // with PushArgument. Recursively allocates from outermost to innermost |
| // environment. |
| void CompilerDeoptInfo::AllocateIncomingParametersRecursive( |
| Environment* env, |
| intptr_t* stack_height) { |
| if (env == NULL) return; |
| AllocateIncomingParametersRecursive(env->outer(), stack_height); |
| for (Environment::ShallowIterator it(env); !it.Done(); it.Advance()) { |
| if (it.CurrentLocation().IsInvalid() && |
| it.CurrentValue()->definition()->IsPushArgument()) { |
| it.SetCurrentLocation(Location::StackSlot((*stack_height)++)); |
| } |
| } |
| } |
| |
| |
| void CompilerDeoptInfo::EmitMaterializations(Environment* env, |
| DeoptInfoBuilder* builder) { |
| for (Environment::DeepIterator it(env); !it.Done(); it.Advance()) { |
| if (it.CurrentLocation().IsInvalid()) { |
| MaterializeObjectInstr* mat = |
| it.CurrentValue()->definition()->AsMaterializeObject(); |
| ASSERT(mat != NULL); |
| builder->AddMaterialization(mat); |
| } |
| } |
| } |
| |
| |
| FlowGraphCompiler::FlowGraphCompiler( |
| Assembler* assembler, |
| FlowGraph* flow_graph, |
| const ParsedFunction& parsed_function, |
| bool is_optimizing, |
| const GrowableArray<const Function*>& inline_id_to_function, |
| const GrowableArray<intptr_t>& caller_inline_id) |
| : thread_(Thread::Current()), |
| isolate_(Isolate::Current()), |
| zone_(Thread::Current()->zone()), |
| assembler_(assembler), |
| parsed_function_(parsed_function), |
| flow_graph_(*flow_graph), |
| block_order_(*flow_graph->CodegenBlockOrder(is_optimizing)), |
| current_block_(NULL), |
| exception_handlers_list_(NULL), |
| pc_descriptors_list_(NULL), |
| stackmap_table_builder_(NULL), |
| block_info_(block_order_.length()), |
| deopt_infos_(), |
| static_calls_target_table_(), |
| is_optimizing_(is_optimizing), |
| may_reoptimize_(false), |
| intrinsic_mode_(false), |
| double_class_(Class::ZoneHandle( |
| isolate_->object_store()->double_class())), |
| mint_class_(Class::ZoneHandle( |
| isolate_->object_store()->mint_class())), |
| float32x4_class_(Class::ZoneHandle( |
| isolate_->object_store()->float32x4_class())), |
| float64x2_class_(Class::ZoneHandle( |
| isolate_->object_store()->float64x2_class())), |
| int32x4_class_(Class::ZoneHandle( |
| isolate_->object_store()->int32x4_class())), |
| list_class_(Class::ZoneHandle( |
| Library::Handle(Library::CoreLibrary()). |
| LookupClass(Symbols::List()))), |
| parallel_move_resolver_(this), |
| pending_deoptimization_env_(NULL), |
| lazy_deopt_pc_offset_(Code::kInvalidPc), |
| deopt_id_to_ic_data_(NULL), |
| edge_counters_array_(Array::ZoneHandle()), |
| inlined_code_intervals_(Array::ZoneHandle(Object::empty_array().raw())), |
| inline_id_to_function_(inline_id_to_function), |
| caller_inline_id_(caller_inline_id) { |
| ASSERT(flow_graph->parsed_function().function().raw() == |
| parsed_function.function().raw()); |
| if (!is_optimizing) { |
| const intptr_t len = thread()->deopt_id(); |
| deopt_id_to_ic_data_ = new(zone()) ZoneGrowableArray<const ICData*>(len); |
| deopt_id_to_ic_data_->SetLength(len); |
| for (intptr_t i = 0; i < len; i++) { |
| (*deopt_id_to_ic_data_)[i] = NULL; |
| } |
| // TODO(fschneider): Abstract iteration into ICDataArrayIterator. |
| const Array& old_saved_ic_data = Array::Handle(zone(), |
| flow_graph->function().ic_data_array()); |
| const intptr_t saved_len = |
| old_saved_ic_data.IsNull() ? 0 : old_saved_ic_data.Length(); |
| for (intptr_t i = 1; i < saved_len; i++) { |
| ICData& ic_data = ICData::ZoneHandle(zone()); |
| ic_data ^= old_saved_ic_data.At(i); |
| (*deopt_id_to_ic_data_)[ic_data.deopt_id()] = &ic_data; |
| } |
| } |
| ASSERT(assembler != NULL); |
| ASSERT(!list_class_.IsNull()); |
| } |
| |
| |
| void FlowGraphCompiler::InitCompiler() { |
| pc_descriptors_list_ = new(zone()) DescriptorList(64); |
| exception_handlers_list_ = new(zone())ExceptionHandlerList(); |
| block_info_.Clear(); |
| // Conservative detection of leaf routines used to remove the stack check |
| // on function entry. |
| bool is_leaf = !parsed_function().function().IsClosureFunction() |
| && is_optimizing() |
| && !flow_graph().IsCompiledForOsr(); |
| // Initialize block info and search optimized (non-OSR) code for calls |
| // indicating a non-leaf routine and calls without IC data indicating |
| // possible reoptimization. |
| for (int i = 0; i < block_order_.length(); ++i) { |
| block_info_.Add(new(zone()) BlockInfo()); |
| if (is_optimizing() && !flow_graph().IsCompiledForOsr()) { |
| BlockEntryInstr* entry = block_order_[i]; |
| for (ForwardInstructionIterator it(entry); !it.Done(); it.Advance()) { |
| Instruction* current = it.Current(); |
| if (current->IsBranch()) { |
| current = current->AsBranch()->comparison(); |
| } |
| // In optimized code, ICData is always set in the instructions. |
| const ICData* ic_data = NULL; |
| if (current->IsInstanceCall()) { |
| ic_data = current->AsInstanceCall()->ic_data(); |
| } |
| if ((ic_data != NULL) && (ic_data->NumberOfUsedChecks() == 0)) { |
| may_reoptimize_ = true; |
| } |
| if (is_leaf && |
| !current->IsCheckStackOverflow() && |
| !current->IsParallelMove()) { |
| // Note that we do not care if the code contains instructions that |
| // can deoptimize. |
| LocationSummary* locs = current->locs(); |
| if ((locs != NULL) && locs->can_call()) { |
| is_leaf = false; |
| } |
| } |
| } |
| } |
| } |
| if (is_leaf) { |
| // Remove the stack overflow check at function entry. |
| Instruction* first = flow_graph_.graph_entry()->normal_entry()->next(); |
| if (first->IsCheckStackOverflow()) first->RemoveFromGraph(); |
| } |
| if (!is_optimizing()) { |
| // Initialize edge counter array. |
| const intptr_t num_counters = flow_graph_.preorder().length(); |
| const Array& edge_counters = |
| Array::Handle(Array::New(num_counters, Heap::kOld)); |
| const Smi& zero_smi = Smi::Handle(Smi::New(0)); |
| for (intptr_t i = 0; i < num_counters; ++i) { |
| edge_counters.SetAt(i, zero_smi); |
| } |
| edge_counters_array_ = edge_counters.raw(); |
| } |
| } |
| |
| |
| bool FlowGraphCompiler::CanOptimize() { |
| return FLAG_optimization_counter_threshold >= 0; |
| } |
| |
| |
| bool FlowGraphCompiler::CanOptimizeFunction() const { |
| return CanOptimize() && !parsed_function().function().HasBreakpoint(); |
| } |
| |
| |
| bool FlowGraphCompiler::CanOSRFunction() const { |
| return FLAG_use_osr & CanOptimizeFunction() && !is_optimizing(); |
| } |
| |
| |
| bool FlowGraphCompiler::ForceSlowPathForStackOverflow() const { |
| if (FLAG_stacktrace_every > 0 || FLAG_deoptimize_every > 0) { |
| return true; |
| } |
| if (FLAG_stacktrace_filter != NULL && |
| strstr(parsed_function().function().ToFullyQualifiedCString(), |
| FLAG_stacktrace_filter) != NULL) { |
| return true; |
| } |
| if (is_optimizing() && |
| FLAG_deoptimize_filter != NULL && |
| strstr(parsed_function().function().ToFullyQualifiedCString(), |
| FLAG_deoptimize_filter) != NULL) { |
| return true; |
| } |
| return false; |
| } |
| |
| |
| static bool IsEmptyBlock(BlockEntryInstr* block) { |
| return !block->IsCatchBlockEntry() && |
| !block->HasNonRedundantParallelMove() && |
| block->next()->IsGoto() && |
| !block->next()->AsGoto()->HasNonRedundantParallelMove() && |
| !block->IsIndirectEntry(); |
| } |
| |
| |
| void FlowGraphCompiler::CompactBlock(BlockEntryInstr* block) { |
| BlockInfo* block_info = block_info_[block->postorder_number()]; |
| |
| // Break out of cycles in the control flow graph. |
| if (block_info->is_marked()) { |
| return; |
| } |
| block_info->mark(); |
| |
| if (IsEmptyBlock(block)) { |
| // For empty blocks, record a corresponding nonempty target as their |
| // jump label. |
| BlockEntryInstr* target = block->next()->AsGoto()->successor(); |
| CompactBlock(target); |
| block_info->set_jump_label(GetJumpLabel(target)); |
| } |
| } |
| |
| |
| void FlowGraphCompiler::CompactBlocks() { |
| // This algorithm does not garbage collect blocks in place, but merely |
| // records forwarding label information. In this way it avoids having to |
| // change join and target entries. |
| Label* nonempty_label = NULL; |
| for (intptr_t i = block_order().length() - 1; i >= 1; --i) { |
| BlockEntryInstr* block = block_order()[i]; |
| |
| // Unoptimized code must emit all possible deoptimization points. |
| if (is_optimizing()) { |
| CompactBlock(block); |
| } |
| |
| // For nonempty blocks, record the next nonempty block in the block |
| // order. Since no code is emitted for empty blocks, control flow is |
| // eligible to fall through to the next nonempty one. |
| if (!WasCompacted(block)) { |
| BlockInfo* block_info = block_info_[block->postorder_number()]; |
| block_info->set_next_nonempty_label(nonempty_label); |
| nonempty_label = GetJumpLabel(block); |
| } |
| } |
| |
| ASSERT(block_order()[0]->IsGraphEntry()); |
| BlockInfo* block_info = block_info_[block_order()[0]->postorder_number()]; |
| block_info->set_next_nonempty_label(nonempty_label); |
| } |
| |
| |
| void FlowGraphCompiler::EmitInstructionPrologue(Instruction* instr) { |
| if (!is_optimizing()) { |
| if (instr->CanBecomeDeoptimizationTarget() && !instr->IsGoto()) { |
| // Instructions that can be deoptimization targets need to record kDeopt |
| // PcDescriptor corresponding to their deopt id. GotoInstr records its |
| // own so that it can control the placement. |
| AddCurrentDescriptor(RawPcDescriptors::kDeopt, |
| instr->deopt_id(), |
| instr->token_pos()); |
| } |
| AllocateRegistersLocally(instr); |
| } else if (instr->MayThrow() && |
| (CurrentTryIndex() != CatchClauseNode::kInvalidTryIndex)) { |
| // Optimized try-block: Sync locals to fixed stack locations. |
| EmitTrySync(instr, CurrentTryIndex()); |
| } |
| } |
| |
| |
| |
| void FlowGraphCompiler::EmitSourceLine(Instruction* instr) { |
| if ((instr->token_pos() == Scanner::kNoSourcePos) || (instr->env() == NULL)) { |
| return; |
| } |
| const Script& script = |
| Script::Handle(zone(), instr->env()->function().script()); |
| intptr_t line_nr; |
| intptr_t column_nr; |
| script.GetTokenLocation(instr->token_pos(), &line_nr, &column_nr); |
| const String& line = String::Handle(zone(), script.GetLine(line_nr)); |
| assembler()->Comment("Line %" Pd " in '%s':\n %s", |
| line_nr, |
| instr->env()->function().ToFullyQualifiedCString(), |
| line.ToCString()); |
| } |
| |
| |
| static void LoopInfoComment( |
| Assembler* assembler, |
| const BlockEntryInstr& block, |
| const ZoneGrowableArray<BlockEntryInstr*>& loop_headers) { |
| if (Assembler::EmittingComments()) { |
| for (intptr_t loop_id = 0; loop_id < loop_headers.length(); ++loop_id) { |
| for (BitVector::Iterator loop_it(loop_headers[loop_id]->loop_info()); |
| !loop_it.Done(); |
| loop_it.Advance()) { |
| if (loop_it.Current() == block.preorder_number()) { |
| assembler->Comment(" Loop %" Pd "", loop_id); |
| } |
| } |
| } |
| } |
| } |
| |
| |
| // We collect intervals while generating code. |
| struct IntervalStruct { |
| // 'start' and 'end' are pc-offsets. |
| intptr_t start; |
| intptr_t inlining_id; |
| IntervalStruct(intptr_t s, intptr_t id) : start(s), inlining_id(id) {} |
| void Dump() { |
| THR_Print("start: 0x%" Px " iid: %" Pd " ", start, inlining_id); |
| } |
| }; |
| |
| |
| void FlowGraphCompiler::VisitBlocks() { |
| CompactBlocks(); |
| const ZoneGrowableArray<BlockEntryInstr*>* loop_headers = NULL; |
| if (Assembler::EmittingComments()) { |
| // 'loop_headers' were cleared, recompute. |
| loop_headers = flow_graph().ComputeLoops(); |
| ASSERT(loop_headers != NULL); |
| } |
| |
| // For collecting intervals of inlined code. |
| GrowableArray<IntervalStruct> intervals; |
| intptr_t prev_offset = 0; |
| intptr_t prev_inlining_id = 0; |
| intptr_t max_inlining_id = 0; |
| for (intptr_t i = 0; i < block_order().length(); ++i) { |
| // Compile the block entry. |
| BlockEntryInstr* entry = block_order()[i]; |
| assembler()->Comment("B%" Pd "", entry->block_id()); |
| set_current_block(entry); |
| |
| if (WasCompacted(entry)) { |
| continue; |
| } |
| |
| #if defined(DEBUG) |
| if (!is_optimizing()) { |
| FrameStateClear(); |
| } |
| #endif |
| |
| LoopInfoComment(assembler(), *entry, *loop_headers); |
| |
| entry->set_offset(assembler()->CodeSize()); |
| entry->EmitNativeCode(this); |
| // Compile all successors until an exit, branch, or a block entry. |
| for (ForwardInstructionIterator it(entry); !it.Done(); it.Advance()) { |
| Instruction* instr = it.Current(); |
| // Compose intervals. |
| if (instr->has_inlining_id() && is_optimizing()) { |
| if (prev_inlining_id != instr->inlining_id()) { |
| intervals.Add(IntervalStruct(prev_offset, prev_inlining_id)); |
| prev_offset = assembler()->CodeSize(); |
| prev_inlining_id = instr->inlining_id(); |
| if (prev_inlining_id > max_inlining_id) { |
| max_inlining_id = prev_inlining_id; |
| } |
| } |
| } |
| if (FLAG_code_comments || |
| FLAG_disassemble || FLAG_disassemble_optimized) { |
| if (FLAG_source_lines) { |
| EmitSourceLine(instr); |
| } |
| EmitComment(instr); |
| } |
| if (instr->IsParallelMove()) { |
| parallel_move_resolver_.EmitNativeCode(instr->AsParallelMove()); |
| } else { |
| EmitInstructionPrologue(instr); |
| ASSERT(pending_deoptimization_env_ == NULL); |
| pending_deoptimization_env_ = instr->env(); |
| instr->EmitNativeCode(this); |
| pending_deoptimization_env_ = NULL; |
| EmitInstructionEpilogue(instr); |
| } |
| |
| #if defined(DEBUG) |
| if (!is_optimizing()) { |
| FrameStateUpdateWith(instr); |
| } |
| #endif |
| } |
| |
| #if defined(DEBUG) |
| ASSERT(is_optimizing() || FrameStateIsSafeToCall()); |
| #endif |
| } |
| |
| if (inline_id_to_function_.length() > max_inlining_id + 1) { |
| // TODO(srdjan): Some inlined function can disappear, |
| // truncate 'inline_id_to_function_'. |
| } |
| |
| if (is_optimizing()) { |
| LogBlock lb; |
| intervals.Add(IntervalStruct(prev_offset, prev_inlining_id)); |
| inlined_code_intervals_ = |
| Array::New(intervals.length() * Code::kInlIntNumEntries, Heap::kOld); |
| Smi& start_h = Smi::Handle(); |
| Smi& caller_inline_id = Smi::Handle(); |
| Smi& inline_id = Smi::Handle(); |
| for (intptr_t i = 0; i < intervals.length(); i++) { |
| if (FLAG_trace_inlining_intervals && is_optimizing()) { |
| const Function& function = |
| *inline_id_to_function_.At(intervals[i].inlining_id); |
| intervals[i].Dump(); |
| THR_Print(" parent iid %" Pd " %s\n", |
| caller_inline_id_[intervals[i].inlining_id], |
| function.ToQualifiedCString()); |
| } |
| |
| const intptr_t id = intervals[i].inlining_id; |
| start_h = Smi::New(intervals[i].start); |
| inline_id = Smi::New(id); |
| caller_inline_id = Smi::New(caller_inline_id_[intervals[i].inlining_id]); |
| |
| const intptr_t p = i * Code::kInlIntNumEntries; |
| inlined_code_intervals_.SetAt(p + Code::kInlIntStart, start_h); |
| inlined_code_intervals_.SetAt(p + Code::kInlIntInliningId, inline_id); |
| } |
| } |
| set_current_block(NULL); |
| if (FLAG_trace_inlining_intervals && is_optimizing()) { |
| LogBlock lb; |
| THR_Print("Intervals:\n"); |
| for (intptr_t cc = 0; cc < caller_inline_id_.length(); cc++) { |
| THR_Print(" iid: %" Pd " caller iid: %" Pd "\n", |
| cc, caller_inline_id_[cc]); |
| } |
| Smi& temp = Smi::Handle(); |
| for (intptr_t i = 0; i < inlined_code_intervals_.Length(); |
| i += Code::kInlIntNumEntries) { |
| temp ^= inlined_code_intervals_.At(i + Code::kInlIntStart); |
| ASSERT(!temp.IsNull()); |
| THR_Print("% " Pd " start: 0x%" Px " ", i, temp.Value()); |
| temp ^= inlined_code_intervals_.At(i + Code::kInlIntInliningId); |
| THR_Print("iid: %" Pd " ", temp.Value()); |
| } |
| } |
| } |
| |
| |
| void FlowGraphCompiler::Bailout(const char* reason) { |
| const Function& function = parsed_function_.function(); |
| Report::MessageF(Report::kBailout, |
| Script::Handle(function.script()), |
| function.token_pos(), |
| "FlowGraphCompiler Bailout: %s %s", |
| String::Handle(function.name()).ToCString(), |
| reason); |
| UNREACHABLE(); |
| } |
| |
| |
| void FlowGraphCompiler::EmitTrySync(Instruction* instr, intptr_t try_index) { |
| ASSERT(is_optimizing()); |
| Environment* env = instr->env()->Outermost(); |
| CatchBlockEntryInstr* catch_block = |
| flow_graph().graph_entry()->GetCatchEntry(try_index); |
| const GrowableArray<Definition*>* idefs = catch_block->initial_definitions(); |
| |
| // Construct a ParallelMove instruction for parameters and locals. Skip the |
| // special locals exception_var and stacktrace_var since they will be filled |
| // when an exception is thrown. Constant locations are known to be the same |
| // at all instructions that may throw, and do not need to be materialized. |
| |
| // Parameters first. |
| intptr_t i = 0; |
| const intptr_t num_non_copied_params = flow_graph().num_non_copied_params(); |
| ParallelMoveInstr* move_instr = new(zone()) ParallelMoveInstr(); |
| for (; i < num_non_copied_params; ++i) { |
| // Don't sync captured parameters. They are not in the environment. |
| if (flow_graph().captured_parameters()->Contains(i)) continue; |
| if ((*idefs)[i]->IsConstant()) continue; // Common constants |
| Location src = env->LocationAt(i); |
| intptr_t dest_index = i - num_non_copied_params; |
| Location dest = Location::StackSlot(dest_index); |
| move_instr->AddMove(dest, src); |
| } |
| |
| // Process locals. Skip exception_var and stacktrace_var. |
| intptr_t local_base = kFirstLocalSlotFromFp + num_non_copied_params; |
| intptr_t ex_idx = local_base - catch_block->exception_var().index(); |
| intptr_t st_idx = local_base - catch_block->stacktrace_var().index(); |
| for (; i < flow_graph().variable_count(); ++i) { |
| // Don't sync captured parameters. They are not in the environment. |
| if (flow_graph().captured_parameters()->Contains(i)) continue; |
| if (i == ex_idx || i == st_idx) continue; |
| if ((*idefs)[i]->IsConstant()) continue; |
| Location src = env->LocationAt(i); |
| ASSERT(!src.IsFpuRegister()); |
| ASSERT(!src.IsDoubleStackSlot()); |
| intptr_t dest_index = i - num_non_copied_params; |
| Location dest = Location::StackSlot(dest_index); |
| move_instr->AddMove(dest, src); |
| // Update safepoint bitmap to indicate that the target location |
| // now contains a pointer. |
| instr->locs()->SetStackBit(dest_index); |
| } |
| parallel_move_resolver()->EmitNativeCode(move_instr); |
| } |
| |
| |
| intptr_t FlowGraphCompiler::StackSize() const { |
| if (is_optimizing_) { |
| return flow_graph_.graph_entry()->spill_slot_count(); |
| } else { |
| return parsed_function_.num_stack_locals() + |
| parsed_function_.num_copied_params(); |
| } |
| } |
| |
| |
| Label* FlowGraphCompiler::GetJumpLabel( |
| BlockEntryInstr* block_entry) const { |
| const intptr_t block_index = block_entry->postorder_number(); |
| return block_info_[block_index]->jump_label(); |
| } |
| |
| |
| bool FlowGraphCompiler::WasCompacted( |
| BlockEntryInstr* block_entry) const { |
| const intptr_t block_index = block_entry->postorder_number(); |
| return block_info_[block_index]->WasCompacted(); |
| } |
| |
| |
| Label* FlowGraphCompiler::NextNonEmptyLabel() const { |
| const intptr_t current_index = current_block()->postorder_number(); |
| return block_info_[current_index]->next_nonempty_label(); |
| } |
| |
| |
| bool FlowGraphCompiler::CanFallThroughTo(BlockEntryInstr* block_entry) const { |
| return NextNonEmptyLabel() == GetJumpLabel(block_entry); |
| } |
| |
| |
| BranchLabels FlowGraphCompiler::CreateBranchLabels(BranchInstr* branch) const { |
| Label* true_label = GetJumpLabel(branch->true_successor()); |
| Label* false_label = GetJumpLabel(branch->false_successor()); |
| Label* fall_through = NextNonEmptyLabel(); |
| BranchLabels result = { true_label, false_label, fall_through }; |
| return result; |
| } |
| |
| |
| void FlowGraphCompiler::AddSlowPathCode(SlowPathCode* code) { |
| slow_path_code_.Add(code); |
| } |
| |
| |
| void FlowGraphCompiler::GenerateDeferredCode() { |
| for (intptr_t i = 0; i < slow_path_code_.length(); i++) { |
| slow_path_code_[i]->GenerateCode(this); |
| } |
| for (intptr_t i = 0; i < deopt_infos_.length(); i++) { |
| deopt_infos_[i]->GenerateCode(this, i); |
| } |
| } |
| |
| |
| void FlowGraphCompiler::AddExceptionHandler(intptr_t try_index, |
| intptr_t outer_try_index, |
| intptr_t pc_offset, |
| const Array& handler_types, |
| bool needs_stacktrace) { |
| exception_handlers_list_->AddHandler(try_index, |
| outer_try_index, |
| pc_offset, |
| handler_types, |
| needs_stacktrace); |
| } |
| |
| |
| void FlowGraphCompiler::SetNeedsStacktrace(intptr_t try_index) { |
| exception_handlers_list_->SetNeedsStacktrace(try_index); |
| } |
| |
| |
| // Uses current pc position and try-index. |
| void FlowGraphCompiler::AddCurrentDescriptor(RawPcDescriptors::Kind kind, |
| intptr_t deopt_id, |
| intptr_t token_pos) { |
| // When running with optimizations disabled, don't emit deopt-descriptors. |
| if (!CanOptimize() && (kind == RawPcDescriptors::kDeopt)) return; |
| pc_descriptors_list()->AddDescriptor(kind, |
| assembler()->CodeSize(), |
| deopt_id, |
| token_pos, |
| CurrentTryIndex()); |
| } |
| |
| |
| void FlowGraphCompiler::AddStaticCallTarget(const Function& func) { |
| ASSERT(func.IsZoneHandle()); |
| static_calls_target_table_.Add( |
| new(zone()) StaticCallsStruct(assembler()->CodeSize(), &func, NULL)); |
| } |
| |
| |
| void FlowGraphCompiler::AddStubCallTarget(const Code& code) { |
| ASSERT(code.IsZoneHandle()); |
| static_calls_target_table_.Add( |
| new(zone()) StaticCallsStruct(assembler()->CodeSize(), NULL, &code)); |
| } |
| |
| |
| void FlowGraphCompiler::AddDeoptIndexAtCall(intptr_t deopt_id, |
| intptr_t token_pos) { |
| ASSERT(is_optimizing()); |
| ASSERT(!intrinsic_mode()); |
| CompilerDeoptInfo* info = |
| new(zone()) CompilerDeoptInfo(deopt_id, |
| ICData::kDeoptAtCall, |
| 0, // No flags. |
| pending_deoptimization_env_); |
| info->set_pc_offset(assembler()->CodeSize()); |
| deopt_infos_.Add(info); |
| } |
| |
| |
| // This function must be in sync with FlowGraphCompiler::SaveLiveRegisters |
| // and FlowGraphCompiler::SlowPathEnvironmentFor. |
| // See StackFrame::VisitObjectPointers for the details of how stack map is |
| // interpreted. |
| void FlowGraphCompiler::RecordSafepoint(LocationSummary* locs) { |
| if (is_optimizing() || locs->live_registers()->HasUntaggedValues()) { |
| const intptr_t spill_area_size = is_optimizing() ? |
| flow_graph_.graph_entry()->spill_slot_count() : 0; |
| |
| RegisterSet* registers = locs->live_registers(); |
| ASSERT(registers != NULL); |
| const intptr_t kFpuRegisterSpillFactor = |
| kFpuRegisterSize / kWordSize; |
| const intptr_t live_registers_size = registers->CpuRegisterCount() + |
| (registers->FpuRegisterCount() * kFpuRegisterSpillFactor); |
| |
| BitmapBuilder* bitmap = locs->stack_bitmap(); |
| |
| // An instruction may have two safepoints in deferred code. The |
| // call to RecordSafepoint has the side-effect of appending the live |
| // registers to the bitmap. This is why the second call to RecordSafepoint |
| // with the same instruction (and same location summary) sees a bitmap that |
| // is larger that StackSize(). It will never be larger than StackSize() + |
| // live_registers_size. |
| ASSERT(bitmap->Length() <= (spill_area_size + live_registers_size)); |
| // The first safepoint will grow the bitmap to be the size of |
| // spill_area_size but the second safepoint will truncate the bitmap and |
| // append the live registers to it again. The bitmap produced by both calls |
| // will be the same. |
| bitmap->SetLength(spill_area_size); |
| |
| // Mark the bits in the stack map in the same order we push registers in |
| // slow path code (see FlowGraphCompiler::SaveLiveRegisters). |
| // |
| // Slow path code can have registers at the safepoint. |
| if (!locs->always_calls()) { |
| RegisterSet* regs = locs->live_registers(); |
| if (regs->FpuRegisterCount() > 0) { |
| // Denote FPU registers with 0 bits in the stackmap. Based on the |
| // assumption that there are normally few live FPU registers, this |
| // encoding is simpler and roughly as compact as storing a separate |
| // count of FPU registers. |
| // |
| // FPU registers have the highest register number at the highest |
| // address (i.e., first in the stackmap). |
| for (intptr_t i = kNumberOfFpuRegisters - 1; i >= 0; --i) { |
| FpuRegister reg = static_cast<FpuRegister>(i); |
| if (regs->ContainsFpuRegister(reg)) { |
| for (intptr_t j = 0; j < kFpuRegisterSpillFactor; ++j) { |
| bitmap->Set(bitmap->Length(), false); |
| } |
| } |
| } |
| } |
| // General purpose registers have the highest register number at the |
| // highest address (i.e., first in the stackmap). |
| for (intptr_t i = kNumberOfCpuRegisters - 1; i >= 0; --i) { |
| Register reg = static_cast<Register>(i); |
| if (locs->live_registers()->ContainsRegister(reg)) { |
| bitmap->Set(bitmap->Length(), locs->live_registers()->IsTagged(reg)); |
| } |
| } |
| } |
| |
| intptr_t register_bit_count = bitmap->Length() - spill_area_size; |
| stackmap_table_builder()->AddEntry(assembler()->CodeSize(), |
| bitmap, |
| register_bit_count); |
| } |
| } |
| |
| |
| // This function must be kept in sync with: |
| // |
| // FlowGraphCompiler::RecordSafepoint |
| // FlowGraphCompiler::SaveLiveRegisters |
| // MaterializeObjectInstr::RemapRegisters |
| // |
| Environment* FlowGraphCompiler::SlowPathEnvironmentFor( |
| Instruction* instruction) { |
| if (instruction->env() == NULL) { |
| ASSERT(!is_optimizing()); |
| return NULL; |
| } |
| |
| Environment* env = instruction->env()->DeepCopy(zone()); |
| // 1. Iterate the registers in the order they will be spilled to compute |
| // the slots they will be spilled to. |
| intptr_t next_slot = StackSize() + env->CountArgsPushed(); |
| RegisterSet* regs = instruction->locs()->live_registers(); |
| intptr_t fpu_reg_slots[kNumberOfFpuRegisters]; |
| intptr_t cpu_reg_slots[kNumberOfCpuRegisters]; |
| const intptr_t kFpuRegisterSpillFactor = kFpuRegisterSize / kWordSize; |
| // FPU registers are spilled first from highest to lowest register number. |
| for (intptr_t i = kNumberOfFpuRegisters - 1; i >= 0; --i) { |
| FpuRegister reg = static_cast<FpuRegister>(i); |
| if (regs->ContainsFpuRegister(reg)) { |
| // We use the lowest address (thus highest index) to identify a |
| // multi-word spill slot. |
| next_slot += kFpuRegisterSpillFactor; |
| fpu_reg_slots[i] = (next_slot - 1); |
| } else { |
| fpu_reg_slots[i] = -1; |
| } |
| } |
| // General purpose registers are spilled from highest to lowest register |
| // number. |
| for (intptr_t i = kNumberOfCpuRegisters - 1; i >= 0; --i) { |
| Register reg = static_cast<Register>(i); |
| if (regs->ContainsRegister(reg)) { |
| cpu_reg_slots[i] = next_slot++; |
| } else { |
| cpu_reg_slots[i] = -1; |
| } |
| } |
| |
| // 2. Iterate the environment and replace register locations with the |
| // corresponding spill slot locations. |
| for (Environment::DeepIterator it(env); !it.Done(); it.Advance()) { |
| Location loc = it.CurrentLocation(); |
| Value* value = it.CurrentValue(); |
| it.SetCurrentLocation(loc.RemapForSlowPath( |
| value->definition(), cpu_reg_slots, fpu_reg_slots)); |
| } |
| |
| return env; |
| } |
| |
| |
| Label* FlowGraphCompiler::AddDeoptStub(intptr_t deopt_id, |
| ICData::DeoptReasonId reason, |
| uint32_t flags) { |
| if (intrinsic_mode()) { |
| return &intrinsic_slow_path_label_; |
| } |
| |
| // No deoptimization allowed when 'always_optimize' is set. |
| ASSERT(!Compiler::always_optimize()); |
| ASSERT(is_optimizing_); |
| CompilerDeoptInfoWithStub* stub = |
| new(zone()) CompilerDeoptInfoWithStub(deopt_id, |
| reason, |
| flags, |
| pending_deoptimization_env_); |
| deopt_infos_.Add(stub); |
| return stub->entry_label(); |
| } |
| |
| |
| void FlowGraphCompiler::FinalizeExceptionHandlers(const Code& code) { |
| ASSERT(exception_handlers_list_ != NULL); |
| const ExceptionHandlers& handlers = ExceptionHandlers::Handle( |
| exception_handlers_list_->FinalizeExceptionHandlers(code.EntryPoint())); |
| code.set_exception_handlers(handlers); |
| if (FLAG_compiler_stats) { |
| Thread* thread = Thread::Current(); |
| INC_STAT(thread, total_code_size, |
| ExceptionHandlers::InstanceSize(handlers.num_entries())); |
| INC_STAT(thread, total_code_size, handlers.num_entries() * sizeof(uword)); |
| } |
| } |
| |
| |
| void FlowGraphCompiler::FinalizePcDescriptors(const Code& code) { |
| ASSERT(pc_descriptors_list_ != NULL); |
| const PcDescriptors& descriptors = PcDescriptors::Handle( |
| pc_descriptors_list_->FinalizePcDescriptors(code.EntryPoint())); |
| if (!is_optimizing_) descriptors.Verify(parsed_function_.function()); |
| code.set_pc_descriptors(descriptors); |
| code.set_lazy_deopt_pc_offset(lazy_deopt_pc_offset_); |
| } |
| |
| |
| RawArray* FlowGraphCompiler::CreateDeoptInfo(Assembler* assembler) { |
| // No deopt information if we 'always_optimize' (no deoptimization allowed). |
| if (Compiler::always_optimize()) { |
| return Array::empty_array().raw(); |
| } |
| // For functions with optional arguments, all incoming arguments are copied |
| // to spill slots. The deoptimization environment does not track them. |
| const Function& function = parsed_function().function(); |
| const intptr_t incoming_arg_count = |
| function.HasOptionalParameters() ? 0 : function.num_fixed_parameters(); |
| DeoptInfoBuilder builder(zone(), incoming_arg_count, assembler); |
| |
| intptr_t deopt_info_table_size = DeoptTable::SizeFor(deopt_infos_.length()); |
| if (deopt_info_table_size == 0) { |
| return Object::empty_array().raw(); |
| } else { |
| const Array& array = |
| Array::Handle(Array::New(deopt_info_table_size, Heap::kOld)); |
| Smi& offset = Smi::Handle(); |
| TypedData& info = TypedData::Handle(); |
| Smi& reason_and_flags = Smi::Handle(); |
| for (intptr_t i = 0; i < deopt_infos_.length(); i++) { |
| offset = Smi::New(deopt_infos_[i]->pc_offset()); |
| info = deopt_infos_[i]->CreateDeoptInfo(this, &builder, array); |
| reason_and_flags = DeoptTable::EncodeReasonAndFlags( |
| deopt_infos_[i]->reason(), |
| deopt_infos_[i]->flags()); |
| DeoptTable::SetEntry(array, i, offset, info, reason_and_flags); |
| } |
| return array.raw(); |
| } |
| } |
| |
| |
| void FlowGraphCompiler::FinalizeStackmaps(const Code& code) { |
| if (stackmap_table_builder_ == NULL) { |
| code.set_stackmaps(Object::null_array()); |
| } else { |
| // Finalize the stack map array and add it to the code object. |
| code.set_stackmaps( |
| Array::Handle(stackmap_table_builder_->FinalizeStackmaps(code))); |
| } |
| } |
| |
| |
| void FlowGraphCompiler::FinalizeVarDescriptors(const Code& code) { |
| if (code.is_optimized()) { |
| // Optimized code does not need variable descriptors. They are |
| // only stored in the unoptimized version. |
| code.set_var_descriptors(Object::empty_var_descriptors()); |
| return; |
| } |
| LocalVarDescriptors& var_descs = LocalVarDescriptors::Handle(); |
| if (parsed_function().node_sequence() == NULL) { |
| // Eager local var descriptors computation for Irregexp function as it is |
| // complicated to factor out. |
| // TODO(srdjan): Consider canonicalizing and reusing the local var |
| // descriptor for IrregexpFunction. |
| ASSERT(flow_graph().IsIrregexpFunction()); |
| var_descs = LocalVarDescriptors::New(1); |
| RawLocalVarDescriptors::VarInfo info; |
| info.set_kind(RawLocalVarDescriptors::kSavedCurrentContext); |
| info.scope_id = 0; |
| info.begin_pos = 0; |
| info.end_pos = 0; |
| info.set_index(parsed_function().current_context_var()->index()); |
| var_descs.SetVar(0, Symbols::CurrentContextVar(), &info); |
| } |
| code.set_var_descriptors(var_descs); |
| } |
| |
| |
| void FlowGraphCompiler::FinalizeStaticCallTargetsTable(const Code& code) { |
| ASSERT(code.static_calls_target_table() == Array::null()); |
| const Array& targets = Array::Handle(zone(), Array::New( |
| (static_calls_target_table_.length() * Code::kSCallTableEntryLength), |
| Heap::kOld)); |
| Smi& smi_offset = Smi::Handle(zone()); |
| for (intptr_t i = 0; i < static_calls_target_table_.length(); i++) { |
| const intptr_t target_ix = Code::kSCallTableEntryLength * i; |
| smi_offset = Smi::New(static_calls_target_table_[i]->offset); |
| targets.SetAt(target_ix + Code::kSCallTableOffsetEntry, smi_offset); |
| if (static_calls_target_table_[i]->function != NULL) { |
| targets.SetAt(target_ix + Code::kSCallTableFunctionEntry, |
| *static_calls_target_table_[i]->function); |
| } |
| if (static_calls_target_table_[i]->code != NULL) { |
| targets.SetAt(target_ix + Code::kSCallTableCodeEntry, |
| *static_calls_target_table_[i]->code); |
| } |
| } |
| code.set_static_calls_target_table(targets); |
| INC_STAT(Thread::Current(), |
| total_code_size, |
| targets.Length() * sizeof(uword)); |
| } |
| |
| |
| // Returns 'true' if regular code generation should be skipped. |
| bool FlowGraphCompiler::TryIntrinsify() { |
| // Intrinsification skips arguments checks, therefore disable if in checked |
| // mode. |
| if (FLAG_intrinsify && !isolate()->flags().type_checks()) { |
| if (parsed_function().function().kind() == RawFunction::kImplicitGetter) { |
| // An implicit getter must have a specific AST structure. |
| const SequenceNode& sequence_node = *parsed_function().node_sequence(); |
| ASSERT(sequence_node.length() == 1); |
| ASSERT(sequence_node.NodeAt(0)->IsReturnNode()); |
| const ReturnNode& return_node = *sequence_node.NodeAt(0)->AsReturnNode(); |
| ASSERT(return_node.value()->IsLoadInstanceFieldNode()); |
| const LoadInstanceFieldNode& load_node = |
| *return_node.value()->AsLoadInstanceFieldNode(); |
| // Only intrinsify getter if the field cannot contain a mutable double. |
| // Reading from a mutable double box requires allocating a fresh double. |
| if (load_node.field().guarded_cid() == kDynamicCid) { |
| GenerateInlinedGetter(load_node.field().Offset()); |
| return true; |
| } |
| return false; |
| } |
| if (parsed_function().function().kind() == RawFunction::kImplicitSetter) { |
| // An implicit setter must have a specific AST structure. |
| // Sequence node has one store node and one return NULL node. |
| const SequenceNode& sequence_node = *parsed_function().node_sequence(); |
| ASSERT(sequence_node.length() == 2); |
| ASSERT(sequence_node.NodeAt(0)->IsStoreInstanceFieldNode()); |
| ASSERT(sequence_node.NodeAt(1)->IsReturnNode()); |
| const StoreInstanceFieldNode& store_node = |
| *sequence_node.NodeAt(0)->AsStoreInstanceFieldNode(); |
| if (store_node.field().guarded_cid() == kDynamicCid) { |
| GenerateInlinedSetter(store_node.field().Offset()); |
| return true; |
| } |
| } |
| } |
| |
| EnterIntrinsicMode(); |
| |
| Intrinsifier::Intrinsify(parsed_function(), this); |
| |
| ExitIntrinsicMode(); |
| // "Deoptimization" from intrinsic continues here. All deoptimization |
| // branches from intrinsic code redirect to here where the slow-path |
| // (normal function body) starts. |
| // This means that there must not be any side-effects in intrinsic code |
| // before any deoptimization point. |
| ASSERT(!intrinsic_slow_path_label_.IsBound()); |
| assembler()->Bind(&intrinsic_slow_path_label_); |
| return false; |
| } |
| |
| |
| void FlowGraphCompiler::GenerateInstanceCall( |
| intptr_t deopt_id, |
| intptr_t token_pos, |
| intptr_t argument_count, |
| LocationSummary* locs, |
| const ICData& ic_data) { |
| if (FLAG_always_megamorphic_calls) { |
| EmitMegamorphicInstanceCall(ic_data, argument_count, |
| deopt_id, token_pos, locs); |
| return; |
| } |
| ASSERT(!ic_data.IsNull()); |
| if (is_optimizing() && (ic_data.NumberOfUsedChecks() == 0)) { |
| // Emit IC call that will count and thus may need reoptimization at |
| // function entry. |
| ASSERT(!is_optimizing() |
| || may_reoptimize() |
| || flow_graph().IsCompiledForOsr()); |
| switch (ic_data.NumArgsTested()) { |
| case 1: |
| EmitOptimizedInstanceCall( |
| *StubCode::OneArgOptimizedCheckInlineCache_entry(), ic_data, |
| argument_count, deopt_id, token_pos, locs); |
| return; |
| case 2: |
| EmitOptimizedInstanceCall( |
| *StubCode::TwoArgsOptimizedCheckInlineCache_entry(), ic_data, |
| argument_count, deopt_id, token_pos, locs); |
| return; |
| default: |
| UNIMPLEMENTED(); |
| } |
| return; |
| } |
| |
| if (is_optimizing() && |
| // Do not make the instance call megamorphic if the callee needs to decode |
| // the calling code sequence to lookup the ic data and verify if a JS |
| // warning has already been issued or not. |
| (!FLAG_warn_on_javascript_compatibility || |
| !ic_data.MayCheckForJSWarning())) { |
| EmitMegamorphicInstanceCall(ic_data, argument_count, |
| deopt_id, token_pos, locs); |
| return; |
| } |
| |
| switch (ic_data.NumArgsTested()) { |
| case 1: |
| EmitInstanceCall( |
| *StubCode::OneArgCheckInlineCache_entry(), ic_data, argument_count, |
| deopt_id, token_pos, locs); |
| break; |
| case 2: |
| EmitInstanceCall( |
| *StubCode::TwoArgsCheckInlineCache_entry(), ic_data, argument_count, |
| deopt_id, token_pos, locs); |
| break; |
| default: |
| UNIMPLEMENTED(); |
| } |
| } |
| |
| |
| void FlowGraphCompiler::GenerateStaticCall(intptr_t deopt_id, |
| intptr_t token_pos, |
| const Function& function, |
| intptr_t argument_count, |
| const Array& argument_names, |
| LocationSummary* locs, |
| const ICData& ic_data) { |
| const Array& arguments_descriptor = Array::ZoneHandle( |
| ic_data.IsNull() ? ArgumentsDescriptor::New(argument_count, |
| argument_names) |
| : ic_data.arguments_descriptor()); |
| // Proper reporting of Javascript incompatibilities requires icdata and |
| // may therefore prevent the optimization of some static calls. |
| if (is_optimizing() && |
| !(FLAG_warn_on_javascript_compatibility && |
| (MethodRecognizer::RecognizeKind(function) == |
| MethodRecognizer::kObjectIdentical))) { |
| EmitOptimizedStaticCall(function, arguments_descriptor, |
| argument_count, deopt_id, token_pos, locs); |
| } else { |
| ICData& call_ic_data = ICData::ZoneHandle(ic_data.raw()); |
| if (call_ic_data.IsNull()) { |
| const intptr_t kNumArgsChecked = 0; |
| call_ic_data = GetOrAddStaticCallICData(deopt_id, |
| function, |
| arguments_descriptor, |
| kNumArgsChecked)->raw(); |
| } |
| EmitUnoptimizedStaticCall(argument_count, deopt_id, token_pos, locs, |
| call_ic_data); |
| } |
| } |
| |
| |
| void FlowGraphCompiler::GenerateNumberTypeCheck(Register kClassIdReg, |
| const AbstractType& type, |
| Label* is_instance_lbl, |
| Label* is_not_instance_lbl) { |
| assembler()->Comment("NumberTypeCheck"); |
| GrowableArray<intptr_t> args; |
| if (type.IsNumberType()) { |
| args.Add(kDoubleCid); |
| args.Add(kMintCid); |
| args.Add(kBigintCid); |
| } else if (type.IsIntType()) { |
| args.Add(kMintCid); |
| args.Add(kBigintCid); |
| } else if (type.IsDoubleType()) { |
| args.Add(kDoubleCid); |
| } |
| CheckClassIds(kClassIdReg, args, is_instance_lbl, is_not_instance_lbl); |
| } |
| |
| |
| void FlowGraphCompiler::GenerateStringTypeCheck(Register kClassIdReg, |
| Label* is_instance_lbl, |
| Label* is_not_instance_lbl) { |
| assembler()->Comment("StringTypeCheck"); |
| GrowableArray<intptr_t> args; |
| args.Add(kOneByteStringCid); |
| args.Add(kTwoByteStringCid); |
| args.Add(kExternalOneByteStringCid); |
| args.Add(kExternalTwoByteStringCid); |
| CheckClassIds(kClassIdReg, args, is_instance_lbl, is_not_instance_lbl); |
| } |
| |
| |
| void FlowGraphCompiler::GenerateListTypeCheck(Register kClassIdReg, |
| Label* is_instance_lbl) { |
| assembler()->Comment("ListTypeCheck"); |
| Label unknown; |
| GrowableArray<intptr_t> args; |
| args.Add(kArrayCid); |
| args.Add(kGrowableObjectArrayCid); |
| args.Add(kImmutableArrayCid); |
| CheckClassIds(kClassIdReg, args, is_instance_lbl, &unknown); |
| assembler()->Bind(&unknown); |
| } |
| |
| |
| void FlowGraphCompiler::EmitComment(Instruction* instr) { |
| char buffer[256]; |
| BufferFormatter f(buffer, sizeof(buffer)); |
| instr->PrintTo(&f); |
| assembler()->Comment("%s", buffer); |
| } |
| |
| |
| bool FlowGraphCompiler::NeedsEdgeCounter(TargetEntryInstr* block) { |
| // Only emit an edge counter if there is not goto at the end of the block, |
| // except for the entry block. |
| return (FLAG_emit_edge_counters |
| && (!block->last_instruction()->IsGoto() |
| || (block == flow_graph().graph_entry()->normal_entry()))); |
| } |
| |
| |
| // Allocate a register that is not explicitly blocked. |
| static Register AllocateFreeRegister(bool* blocked_registers) { |
| for (intptr_t regno = 0; regno < kNumberOfCpuRegisters; regno++) { |
| if (!blocked_registers[regno]) { |
| blocked_registers[regno] = true; |
| return static_cast<Register>(regno); |
| } |
| } |
| UNREACHABLE(); |
| return kNoRegister; |
| } |
| |
| |
| static uword RegMaskBit(Register reg) { |
| return ((reg) != kNoRegister) ? (1 << (reg)) : 0; |
| } |
| |
| |
| // Mask of globally reserved registers. Some other registers are only reserved |
| // in particular code (e.g., ARGS_DESC_REG in intrinsics). |
| static const uword kReservedCpuRegisters = RegMaskBit(SPREG) |
| | RegMaskBit(FPREG) |
| | RegMaskBit(TMP) |
| | RegMaskBit(TMP2) |
| | RegMaskBit(PP) |
| | RegMaskBit(THR); |
| |
| |
| void FlowGraphCompiler::AllocateRegistersLocally(Instruction* instr) { |
| ASSERT(!is_optimizing()); |
| |
| instr->InitializeLocationSummary(zone(), |
| false); // Not optimizing. |
| LocationSummary* locs = instr->locs(); |
| |
| bool blocked_registers[kNumberOfCpuRegisters]; |
| |
| // Mark all available registers free. |
| for (intptr_t i = 0; i < kNumberOfCpuRegisters; i++) { |
| blocked_registers[i] = false; |
| } |
| |
| // Block all registers globally reserved by the assembler, etc. |
| for (intptr_t i = 0; i < kNumberOfCpuRegisters; i++) { |
| if ((kReservedCpuRegisters & (1 << i)) != 0) { |
| blocked_registers[i] = true; |
| } |
| } |
| |
| // Mark all fixed input, temp and output registers as used. |
| for (intptr_t i = 0; i < locs->input_count(); i++) { |
| Location loc = locs->in(i); |
| if (loc.IsRegister()) { |
| // Check that a register is not specified twice in the summary. |
| ASSERT(!blocked_registers[loc.reg()]); |
| blocked_registers[loc.reg()] = true; |
| } |
| } |
| |
| for (intptr_t i = 0; i < locs->temp_count(); i++) { |
| Location loc = locs->temp(i); |
| if (loc.IsRegister()) { |
| // Check that a register is not specified twice in the summary. |
| ASSERT(!blocked_registers[loc.reg()]); |
| blocked_registers[loc.reg()] = true; |
| } |
| } |
| |
| if (locs->out(0).IsRegister()) { |
| // Fixed output registers are allowed to overlap with |
| // temps and inputs. |
| blocked_registers[locs->out(0).reg()] = true; |
| } |
| |
| // Block all non-free registers. |
| for (intptr_t i = 0; i < kFirstFreeCpuRegister; i++) { |
| blocked_registers[i] = true; |
| } |
| for (intptr_t i = kLastFreeCpuRegister + 1; i < kNumberOfCpuRegisters; i++) { |
| blocked_registers[i] = true; |
| } |
| |
| // Allocate all unallocated input locations. |
| const bool should_pop = !instr->IsPushArgument() && !instr->IsPushTemp(); |
| for (intptr_t i = locs->input_count() - 1; i >= 0; i--) { |
| Location loc = locs->in(i); |
| Register reg = kNoRegister; |
| if (loc.IsRegister()) { |
| reg = loc.reg(); |
| } else if (loc.IsUnallocated() || loc.IsConstant()) { |
| ASSERT(loc.IsConstant() || |
| ((loc.policy() == Location::kRequiresRegister) || |
| (loc.policy() == Location::kWritableRegister) || |
| (loc.policy() == Location::kAny))); |
| reg = AllocateFreeRegister(blocked_registers); |
| locs->set_in(i, Location::RegisterLocation(reg)); |
| } |
| ASSERT(reg != kNoRegister); |
| |
| // Inputs are consumed from the simulated frame. In case of a call argument |
| // we leave it until the call instruction. |
| if (should_pop) { |
| assembler()->PopRegister(reg); |
| } |
| } |
| |
| // Allocate all unallocated temp locations. |
| for (intptr_t i = 0; i < locs->temp_count(); i++) { |
| Location loc = locs->temp(i); |
| if (loc.IsUnallocated()) { |
| ASSERT(loc.policy() == Location::kRequiresRegister); |
| loc = Location::RegisterLocation( |
| AllocateFreeRegister(blocked_registers)); |
| locs->set_temp(i, loc); |
| } |
| } |
| |
| Location result_location = locs->out(0); |
| if (result_location.IsUnallocated()) { |
| switch (result_location.policy()) { |
| case Location::kAny: |
| case Location::kPrefersRegister: |
| case Location::kRequiresRegister: |
| case Location::kWritableRegister: |
| result_location = Location::RegisterLocation( |
| AllocateFreeRegister(blocked_registers)); |
| break; |
| case Location::kSameAsFirstInput: |
| result_location = locs->in(0); |
| break; |
| case Location::kRequiresFpuRegister: |
| UNREACHABLE(); |
| break; |
| } |
| locs->set_out(0, result_location); |
| } |
| } |
| |
| |
| ParallelMoveResolver::ParallelMoveResolver(FlowGraphCompiler* compiler) |
| : compiler_(compiler), moves_(32) {} |
| |
| |
| void ParallelMoveResolver::EmitNativeCode(ParallelMoveInstr* parallel_move) { |
| ASSERT(moves_.is_empty()); |
| // Build up a worklist of moves. |
| BuildInitialMoveList(parallel_move); |
| |
| for (int i = 0; i < moves_.length(); ++i) { |
| const MoveOperands& move = *moves_[i]; |
| // Skip constants to perform them last. They don't block other moves |
| // and skipping such moves with register destinations keeps those |
| // registers free for the whole algorithm. |
| if (!move.IsEliminated() && !move.src().IsConstant()) PerformMove(i); |
| } |
| |
| // Perform the moves with constant sources. |
| for (int i = 0; i < moves_.length(); ++i) { |
| const MoveOperands& move = *moves_[i]; |
| if (!move.IsEliminated()) { |
| ASSERT(move.src().IsConstant()); |
| EmitMove(i); |
| } |
| } |
| |
| moves_.Clear(); |
| } |
| |
| |
| void ParallelMoveResolver::BuildInitialMoveList( |
| ParallelMoveInstr* parallel_move) { |
| // Perform a linear sweep of the moves to add them to the initial list of |
| // moves to perform, ignoring any move that is redundant (the source is |
| // the same as the destination, the destination is ignored and |
| // unallocated, or the move was already eliminated). |
| for (int i = 0; i < parallel_move->NumMoves(); i++) { |
| MoveOperands* move = parallel_move->MoveOperandsAt(i); |
| if (!move->IsRedundant()) moves_.Add(move); |
| } |
| } |
| |
| |
| void ParallelMoveResolver::PerformMove(int index) { |
| // Each call to this function performs a move and deletes it from the move |
| // graph. We first recursively perform any move blocking this one. We |
| // mark a move as "pending" on entry to PerformMove in order to detect |
| // cycles in the move graph. We use operand swaps to resolve cycles, |
| // which means that a call to PerformMove could change any source operand |
| // in the move graph. |
| |
| ASSERT(!moves_[index]->IsPending()); |
| ASSERT(!moves_[index]->IsRedundant()); |
| |
| // Clear this move's destination to indicate a pending move. The actual |
| // destination is saved in a stack-allocated local. Recursion may allow |
| // multiple moves to be pending. |
| ASSERT(!moves_[index]->src().IsInvalid()); |
| Location destination = moves_[index]->MarkPending(); |
| |
| // Perform a depth-first traversal of the move graph to resolve |
| // dependencies. Any unperformed, unpending move with a source the same |
| // as this one's destination blocks this one so recursively perform all |
| // such moves. |
| for (int i = 0; i < moves_.length(); ++i) { |
| const MoveOperands& other_move = *moves_[i]; |
| if (other_move.Blocks(destination) && !other_move.IsPending()) { |
| // Though PerformMove can change any source operand in the move graph, |
| // this call cannot create a blocking move via a swap (this loop does |
| // not miss any). Assume there is a non-blocking move with source A |
| // and this move is blocked on source B and there is a swap of A and |
| // B. Then A and B must be involved in the same cycle (or they would |
| // not be swapped). Since this move's destination is B and there is |
| // only a single incoming edge to an operand, this move must also be |
| // involved in the same cycle. In that case, the blocking move will |
| // be created but will be "pending" when we return from PerformMove. |
| PerformMove(i); |
| } |
| } |
| |
| // We are about to resolve this move and don't need it marked as |
| // pending, so restore its destination. |
| moves_[index]->ClearPending(destination); |
| |
| // This move's source may have changed due to swaps to resolve cycles and |
| // so it may now be the last move in the cycle. If so remove it. |
| if (moves_[index]->src().Equals(destination)) { |
| moves_[index]->Eliminate(); |
| return; |
| } |
| |
| // The move may be blocked on a (at most one) pending move, in which case |
| // we have a cycle. Search for such a blocking move and perform a swap to |
| // resolve it. |
| for (int i = 0; i < moves_.length(); ++i) { |
| const MoveOperands& other_move = *moves_[i]; |
| if (other_move.Blocks(destination)) { |
| ASSERT(other_move.IsPending()); |
| EmitSwap(index); |
| return; |
| } |
| } |
| |
| // This move is not blocked. |
| EmitMove(index); |
| } |
| |
| |
| bool ParallelMoveResolver::IsScratchLocation(Location loc) { |
| for (int i = 0; i < moves_.length(); ++i) { |
| if (moves_[i]->Blocks(loc)) { |
| return false; |
| } |
| } |
| |
| for (int i = 0; i < moves_.length(); ++i) { |
| if (moves_[i]->dest().Equals(loc)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| |
| intptr_t ParallelMoveResolver::AllocateScratchRegister( |
| Location::Kind kind, |
| uword blocked_mask, |
| intptr_t first_free_register, |
| intptr_t last_free_register, |
| bool* spilled) { |
| COMPILE_ASSERT(static_cast<intptr_t>(sizeof(blocked_mask)) * kBitsPerByte >= |
| kNumberOfFpuRegisters); |
| COMPILE_ASSERT(static_cast<intptr_t>(sizeof(blocked_mask)) * kBitsPerByte >= |
| kNumberOfCpuRegisters); |
| intptr_t scratch = -1; |
| for (intptr_t reg = first_free_register; reg <= last_free_register; reg++) { |
| if ((((1 << reg) & blocked_mask) == 0) && |
| IsScratchLocation(Location::MachineRegisterLocation(kind, reg))) { |
| scratch = reg; |
| break; |
| } |
| } |
| |
| if (scratch == -1) { |
| *spilled = true; |
| for (intptr_t reg = first_free_register; reg <= last_free_register; reg++) { |
| if (((1 << reg) & blocked_mask) == 0) { |
| scratch = reg; |
| break; |
| } |
| } |
| } else { |
| *spilled = false; |
| } |
| |
| return scratch; |
| } |
| |
| |
| ParallelMoveResolver::ScratchFpuRegisterScope::ScratchFpuRegisterScope( |
| ParallelMoveResolver* resolver, FpuRegister blocked) |
| : resolver_(resolver), |
| reg_(kNoFpuRegister), |
| spilled_(false) { |
| COMPILE_ASSERT(FpuTMP != kNoFpuRegister); |
| uword blocked_mask = ((blocked != kNoFpuRegister) ? 1 << blocked : 0) |
| | 1 << FpuTMP; |
| reg_ = static_cast<FpuRegister>( |
| resolver_->AllocateScratchRegister(Location::kFpuRegister, |
| blocked_mask, |
| 0, |
| kNumberOfFpuRegisters - 1, |
| &spilled_)); |
| |
| if (spilled_) { |
| resolver->SpillFpuScratch(reg_); |
| } |
| } |
| |
| |
| ParallelMoveResolver::ScratchFpuRegisterScope::~ScratchFpuRegisterScope() { |
| if (spilled_) { |
| resolver_->RestoreFpuScratch(reg_); |
| } |
| } |
| |
| |
| ParallelMoveResolver::ScratchRegisterScope::ScratchRegisterScope( |
| ParallelMoveResolver* resolver, Register blocked) |
| : resolver_(resolver), |
| reg_(kNoRegister), |
| spilled_(false) { |
| uword blocked_mask = RegMaskBit(blocked) | kReservedCpuRegisters; |
| if (resolver->compiler_->intrinsic_mode()) { |
| // Block additional registers that must be preserved for intrinsics. |
| blocked_mask |= RegMaskBit(ARGS_DESC_REG); |
| } |
| reg_ = static_cast<Register>( |
| resolver_->AllocateScratchRegister(Location::kRegister, |
| blocked_mask, |
| kFirstFreeCpuRegister, |
| kLastFreeCpuRegister, |
| &spilled_)); |
| |
| if (spilled_) { |
| resolver->SpillScratch(reg_); |
| } |
| } |
| |
| |
| ParallelMoveResolver::ScratchRegisterScope::~ScratchRegisterScope() { |
| if (spilled_) { |
| resolver_->RestoreScratch(reg_); |
| } |
| } |
| |
| |
| static int HighestCountFirst(const CidTarget* a, const CidTarget* b) { |
| // Negative if 'a' should sort before 'b'. |
| return b->count - a->count; |
| } |
| |
| |
| // Returns 'sorted' array in decreasing count order. |
| // The expected number of elements to sort is less than 10. |
| void FlowGraphCompiler::SortICDataByCount(const ICData& ic_data, |
| GrowableArray<CidTarget>* sorted, |
| bool drop_smi) { |
| ASSERT(ic_data.NumArgsTested() == 1); |
| const intptr_t len = ic_data.NumberOfChecks(); |
| sorted->Clear(); |
| |
| for (int i = 0; i < len; i++) { |
| intptr_t receiver_cid = ic_data.GetReceiverClassIdAt(i); |
| if (drop_smi && (receiver_cid == kSmiCid)) continue; |
| sorted->Add(CidTarget(receiver_cid, |
| &Function::ZoneHandle(ic_data.GetTargetAt(i)), |
| ic_data.GetCountAt(i))); |
| } |
| sorted->Sort(HighestCountFirst); |
| } |
| |
| |
| const ICData* FlowGraphCompiler::GetOrAddInstanceCallICData( |
| intptr_t deopt_id, |
| const String& target_name, |
| const Array& arguments_descriptor, |
| intptr_t num_args_tested) { |
| if ((deopt_id_to_ic_data_ != NULL) && |
| ((*deopt_id_to_ic_data_)[deopt_id] != NULL)) { |
| const ICData* res = (*deopt_id_to_ic_data_)[deopt_id]; |
| ASSERT(res->deopt_id() == deopt_id); |
| ASSERT(res->target_name() == target_name.raw()); |
| ASSERT(res->NumArgsTested() == num_args_tested); |
| return res; |
| } |
| const ICData& ic_data = ICData::ZoneHandle(zone(), ICData::New( |
| parsed_function().function(), target_name, |
| arguments_descriptor, deopt_id, num_args_tested)); |
| if (deopt_id_to_ic_data_ != NULL) { |
| (*deopt_id_to_ic_data_)[deopt_id] = &ic_data; |
| } |
| return &ic_data; |
| } |
| |
| |
| const ICData* FlowGraphCompiler::GetOrAddStaticCallICData( |
| intptr_t deopt_id, |
| const Function& target, |
| const Array& arguments_descriptor, |
| intptr_t num_args_tested) { |
| if ((deopt_id_to_ic_data_ != NULL) && |
| ((*deopt_id_to_ic_data_)[deopt_id] != NULL)) { |
| const ICData* res = (*deopt_id_to_ic_data_)[deopt_id]; |
| ASSERT(res->deopt_id() == deopt_id); |
| ASSERT(res->target_name() == target.name()); |
| ASSERT(res->NumArgsTested() == num_args_tested); |
| return res; |
| } |
| const ICData& ic_data = ICData::ZoneHandle(zone(), ICData::New( |
| parsed_function().function(), String::Handle(zone(), target.name()), |
| arguments_descriptor, deopt_id, num_args_tested)); |
| ic_data.AddTarget(target); |
| if (deopt_id_to_ic_data_ != NULL) { |
| (*deopt_id_to_ic_data_)[deopt_id] = &ic_data; |
| } |
| return &ic_data; |
| } |
| |
| |
| intptr_t FlowGraphCompiler::GetOptimizationThreshold() const { |
| intptr_t threshold; |
| if (is_optimizing()) { |
| threshold = FLAG_reoptimization_counter_threshold; |
| } else if (parsed_function_.function().IsIrregexpFunction()) { |
| threshold = FLAG_regexp_optimization_counter_threshold; |
| } else { |
| const intptr_t basic_blocks = flow_graph().preorder().length(); |
| ASSERT(basic_blocks > 0); |
| threshold = FLAG_optimization_counter_scale * basic_blocks + |
| FLAG_min_optimization_counter_threshold; |
| if (threshold > FLAG_optimization_counter_threshold) { |
| threshold = FLAG_optimization_counter_threshold; |
| } |
| } |
| return threshold; |
| } |
| |
| |
| const Class& FlowGraphCompiler::BoxClassFor(Representation rep) { |
| switch (rep) { |
| case kUnboxedDouble: |
| return double_class(); |
| case kUnboxedFloat32x4: |
| return float32x4_class(); |
| case kUnboxedFloat64x2: |
| return float64x2_class(); |
| case kUnboxedInt32x4: |
| return int32x4_class(); |
| case kUnboxedMint: |
| return mint_class(); |
| default: |
| UNREACHABLE(); |
| return Class::ZoneHandle(); |
| } |
| } |
| |
| |
| RawArray* FlowGraphCompiler::InliningIdToFunction() const { |
| if (inline_id_to_function_.length() == 0) { |
| return Object::empty_array().raw(); |
| } |
| const Array& res = Array::Handle( |
| Array::New(inline_id_to_function_.length(), Heap::kOld)); |
| for (intptr_t i = 0; i < inline_id_to_function_.length(); i++) { |
| res.SetAt(i, *inline_id_to_function_[i]); |
| } |
| return res.raw(); |
| } |
| |
| |
| RawArray* FlowGraphCompiler::CallerInliningIdMap() const { |
| if (caller_inline_id_.length() == 0) { |
| return Object::empty_array().raw(); |
| } |
| const Array& res = Array::Handle( |
| Array::New(caller_inline_id_.length(), Heap::kOld)); |
| Smi& smi = Smi::Handle(); |
| for (intptr_t i = 0; i < caller_inline_id_.length(); i++) { |
| smi = Smi::New(caller_inline_id_[i]); |
| res.SetAt(i, smi); |
| } |
| return res.raw(); |
| } |
| |
| |
| void FlowGraphCompiler::EmitPolymorphicInstanceCall( |
| const ICData& ic_data, |
| intptr_t argument_count, |
| const Array& argument_names, |
| intptr_t deopt_id, |
| intptr_t token_pos, |
| LocationSummary* locs) { |
| if (FLAG_polymorphic_with_deopt) { |
| Label* deopt = AddDeoptStub(deopt_id, |
| ICData::kDeoptPolymorphicInstanceCallTestFail); |
| Label ok; |
| EmitTestAndCall(ic_data, argument_count, argument_names, |
| deopt, // No cid match. |
| &ok, // Found cid. |
| deopt_id, token_pos, locs); |
| assembler()->Bind(&ok); |
| } else { |
| // Instead of deoptimizing, do a megamorphic call when no matching |
| // cid found. |
| Label megamorphic, ok; |
| EmitTestAndCall(ic_data, argument_count, argument_names, |
| &megamorphic, // No cid match. |
| &ok, // Found cid. |
| deopt_id, token_pos, locs); |
| // Fall through if last test is match. |
| assembler()->Jump(&ok); |
| assembler()->Bind(&megamorphic); |
| EmitMegamorphicInstanceCall(ic_data, argument_count, deopt_id, |
| token_pos, locs); |
| assembler()->Bind(&ok); |
| } |
| } |
| |
| |
| #if defined(DEBUG) |
| void FlowGraphCompiler::FrameStateUpdateWith(Instruction* instr) { |
| ASSERT(!is_optimizing()); |
| |
| switch (instr->tag()) { |
| case Instruction::kPushArgument: |
| case Instruction::kPushTemp: |
| // Do nothing. |
| break; |
| |
| case Instruction::kDropTemps: |
| FrameStatePop(instr->locs()->input_count() + |
| instr->AsDropTemps()->num_temps()); |
| break; |
| |
| default: |
| FrameStatePop(instr->locs()->input_count()); |
| break; |
| } |
| |
| ASSERT(!instr->locs()->can_call() || FrameStateIsSafeToCall()); |
| |
| FrameStatePop(instr->ArgumentCount()); |
| Definition* defn = instr->AsDefinition(); |
| if ((defn != NULL) && defn->HasTemp()) { |
| FrameStatePush(defn); |
| } |
| } |
| |
| |
| void FlowGraphCompiler::FrameStatePush(Definition* defn) { |
| Representation rep = defn->representation(); |
| if ((rep == kUnboxedDouble) || |
| (rep == kUnboxedFloat64x2) || |
| (rep == kUnboxedFloat32x4)) { |
| // LoadField instruction lies about its representation in the unoptimized |
| // code because Definition::representation() can't depend on the type of |
| // compilation but MakeLocationSummary and EmitNativeCode can. |
| ASSERT(defn->IsLoadField() && defn->AsLoadField()->IsUnboxedLoad()); |
| ASSERT(defn->locs()->out(0).IsRegister()); |
| rep = kTagged; |
| } |
| ASSERT(!is_optimizing()); |
| ASSERT((rep == kTagged) || (rep == kUntagged)); |
| ASSERT(rep != kUntagged || flow_graph_.IsIrregexpFunction()); |
| frame_state_.Add(rep); |
| } |
| |
| |
| void FlowGraphCompiler::FrameStatePop(intptr_t count) { |
| ASSERT(!is_optimizing()); |
| frame_state_.TruncateTo(Utils::Maximum(static_cast<intptr_t>(0), |
| frame_state_.length() - count)); |
| } |
| |
| |
| bool FlowGraphCompiler::FrameStateIsSafeToCall() { |
| ASSERT(!is_optimizing()); |
| for (intptr_t i = 0; i < frame_state_.length(); i++) { |
| if (frame_state_[i] != kTagged) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| |
| void FlowGraphCompiler::FrameStateClear() { |
| ASSERT(!is_optimizing()); |
| frame_state_.TruncateTo(0); |
| } |
| #endif |
| |
| |
| } // namespace dart |