1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
2 | // for details. All rights reserved. Use of this source code is governed by a |
3 | // BSD-style license that can be found in the LICENSE file. |
4 | |
5 | #include "vm/compiler/backend/flow_graph_compiler.h" |
6 | #include "vm/globals.h" // Needed here to get TARGET_ARCH_XXX. |
7 | |
8 | #include "platform/utils.h" |
9 | #include "vm/bit_vector.h" |
10 | #include "vm/compiler/backend/code_statistics.h" |
11 | #include "vm/compiler/backend/il_printer.h" |
12 | #include "vm/compiler/backend/inliner.h" |
13 | #include "vm/compiler/backend/linearscan.h" |
14 | #include "vm/compiler/backend/locations.h" |
15 | #include "vm/compiler/backend/loops.h" |
16 | #include "vm/compiler/cha.h" |
17 | #include "vm/compiler/intrinsifier.h" |
18 | #include "vm/compiler/jit/compiler.h" |
19 | #include "vm/dart_entry.h" |
20 | #include "vm/debugger.h" |
21 | #include "vm/deopt_instructions.h" |
22 | #include "vm/exceptions.h" |
23 | #include "vm/flags.h" |
24 | #include "vm/kernel_isolate.h" |
25 | #include "vm/log.h" |
26 | #include "vm/longjump.h" |
27 | #include "vm/object_store.h" |
28 | #include "vm/parser.h" |
29 | #include "vm/raw_object.h" |
30 | #include "vm/resolver.h" |
31 | #include "vm/service_isolate.h" |
32 | #include "vm/stack_frame.h" |
33 | #include "vm/stub_code.h" |
34 | #include "vm/symbols.h" |
35 | #include "vm/timeline.h" |
36 | #include "vm/type_testing_stubs.h" |
37 | |
38 | namespace dart { |
39 | |
40 | DEFINE_FLAG(bool, |
41 | trace_inlining_intervals, |
42 | false, |
43 | "Inlining interval diagnostics" ); |
44 | |
45 | DEFINE_FLAG(bool, enable_peephole, true, "Enable peephole optimization" ); |
46 | |
47 | DEFINE_FLAG(bool, |
48 | enable_simd_inline, |
49 | true, |
50 | "Enable inlining of SIMD related method calls." ); |
51 | DEFINE_FLAG(int, |
52 | min_optimization_counter_threshold, |
53 | 5000, |
54 | "The minimum invocation count for a function." ); |
55 | DEFINE_FLAG(int, |
56 | optimization_counter_scale, |
57 | 2000, |
58 | "The scale of invocation count, by size of the function." ); |
59 | DEFINE_FLAG(bool, source_lines, false, "Emit source line as assembly comment." ); |
60 | |
61 | DECLARE_FLAG(charp, deoptimize_filter); |
62 | DECLARE_FLAG(bool, intrinsify); |
63 | DECLARE_FLAG(int, regexp_optimization_counter_threshold); |
64 | DECLARE_FLAG(int, reoptimization_counter_threshold); |
65 | DECLARE_FLAG(int, stacktrace_every); |
66 | DECLARE_FLAG(charp, stacktrace_filter); |
67 | DECLARE_FLAG(int, gc_every); |
68 | DECLARE_FLAG(bool, trace_compiler); |
69 | |
70 | // Assign locations to incoming arguments, i.e., values pushed above spill slots |
71 | // with PushArgument. Recursively allocates from outermost to innermost |
72 | // environment. |
73 | void CompilerDeoptInfo::AllocateIncomingParametersRecursive( |
74 | Environment* env, |
75 | intptr_t* stack_height) { |
76 | if (env == NULL) return; |
77 | AllocateIncomingParametersRecursive(env->outer(), stack_height); |
78 | for (Environment::ShallowIterator it(env); !it.Done(); it.Advance()) { |
79 | if (it.CurrentLocation().IsInvalid() && |
80 | it.CurrentValue()->definition()->IsPushArgument()) { |
81 | it.SetCurrentLocation(Location::StackSlot( |
82 | compiler::target::frame_layout.FrameSlotForVariableIndex( |
83 | -*stack_height), |
84 | FPREG)); |
85 | (*stack_height)++; |
86 | } |
87 | } |
88 | } |
89 | |
90 | void CompilerDeoptInfo::EmitMaterializations(Environment* env, |
91 | DeoptInfoBuilder* builder) { |
92 | for (Environment::DeepIterator it(env); !it.Done(); it.Advance()) { |
93 | if (it.CurrentLocation().IsInvalid()) { |
94 | MaterializeObjectInstr* mat = |
95 | it.CurrentValue()->definition()->AsMaterializeObject(); |
96 | ASSERT(mat != NULL); |
97 | builder->AddMaterialization(mat); |
98 | } |
99 | } |
100 | } |
101 | |
102 | FlowGraphCompiler::FlowGraphCompiler( |
103 | compiler::Assembler* assembler, |
104 | FlowGraph* flow_graph, |
105 | const ParsedFunction& parsed_function, |
106 | bool is_optimizing, |
107 | SpeculativeInliningPolicy* speculative_policy, |
108 | const GrowableArray<const Function*>& inline_id_to_function, |
109 | const GrowableArray<TokenPosition>& inline_id_to_token_pos, |
110 | const GrowableArray<intptr_t>& caller_inline_id, |
111 | ZoneGrowableArray<const ICData*>* deopt_id_to_ic_data, |
112 | CodeStatistics* stats /* = NULL */) |
113 | : thread_(Thread::Current()), |
114 | zone_(Thread::Current()->zone()), |
115 | assembler_(assembler), |
116 | parsed_function_(parsed_function), |
117 | flow_graph_(*flow_graph), |
118 | block_order_(*flow_graph->CodegenBlockOrder(is_optimizing)), |
119 | current_block_(nullptr), |
120 | exception_handlers_list_(nullptr), |
121 | pc_descriptors_list_(nullptr), |
122 | compressed_stackmaps_builder_(nullptr), |
123 | code_source_map_builder_(nullptr), |
124 | catch_entry_moves_maps_builder_(nullptr), |
125 | block_info_(block_order_.length()), |
126 | deopt_infos_(), |
127 | static_calls_target_table_(), |
128 | indirect_gotos_(), |
129 | is_optimizing_(is_optimizing), |
130 | speculative_policy_(speculative_policy), |
131 | may_reoptimize_(false), |
132 | intrinsic_mode_(false), |
133 | stats_(stats), |
134 | double_class_( |
135 | Class::ZoneHandle(isolate()->object_store()->double_class())), |
136 | mint_class_(Class::ZoneHandle(isolate()->object_store()->mint_class())), |
137 | float32x4_class_( |
138 | Class::ZoneHandle(isolate()->object_store()->float32x4_class())), |
139 | float64x2_class_( |
140 | Class::ZoneHandle(isolate()->object_store()->float64x2_class())), |
141 | int32x4_class_( |
142 | Class::ZoneHandle(isolate()->object_store()->int32x4_class())), |
143 | list_class_(Class::ZoneHandle(Library::Handle(Library::CoreLibrary()) |
144 | .LookupClass(Symbols::List()))), |
145 | parallel_move_resolver_(this), |
146 | pending_deoptimization_env_(NULL), |
147 | deopt_id_to_ic_data_(deopt_id_to_ic_data), |
148 | edge_counters_array_(Array::ZoneHandle()) { |
149 | ASSERT(flow_graph->parsed_function().function().raw() == |
150 | parsed_function.function().raw()); |
151 | if (is_optimizing) { |
152 | // No need to collect extra ICData objects created during compilation. |
153 | deopt_id_to_ic_data_ = nullptr; |
154 | } else { |
155 | const intptr_t len = thread()->compiler_state().deopt_id(); |
156 | deopt_id_to_ic_data_->EnsureLength(len, nullptr); |
157 | } |
158 | ASSERT(assembler != NULL); |
159 | ASSERT(!list_class_.IsNull()); |
160 | |
161 | #if defined(PRODUCT) |
162 | const bool stack_traces_only = true; |
163 | #else |
164 | const bool stack_traces_only = false; |
165 | #endif |
166 | code_source_map_builder_ = new (zone_) |
167 | CodeSourceMapBuilder(stack_traces_only, caller_inline_id, |
168 | inline_id_to_token_pos, inline_id_to_function); |
169 | |
170 | ArchSpecificInitialization(); |
171 | } |
172 | |
173 | bool FlowGraphCompiler::IsUnboxedField(const Field& field) { |
174 | // The `field.is_non_nullable_integer()` is set in the kernel loader and can |
175 | // only be set if we consume a AOT kernel (annotated with inferred types). |
176 | ASSERT(!field.is_non_nullable_integer() || FLAG_precompiled_mode); |
177 | const bool valid_class = |
178 | (SupportsUnboxedDoubles() && (field.guarded_cid() == kDoubleCid)) || |
179 | (SupportsUnboxedSimd128() && (field.guarded_cid() == kFloat32x4Cid)) || |
180 | (SupportsUnboxedSimd128() && (field.guarded_cid() == kFloat64x2Cid)) || |
181 | field.is_non_nullable_integer(); |
182 | return field.is_unboxing_candidate() && !field.is_nullable() && valid_class; |
183 | } |
184 | |
185 | bool FlowGraphCompiler::IsPotentialUnboxedField(const Field& field) { |
186 | if (FLAG_precompiled_mode) { |
187 | // kernel_loader.cc:ReadInferredType sets the guarded cid for fields based |
188 | // on inferred types from TFA (if available). The guarded cid is therefore |
189 | // proven to be correct. |
190 | return IsUnboxedField(field); |
191 | } |
192 | return field.is_unboxing_candidate() && |
193 | (FlowGraphCompiler::IsUnboxedField(field) || |
194 | (field.guarded_cid() == kIllegalCid)); |
195 | } |
196 | |
197 | void FlowGraphCompiler::InitCompiler() { |
198 | pc_descriptors_list_ = new (zone()) DescriptorList(64); |
199 | exception_handlers_list_ = new (zone()) ExceptionHandlerList(); |
200 | #if defined(DART_PRECOMPILER) |
201 | catch_entry_moves_maps_builder_ = new (zone()) CatchEntryMovesMapBuilder(); |
202 | #endif |
203 | block_info_.Clear(); |
204 | // Initialize block info and search optimized (non-OSR) code for calls |
205 | // indicating a non-leaf routine and calls without IC data indicating |
206 | // possible reoptimization. |
207 | |
208 | for (int i = 0; i < block_order_.length(); ++i) { |
209 | block_info_.Add(new (zone()) BlockInfo()); |
210 | if (is_optimizing() && !flow_graph().IsCompiledForOsr()) { |
211 | BlockEntryInstr* entry = block_order_[i]; |
212 | for (ForwardInstructionIterator it(entry); !it.Done(); it.Advance()) { |
213 | Instruction* current = it.Current(); |
214 | if (current->IsBranch()) { |
215 | current = current->AsBranch()->comparison(); |
216 | } |
217 | // In optimized code, ICData is always set in the instructions. |
218 | const ICData* ic_data = NULL; |
219 | if (current->IsInstanceCall()) { |
220 | ic_data = current->AsInstanceCall()->ic_data(); |
221 | } |
222 | if ((ic_data != NULL) && (ic_data->NumberOfUsedChecks() == 0)) { |
223 | may_reoptimize_ = true; |
224 | } |
225 | } |
226 | } |
227 | } |
228 | |
229 | if (!is_optimizing() && FLAG_reorder_basic_blocks) { |
230 | // Initialize edge counter array. |
231 | const intptr_t num_counters = flow_graph_.preorder().length(); |
232 | const Array& edge_counters = |
233 | Array::Handle(Array::New(num_counters, Heap::kOld)); |
234 | for (intptr_t i = 0; i < num_counters; ++i) { |
235 | edge_counters.SetAt(i, Object::smi_zero()); |
236 | } |
237 | edge_counters_array_ = edge_counters.raw(); |
238 | } |
239 | } |
240 | |
241 | bool FlowGraphCompiler::CanOptimize() { |
242 | return FLAG_optimization_counter_threshold >= 0; |
243 | } |
244 | |
245 | bool FlowGraphCompiler::CanOptimizeFunction() const { |
246 | return CanOptimize() && !parsed_function().function().HasBreakpoint(); |
247 | } |
248 | |
249 | bool FlowGraphCompiler::CanOSRFunction() const { |
250 | return isolate()->use_osr() && CanOptimizeFunction() && !is_optimizing(); |
251 | } |
252 | |
253 | void FlowGraphCompiler::InsertBSSRelocation(BSS::Relocation reloc) { |
254 | const intptr_t offset = assembler()->InsertAlignedRelocation(reloc); |
255 | AddDescriptor(PcDescriptorsLayout::kBSSRelocation, /*pc_offset=*/offset, |
256 | /*deopt_id=*/DeoptId::kNone, TokenPosition::kNoSource, |
257 | /*try_index=*/-1); |
258 | } |
259 | |
260 | bool FlowGraphCompiler::ForceSlowPathForStackOverflow() const { |
261 | #if !defined(PRODUCT) |
262 | if ((FLAG_stacktrace_every > 0) || (FLAG_deoptimize_every > 0) || |
263 | (FLAG_gc_every > 0) || |
264 | (isolate()->reload_every_n_stack_overflow_checks() > 0)) { |
265 | if (!Isolate::IsVMInternalIsolate(isolate())) { |
266 | return true; |
267 | } |
268 | } |
269 | if (FLAG_stacktrace_filter != NULL && |
270 | strstr(parsed_function().function().ToFullyQualifiedCString(), |
271 | FLAG_stacktrace_filter) != NULL) { |
272 | return true; |
273 | } |
274 | if (is_optimizing() && FLAG_deoptimize_filter != NULL && |
275 | strstr(parsed_function().function().ToFullyQualifiedCString(), |
276 | FLAG_deoptimize_filter) != NULL) { |
277 | return true; |
278 | } |
279 | #endif // !defined(PRODUCT) |
280 | return false; |
281 | } |
282 | |
283 | bool FlowGraphCompiler::IsEmptyBlock(BlockEntryInstr* block) const { |
284 | // Entry-points cannot be merged because they must have assembly |
285 | // prologue emitted which should not be included in any block they jump to. |
286 | return !block->IsGraphEntry() && !block->IsFunctionEntry() && |
287 | !block->IsCatchBlockEntry() && !block->IsOsrEntry() && |
288 | !block->IsIndirectEntry() && !block->HasNonRedundantParallelMove() && |
289 | block->next()->IsGoto() && |
290 | !block->next()->AsGoto()->HasNonRedundantParallelMove(); |
291 | } |
292 | |
293 | void FlowGraphCompiler::CompactBlock(BlockEntryInstr* block) { |
294 | BlockInfo* block_info = block_info_[block->postorder_number()]; |
295 | |
296 | // Break out of cycles in the control flow graph. |
297 | if (block_info->is_marked()) { |
298 | return; |
299 | } |
300 | block_info->mark(); |
301 | |
302 | if (IsEmptyBlock(block)) { |
303 | // For empty blocks, record a corresponding nonempty target as their |
304 | // jump label. |
305 | BlockEntryInstr* target = block->next()->AsGoto()->successor(); |
306 | CompactBlock(target); |
307 | block_info->set_jump_label(GetJumpLabel(target)); |
308 | } |
309 | } |
310 | |
311 | void FlowGraphCompiler::CompactBlocks() { |
312 | // This algorithm does not garbage collect blocks in place, but merely |
313 | // records forwarding label information. In this way it avoids having to |
314 | // change join and target entries. |
315 | compiler::Label* nonempty_label = NULL; |
316 | for (intptr_t i = block_order().length() - 1; i >= 1; --i) { |
317 | BlockEntryInstr* block = block_order()[i]; |
318 | |
319 | // Unoptimized code must emit all possible deoptimization points. |
320 | if (is_optimizing()) { |
321 | CompactBlock(block); |
322 | } |
323 | |
324 | // For nonempty blocks, record the next nonempty block in the block |
325 | // order. Since no code is emitted for empty blocks, control flow is |
326 | // eligible to fall through to the next nonempty one. |
327 | if (!WasCompacted(block)) { |
328 | BlockInfo* block_info = block_info_[block->postorder_number()]; |
329 | block_info->set_next_nonempty_label(nonempty_label); |
330 | nonempty_label = GetJumpLabel(block); |
331 | } |
332 | } |
333 | |
334 | ASSERT(block_order()[0]->IsGraphEntry()); |
335 | BlockInfo* block_info = block_info_[block_order()[0]->postorder_number()]; |
336 | block_info->set_next_nonempty_label(nonempty_label); |
337 | } |
338 | |
339 | #if defined(DART_PRECOMPILER) |
340 | static intptr_t LocationToStackIndex(const Location& src) { |
341 | ASSERT(src.HasStackIndex()); |
342 | return -compiler::target::frame_layout.VariableIndexForFrameSlot( |
343 | src.stack_index()); |
344 | } |
345 | |
346 | static CatchEntryMove CatchEntryMoveFor(compiler::Assembler* assembler, |
347 | Representation src_type, |
348 | const Location& src, |
349 | intptr_t dst_index) { |
350 | if (src.IsConstant()) { |
351 | // Skip dead locations. |
352 | if (src.constant().raw() == Symbols::OptimizedOut().raw()) { |
353 | return CatchEntryMove(); |
354 | } |
355 | const intptr_t pool_index = |
356 | assembler->object_pool_builder().FindObject(src.constant()); |
357 | return CatchEntryMove::FromSlot(CatchEntryMove::SourceKind::kConstant, |
358 | pool_index, dst_index); |
359 | } |
360 | |
361 | if (src.IsPairLocation()) { |
362 | const auto lo_loc = src.AsPairLocation()->At(0); |
363 | const auto hi_loc = src.AsPairLocation()->At(1); |
364 | ASSERT(lo_loc.IsStackSlot() && hi_loc.IsStackSlot()); |
365 | return CatchEntryMove::FromSlot( |
366 | CatchEntryMove::SourceKind::kInt64PairSlot, |
367 | CatchEntryMove::EncodePairSource(LocationToStackIndex(lo_loc), |
368 | LocationToStackIndex(hi_loc)), |
369 | dst_index); |
370 | } |
371 | |
372 | CatchEntryMove::SourceKind src_kind; |
373 | switch (src_type) { |
374 | case kTagged: |
375 | src_kind = CatchEntryMove::SourceKind::kTaggedSlot; |
376 | break; |
377 | case kUnboxedInt64: |
378 | src_kind = CatchEntryMove::SourceKind::kInt64Slot; |
379 | break; |
380 | case kUnboxedInt32: |
381 | src_kind = CatchEntryMove::SourceKind::kInt32Slot; |
382 | break; |
383 | case kUnboxedUint32: |
384 | src_kind = CatchEntryMove::SourceKind::kUint32Slot; |
385 | break; |
386 | case kUnboxedDouble: |
387 | src_kind = CatchEntryMove::SourceKind::kDoubleSlot; |
388 | break; |
389 | case kUnboxedFloat32x4: |
390 | src_kind = CatchEntryMove::SourceKind::kFloat32x4Slot; |
391 | break; |
392 | case kUnboxedFloat64x2: |
393 | src_kind = CatchEntryMove::SourceKind::kFloat64x2Slot; |
394 | break; |
395 | case kUnboxedInt32x4: |
396 | src_kind = CatchEntryMove::SourceKind::kInt32x4Slot; |
397 | break; |
398 | default: |
399 | UNREACHABLE(); |
400 | break; |
401 | } |
402 | |
403 | return CatchEntryMove::FromSlot(src_kind, LocationToStackIndex(src), |
404 | dst_index); |
405 | } |
406 | #endif |
407 | |
408 | void FlowGraphCompiler::RecordCatchEntryMoves(Environment* env, |
409 | intptr_t try_index) { |
410 | #if defined(DART_PRECOMPILER) |
411 | env = env ? env : pending_deoptimization_env_; |
412 | try_index = try_index != kInvalidTryIndex ? try_index : CurrentTryIndex(); |
413 | if (is_optimizing() && env != nullptr && (try_index != kInvalidTryIndex)) { |
414 | env = env->Outermost(); |
415 | CatchBlockEntryInstr* catch_block = |
416 | flow_graph().graph_entry()->GetCatchEntry(try_index); |
417 | const GrowableArray<Definition*>* idefs = |
418 | catch_block->initial_definitions(); |
419 | catch_entry_moves_maps_builder_->NewMapping(assembler()->CodeSize()); |
420 | |
421 | const intptr_t num_direct_parameters = flow_graph().num_direct_parameters(); |
422 | const intptr_t ex_idx = |
423 | catch_block->raw_exception_var() != nullptr |
424 | ? flow_graph().EnvIndex(catch_block->raw_exception_var()) |
425 | : -1; |
426 | const intptr_t st_idx = |
427 | catch_block->raw_stacktrace_var() != nullptr |
428 | ? flow_graph().EnvIndex(catch_block->raw_stacktrace_var()) |
429 | : -1; |
430 | for (intptr_t i = 0; i < flow_graph().variable_count(); ++i) { |
431 | // Don't sync captured parameters. They are not in the environment. |
432 | if (flow_graph().captured_parameters()->Contains(i)) continue; |
433 | // Don't sync exception or stack trace variables. |
434 | if (i == ex_idx || i == st_idx) continue; |
435 | // Don't sync values that have been replaced with constants. |
436 | if ((*idefs)[i]->IsConstant()) continue; |
437 | |
438 | Location src = env->LocationAt(i); |
439 | // Can only occur if AllocationSinking is enabled - and it is disabled |
440 | // in functions with try. |
441 | ASSERT(!src.IsInvalid()); |
442 | const Representation src_type = |
443 | env->ValueAt(i)->definition()->representation(); |
444 | intptr_t dest_index = i - num_direct_parameters; |
445 | const auto move = |
446 | CatchEntryMoveFor(assembler(), src_type, src, dest_index); |
447 | if (!move.IsRedundant()) { |
448 | catch_entry_moves_maps_builder_->Append(move); |
449 | } |
450 | } |
451 | |
452 | catch_entry_moves_maps_builder_->EndMapping(); |
453 | } |
454 | #endif // defined(DART_PRECOMPILER) || defined(DART_PRECOMPILED_RUNTIME) |
455 | } |
456 | |
457 | void FlowGraphCompiler::EmitCallsiteMetadata(TokenPosition token_pos, |
458 | intptr_t deopt_id, |
459 | PcDescriptorsLayout::Kind kind, |
460 | LocationSummary* locs, |
461 | Environment* env) { |
462 | AddCurrentDescriptor(kind, deopt_id, token_pos); |
463 | RecordSafepoint(locs); |
464 | RecordCatchEntryMoves(env); |
465 | if ((deopt_id != DeoptId::kNone) && !FLAG_precompiled_mode) { |
466 | // Marks either the continuation point in unoptimized code or the |
467 | // deoptimization point in optimized code, after call. |
468 | const intptr_t deopt_id_after = DeoptId::ToDeoptAfter(deopt_id); |
469 | if (is_optimizing()) { |
470 | AddDeoptIndexAtCall(deopt_id_after); |
471 | } else { |
472 | // Add deoptimization continuation point after the call and before the |
473 | // arguments are removed. |
474 | AddCurrentDescriptor(PcDescriptorsLayout::kDeopt, deopt_id_after, |
475 | token_pos); |
476 | } |
477 | } |
478 | } |
479 | |
480 | void FlowGraphCompiler::EmitYieldPositionMetadata(TokenPosition token_pos, |
481 | intptr_t yield_index) { |
482 | AddDescriptor(PcDescriptorsLayout::kOther, assembler()->CodeSize(), |
483 | DeoptId::kNone, token_pos, CurrentTryIndex(), yield_index); |
484 | } |
485 | |
486 | void FlowGraphCompiler::EmitInstructionPrologue(Instruction* instr) { |
487 | if (!is_optimizing()) { |
488 | if (instr->CanBecomeDeoptimizationTarget() && !instr->IsGoto()) { |
489 | // Instructions that can be deoptimization targets need to record kDeopt |
490 | // PcDescriptor corresponding to their deopt id. GotoInstr records its |
491 | // own so that it can control the placement. |
492 | AddCurrentDescriptor(PcDescriptorsLayout::kDeopt, instr->deopt_id(), |
493 | instr->token_pos()); |
494 | } |
495 | AllocateRegistersLocally(instr); |
496 | } |
497 | } |
498 | |
499 | void FlowGraphCompiler::EmitSourceLine(Instruction* instr) { |
500 | if (!instr->token_pos().IsReal() || (instr->env() == NULL)) { |
501 | return; |
502 | } |
503 | const Script& script = |
504 | Script::Handle(zone(), instr->env()->function().script()); |
505 | intptr_t line_nr; |
506 | intptr_t column_nr; |
507 | script.GetTokenLocation(instr->token_pos(), &line_nr, &column_nr); |
508 | const String& line = String::Handle(zone(), script.GetLine(line_nr)); |
509 | assembler()->Comment("Line %" Pd " in '%s':\n %s" , line_nr, |
510 | instr->env()->function().ToFullyQualifiedCString(), |
511 | line.ToCString()); |
512 | } |
513 | |
514 | static bool IsPusher(Instruction* instr) { |
515 | if (auto def = instr->AsDefinition()) { |
516 | return def->HasTemp(); |
517 | } |
518 | return false; |
519 | } |
520 | |
521 | static bool IsPopper(Instruction* instr) { |
522 | // TODO(ajcbik): even allow deopt targets by making environment aware? |
523 | if (!instr->CanBecomeDeoptimizationTarget()) { |
524 | return !instr->IsPushArgument() && instr->ArgumentCount() == 0 && |
525 | instr->InputCount() > 0; |
526 | } |
527 | return false; |
528 | } |
529 | |
530 | bool FlowGraphCompiler::IsPeephole(Instruction* instr) const { |
531 | if (FLAG_enable_peephole && !is_optimizing()) { |
532 | return IsPusher(instr) && IsPopper(instr->next()); |
533 | } |
534 | return false; |
535 | } |
536 | |
537 | void FlowGraphCompiler::VisitBlocks() { |
538 | CompactBlocks(); |
539 | if (compiler::Assembler::EmittingComments()) { |
540 | // The loop_info fields were cleared, recompute. |
541 | flow_graph().ComputeLoops(); |
542 | } |
543 | |
544 | // In precompiled mode, we require the function entry to come first (after the |
545 | // graph entry), since the polymorphic check is performed in the function |
546 | // entry (see Instructions::EntryPoint). |
547 | if (FLAG_precompiled_mode) { |
548 | ASSERT(block_order()[1] == flow_graph().graph_entry()->normal_entry()); |
549 | } |
550 | |
551 | for (intptr_t i = 0; i < block_order().length(); ++i) { |
552 | // Compile the block entry. |
553 | BlockEntryInstr* entry = block_order()[i]; |
554 | assembler()->Comment("B%" Pd "" , entry->block_id()); |
555 | set_current_block(entry); |
556 | |
557 | if (WasCompacted(entry)) { |
558 | continue; |
559 | } |
560 | |
561 | #if defined(DEBUG) |
562 | if (!is_optimizing()) { |
563 | FrameStateClear(); |
564 | } |
565 | #endif |
566 | |
567 | if (compiler::Assembler::EmittingComments()) { |
568 | for (LoopInfo* l = entry->loop_info(); l != nullptr; l = l->outer()) { |
569 | assembler()->Comment(" Loop %" Pd "" , l->id()); |
570 | } |
571 | } |
572 | |
573 | BeginCodeSourceRange(); |
574 | ASSERT(pending_deoptimization_env_ == NULL); |
575 | pending_deoptimization_env_ = entry->env(); |
576 | set_current_instruction(entry); |
577 | StatsBegin(entry); |
578 | entry->EmitNativeCode(this); |
579 | StatsEnd(entry); |
580 | set_current_instruction(nullptr); |
581 | pending_deoptimization_env_ = NULL; |
582 | EndCodeSourceRange(entry->token_pos()); |
583 | |
584 | if (skip_body_compilation()) { |
585 | ASSERT(entry == flow_graph().graph_entry()->normal_entry()); |
586 | break; |
587 | } |
588 | |
589 | // Compile all successors until an exit, branch, or a block entry. |
590 | for (ForwardInstructionIterator it(entry); !it.Done(); it.Advance()) { |
591 | Instruction* instr = it.Current(); |
592 | set_current_instruction(instr); |
593 | StatsBegin(instr); |
594 | |
595 | // Compose intervals. |
596 | code_source_map_builder_->StartInliningInterval(assembler()->CodeSize(), |
597 | instr->inlining_id()); |
598 | if (FLAG_code_comments || FLAG_disassemble || |
599 | FLAG_disassemble_optimized) { |
600 | if (FLAG_source_lines) { |
601 | EmitSourceLine(instr); |
602 | } |
603 | EmitComment(instr); |
604 | } |
605 | if (instr->IsParallelMove()) { |
606 | parallel_move_resolver_.EmitNativeCode(instr->AsParallelMove()); |
607 | } else { |
608 | BeginCodeSourceRange(); |
609 | EmitInstructionPrologue(instr); |
610 | ASSERT(pending_deoptimization_env_ == NULL); |
611 | pending_deoptimization_env_ = instr->env(); |
612 | DEBUG_ONLY(current_instruction_ = instr); |
613 | instr->EmitNativeCode(this); |
614 | DEBUG_ONLY(current_instruction_ = nullptr); |
615 | pending_deoptimization_env_ = NULL; |
616 | if (IsPeephole(instr)) { |
617 | ASSERT(top_of_stack_ == nullptr); |
618 | top_of_stack_ = instr->AsDefinition(); |
619 | } else { |
620 | EmitInstructionEpilogue(instr); |
621 | } |
622 | EndCodeSourceRange(instr->token_pos()); |
623 | } |
624 | |
625 | #if defined(DEBUG) |
626 | if (!is_optimizing()) { |
627 | FrameStateUpdateWith(instr); |
628 | } |
629 | #endif |
630 | StatsEnd(instr); |
631 | set_current_instruction(nullptr); |
632 | |
633 | if (auto indirect_goto = instr->AsIndirectGoto()) { |
634 | indirect_gotos_.Add(indirect_goto); |
635 | } |
636 | } |
637 | |
638 | #if defined(DEBUG) |
639 | ASSERT(is_optimizing() || FrameStateIsSafeToCall()); |
640 | #endif |
641 | } |
642 | |
643 | set_current_block(NULL); |
644 | } |
645 | |
646 | void FlowGraphCompiler::Bailout(const char* reason) { |
647 | parsed_function_.Bailout("FlowGraphCompiler" , reason); |
648 | } |
649 | |
650 | intptr_t FlowGraphCompiler::StackSize() const { |
651 | if (is_optimizing_) { |
652 | return flow_graph_.graph_entry()->spill_slot_count(); |
653 | } else { |
654 | return parsed_function_.num_stack_locals(); |
655 | } |
656 | } |
657 | |
658 | intptr_t FlowGraphCompiler::() const { |
659 | ASSERT(flow_graph().IsCompiledForOsr()); |
660 | const intptr_t stack_depth = |
661 | flow_graph().graph_entry()->osr_entry()->stack_depth(); |
662 | const intptr_t num_stack_locals = flow_graph().num_stack_locals(); |
663 | return StackSize() - stack_depth - num_stack_locals; |
664 | } |
665 | |
666 | compiler::Label* FlowGraphCompiler::GetJumpLabel( |
667 | BlockEntryInstr* block_entry) const { |
668 | const intptr_t block_index = block_entry->postorder_number(); |
669 | return block_info_[block_index]->jump_label(); |
670 | } |
671 | |
672 | bool FlowGraphCompiler::WasCompacted(BlockEntryInstr* block_entry) const { |
673 | const intptr_t block_index = block_entry->postorder_number(); |
674 | return block_info_[block_index]->WasCompacted(); |
675 | } |
676 | |
677 | compiler::Label* FlowGraphCompiler::NextNonEmptyLabel() const { |
678 | const intptr_t current_index = current_block()->postorder_number(); |
679 | return block_info_[current_index]->next_nonempty_label(); |
680 | } |
681 | |
682 | bool FlowGraphCompiler::CanFallThroughTo(BlockEntryInstr* block_entry) const { |
683 | return NextNonEmptyLabel() == GetJumpLabel(block_entry); |
684 | } |
685 | |
686 | BranchLabels FlowGraphCompiler::CreateBranchLabels(BranchInstr* branch) const { |
687 | compiler::Label* true_label = GetJumpLabel(branch->true_successor()); |
688 | compiler::Label* false_label = GetJumpLabel(branch->false_successor()); |
689 | compiler::Label* fall_through = NextNonEmptyLabel(); |
690 | BranchLabels result = {true_label, false_label, fall_through}; |
691 | return result; |
692 | } |
693 | |
694 | void FlowGraphCompiler::AddSlowPathCode(SlowPathCode* code) { |
695 | slow_path_code_.Add(code); |
696 | } |
697 | |
698 | void FlowGraphCompiler::GenerateDeferredCode() { |
699 | for (intptr_t i = 0; i < slow_path_code_.length(); i++) { |
700 | SlowPathCode* const slow_path = slow_path_code_[i]; |
701 | const CombinedCodeStatistics::EntryCounter stats_tag = |
702 | CombinedCodeStatistics::SlowPathCounterFor( |
703 | slow_path->instruction()->tag()); |
704 | set_current_instruction(slow_path->instruction()); |
705 | SpecialStatsBegin(stats_tag); |
706 | BeginCodeSourceRange(); |
707 | DEBUG_ONLY(current_instruction_ = slow_path->instruction()); |
708 | slow_path->GenerateCode(this); |
709 | DEBUG_ONLY(current_instruction_ = nullptr); |
710 | EndCodeSourceRange(slow_path->instruction()->token_pos()); |
711 | SpecialStatsEnd(stats_tag); |
712 | set_current_instruction(nullptr); |
713 | } |
714 | for (intptr_t i = 0; i < deopt_infos_.length(); i++) { |
715 | BeginCodeSourceRange(); |
716 | deopt_infos_[i]->GenerateCode(this, i); |
717 | EndCodeSourceRange(TokenPosition::kDeferredDeoptInfo); |
718 | } |
719 | } |
720 | |
721 | void FlowGraphCompiler::AddExceptionHandler(intptr_t try_index, |
722 | intptr_t outer_try_index, |
723 | intptr_t pc_offset, |
724 | bool is_generated, |
725 | const Array& handler_types, |
726 | bool needs_stacktrace) { |
727 | exception_handlers_list_->AddHandler(try_index, outer_try_index, pc_offset, |
728 | is_generated, handler_types, |
729 | needs_stacktrace); |
730 | } |
731 | |
732 | void FlowGraphCompiler::SetNeedsStackTrace(intptr_t try_index) { |
733 | exception_handlers_list_->SetNeedsStackTrace(try_index); |
734 | } |
735 | |
736 | void FlowGraphCompiler::AddDescriptor(PcDescriptorsLayout::Kind kind, |
737 | intptr_t pc_offset, |
738 | intptr_t deopt_id, |
739 | TokenPosition token_pos, |
740 | intptr_t try_index, |
741 | intptr_t yield_index) { |
742 | code_source_map_builder_->NoteDescriptor(kind, pc_offset, token_pos); |
743 | // Don't emit deopt-descriptors in AOT mode. |
744 | if (FLAG_precompiled_mode && (kind == PcDescriptorsLayout::kDeopt)) return; |
745 | pc_descriptors_list_->AddDescriptor(kind, pc_offset, deopt_id, token_pos, |
746 | try_index, yield_index); |
747 | } |
748 | |
749 | // Uses current pc position and try-index. |
750 | void FlowGraphCompiler::AddCurrentDescriptor(PcDescriptorsLayout::Kind kind, |
751 | intptr_t deopt_id, |
752 | TokenPosition token_pos) { |
753 | AddDescriptor(kind, assembler()->CodeSize(), deopt_id, token_pos, |
754 | CurrentTryIndex()); |
755 | } |
756 | |
757 | void FlowGraphCompiler::AddNullCheck(TokenPosition token_pos, |
758 | const String& name) { |
759 | #if defined(DART_PRECOMPILER) |
760 | // If we are generating an AOT snapshot and have DWARF stack traces enabled, |
761 | // the AOT runtime is unable to obtain the pool index at runtime. Therefore, |
762 | // there is no reason to put the name into the pool in the first place. |
763 | // TODO(dartbug.com/40605): Move this info to the pc descriptors. |
764 | if (FLAG_precompiled_mode && FLAG_dwarf_stack_traces_mode) return; |
765 | #endif |
766 | const intptr_t name_index = |
767 | assembler()->object_pool_builder().FindObject(name); |
768 | code_source_map_builder_->NoteNullCheck(assembler()->CodeSize(), token_pos, |
769 | name_index); |
770 | } |
771 | |
772 | void FlowGraphCompiler::AddPcRelativeCallTarget(const Function& function, |
773 | Code::EntryKind entry_kind) { |
774 | ASSERT(function.IsZoneHandle()); |
775 | const auto entry_point = entry_kind == Code::EntryKind::kUnchecked |
776 | ? Code::kUncheckedEntry |
777 | : Code::kDefaultEntry; |
778 | static_calls_target_table_.Add(new (zone()) StaticCallsStruct( |
779 | Code::kPcRelativeCall, entry_point, assembler()->CodeSize(), &function, |
780 | nullptr, nullptr)); |
781 | } |
782 | |
783 | void FlowGraphCompiler::AddPcRelativeCallStubTarget(const Code& stub_code) { |
784 | ASSERT(stub_code.IsZoneHandle() || stub_code.IsReadOnlyHandle()); |
785 | ASSERT(!stub_code.IsNull()); |
786 | static_calls_target_table_.Add(new (zone()) StaticCallsStruct( |
787 | Code::kPcRelativeCall, Code::kDefaultEntry, assembler()->CodeSize(), |
788 | nullptr, &stub_code, nullptr)); |
789 | } |
790 | |
791 | void FlowGraphCompiler::AddPcRelativeTailCallStubTarget(const Code& stub_code) { |
792 | ASSERT(stub_code.IsZoneHandle() || stub_code.IsReadOnlyHandle()); |
793 | ASSERT(!stub_code.IsNull()); |
794 | static_calls_target_table_.Add(new (zone()) StaticCallsStruct( |
795 | Code::kPcRelativeTailCall, Code::kDefaultEntry, assembler()->CodeSize(), |
796 | nullptr, &stub_code, nullptr)); |
797 | } |
798 | |
799 | void FlowGraphCompiler::AddPcRelativeTTSCallTypeTarget( |
800 | const AbstractType& dst_type) { |
801 | ASSERT(dst_type.IsZoneHandle() || dst_type.IsReadOnlyHandle()); |
802 | ASSERT(!dst_type.IsNull()); |
803 | static_calls_target_table_.Add(new (zone()) StaticCallsStruct( |
804 | Code::kPcRelativeTTSCall, Code::kDefaultEntry, assembler()->CodeSize(), |
805 | nullptr, nullptr, &dst_type)); |
806 | } |
807 | |
808 | void FlowGraphCompiler::AddStaticCallTarget(const Function& func, |
809 | Code::EntryKind entry_kind) { |
810 | ASSERT(func.IsZoneHandle()); |
811 | const auto entry_point = entry_kind == Code::EntryKind::kUnchecked |
812 | ? Code::kUncheckedEntry |
813 | : Code::kDefaultEntry; |
814 | static_calls_target_table_.Add(new (zone()) StaticCallsStruct( |
815 | Code::kCallViaCode, entry_point, assembler()->CodeSize(), &func, nullptr, |
816 | nullptr)); |
817 | } |
818 | |
819 | void FlowGraphCompiler::AddStubCallTarget(const Code& code) { |
820 | ASSERT(code.IsZoneHandle() || code.IsReadOnlyHandle()); |
821 | static_calls_target_table_.Add(new (zone()) StaticCallsStruct( |
822 | Code::kCallViaCode, Code::kDefaultEntry, assembler()->CodeSize(), nullptr, |
823 | &code, nullptr)); |
824 | } |
825 | |
826 | void FlowGraphCompiler::AddDispatchTableCallTarget( |
827 | const compiler::TableSelector* selector) { |
828 | dispatch_table_call_targets_.Add(selector); |
829 | } |
830 | |
831 | CompilerDeoptInfo* FlowGraphCompiler::AddDeoptIndexAtCall(intptr_t deopt_id) { |
832 | ASSERT(is_optimizing()); |
833 | ASSERT(!intrinsic_mode()); |
834 | ASSERT(!FLAG_precompiled_mode); |
835 | CompilerDeoptInfo* info = |
836 | new (zone()) CompilerDeoptInfo(deopt_id, ICData::kDeoptAtCall, |
837 | 0, // No flags. |
838 | pending_deoptimization_env_); |
839 | info->set_pc_offset(assembler()->CodeSize()); |
840 | deopt_infos_.Add(info); |
841 | return info; |
842 | } |
843 | |
844 | CompilerDeoptInfo* FlowGraphCompiler::AddSlowPathDeoptInfo(intptr_t deopt_id, |
845 | Environment* env) { |
846 | ASSERT(deopt_id != DeoptId::kNone); |
847 | CompilerDeoptInfo* info = |
848 | new (zone()) CompilerDeoptInfo(deopt_id, ICData::kDeoptUnknown, 0, env); |
849 | info->set_pc_offset(assembler()->CodeSize()); |
850 | deopt_infos_.Add(info); |
851 | return info; |
852 | } |
853 | |
854 | // This function must be in sync with FlowGraphCompiler::SaveLiveRegisters |
855 | // and FlowGraphCompiler::SlowPathEnvironmentFor. |
856 | // See StackFrame::VisitObjectPointers for the details of how stack map is |
857 | // interpreted. |
858 | void FlowGraphCompiler::RecordSafepoint(LocationSummary* locs, |
859 | intptr_t slow_path_argument_count) { |
860 | if (is_optimizing() || locs->live_registers()->HasUntaggedValues()) { |
861 | const intptr_t spill_area_size = |
862 | is_optimizing() ? flow_graph_.graph_entry()->spill_slot_count() : 0; |
863 | |
864 | RegisterSet* registers = locs->live_registers(); |
865 | ASSERT(registers != NULL); |
866 | const intptr_t kFpuRegisterSpillFactor = |
867 | kFpuRegisterSize / compiler::target::kWordSize; |
868 | intptr_t saved_registers_size = 0; |
869 | const bool using_shared_stub = locs->call_on_shared_slow_path(); |
870 | if (using_shared_stub) { |
871 | saved_registers_size = |
872 | Utils::CountOneBitsWord(kDartAvailableCpuRegs) + |
873 | (registers->FpuRegisterCount() > 0 |
874 | ? kFpuRegisterSpillFactor * kNumberOfFpuRegisters |
875 | : 0) + |
876 | 1 /*saved PC*/; |
877 | } else { |
878 | saved_registers_size = |
879 | registers->CpuRegisterCount() + |
880 | (registers->FpuRegisterCount() * kFpuRegisterSpillFactor); |
881 | } |
882 | |
883 | BitmapBuilder* bitmap = locs->stack_bitmap(); |
884 | |
885 | // An instruction may have two safepoints in deferred code. The |
886 | // call to RecordSafepoint has the side-effect of appending the live |
887 | // registers to the bitmap. This is why the second call to RecordSafepoint |
888 | // with the same instruction (and same location summary) sees a bitmap that |
889 | // is larger that StackSize(). It will never be larger than StackSize() + |
890 | // unboxed_arg_bits_count + live_registers_size. |
891 | // The first safepoint will grow the bitmap to be the size of |
892 | // spill_area_size but the second safepoint will truncate the bitmap and |
893 | // append the bits for arguments and live registers to it again. |
894 | const intptr_t bitmap_previous_length = bitmap->Length(); |
895 | bitmap->SetLength(spill_area_size); |
896 | |
897 | intptr_t unboxed_arg_bits_count = 0; |
898 | |
899 | auto instr = current_instruction(); |
900 | const intptr_t args_count = instr->ArgumentCount(); |
901 | bool pushed_unboxed = false; |
902 | |
903 | for (intptr_t i = 0; i < args_count; i++) { |
904 | auto push_arg = |
905 | instr->ArgumentValueAt(i)->instruction()->AsPushArgument(); |
906 | switch (push_arg->representation()) { |
907 | case kUnboxedInt64: |
908 | bitmap->SetRange( |
909 | bitmap->Length(), |
910 | bitmap->Length() + compiler::target::kIntSpillFactor - 1, false); |
911 | unboxed_arg_bits_count += compiler::target::kIntSpillFactor; |
912 | pushed_unboxed = true; |
913 | break; |
914 | case kUnboxedDouble: |
915 | bitmap->SetRange( |
916 | bitmap->Length(), |
917 | bitmap->Length() + compiler::target::kDoubleSpillFactor - 1, |
918 | false); |
919 | unboxed_arg_bits_count += compiler::target::kDoubleSpillFactor; |
920 | pushed_unboxed = true; |
921 | break; |
922 | case kTagged: |
923 | if (!pushed_unboxed) { |
924 | // GC considers everything to be tagged between prefix of stack |
925 | // frame (spill area size) and postfix of stack frame (e.g. slow |
926 | // path arguments, shared pushed registers). |
927 | // From the first unboxed argument on we will include bits in the |
928 | // postfix. |
929 | continue; |
930 | } |
931 | bitmap->Set(bitmap->Length(), true); |
932 | unboxed_arg_bits_count++; |
933 | break; |
934 | default: |
935 | UNREACHABLE(); |
936 | break; |
937 | } |
938 | } |
939 | ASSERT(bitmap_previous_length <= |
940 | (spill_area_size + unboxed_arg_bits_count + saved_registers_size)); |
941 | |
942 | ASSERT(slow_path_argument_count == 0 || !using_shared_stub); |
943 | |
944 | // Mark the bits in the stack map in the same order we push registers in |
945 | // slow path code (see FlowGraphCompiler::SaveLiveRegisters). |
946 | // |
947 | // Slow path code can have registers at the safepoint. |
948 | if (!locs->always_calls() && !using_shared_stub) { |
949 | RegisterSet* regs = locs->live_registers(); |
950 | if (regs->FpuRegisterCount() > 0) { |
951 | // Denote FPU registers with 0 bits in the stackmap. Based on the |
952 | // assumption that there are normally few live FPU registers, this |
953 | // encoding is simpler and roughly as compact as storing a separate |
954 | // count of FPU registers. |
955 | // |
956 | // FPU registers have the highest register number at the highest |
957 | // address (i.e., first in the stackmap). |
958 | for (intptr_t i = kNumberOfFpuRegisters - 1; i >= 0; --i) { |
959 | FpuRegister reg = static_cast<FpuRegister>(i); |
960 | if (regs->ContainsFpuRegister(reg)) { |
961 | for (intptr_t j = 0; j < kFpuRegisterSpillFactor; ++j) { |
962 | bitmap->Set(bitmap->Length(), false); |
963 | } |
964 | } |
965 | } |
966 | } |
967 | |
968 | // General purpose registers have the highest register number at the |
969 | // highest address (i.e., first in the stackmap). |
970 | for (intptr_t i = kNumberOfCpuRegisters - 1; i >= 0; --i) { |
971 | Register reg = static_cast<Register>(i); |
972 | if (locs->live_registers()->ContainsRegister(reg)) { |
973 | bitmap->Set(bitmap->Length(), locs->live_registers()->IsTagged(reg)); |
974 | } |
975 | } |
976 | } |
977 | |
978 | if (using_shared_stub) { |
979 | // To simplify the code in the shared stub, we create an untagged hole |
980 | // in the stack frame where the shared stub can leave the return address |
981 | // before saving registers. |
982 | bitmap->Set(bitmap->Length(), false); |
983 | if (registers->FpuRegisterCount() > 0) { |
984 | bitmap->SetRange(bitmap->Length(), |
985 | bitmap->Length() + |
986 | kNumberOfFpuRegisters * kFpuRegisterSpillFactor - |
987 | 1, |
988 | false); |
989 | } |
990 | for (intptr_t i = kNumberOfCpuRegisters - 1; i >= 0; --i) { |
991 | if ((kReservedCpuRegisters & (1 << i)) != 0) continue; |
992 | const Register reg = static_cast<Register>(i); |
993 | bitmap->Set(bitmap->Length(), |
994 | locs->live_registers()->ContainsRegister(reg) && |
995 | locs->live_registers()->IsTagged(reg)); |
996 | } |
997 | } |
998 | |
999 | // Arguments pushed after live registers in the slow path are tagged. |
1000 | for (intptr_t i = 0; i < slow_path_argument_count; ++i) { |
1001 | bitmap->Set(bitmap->Length(), true); |
1002 | } |
1003 | |
1004 | compressed_stackmaps_builder()->AddEntry(assembler()->CodeSize(), bitmap, |
1005 | spill_area_size); |
1006 | } |
1007 | } |
1008 | |
1009 | // This function must be kept in sync with: |
1010 | // |
1011 | // FlowGraphCompiler::RecordSafepoint |
1012 | // FlowGraphCompiler::SaveLiveRegisters |
1013 | // MaterializeObjectInstr::RemapRegisters |
1014 | // |
1015 | Environment* FlowGraphCompiler::SlowPathEnvironmentFor( |
1016 | Environment* env, |
1017 | LocationSummary* locs, |
1018 | intptr_t num_slow_path_args) { |
1019 | const bool using_shared_stub = locs->call_on_shared_slow_path(); |
1020 | const bool shared_stub_save_fpu_registers = |
1021 | using_shared_stub && locs->live_registers()->FpuRegisterCount() > 0; |
1022 | // TODO(sjindel): Modify logic below to account for slow-path args with shared |
1023 | // stubs. |
1024 | ASSERT(!using_shared_stub || num_slow_path_args == 0); |
1025 | if (env == nullptr) { |
1026 | // In AOT, environments can be removed by EliminateEnvironments pass |
1027 | // (if not in a try block). |
1028 | ASSERT(!is_optimizing() || FLAG_precompiled_mode); |
1029 | return nullptr; |
1030 | } |
1031 | |
1032 | Environment* slow_path_env = env->DeepCopy(zone()); |
1033 | // 1. Iterate the registers in the order they will be spilled to compute |
1034 | // the slots they will be spilled to. |
1035 | intptr_t next_slot = StackSize() + slow_path_env->CountArgsPushed(); |
1036 | if (using_shared_stub) { |
1037 | // The PC from the call to the shared stub is pushed here. |
1038 | next_slot++; |
1039 | } |
1040 | RegisterSet* regs = locs->live_registers(); |
1041 | intptr_t fpu_reg_slots[kNumberOfFpuRegisters]; |
1042 | intptr_t cpu_reg_slots[kNumberOfCpuRegisters]; |
1043 | const intptr_t kFpuRegisterSpillFactor = |
1044 | kFpuRegisterSize / compiler::target::kWordSize; |
1045 | // FPU registers are spilled first from highest to lowest register number. |
1046 | for (intptr_t i = kNumberOfFpuRegisters - 1; i >= 0; --i) { |
1047 | FpuRegister reg = static_cast<FpuRegister>(i); |
1048 | if (regs->ContainsFpuRegister(reg)) { |
1049 | // We use the lowest address (thus highest index) to identify a |
1050 | // multi-word spill slot. |
1051 | next_slot += kFpuRegisterSpillFactor; |
1052 | fpu_reg_slots[i] = (next_slot - 1); |
1053 | } else { |
1054 | if (using_shared_stub && shared_stub_save_fpu_registers) { |
1055 | next_slot += kFpuRegisterSpillFactor; |
1056 | } |
1057 | fpu_reg_slots[i] = -1; |
1058 | } |
1059 | } |
1060 | // General purpose registers are spilled from highest to lowest register |
1061 | // number. |
1062 | for (intptr_t i = kNumberOfCpuRegisters - 1; i >= 0; --i) { |
1063 | if ((kReservedCpuRegisters & (1 << i)) != 0) continue; |
1064 | Register reg = static_cast<Register>(i); |
1065 | if (regs->ContainsRegister(reg)) { |
1066 | cpu_reg_slots[i] = next_slot++; |
1067 | } else { |
1068 | if (using_shared_stub) next_slot++; |
1069 | cpu_reg_slots[i] = -1; |
1070 | } |
1071 | } |
1072 | |
1073 | // 2. Iterate the environment and replace register locations with the |
1074 | // corresponding spill slot locations. |
1075 | for (Environment::DeepIterator it(slow_path_env); !it.Done(); it.Advance()) { |
1076 | Location loc = it.CurrentLocation(); |
1077 | Value* value = it.CurrentValue(); |
1078 | it.SetCurrentLocation(LocationRemapForSlowPath( |
1079 | loc, value->definition(), cpu_reg_slots, fpu_reg_slots)); |
1080 | } |
1081 | |
1082 | return slow_path_env; |
1083 | } |
1084 | |
1085 | compiler::Label* FlowGraphCompiler::AddDeoptStub(intptr_t deopt_id, |
1086 | ICData::DeoptReasonId reason, |
1087 | uint32_t flags) { |
1088 | if (intrinsic_mode()) { |
1089 | return intrinsic_slow_path_label_; |
1090 | } |
1091 | |
1092 | // No deoptimization allowed when 'FLAG_precompiled_mode' is set. |
1093 | if (FLAG_precompiled_mode) { |
1094 | if (FLAG_trace_compiler) { |
1095 | THR_Print( |
1096 | "Retrying compilation %s, suppressing inlining of deopt_id:%" Pd "\n" , |
1097 | parsed_function_.function().ToFullyQualifiedCString(), deopt_id); |
1098 | } |
1099 | ASSERT(speculative_policy_->AllowsSpeculativeInlining()); |
1100 | ASSERT(deopt_id != 0); // longjmp must return non-zero value. |
1101 | Thread::Current()->long_jump_base()->Jump( |
1102 | deopt_id, Object::speculative_inlining_error()); |
1103 | } |
1104 | |
1105 | ASSERT(is_optimizing_); |
1106 | CompilerDeoptInfoWithStub* stub = new (zone()) CompilerDeoptInfoWithStub( |
1107 | deopt_id, reason, flags, pending_deoptimization_env_); |
1108 | deopt_infos_.Add(stub); |
1109 | return stub->entry_label(); |
1110 | } |
1111 | |
1112 | void FlowGraphCompiler::FinalizeExceptionHandlers(const Code& code) { |
1113 | ASSERT(exception_handlers_list_ != NULL); |
1114 | const ExceptionHandlers& handlers = ExceptionHandlers::Handle( |
1115 | exception_handlers_list_->FinalizeExceptionHandlers(code.PayloadStart())); |
1116 | code.set_exception_handlers(handlers); |
1117 | } |
1118 | |
1119 | void FlowGraphCompiler::FinalizePcDescriptors(const Code& code) { |
1120 | ASSERT(pc_descriptors_list_ != NULL); |
1121 | const PcDescriptors& descriptors = PcDescriptors::Handle( |
1122 | pc_descriptors_list_->FinalizePcDescriptors(code.PayloadStart())); |
1123 | if (!is_optimizing_) descriptors.Verify(parsed_function_.function()); |
1124 | code.set_pc_descriptors(descriptors); |
1125 | } |
1126 | |
1127 | ArrayPtr FlowGraphCompiler::CreateDeoptInfo(compiler::Assembler* assembler) { |
1128 | // No deopt information if we precompile (no deoptimization allowed). |
1129 | if (FLAG_precompiled_mode) { |
1130 | return Array::empty_array().raw(); |
1131 | } |
1132 | // For functions with optional arguments, all incoming arguments are copied |
1133 | // to spill slots. The deoptimization environment does not track them. |
1134 | const Function& function = parsed_function().function(); |
1135 | const intptr_t incoming_arg_count = |
1136 | function.HasOptionalParameters() ? 0 : function.num_fixed_parameters(); |
1137 | DeoptInfoBuilder builder(zone(), incoming_arg_count, assembler); |
1138 | |
1139 | intptr_t deopt_info_table_size = DeoptTable::SizeFor(deopt_infos_.length()); |
1140 | if (deopt_info_table_size == 0) { |
1141 | return Object::empty_array().raw(); |
1142 | } else { |
1143 | const Array& array = |
1144 | Array::Handle(Array::New(deopt_info_table_size, Heap::kOld)); |
1145 | Smi& offset = Smi::Handle(); |
1146 | TypedData& info = TypedData::Handle(); |
1147 | Smi& reason_and_flags = Smi::Handle(); |
1148 | for (intptr_t i = 0; i < deopt_infos_.length(); i++) { |
1149 | offset = Smi::New(deopt_infos_[i]->pc_offset()); |
1150 | info = deopt_infos_[i]->CreateDeoptInfo(this, &builder, array); |
1151 | reason_and_flags = DeoptTable::EncodeReasonAndFlags( |
1152 | deopt_infos_[i]->reason(), deopt_infos_[i]->flags()); |
1153 | DeoptTable::SetEntry(array, i, offset, info, reason_and_flags); |
1154 | } |
1155 | return array.raw(); |
1156 | } |
1157 | } |
1158 | |
1159 | void FlowGraphCompiler::FinalizeStackMaps(const Code& code) { |
1160 | if (compressed_stackmaps_builder_ == NULL) { |
1161 | code.set_compressed_stackmaps( |
1162 | CompressedStackMaps::Handle(CompressedStackMaps::null())); |
1163 | } else { |
1164 | // Finalize the compressed stack maps and add it to the code object. |
1165 | const auto& maps = |
1166 | CompressedStackMaps::Handle(compressed_stackmaps_builder_->Finalize()); |
1167 | code.set_compressed_stackmaps(maps); |
1168 | } |
1169 | } |
1170 | |
1171 | void FlowGraphCompiler::FinalizeVarDescriptors(const Code& code) { |
1172 | #if defined(PRODUCT) |
1173 | // No debugger: no var descriptors. |
1174 | #else |
1175 | if (code.is_optimized()) { |
1176 | // Optimized code does not need variable descriptors. They are |
1177 | // only stored in the unoptimized version. |
1178 | code.set_var_descriptors(Object::empty_var_descriptors()); |
1179 | return; |
1180 | } |
1181 | LocalVarDescriptors& var_descs = LocalVarDescriptors::Handle(); |
1182 | if (flow_graph().IsIrregexpFunction()) { |
1183 | // Eager local var descriptors computation for Irregexp function as it is |
1184 | // complicated to factor out. |
1185 | // TODO(srdjan): Consider canonicalizing and reusing the local var |
1186 | // descriptor for IrregexpFunction. |
1187 | ASSERT(parsed_function().scope() == nullptr); |
1188 | var_descs = LocalVarDescriptors::New(1); |
1189 | LocalVarDescriptorsLayout::VarInfo info; |
1190 | info.set_kind(LocalVarDescriptorsLayout::kSavedCurrentContext); |
1191 | info.scope_id = 0; |
1192 | info.begin_pos = TokenPosition::kMinSource; |
1193 | info.end_pos = TokenPosition::kMinSource; |
1194 | info.set_index(compiler::target::frame_layout.FrameSlotForVariable( |
1195 | parsed_function().current_context_var())); |
1196 | var_descs.SetVar(0, Symbols::CurrentContextVar(), &info); |
1197 | } |
1198 | code.set_var_descriptors(var_descs); |
1199 | #endif |
1200 | } |
1201 | |
1202 | void FlowGraphCompiler::FinalizeCatchEntryMovesMap(const Code& code) { |
1203 | #if defined(DART_PRECOMPILER) |
1204 | if (FLAG_precompiled_mode) { |
1205 | TypedData& maps = TypedData::Handle( |
1206 | catch_entry_moves_maps_builder_->FinalizeCatchEntryMovesMap()); |
1207 | code.set_catch_entry_moves_maps(maps); |
1208 | return; |
1209 | } |
1210 | #endif |
1211 | code.set_num_variables(flow_graph().variable_count()); |
1212 | } |
1213 | |
1214 | void FlowGraphCompiler::FinalizeStaticCallTargetsTable(const Code& code) { |
1215 | ASSERT(code.static_calls_target_table() == Array::null()); |
1216 | const auto& calls = static_calls_target_table_; |
1217 | const intptr_t array_length = calls.length() * Code::kSCallTableEntryLength; |
1218 | const auto& targets = |
1219 | Array::Handle(zone(), Array::New(array_length, Heap::kOld)); |
1220 | |
1221 | StaticCallsTable entries(targets); |
1222 | auto& kind_type_and_offset = Smi::Handle(zone()); |
1223 | for (intptr_t i = 0; i < calls.length(); i++) { |
1224 | auto entry = calls[i]; |
1225 | kind_type_and_offset = |
1226 | Smi::New(Code::KindField::encode(entry->call_kind) | |
1227 | Code::EntryPointField::encode(entry->entry_point) | |
1228 | Code::OffsetField::encode(entry->offset)); |
1229 | auto view = entries[i]; |
1230 | view.Set<Code::kSCallTableKindAndOffset>(kind_type_and_offset); |
1231 | const Object* target = nullptr; |
1232 | if (entry->function != nullptr) { |
1233 | target = entry->function; |
1234 | view.Set<Code::kSCallTableFunctionTarget>(*entry->function); |
1235 | } |
1236 | if (entry->code != nullptr) { |
1237 | ASSERT(target == nullptr); |
1238 | target = entry->code; |
1239 | view.Set<Code::kSCallTableCodeOrTypeTarget>(*entry->code); |
1240 | } |
1241 | if (entry->dst_type != nullptr) { |
1242 | ASSERT(target == nullptr); |
1243 | view.Set<Code::kSCallTableCodeOrTypeTarget>(*entry->dst_type); |
1244 | } |
1245 | } |
1246 | code.set_static_calls_target_table(targets); |
1247 | } |
1248 | |
1249 | void FlowGraphCompiler::FinalizeCodeSourceMap(const Code& code) { |
1250 | const Array& inlined_id_array = |
1251 | Array::Handle(zone(), code_source_map_builder_->InliningIdToFunction()); |
1252 | code.set_inlined_id_to_function(inlined_id_array); |
1253 | |
1254 | const CodeSourceMap& map = |
1255 | CodeSourceMap::Handle(code_source_map_builder_->Finalize()); |
1256 | code.set_code_source_map(map); |
1257 | |
1258 | #if defined(DEBUG) |
1259 | // Force simulation through the last pc offset. This checks we can decode |
1260 | // the whole CodeSourceMap without hitting an unknown opcode, stack underflow, |
1261 | // etc. |
1262 | GrowableArray<const Function*> fs; |
1263 | GrowableArray<TokenPosition> tokens; |
1264 | code.GetInlinedFunctionsAtInstruction(code.Size() - 1, &fs, &tokens); |
1265 | #endif |
1266 | } |
1267 | |
1268 | // Returns 'true' if regular code generation should be skipped. |
1269 | bool FlowGraphCompiler::TryIntrinsify() { |
1270 | if (TryIntrinsifyHelper()) { |
1271 | fully_intrinsified_ = true; |
1272 | return true; |
1273 | } |
1274 | return false; |
1275 | } |
1276 | |
1277 | bool FlowGraphCompiler::TryIntrinsifyHelper() { |
1278 | compiler::Label exit; |
1279 | set_intrinsic_slow_path_label(&exit); |
1280 | |
1281 | if (FLAG_intrinsify) { |
1282 | const auto& function = parsed_function().function(); |
1283 | if (function.IsMethodExtractor()) { |
1284 | #if !defined(TARGET_ARCH_IA32) |
1285 | auto& = |
1286 | Function::ZoneHandle(function.extracted_method_closure()); |
1287 | auto& klass = Class::Handle(extracted_method.Owner()); |
1288 | const intptr_t type_arguments_field_offset = |
1289 | compiler::target::Class::HasTypeArgumentsField(klass) |
1290 | ? (compiler::target::Class::TypeArgumentsFieldOffset(klass) - |
1291 | kHeapObjectTag) |
1292 | : 0; |
1293 | |
1294 | SpecialStatsBegin(CombinedCodeStatistics::kTagIntrinsics); |
1295 | GenerateMethodExtractorIntrinsic(extracted_method, |
1296 | type_arguments_field_offset); |
1297 | SpecialStatsEnd(CombinedCodeStatistics::kTagIntrinsics); |
1298 | return true; |
1299 | #endif // !defined(TARGET_ARCH_IA32) |
1300 | } |
1301 | } |
1302 | |
1303 | EnterIntrinsicMode(); |
1304 | |
1305 | SpecialStatsBegin(CombinedCodeStatistics::kTagIntrinsics); |
1306 | bool complete = compiler::Intrinsifier::Intrinsify(parsed_function(), this); |
1307 | SpecialStatsEnd(CombinedCodeStatistics::kTagIntrinsics); |
1308 | |
1309 | ExitIntrinsicMode(); |
1310 | |
1311 | // "Deoptimization" from intrinsic continues here. All deoptimization |
1312 | // branches from intrinsic code redirect to here where the slow-path |
1313 | // (normal function body) starts. |
1314 | // This means that there must not be any side-effects in intrinsic code |
1315 | // before any deoptimization point. |
1316 | assembler()->Bind(intrinsic_slow_path_label()); |
1317 | set_intrinsic_slow_path_label(nullptr); |
1318 | return complete; |
1319 | } |
1320 | |
1321 | void FlowGraphCompiler::GenerateStubCall(TokenPosition token_pos, |
1322 | const Code& stub, |
1323 | PcDescriptorsLayout::Kind kind, |
1324 | LocationSummary* locs, |
1325 | intptr_t deopt_id, |
1326 | Environment* env) { |
1327 | EmitCallToStub(stub); |
1328 | EmitCallsiteMetadata(token_pos, deopt_id, kind, locs, env); |
1329 | } |
1330 | |
1331 | static const Code& StubEntryFor(const ICData& ic_data, bool optimized) { |
1332 | switch (ic_data.NumArgsTested()) { |
1333 | case 1: |
1334 | #if defined(TARGET_ARCH_X64) |
1335 | if (ic_data.is_tracking_exactness()) { |
1336 | if (optimized) { |
1337 | return StubCode::OneArgOptimizedCheckInlineCacheWithExactnessCheck(); |
1338 | } else { |
1339 | return StubCode::OneArgCheckInlineCacheWithExactnessCheck(); |
1340 | } |
1341 | } |
1342 | #else |
1343 | // TODO(dartbug.com/34170) Port exactness tracking to other platforms. |
1344 | ASSERT(!ic_data.is_tracking_exactness()); |
1345 | #endif |
1346 | return optimized ? StubCode::OneArgOptimizedCheckInlineCache() |
1347 | : StubCode::OneArgCheckInlineCache(); |
1348 | case 2: |
1349 | ASSERT(!ic_data.is_tracking_exactness()); |
1350 | return optimized ? StubCode::TwoArgsOptimizedCheckInlineCache() |
1351 | : StubCode::TwoArgsCheckInlineCache(); |
1352 | default: |
1353 | ic_data.Print(); |
1354 | UNIMPLEMENTED(); |
1355 | return Code::Handle(); |
1356 | } |
1357 | } |
1358 | |
1359 | void FlowGraphCompiler::GenerateInstanceCall(intptr_t deopt_id, |
1360 | TokenPosition token_pos, |
1361 | LocationSummary* locs, |
1362 | const ICData& ic_data_in, |
1363 | Code::EntryKind entry_kind, |
1364 | bool receiver_can_be_smi) { |
1365 | ICData& ic_data = ICData::ZoneHandle(ic_data_in.Original()); |
1366 | if (FLAG_precompiled_mode) { |
1367 | ic_data = ic_data.AsUnaryClassChecks(); |
1368 | EmitInstanceCallAOT(ic_data, deopt_id, token_pos, locs, entry_kind, |
1369 | receiver_can_be_smi); |
1370 | return; |
1371 | } |
1372 | ASSERT(!ic_data.IsNull()); |
1373 | if (is_optimizing() && (ic_data_in.NumberOfUsedChecks() == 0)) { |
1374 | // Emit IC call that will count and thus may need reoptimization at |
1375 | // function entry. |
1376 | ASSERT(may_reoptimize() || flow_graph().IsCompiledForOsr()); |
1377 | EmitOptimizedInstanceCall(StubEntryFor(ic_data, /*optimized=*/true), |
1378 | ic_data, deopt_id, token_pos, locs, entry_kind); |
1379 | return; |
1380 | } |
1381 | |
1382 | if (is_optimizing()) { |
1383 | EmitMegamorphicInstanceCall(ic_data_in, deopt_id, token_pos, locs, |
1384 | kInvalidTryIndex); |
1385 | return; |
1386 | } |
1387 | |
1388 | EmitInstanceCallJIT(StubEntryFor(ic_data, /*optimized=*/false), ic_data, |
1389 | deopt_id, token_pos, locs, entry_kind); |
1390 | } |
1391 | |
1392 | void FlowGraphCompiler::GenerateStaticCall(intptr_t deopt_id, |
1393 | TokenPosition token_pos, |
1394 | const Function& function, |
1395 | ArgumentsInfo args_info, |
1396 | LocationSummary* locs, |
1397 | const ICData& ic_data_in, |
1398 | ICData::RebindRule rebind_rule, |
1399 | Code::EntryKind entry_kind) { |
1400 | const ICData& ic_data = ICData::ZoneHandle(ic_data_in.Original()); |
1401 | const Array& arguments_descriptor = Array::ZoneHandle( |
1402 | zone(), ic_data.IsNull() ? args_info.ToArgumentsDescriptor() |
1403 | : ic_data.arguments_descriptor()); |
1404 | ASSERT(ArgumentsDescriptor(arguments_descriptor).TypeArgsLen() == |
1405 | args_info.type_args_len); |
1406 | ASSERT(ArgumentsDescriptor(arguments_descriptor).Count() == |
1407 | args_info.count_without_type_args); |
1408 | ASSERT(ArgumentsDescriptor(arguments_descriptor).Size() == |
1409 | args_info.size_without_type_args); |
1410 | // Force-optimized functions lack the deopt info which allows patching of |
1411 | // optimized static calls. |
1412 | if (is_optimizing() && (!ForcedOptimization() || FLAG_precompiled_mode)) { |
1413 | EmitOptimizedStaticCall(function, arguments_descriptor, |
1414 | args_info.size_with_type_args, deopt_id, token_pos, |
1415 | locs, entry_kind); |
1416 | } else { |
1417 | ICData& call_ic_data = ICData::ZoneHandle(zone(), ic_data.raw()); |
1418 | if (call_ic_data.IsNull()) { |
1419 | const intptr_t kNumArgsChecked = 0; |
1420 | call_ic_data = |
1421 | GetOrAddStaticCallICData(deopt_id, function, arguments_descriptor, |
1422 | kNumArgsChecked, rebind_rule) |
1423 | ->raw(); |
1424 | call_ic_data = call_ic_data.Original(); |
1425 | } |
1426 | AddCurrentDescriptor(PcDescriptorsLayout::kRewind, deopt_id, token_pos); |
1427 | EmitUnoptimizedStaticCall(args_info.size_with_type_args, deopt_id, |
1428 | token_pos, locs, call_ic_data, entry_kind); |
1429 | } |
1430 | } |
1431 | |
1432 | void FlowGraphCompiler::GenerateNumberTypeCheck( |
1433 | Register class_id_reg, |
1434 | const AbstractType& type, |
1435 | compiler::Label* is_instance_lbl, |
1436 | compiler::Label* is_not_instance_lbl) { |
1437 | assembler()->Comment("NumberTypeCheck" ); |
1438 | GrowableArray<intptr_t> args; |
1439 | if (type.IsNumberType()) { |
1440 | args.Add(kDoubleCid); |
1441 | args.Add(kMintCid); |
1442 | } else if (type.IsIntType()) { |
1443 | args.Add(kMintCid); |
1444 | } else if (type.IsDoubleType()) { |
1445 | args.Add(kDoubleCid); |
1446 | } |
1447 | CheckClassIds(class_id_reg, args, is_instance_lbl, is_not_instance_lbl); |
1448 | } |
1449 | |
1450 | void FlowGraphCompiler::GenerateStringTypeCheck( |
1451 | Register class_id_reg, |
1452 | compiler::Label* is_instance_lbl, |
1453 | compiler::Label* is_not_instance_lbl) { |
1454 | assembler()->Comment("StringTypeCheck" ); |
1455 | GrowableArray<intptr_t> args; |
1456 | args.Add(kOneByteStringCid); |
1457 | args.Add(kTwoByteStringCid); |
1458 | args.Add(kExternalOneByteStringCid); |
1459 | args.Add(kExternalTwoByteStringCid); |
1460 | CheckClassIds(class_id_reg, args, is_instance_lbl, is_not_instance_lbl); |
1461 | } |
1462 | |
1463 | void FlowGraphCompiler::GenerateListTypeCheck( |
1464 | Register class_id_reg, |
1465 | compiler::Label* is_instance_lbl) { |
1466 | assembler()->Comment("ListTypeCheck" ); |
1467 | compiler::Label unknown; |
1468 | GrowableArray<intptr_t> args; |
1469 | args.Add(kArrayCid); |
1470 | args.Add(kGrowableObjectArrayCid); |
1471 | args.Add(kImmutableArrayCid); |
1472 | CheckClassIds(class_id_reg, args, is_instance_lbl, &unknown); |
1473 | assembler()->Bind(&unknown); |
1474 | } |
1475 | |
1476 | void FlowGraphCompiler::(Instruction* instr) { |
1477 | if (!FLAG_support_il_printer || !FLAG_support_disassembler) { |
1478 | return; |
1479 | } |
1480 | #ifndef PRODUCT |
1481 | char buffer[256]; |
1482 | BufferFormatter f(buffer, sizeof(buffer)); |
1483 | instr->PrintTo(&f); |
1484 | assembler()->Comment("%s" , buffer); |
1485 | #endif |
1486 | } |
1487 | |
1488 | bool FlowGraphCompiler::NeedsEdgeCounter(BlockEntryInstr* block) { |
1489 | // Only emit an edge counter if there is not goto at the end of the block, |
1490 | // except for the entry block. |
1491 | return FLAG_reorder_basic_blocks && |
1492 | (!block->last_instruction()->IsGoto() || block->IsFunctionEntry()); |
1493 | } |
1494 | |
1495 | // Allocate a register that is not explictly blocked. |
1496 | static Register AllocateFreeRegister(bool* blocked_registers) { |
1497 | for (intptr_t regno = 0; regno < kNumberOfCpuRegisters; regno++) { |
1498 | if (!blocked_registers[regno]) { |
1499 | blocked_registers[regno] = true; |
1500 | return static_cast<Register>(regno); |
1501 | } |
1502 | } |
1503 | UNREACHABLE(); |
1504 | return kNoRegister; |
1505 | } |
1506 | |
1507 | void FlowGraphCompiler::AllocateRegistersLocally(Instruction* instr) { |
1508 | ASSERT(!is_optimizing()); |
1509 | instr->InitializeLocationSummary(zone(), false); // Not optimizing. |
1510 | |
1511 | LocationSummary* locs = instr->locs(); |
1512 | |
1513 | bool blocked_registers[kNumberOfCpuRegisters]; |
1514 | |
1515 | // Connect input with peephole output for some special cases. All other |
1516 | // cases are handled by simply allocating registers and generating code. |
1517 | if (top_of_stack_ != nullptr) { |
1518 | const intptr_t p = locs->input_count() - 1; |
1519 | Location peephole = top_of_stack_->locs()->out(0); |
1520 | if (locs->in(p).IsUnallocated() || locs->in(p).IsConstant()) { |
1521 | // If input is unallocated, match with an output register, if set. Also, |
1522 | // if input is a direct constant, but the peephole output is a register, |
1523 | // use that register to avoid wasting the already generated code. |
1524 | if (peephole.IsRegister()) { |
1525 | locs->set_in(p, Location::RegisterLocation(peephole.reg())); |
1526 | } |
1527 | } |
1528 | } |
1529 | |
1530 | // Block all registers globally reserved by the assembler, etc and mark |
1531 | // the rest as free. |
1532 | for (intptr_t i = 0; i < kNumberOfCpuRegisters; i++) { |
1533 | blocked_registers[i] = (kDartAvailableCpuRegs & (1 << i)) == 0; |
1534 | } |
1535 | |
1536 | // Mark all fixed input, temp and output registers as used. |
1537 | for (intptr_t i = 0; i < locs->input_count(); i++) { |
1538 | Location loc = locs->in(i); |
1539 | if (loc.IsRegister()) { |
1540 | // Check that a register is not specified twice in the summary. |
1541 | ASSERT(!blocked_registers[loc.reg()]); |
1542 | blocked_registers[loc.reg()] = true; |
1543 | } |
1544 | } |
1545 | |
1546 | for (intptr_t i = 0; i < locs->temp_count(); i++) { |
1547 | Location loc = locs->temp(i); |
1548 | if (loc.IsRegister()) { |
1549 | // Check that a register is not specified twice in the summary. |
1550 | ASSERT(!blocked_registers[loc.reg()]); |
1551 | blocked_registers[loc.reg()] = true; |
1552 | } |
1553 | } |
1554 | |
1555 | if (locs->out(0).IsRegister()) { |
1556 | // Fixed output registers are allowed to overlap with |
1557 | // temps and inputs. |
1558 | blocked_registers[locs->out(0).reg()] = true; |
1559 | } |
1560 | |
1561 | // Allocate all unallocated input locations. |
1562 | const bool should_pop = !instr->IsPushArgument(); |
1563 | for (intptr_t i = locs->input_count() - 1; i >= 0; i--) { |
1564 | Location loc = locs->in(i); |
1565 | Register reg = kNoRegister; |
1566 | if (loc.IsRegister()) { |
1567 | reg = loc.reg(); |
1568 | } else if (loc.IsUnallocated()) { |
1569 | ASSERT((loc.policy() == Location::kRequiresRegister) || |
1570 | (loc.policy() == Location::kWritableRegister) || |
1571 | (loc.policy() == Location::kPrefersRegister) || |
1572 | (loc.policy() == Location::kAny)); |
1573 | reg = AllocateFreeRegister(blocked_registers); |
1574 | locs->set_in(i, Location::RegisterLocation(reg)); |
1575 | } |
1576 | ASSERT(reg != kNoRegister || loc.IsConstant()); |
1577 | |
1578 | // Inputs are consumed from the simulated frame (or a peephole push/pop). |
1579 | // In case of a call argument we leave it until the call instruction. |
1580 | if (should_pop) { |
1581 | if (top_of_stack_ != nullptr) { |
1582 | if (!loc.IsConstant()) { |
1583 | // Moves top of stack location of the peephole into the required |
1584 | // input. None of the required moves needs a temp register allocator. |
1585 | EmitMove(locs->in(i), top_of_stack_->locs()->out(0), nullptr); |
1586 | } |
1587 | top_of_stack_ = nullptr; // consumed! |
1588 | } else if (loc.IsConstant()) { |
1589 | assembler()->Drop(1); |
1590 | } else { |
1591 | assembler()->PopRegister(reg); |
1592 | } |
1593 | } |
1594 | } |
1595 | |
1596 | // Allocate all unallocated temp locations. |
1597 | for (intptr_t i = 0; i < locs->temp_count(); i++) { |
1598 | Location loc = locs->temp(i); |
1599 | if (loc.IsUnallocated()) { |
1600 | ASSERT(loc.policy() == Location::kRequiresRegister); |
1601 | loc = Location::RegisterLocation(AllocateFreeRegister(blocked_registers)); |
1602 | locs->set_temp(i, loc); |
1603 | } |
1604 | } |
1605 | |
1606 | Location result_location = locs->out(0); |
1607 | if (result_location.IsUnallocated()) { |
1608 | switch (result_location.policy()) { |
1609 | case Location::kAny: |
1610 | case Location::kPrefersRegister: |
1611 | case Location::kRequiresRegister: |
1612 | case Location::kWritableRegister: |
1613 | result_location = |
1614 | Location::RegisterLocation(AllocateFreeRegister(blocked_registers)); |
1615 | break; |
1616 | case Location::kSameAsFirstInput: |
1617 | result_location = locs->in(0); |
1618 | break; |
1619 | case Location::kRequiresFpuRegister: |
1620 | UNREACHABLE(); |
1621 | break; |
1622 | } |
1623 | locs->set_out(0, result_location); |
1624 | } |
1625 | } |
1626 | |
1627 | static uword RegMaskBit(Register reg) { |
1628 | return ((reg) != kNoRegister) ? (1 << (reg)) : 0; |
1629 | } |
1630 | |
1631 | ParallelMoveResolver::ParallelMoveResolver(FlowGraphCompiler* compiler) |
1632 | : compiler_(compiler), moves_(32) {} |
1633 | |
1634 | void ParallelMoveResolver::EmitNativeCode(ParallelMoveInstr* parallel_move) { |
1635 | ASSERT(moves_.is_empty()); |
1636 | // Build up a worklist of moves. |
1637 | BuildInitialMoveList(parallel_move); |
1638 | |
1639 | for (int i = 0; i < moves_.length(); ++i) { |
1640 | const MoveOperands& move = *moves_[i]; |
1641 | // Skip constants to perform them last. They don't block other moves |
1642 | // and skipping such moves with register destinations keeps those |
1643 | // registers free for the whole algorithm. |
1644 | if (!move.IsEliminated() && !move.src().IsConstant()) PerformMove(i); |
1645 | } |
1646 | |
1647 | // Perform the moves with constant sources. |
1648 | for (int i = 0; i < moves_.length(); ++i) { |
1649 | const MoveOperands& move = *moves_[i]; |
1650 | if (!move.IsEliminated()) { |
1651 | ASSERT(move.src().IsConstant()); |
1652 | compiler_->BeginCodeSourceRange(); |
1653 | EmitMove(i); |
1654 | compiler_->EndCodeSourceRange(TokenPosition::kParallelMove); |
1655 | } |
1656 | } |
1657 | |
1658 | moves_.Clear(); |
1659 | } |
1660 | |
1661 | void ParallelMoveResolver::BuildInitialMoveList( |
1662 | ParallelMoveInstr* parallel_move) { |
1663 | // Perform a linear sweep of the moves to add them to the initial list of |
1664 | // moves to perform, ignoring any move that is redundant (the source is |
1665 | // the same as the destination, the destination is ignored and |
1666 | // unallocated, or the move was already eliminated). |
1667 | for (int i = 0; i < parallel_move->NumMoves(); i++) { |
1668 | MoveOperands* move = parallel_move->MoveOperandsAt(i); |
1669 | if (!move->IsRedundant()) moves_.Add(move); |
1670 | } |
1671 | } |
1672 | |
1673 | void ParallelMoveResolver::PerformMove(int index) { |
1674 | // Each call to this function performs a move and deletes it from the move |
1675 | // graph. We first recursively perform any move blocking this one. We |
1676 | // mark a move as "pending" on entry to PerformMove in order to detect |
1677 | // cycles in the move graph. We use operand swaps to resolve cycles, |
1678 | // which means that a call to PerformMove could change any source operand |
1679 | // in the move graph. |
1680 | |
1681 | ASSERT(!moves_[index]->IsPending()); |
1682 | ASSERT(!moves_[index]->IsRedundant()); |
1683 | |
1684 | // Clear this move's destination to indicate a pending move. The actual |
1685 | // destination is saved in a stack-allocated local. Recursion may allow |
1686 | // multiple moves to be pending. |
1687 | ASSERT(!moves_[index]->src().IsInvalid()); |
1688 | Location destination = moves_[index]->MarkPending(); |
1689 | |
1690 | // Perform a depth-first traversal of the move graph to resolve |
1691 | // dependencies. Any unperformed, unpending move with a source the same |
1692 | // as this one's destination blocks this one so recursively perform all |
1693 | // such moves. |
1694 | for (int i = 0; i < moves_.length(); ++i) { |
1695 | const MoveOperands& other_move = *moves_[i]; |
1696 | if (other_move.Blocks(destination) && !other_move.IsPending()) { |
1697 | // Though PerformMove can change any source operand in the move graph, |
1698 | // this call cannot create a blocking move via a swap (this loop does |
1699 | // not miss any). Assume there is a non-blocking move with source A |
1700 | // and this move is blocked on source B and there is a swap of A and |
1701 | // B. Then A and B must be involved in the same cycle (or they would |
1702 | // not be swapped). Since this move's destination is B and there is |
1703 | // only a single incoming edge to an operand, this move must also be |
1704 | // involved in the same cycle. In that case, the blocking move will |
1705 | // be created but will be "pending" when we return from PerformMove. |
1706 | PerformMove(i); |
1707 | } |
1708 | } |
1709 | |
1710 | // We are about to resolve this move and don't need it marked as |
1711 | // pending, so restore its destination. |
1712 | moves_[index]->ClearPending(destination); |
1713 | |
1714 | // This move's source may have changed due to swaps to resolve cycles and |
1715 | // so it may now be the last move in the cycle. If so remove it. |
1716 | if (moves_[index]->src().Equals(destination)) { |
1717 | moves_[index]->Eliminate(); |
1718 | return; |
1719 | } |
1720 | |
1721 | // The move may be blocked on a (at most one) pending move, in which case |
1722 | // we have a cycle. Search for such a blocking move and perform a swap to |
1723 | // resolve it. |
1724 | for (int i = 0; i < moves_.length(); ++i) { |
1725 | const MoveOperands& other_move = *moves_[i]; |
1726 | if (other_move.Blocks(destination)) { |
1727 | ASSERT(other_move.IsPending()); |
1728 | compiler_->BeginCodeSourceRange(); |
1729 | EmitSwap(index); |
1730 | compiler_->EndCodeSourceRange(TokenPosition::kParallelMove); |
1731 | return; |
1732 | } |
1733 | } |
1734 | |
1735 | // This move is not blocked. |
1736 | compiler_->BeginCodeSourceRange(); |
1737 | EmitMove(index); |
1738 | compiler_->EndCodeSourceRange(TokenPosition::kParallelMove); |
1739 | } |
1740 | |
1741 | void ParallelMoveResolver::EmitMove(int index) { |
1742 | MoveOperands* const move = moves_[index]; |
1743 | const Location dst = move->dest(); |
1744 | if (dst.IsStackSlot() || dst.IsDoubleStackSlot()) { |
1745 | ASSERT((dst.base_reg() != FPREG) || |
1746 | ((-compiler::target::frame_layout.VariableIndexForFrameSlot( |
1747 | dst.stack_index())) < compiler_->StackSize())); |
1748 | } |
1749 | const Location src = move->src(); |
1750 | ParallelMoveResolver::TemporaryAllocator temp(this, /*blocked=*/kNoRegister); |
1751 | compiler_->EmitMove(dst, src, &temp); |
1752 | #if defined(DEBUG) |
1753 | // Allocating a scratch register here may cause stack spilling. Neither the |
1754 | // source nor destination register should be SP-relative in that case. |
1755 | for (const Location& loc : {dst, src}) { |
1756 | ASSERT(!temp.DidAllocateTemporary() || !loc.HasStackIndex() || |
1757 | loc.base_reg() != SPREG); |
1758 | } |
1759 | #endif |
1760 | move->Eliminate(); |
1761 | } |
1762 | |
1763 | bool ParallelMoveResolver::IsScratchLocation(Location loc) { |
1764 | for (int i = 0; i < moves_.length(); ++i) { |
1765 | if (moves_[i]->Blocks(loc)) { |
1766 | return false; |
1767 | } |
1768 | } |
1769 | |
1770 | for (int i = 0; i < moves_.length(); ++i) { |
1771 | if (moves_[i]->dest().Equals(loc)) { |
1772 | return true; |
1773 | } |
1774 | } |
1775 | |
1776 | return false; |
1777 | } |
1778 | |
1779 | intptr_t ParallelMoveResolver::AllocateScratchRegister( |
1780 | Location::Kind kind, |
1781 | uword blocked_mask, |
1782 | intptr_t first_free_register, |
1783 | intptr_t last_free_register, |
1784 | bool* spilled) { |
1785 | COMPILE_ASSERT(static_cast<intptr_t>(sizeof(blocked_mask)) * kBitsPerByte >= |
1786 | kNumberOfFpuRegisters); |
1787 | COMPILE_ASSERT(static_cast<intptr_t>(sizeof(blocked_mask)) * kBitsPerByte >= |
1788 | kNumberOfCpuRegisters); |
1789 | intptr_t scratch = -1; |
1790 | for (intptr_t reg = first_free_register; reg <= last_free_register; reg++) { |
1791 | if ((((1 << reg) & blocked_mask) == 0) && |
1792 | IsScratchLocation(Location::MachineRegisterLocation(kind, reg))) { |
1793 | scratch = reg; |
1794 | break; |
1795 | } |
1796 | } |
1797 | |
1798 | if (scratch == -1) { |
1799 | *spilled = true; |
1800 | for (intptr_t reg = first_free_register; reg <= last_free_register; reg++) { |
1801 | if (((1 << reg) & blocked_mask) == 0) { |
1802 | scratch = reg; |
1803 | break; |
1804 | } |
1805 | } |
1806 | } else { |
1807 | *spilled = false; |
1808 | } |
1809 | |
1810 | return scratch; |
1811 | } |
1812 | |
1813 | ParallelMoveResolver::ScratchFpuRegisterScope::ScratchFpuRegisterScope( |
1814 | ParallelMoveResolver* resolver, |
1815 | FpuRegister blocked) |
1816 | : resolver_(resolver), reg_(kNoFpuRegister), spilled_(false) { |
1817 | COMPILE_ASSERT(FpuTMP != kNoFpuRegister); |
1818 | uword blocked_mask = |
1819 | ((blocked != kNoFpuRegister) ? 1 << blocked : 0) | 1 << FpuTMP; |
1820 | reg_ = static_cast<FpuRegister>(resolver_->AllocateScratchRegister( |
1821 | Location::kFpuRegister, blocked_mask, 0, kNumberOfFpuRegisters - 1, |
1822 | &spilled_)); |
1823 | |
1824 | if (spilled_) { |
1825 | resolver->SpillFpuScratch(reg_); |
1826 | } |
1827 | } |
1828 | |
1829 | ParallelMoveResolver::ScratchFpuRegisterScope::~ScratchFpuRegisterScope() { |
1830 | if (spilled_) { |
1831 | resolver_->RestoreFpuScratch(reg_); |
1832 | } |
1833 | } |
1834 | |
1835 | ParallelMoveResolver::TemporaryAllocator::TemporaryAllocator( |
1836 | ParallelMoveResolver* resolver, |
1837 | Register blocked) |
1838 | : resolver_(resolver), |
1839 | blocked_(blocked), |
1840 | reg_(kNoRegister), |
1841 | spilled_(false) {} |
1842 | |
1843 | Register ParallelMoveResolver::TemporaryAllocator::AllocateTemporary() { |
1844 | ASSERT(reg_ == kNoRegister); |
1845 | |
1846 | uword blocked_mask = RegMaskBit(blocked_) | kReservedCpuRegisters; |
1847 | if (resolver_->compiler_->intrinsic_mode()) { |
1848 | // Block additional registers that must be preserved for intrinsics. |
1849 | blocked_mask |= RegMaskBit(ARGS_DESC_REG); |
1850 | #if !defined(TARGET_ARCH_IA32) |
1851 | // Need to preserve CODE_REG to be able to store the PC marker |
1852 | // and load the pool pointer. |
1853 | blocked_mask |= RegMaskBit(CODE_REG); |
1854 | #endif |
1855 | } |
1856 | reg_ = static_cast<Register>( |
1857 | resolver_->AllocateScratchRegister(Location::kRegister, blocked_mask, 0, |
1858 | kNumberOfCpuRegisters - 1, &spilled_)); |
1859 | |
1860 | if (spilled_) { |
1861 | resolver_->SpillScratch(reg_); |
1862 | } |
1863 | |
1864 | DEBUG_ONLY(allocated_ = true;) |
1865 | return reg_; |
1866 | } |
1867 | |
1868 | void ParallelMoveResolver::TemporaryAllocator::ReleaseTemporary() { |
1869 | if (spilled_) { |
1870 | resolver_->RestoreScratch(reg_); |
1871 | } |
1872 | reg_ = kNoRegister; |
1873 | } |
1874 | |
1875 | ParallelMoveResolver::ScratchRegisterScope::ScratchRegisterScope( |
1876 | ParallelMoveResolver* resolver, |
1877 | Register blocked) |
1878 | : allocator_(resolver, blocked) { |
1879 | reg_ = allocator_.AllocateTemporary(); |
1880 | } |
1881 | |
1882 | ParallelMoveResolver::ScratchRegisterScope::~ScratchRegisterScope() { |
1883 | allocator_.ReleaseTemporary(); |
1884 | } |
1885 | |
1886 | const ICData* FlowGraphCompiler::GetOrAddInstanceCallICData( |
1887 | intptr_t deopt_id, |
1888 | const String& target_name, |
1889 | const Array& arguments_descriptor, |
1890 | intptr_t num_args_tested, |
1891 | const AbstractType& receiver_type) { |
1892 | if ((deopt_id_to_ic_data_ != NULL) && |
1893 | ((*deopt_id_to_ic_data_)[deopt_id] != NULL)) { |
1894 | const ICData* res = (*deopt_id_to_ic_data_)[deopt_id]; |
1895 | ASSERT(res->deopt_id() == deopt_id); |
1896 | ASSERT(res->target_name() == target_name.raw()); |
1897 | ASSERT(res->NumArgsTested() == num_args_tested); |
1898 | ASSERT(res->TypeArgsLen() == |
1899 | ArgumentsDescriptor(arguments_descriptor).TypeArgsLen()); |
1900 | ASSERT(!res->is_static_call()); |
1901 | ASSERT(res->receivers_static_type() == receiver_type.raw()); |
1902 | return res; |
1903 | } |
1904 | const ICData& ic_data = ICData::ZoneHandle( |
1905 | zone(), ICData::New(parsed_function().function(), target_name, |
1906 | arguments_descriptor, deopt_id, num_args_tested, |
1907 | ICData::kInstance, receiver_type)); |
1908 | if (deopt_id_to_ic_data_ != NULL) { |
1909 | (*deopt_id_to_ic_data_)[deopt_id] = &ic_data; |
1910 | } |
1911 | ASSERT(!ic_data.is_static_call()); |
1912 | return &ic_data; |
1913 | } |
1914 | |
1915 | const ICData* FlowGraphCompiler::GetOrAddStaticCallICData( |
1916 | intptr_t deopt_id, |
1917 | const Function& target, |
1918 | const Array& arguments_descriptor, |
1919 | intptr_t num_args_tested, |
1920 | ICData::RebindRule rebind_rule) { |
1921 | if ((deopt_id_to_ic_data_ != NULL) && |
1922 | ((*deopt_id_to_ic_data_)[deopt_id] != NULL)) { |
1923 | const ICData* res = (*deopt_id_to_ic_data_)[deopt_id]; |
1924 | ASSERT(res->deopt_id() == deopt_id); |
1925 | ASSERT(res->target_name() == target.name()); |
1926 | ASSERT(res->NumArgsTested() == num_args_tested); |
1927 | ASSERT(res->TypeArgsLen() == |
1928 | ArgumentsDescriptor(arguments_descriptor).TypeArgsLen()); |
1929 | ASSERT(res->is_static_call()); |
1930 | return res; |
1931 | } |
1932 | |
1933 | const ICData& ic_data = ICData::ZoneHandle( |
1934 | zone(), |
1935 | ICData::New(parsed_function().function(), |
1936 | String::Handle(zone(), target.name()), arguments_descriptor, |
1937 | deopt_id, num_args_tested, rebind_rule)); |
1938 | ic_data.AddTarget(target); |
1939 | if (deopt_id_to_ic_data_ != NULL) { |
1940 | (*deopt_id_to_ic_data_)[deopt_id] = &ic_data; |
1941 | } |
1942 | return &ic_data; |
1943 | } |
1944 | |
1945 | intptr_t FlowGraphCompiler::GetOptimizationThreshold() const { |
1946 | intptr_t threshold; |
1947 | if (is_optimizing()) { |
1948 | threshold = FLAG_reoptimization_counter_threshold; |
1949 | } else if (parsed_function_.function().IsIrregexpFunction()) { |
1950 | threshold = FLAG_regexp_optimization_counter_threshold; |
1951 | } else if (FLAG_randomize_optimization_counter) { |
1952 | threshold = Thread::Current()->GetRandomUInt64() % |
1953 | FLAG_optimization_counter_threshold; |
1954 | } else { |
1955 | const intptr_t basic_blocks = flow_graph().preorder().length(); |
1956 | ASSERT(basic_blocks > 0); |
1957 | threshold = FLAG_optimization_counter_scale * basic_blocks + |
1958 | FLAG_min_optimization_counter_threshold; |
1959 | if (threshold > FLAG_optimization_counter_threshold) { |
1960 | threshold = FLAG_optimization_counter_threshold; |
1961 | } |
1962 | } |
1963 | |
1964 | // Threshold = 0 doesn't make sense because we increment the counter before |
1965 | // testing against the threshold. Perhaps we could interpret it to mean |
1966 | // "generate optimized code immediately without unoptimized compilation |
1967 | // first", but this isn't supported in our pipeline because there would be no |
1968 | // code for the optimized code to deoptimize into. |
1969 | if (threshold == 0) threshold = 1; |
1970 | |
1971 | // See Compiler::CanOptimizeFunction. In short, we have to allow the |
1972 | // unoptimized code to run at least once to prevent an infinite compilation |
1973 | // loop. |
1974 | if (threshold == 1 && parsed_function().function().HasBreakpoint()) { |
1975 | threshold = 2; |
1976 | } |
1977 | |
1978 | return threshold; |
1979 | } |
1980 | |
1981 | const Class& FlowGraphCompiler::BoxClassFor(Representation rep) { |
1982 | switch (rep) { |
1983 | case kUnboxedFloat: |
1984 | case kUnboxedDouble: |
1985 | return double_class(); |
1986 | case kUnboxedFloat32x4: |
1987 | return float32x4_class(); |
1988 | case kUnboxedFloat64x2: |
1989 | return float64x2_class(); |
1990 | case kUnboxedInt32x4: |
1991 | return int32x4_class(); |
1992 | case kUnboxedInt64: |
1993 | return mint_class(); |
1994 | default: |
1995 | UNREACHABLE(); |
1996 | return Class::ZoneHandle(); |
1997 | } |
1998 | } |
1999 | |
2000 | void FlowGraphCompiler::BeginCodeSourceRange() { |
2001 | code_source_map_builder_->BeginCodeSourceRange(assembler()->CodeSize()); |
2002 | } |
2003 | |
2004 | void FlowGraphCompiler::EndCodeSourceRange(TokenPosition token_pos) { |
2005 | code_source_map_builder_->EndCodeSourceRange(assembler()->CodeSize(), |
2006 | token_pos); |
2007 | } |
2008 | |
2009 | const CallTargets* FlowGraphCompiler::ResolveCallTargetsForReceiverCid( |
2010 | intptr_t cid, |
2011 | const String& selector, |
2012 | const Array& args_desc_array) { |
2013 | Zone* zone = Thread::Current()->zone(); |
2014 | |
2015 | ArgumentsDescriptor args_desc(args_desc_array); |
2016 | |
2017 | Function& fn = Function::ZoneHandle(zone); |
2018 | if (!LookupMethodFor(cid, selector, args_desc, &fn)) return NULL; |
2019 | |
2020 | CallTargets* targets = new (zone) CallTargets(zone); |
2021 | targets->Add(new (zone) TargetInfo(cid, cid, &fn, /* count = */ 1, |
2022 | StaticTypeExactnessState::NotTracking())); |
2023 | |
2024 | return targets; |
2025 | } |
2026 | |
2027 | bool FlowGraphCompiler::LookupMethodFor(int class_id, |
2028 | const String& name, |
2029 | const ArgumentsDescriptor& args_desc, |
2030 | Function* fn_return, |
2031 | bool* class_is_abstract_return) { |
2032 | Thread* thread = Thread::Current(); |
2033 | Isolate* isolate = thread->isolate(); |
2034 | Zone* zone = thread->zone(); |
2035 | if (class_id < 0) return false; |
2036 | if (class_id >= isolate->class_table()->NumCids()) return false; |
2037 | |
2038 | ClassPtr raw_class = isolate->class_table()->At(class_id); |
2039 | if (raw_class == nullptr) return false; |
2040 | Class& cls = Class::Handle(zone, raw_class); |
2041 | if (cls.IsNull()) return false; |
2042 | if (!cls.is_finalized()) return false; |
2043 | if (Array::Handle(cls.functions()).IsNull()) return false; |
2044 | |
2045 | if (class_is_abstract_return != NULL) { |
2046 | *class_is_abstract_return = cls.is_abstract(); |
2047 | } |
2048 | const bool allow_add = false; |
2049 | Function& target_function = |
2050 | Function::Handle(zone, Resolver::ResolveDynamicForReceiverClass( |
2051 | cls, name, args_desc, allow_add)); |
2052 | if (target_function.IsNull()) return false; |
2053 | *fn_return = target_function.raw(); |
2054 | return true; |
2055 | } |
2056 | |
2057 | void FlowGraphCompiler::EmitPolymorphicInstanceCall( |
2058 | const PolymorphicInstanceCallInstr* call, |
2059 | const CallTargets& targets, |
2060 | ArgumentsInfo args_info, |
2061 | intptr_t deopt_id, |
2062 | TokenPosition token_pos, |
2063 | LocationSummary* locs, |
2064 | bool complete, |
2065 | intptr_t total_ic_calls, |
2066 | bool receiver_can_be_smi) { |
2067 | ASSERT(call != nullptr); |
2068 | if (FLAG_polymorphic_with_deopt) { |
2069 | compiler::Label* deopt = |
2070 | AddDeoptStub(deopt_id, ICData::kDeoptPolymorphicInstanceCallTestFail); |
2071 | compiler::Label ok; |
2072 | EmitTestAndCall(targets, call->function_name(), args_info, |
2073 | deopt, // No cid match. |
2074 | &ok, // Found cid. |
2075 | deopt_id, token_pos, locs, complete, total_ic_calls, |
2076 | call->entry_kind()); |
2077 | assembler()->Bind(&ok); |
2078 | } else { |
2079 | if (complete) { |
2080 | compiler::Label ok; |
2081 | EmitTestAndCall(targets, call->function_name(), args_info, |
2082 | NULL, // No cid match. |
2083 | &ok, // Found cid. |
2084 | deopt_id, token_pos, locs, true, total_ic_calls, |
2085 | call->entry_kind()); |
2086 | assembler()->Bind(&ok); |
2087 | } else { |
2088 | const ICData& unary_checks = |
2089 | ICData::ZoneHandle(zone(), call->ic_data()->AsUnaryClassChecks()); |
2090 | EmitInstanceCallAOT(unary_checks, deopt_id, token_pos, locs, |
2091 | call->entry_kind(), receiver_can_be_smi); |
2092 | } |
2093 | } |
2094 | } |
2095 | |
2096 | #define __ assembler()-> |
2097 | void FlowGraphCompiler::EmitTestAndCall(const CallTargets& targets, |
2098 | const String& function_name, |
2099 | ArgumentsInfo args_info, |
2100 | compiler::Label* failed, |
2101 | compiler::Label* match_found, |
2102 | intptr_t deopt_id, |
2103 | TokenPosition token_index, |
2104 | LocationSummary* locs, |
2105 | bool complete, |
2106 | intptr_t total_ic_calls, |
2107 | Code::EntryKind entry_kind) { |
2108 | ASSERT(is_optimizing()); |
2109 | ASSERT(complete || (failed != nullptr)); // Complete calls can't fail. |
2110 | |
2111 | const Array& arguments_descriptor = |
2112 | Array::ZoneHandle(zone(), args_info.ToArgumentsDescriptor()); |
2113 | EmitTestAndCallLoadReceiver(args_info.count_without_type_args, |
2114 | arguments_descriptor); |
2115 | |
2116 | static const int kNoCase = -1; |
2117 | int smi_case = kNoCase; |
2118 | int which_case_to_skip = kNoCase; |
2119 | |
2120 | const int length = targets.length(); |
2121 | ASSERT(length > 0); |
2122 | int non_smi_length = length; |
2123 | |
2124 | // Find out if one of the classes in one of the cases is the Smi class. We |
2125 | // will be handling that specially. |
2126 | for (int i = 0; i < length; i++) { |
2127 | const intptr_t start = targets[i].cid_start; |
2128 | if (start > kSmiCid) continue; |
2129 | const intptr_t end = targets[i].cid_end; |
2130 | if (end >= kSmiCid) { |
2131 | smi_case = i; |
2132 | if (start == kSmiCid && end == kSmiCid) { |
2133 | // If this case has only the Smi class then we won't need to emit it at |
2134 | // all later. |
2135 | which_case_to_skip = i; |
2136 | non_smi_length--; |
2137 | } |
2138 | break; |
2139 | } |
2140 | } |
2141 | |
2142 | if (smi_case != kNoCase) { |
2143 | compiler::Label after_smi_test; |
2144 | // If the call is complete and there are no other possible receiver |
2145 | // classes - then receiver can only be a smi value and we don't need |
2146 | // to check if it is a smi. |
2147 | if (!(complete && non_smi_length == 0)) { |
2148 | EmitTestAndCallSmiBranch(non_smi_length == 0 ? failed : &after_smi_test, |
2149 | /* jump_if_smi= */ false); |
2150 | } |
2151 | |
2152 | // Do not use the code from the function, but let the code be patched so |
2153 | // that we can record the outgoing edges to other code. |
2154 | const Function& function = *targets.TargetAt(smi_case)->target; |
2155 | GenerateStaticDartCall(deopt_id, token_index, PcDescriptorsLayout::kOther, |
2156 | locs, function, entry_kind); |
2157 | __ Drop(args_info.size_with_type_args); |
2158 | if (match_found != NULL) { |
2159 | __ Jump(match_found); |
2160 | } |
2161 | __ Bind(&after_smi_test); |
2162 | } else { |
2163 | if (!complete) { |
2164 | // Smi is not a valid class. |
2165 | EmitTestAndCallSmiBranch(failed, /* jump_if_smi = */ true); |
2166 | } |
2167 | } |
2168 | |
2169 | if (non_smi_length == 0) { |
2170 | // If non_smi_length is 0 then only a Smi check was needed; the Smi check |
2171 | // above will fail if there was only one check and receiver is not Smi. |
2172 | return; |
2173 | } |
2174 | |
2175 | bool add_megamorphic_call = false; |
2176 | int bias = 0; |
2177 | |
2178 | // Value is not Smi. |
2179 | EmitTestAndCallLoadCid(EmitTestCidRegister()); |
2180 | |
2181 | int last_check = which_case_to_skip == length - 1 ? length - 2 : length - 1; |
2182 | |
2183 | for (intptr_t i = 0; i < length; i++) { |
2184 | if (i == which_case_to_skip) continue; |
2185 | const bool is_last_check = (i == last_check); |
2186 | const int count = targets.TargetAt(i)->count; |
2187 | if (!is_last_check && !complete && count < (total_ic_calls >> 5)) { |
2188 | // This case is hit too rarely to be worth writing class-id checks inline |
2189 | // for. Note that we can't do this for calls with only one target because |
2190 | // the type propagator may have made use of that and expects a deopt if |
2191 | // a new class is seen at this calls site. See IsMonomorphic. |
2192 | add_megamorphic_call = true; |
2193 | break; |
2194 | } |
2195 | compiler::Label next_test; |
2196 | if (!complete || !is_last_check) { |
2197 | bias = EmitTestAndCallCheckCid(assembler(), |
2198 | is_last_check ? failed : &next_test, |
2199 | EmitTestCidRegister(), targets[i], bias, |
2200 | /*jump_on_miss =*/true); |
2201 | } |
2202 | // Do not use the code from the function, but let the code be patched so |
2203 | // that we can record the outgoing edges to other code. |
2204 | const Function& function = *targets.TargetAt(i)->target; |
2205 | GenerateStaticDartCall(deopt_id, token_index, PcDescriptorsLayout::kOther, |
2206 | locs, function, entry_kind); |
2207 | __ Drop(args_info.size_with_type_args); |
2208 | if (!is_last_check || add_megamorphic_call) { |
2209 | __ Jump(match_found); |
2210 | } |
2211 | __ Bind(&next_test); |
2212 | } |
2213 | if (add_megamorphic_call) { |
2214 | int try_index = kInvalidTryIndex; |
2215 | EmitMegamorphicInstanceCall(function_name, arguments_descriptor, deopt_id, |
2216 | token_index, locs, try_index); |
2217 | } |
2218 | } |
2219 | |
2220 | bool FlowGraphCompiler::GenerateSubtypeRangeCheck(Register class_id_reg, |
2221 | const Class& type_class, |
2222 | compiler::Label* is_subtype) { |
2223 | HierarchyInfo* hi = Thread::Current()->hierarchy_info(); |
2224 | if (hi != NULL) { |
2225 | const CidRangeVector& ranges = |
2226 | hi->SubtypeRangesForClass(type_class, |
2227 | /*include_abstract=*/false, |
2228 | /*exclude_null=*/false); |
2229 | if (ranges.length() <= kMaxNumberOfCidRangesToTest) { |
2230 | GenerateCidRangesCheck(assembler(), class_id_reg, ranges, is_subtype); |
2231 | return true; |
2232 | } |
2233 | } |
2234 | |
2235 | // We don't have cid-ranges for subclasses, so we'll just test against the |
2236 | // class directly if it's non-abstract. |
2237 | if (!type_class.is_abstract()) { |
2238 | __ CompareImmediate(class_id_reg, type_class.id()); |
2239 | __ BranchIf(EQUAL, is_subtype); |
2240 | } |
2241 | return false; |
2242 | } |
2243 | |
2244 | void FlowGraphCompiler::GenerateCidRangesCheck( |
2245 | compiler::Assembler* assembler, |
2246 | Register class_id_reg, |
2247 | const CidRangeVector& cid_ranges, |
2248 | compiler::Label* inside_range_lbl, |
2249 | compiler::Label* outside_range_lbl, |
2250 | bool fall_through_if_inside) { |
2251 | // If there are no valid class ranges, the check will fail. If we are |
2252 | // supposed to fall-through in the positive case, we'll explicitly jump to |
2253 | // the [outside_range_lbl]. |
2254 | if (cid_ranges.length() == 1 && cid_ranges[0].IsIllegalRange()) { |
2255 | if (fall_through_if_inside) { |
2256 | assembler->Jump(outside_range_lbl); |
2257 | } |
2258 | return; |
2259 | } |
2260 | |
2261 | int bias = 0; |
2262 | for (intptr_t i = 0; i < cid_ranges.length(); ++i) { |
2263 | const CidRangeValue& range = cid_ranges[i]; |
2264 | RELEASE_ASSERT(!range.IsIllegalRange()); |
2265 | const bool last_round = i == (cid_ranges.length() - 1); |
2266 | |
2267 | compiler::Label* jump_label = last_round && fall_through_if_inside |
2268 | ? outside_range_lbl |
2269 | : inside_range_lbl; |
2270 | const bool jump_on_miss = last_round && fall_through_if_inside; |
2271 | |
2272 | bias = EmitTestAndCallCheckCid(assembler, jump_label, class_id_reg, range, |
2273 | bias, jump_on_miss); |
2274 | } |
2275 | } |
2276 | |
2277 | bool FlowGraphCompiler::CheckAssertAssignableTypeTestingABILocations( |
2278 | const LocationSummary& locs) { |
2279 | ASSERT(locs.in(0).IsRegister() && |
2280 | locs.in(0).reg() == TypeTestABI::kInstanceReg); |
2281 | ASSERT((locs.in(1).IsConstant() && locs.in(1).constant().IsAbstractType()) || |
2282 | (locs.in(1).IsRegister() && |
2283 | locs.in(1).reg() == TypeTestABI::kDstTypeReg)); |
2284 | ASSERT(locs.in(2).IsRegister() && |
2285 | locs.in(2).reg() == TypeTestABI::kInstantiatorTypeArgumentsReg); |
2286 | ASSERT(locs.in(3).IsRegister() && |
2287 | locs.in(3).reg() == TypeTestABI::kFunctionTypeArgumentsReg); |
2288 | ASSERT(locs.out(0).IsRegister() && |
2289 | locs.out(0).reg() == TypeTestABI::kInstanceReg); |
2290 | return true; |
2291 | } |
2292 | |
2293 | bool FlowGraphCompiler::ShouldUseTypeTestingStubFor(bool optimizing, |
2294 | const AbstractType& type) { |
2295 | return FLAG_precompiled_mode || |
2296 | (optimizing && |
2297 | (type.IsTypeParameter() || (type.IsType() && type.IsInstantiated()))); |
2298 | } |
2299 | |
2300 | FlowGraphCompiler::TypeTestStubKind |
2301 | FlowGraphCompiler::GetTypeTestStubKindForTypeParameter( |
2302 | const TypeParameter& type_param) { |
2303 | // If it's guaranteed, by type-parameter bound, that the type parameter will |
2304 | // never have a value of a function type, then we can safely do a 4-type |
2305 | // test instead of a 6-type test. |
2306 | AbstractType& bound = AbstractType::Handle(zone(), type_param.bound()); |
2307 | bound = bound.UnwrapFutureOr(); |
2308 | return !bound.IsTopTypeForSubtyping() && !bound.IsObjectType() && |
2309 | !bound.IsFunctionType() && !bound.IsDartFunctionType() && |
2310 | bound.IsType() |
2311 | ? kTestTypeFourArgs |
2312 | : kTestTypeSixArgs; |
2313 | } |
2314 | |
2315 | void FlowGraphCompiler::GenerateAssertAssignableViaTypeTestingStub( |
2316 | CompileType* receiver_type, |
2317 | const AbstractType& dst_type, |
2318 | const String& dst_name, |
2319 | const Register dst_type_reg_to_call, |
2320 | const Register scratch_reg, |
2321 | compiler::Label* done) { |
2322 | #if defined(TARGET_ARCH_IA32) |
2323 | // ia32 does not have support for TypeTestingStubs. |
2324 | UNREACHABLE(); |
2325 | #else |
2326 | TypeUsageInfo* type_usage_info = thread()->type_usage_info(); |
2327 | |
2328 | // Special case: non-nullable Object. |
2329 | // Top types should be handled by the caller and cannot reach here. |
2330 | ASSERT(!dst_type.IsTopTypeForSubtyping()); |
2331 | if (dst_type.IsObjectType()) { |
2332 | ASSERT(dst_type.IsNonNullable() && isolate()->null_safety()); |
2333 | __ CompareObject(TypeTestABI::kInstanceReg, Object::null_object()); |
2334 | __ BranchIf(NOT_EQUAL, done); |
2335 | // Fall back to type testing stub. |
2336 | __ LoadObject(TypeTestABI::kDstTypeReg, dst_type); |
2337 | return; |
2338 | } |
2339 | |
2340 | // If the int type is assignable to [dst_type] we special case it on the |
2341 | // caller side! |
2342 | const Type& int_type = Type::Handle(zone(), Type::IntType()); |
2343 | bool is_non_smi = false; |
2344 | if (int_type.IsSubtypeOf(dst_type, Heap::kOld)) { |
2345 | __ BranchIfSmi(TypeTestABI::kInstanceReg, done); |
2346 | is_non_smi = true; |
2347 | } else if (!receiver_type->CanBeSmi()) { |
2348 | is_non_smi = true; |
2349 | } |
2350 | |
2351 | // We use two type registers iff the dst type is a type parameter. |
2352 | // We "dereference" the type parameter for the TTS call but leave the type |
2353 | // parameter in the TypeTestABI::kDstTypeReg for fallback into |
2354 | // SubtypeTestCache. |
2355 | ASSERT(dst_type_reg_to_call == kNoRegister || |
2356 | (dst_type.IsTypeParameter() == |
2357 | (TypeTestABI::kDstTypeReg != dst_type_reg_to_call))); |
2358 | |
2359 | // We can handle certain types very efficiently on the call site (with a |
2360 | // bailout to the normal stub, which will do a runtime call). |
2361 | if (dst_type.IsTypeParameter()) { |
2362 | // In NNBD strong mode we need to handle null instance before calling TTS |
2363 | // if type parameter is nullable or legacy because type parameter can be |
2364 | // instantiated with a non-nullable type which rejects null. |
2365 | // In NNBD weak mode or if type parameter is non-nullable or has |
2366 | // undetermined nullability null instance is correctly handled by TTS. |
2367 | if (isolate()->null_safety() && |
2368 | (dst_type.IsNullable() || dst_type.IsLegacy())) { |
2369 | __ CompareObject(TypeTestABI::kInstanceReg, Object::null_object()); |
2370 | __ BranchIf(EQUAL, done); |
2371 | } |
2372 | const TypeParameter& type_param = TypeParameter::Cast(dst_type); |
2373 | const Register kTypeArgumentsReg = |
2374 | type_param.IsClassTypeParameter() |
2375 | ? TypeTestABI::kInstantiatorTypeArgumentsReg |
2376 | : TypeTestABI::kFunctionTypeArgumentsReg; |
2377 | |
2378 | // Check if type arguments are null, i.e. equivalent to vector of dynamic. |
2379 | __ CompareObject(kTypeArgumentsReg, Object::null_object()); |
2380 | __ BranchIf(EQUAL, done); |
2381 | __ LoadField( |
2382 | dst_type_reg_to_call, |
2383 | compiler::FieldAddress(kTypeArgumentsReg, |
2384 | compiler::target::TypeArguments::type_at_offset( |
2385 | type_param.index()))); |
2386 | __ LoadObject(TypeTestABI::kDstTypeReg, type_param); |
2387 | if (type_usage_info != NULL) { |
2388 | type_usage_info->UseTypeInAssertAssignable(dst_type); |
2389 | } |
2390 | } else { |
2391 | HierarchyInfo* hi = Thread::Current()->hierarchy_info(); |
2392 | if (hi != NULL) { |
2393 | const Class& type_class = Class::Handle(zone(), dst_type.type_class()); |
2394 | |
2395 | bool check_handled_at_callsite = false; |
2396 | bool used_cid_range_check = false; |
2397 | const bool can_use_simple_cid_range_test = |
2398 | hi->CanUseSubtypeRangeCheckFor(dst_type); |
2399 | if (can_use_simple_cid_range_test) { |
2400 | const CidRangeVector& ranges = hi->SubtypeRangesForClass( |
2401 | type_class, |
2402 | /*include_abstract=*/false, |
2403 | /*exclude_null=*/!Instance::NullIsAssignableTo(dst_type)); |
2404 | if (ranges.length() <= kMaxNumberOfCidRangesToTest) { |
2405 | if (is_non_smi) { |
2406 | __ LoadClassId(scratch_reg, TypeTestABI::kInstanceReg); |
2407 | } else { |
2408 | __ LoadClassIdMayBeSmi(scratch_reg, TypeTestABI::kInstanceReg); |
2409 | } |
2410 | GenerateCidRangesCheck(assembler(), scratch_reg, ranges, done); |
2411 | used_cid_range_check = true; |
2412 | check_handled_at_callsite = true; |
2413 | } |
2414 | } |
2415 | |
2416 | if (!used_cid_range_check && can_use_simple_cid_range_test && |
2417 | IsListClass(type_class)) { |
2418 | __ LoadClassIdMayBeSmi(scratch_reg, TypeTestABI::kInstanceReg); |
2419 | GenerateListTypeCheck(scratch_reg, done); |
2420 | used_cid_range_check = true; |
2421 | } |
2422 | |
2423 | // If we haven't handled the positive case of the type check on the |
2424 | // call-site, we want an optimized type testing stub and therefore record |
2425 | // it in the [TypeUsageInfo]. |
2426 | if (!check_handled_at_callsite) { |
2427 | if (type_usage_info != NULL) { |
2428 | type_usage_info->UseTypeInAssertAssignable(dst_type); |
2429 | } else { |
2430 | ASSERT(!FLAG_precompiled_mode); |
2431 | } |
2432 | } |
2433 | } |
2434 | __ LoadObject(TypeTestABI::kDstTypeReg, dst_type); |
2435 | } |
2436 | #endif // defined(TARGET_ARCH_IA32) |
2437 | } |
2438 | |
2439 | #undef __ |
2440 | |
2441 | #if defined(DEBUG) |
2442 | void FlowGraphCompiler::FrameStateUpdateWith(Instruction* instr) { |
2443 | ASSERT(!is_optimizing()); |
2444 | |
2445 | switch (instr->tag()) { |
2446 | case Instruction::kPushArgument: |
2447 | // Do nothing. |
2448 | break; |
2449 | |
2450 | case Instruction::kDropTemps: |
2451 | FrameStatePop(instr->locs()->input_count() + |
2452 | instr->AsDropTemps()->num_temps()); |
2453 | break; |
2454 | |
2455 | default: |
2456 | FrameStatePop(instr->locs()->input_count()); |
2457 | break; |
2458 | } |
2459 | |
2460 | ASSERT(!instr->locs()->can_call() || FrameStateIsSafeToCall()); |
2461 | |
2462 | FrameStatePop(instr->ArgumentCount()); |
2463 | Definition* defn = instr->AsDefinition(); |
2464 | if ((defn != NULL) && defn->HasTemp()) { |
2465 | FrameStatePush(defn); |
2466 | } |
2467 | } |
2468 | |
2469 | void FlowGraphCompiler::FrameStatePush(Definition* defn) { |
2470 | Representation rep = defn->representation(); |
2471 | if ((rep == kUnboxedDouble) || (rep == kUnboxedFloat64x2) || |
2472 | (rep == kUnboxedFloat32x4)) { |
2473 | // LoadField instruction lies about its representation in the unoptimized |
2474 | // code because Definition::representation() can't depend on the type of |
2475 | // compilation but MakeLocationSummary and EmitNativeCode can. |
2476 | ASSERT(defn->IsLoadField() && defn->AsLoadField()->IsUnboxedLoad()); |
2477 | ASSERT(defn->locs()->out(0).IsRegister()); |
2478 | rep = kTagged; |
2479 | } |
2480 | ASSERT(!is_optimizing()); |
2481 | ASSERT((rep == kTagged) || (rep == kUntagged)); |
2482 | ASSERT(rep != kUntagged || flow_graph_.IsIrregexpFunction()); |
2483 | frame_state_.Add(rep); |
2484 | } |
2485 | |
2486 | void FlowGraphCompiler::FrameStatePop(intptr_t count) { |
2487 | ASSERT(!is_optimizing()); |
2488 | frame_state_.TruncateTo( |
2489 | Utils::Maximum(static_cast<intptr_t>(0), frame_state_.length() - count)); |
2490 | } |
2491 | |
2492 | bool FlowGraphCompiler::FrameStateIsSafeToCall() { |
2493 | ASSERT(!is_optimizing()); |
2494 | for (intptr_t i = 0; i < frame_state_.length(); i++) { |
2495 | if (frame_state_[i] != kTagged) { |
2496 | return false; |
2497 | } |
2498 | } |
2499 | return true; |
2500 | } |
2501 | |
2502 | void FlowGraphCompiler::FrameStateClear() { |
2503 | ASSERT(!is_optimizing()); |
2504 | frame_state_.TruncateTo(0); |
2505 | } |
2506 | #endif // defined(DEBUG) |
2507 | |
2508 | #define __ compiler->assembler()-> |
2509 | |
2510 | void ThrowErrorSlowPathCode::EmitNativeCode(FlowGraphCompiler* compiler) { |
2511 | if (compiler::Assembler::EmittingComments()) { |
2512 | __ Comment("slow path %s operation" , name()); |
2513 | } |
2514 | const bool use_shared_stub = |
2515 | instruction()->UseSharedSlowPathStub(compiler->is_optimizing()); |
2516 | const bool live_fpu_registers = |
2517 | instruction()->locs()->live_registers()->FpuRegisterCount() > 0; |
2518 | __ Bind(entry_label()); |
2519 | EmitCodeAtSlowPathEntry(compiler); |
2520 | LocationSummary* locs = instruction()->locs(); |
2521 | // Save registers as they are needed for lazy deopt / exception handling. |
2522 | if (use_shared_stub) { |
2523 | EmitSharedStubCall(compiler, live_fpu_registers); |
2524 | } else { |
2525 | compiler->SaveLiveRegisters(locs); |
2526 | intptr_t i = 0; |
2527 | if (num_args_ % 2 != 0) { |
2528 | __ PushRegister(locs->in(i).reg()); |
2529 | ++i; |
2530 | } |
2531 | for (; i < num_args_; i += 2) { |
2532 | __ PushRegisterPair(locs->in(i + 1).reg(), locs->in(i).reg()); |
2533 | } |
2534 | __ CallRuntime(runtime_entry_, num_args_); |
2535 | } |
2536 | const intptr_t deopt_id = instruction()->deopt_id(); |
2537 | compiler->AddDescriptor(PcDescriptorsLayout::kOther, |
2538 | compiler->assembler()->CodeSize(), deopt_id, |
2539 | instruction()->token_pos(), try_index_); |
2540 | AddMetadataForRuntimeCall(compiler); |
2541 | compiler->RecordSafepoint(locs, num_args_); |
2542 | if ((try_index_ != kInvalidTryIndex) || |
2543 | (compiler->CurrentTryIndex() != kInvalidTryIndex)) { |
2544 | Environment* env = |
2545 | compiler->SlowPathEnvironmentFor(instruction(), num_args_); |
2546 | if (FLAG_precompiled_mode) { |
2547 | compiler->RecordCatchEntryMoves(env, try_index_); |
2548 | } else if (env != nullptr) { |
2549 | compiler->AddSlowPathDeoptInfo(deopt_id, env); |
2550 | } |
2551 | } |
2552 | if (!use_shared_stub) { |
2553 | __ Breakpoint(); |
2554 | } |
2555 | } |
2556 | |
2557 | const char* NullErrorSlowPath::name() { |
2558 | switch (exception_type()) { |
2559 | case CheckNullInstr::kNoSuchMethod: |
2560 | return "check null (nsm)" ; |
2561 | case CheckNullInstr::kArgumentError: |
2562 | return "check null (arg)" ; |
2563 | case CheckNullInstr::kCastError: |
2564 | return "check null (cast)" ; |
2565 | } |
2566 | UNREACHABLE(); |
2567 | } |
2568 | |
2569 | const RuntimeEntry& NullErrorSlowPath::GetRuntimeEntry( |
2570 | CheckNullInstr::ExceptionType exception_type) { |
2571 | switch (exception_type) { |
2572 | case CheckNullInstr::kNoSuchMethod: |
2573 | return kNullErrorRuntimeEntry; |
2574 | case CheckNullInstr::kArgumentError: |
2575 | return kArgumentNullErrorRuntimeEntry; |
2576 | case CheckNullInstr::kCastError: |
2577 | return kNullCastErrorRuntimeEntry; |
2578 | } |
2579 | UNREACHABLE(); |
2580 | } |
2581 | |
2582 | CodePtr NullErrorSlowPath::GetStub(FlowGraphCompiler* compiler, |
2583 | CheckNullInstr::ExceptionType exception_type, |
2584 | bool save_fpu_registers) { |
2585 | auto object_store = compiler->isolate()->object_store(); |
2586 | switch (exception_type) { |
2587 | case CheckNullInstr::kNoSuchMethod: |
2588 | return save_fpu_registers |
2589 | ? object_store->null_error_stub_with_fpu_regs_stub() |
2590 | : object_store->null_error_stub_without_fpu_regs_stub(); |
2591 | case CheckNullInstr::kArgumentError: |
2592 | return save_fpu_registers |
2593 | ? object_store->null_arg_error_stub_with_fpu_regs_stub() |
2594 | : object_store->null_arg_error_stub_without_fpu_regs_stub(); |
2595 | case CheckNullInstr::kCastError: |
2596 | return save_fpu_registers |
2597 | ? object_store->null_cast_error_stub_with_fpu_regs_stub() |
2598 | : object_store->null_cast_error_stub_without_fpu_regs_stub(); |
2599 | } |
2600 | UNREACHABLE(); |
2601 | } |
2602 | |
2603 | void NullErrorSlowPath::EmitSharedStubCall(FlowGraphCompiler* compiler, |
2604 | bool save_fpu_registers) { |
2605 | #if defined(TARGET_ARCH_IA32) |
2606 | UNREACHABLE(); |
2607 | #else |
2608 | const auto& stub = |
2609 | Code::ZoneHandle(compiler->zone(), |
2610 | GetStub(compiler, exception_type(), save_fpu_registers)); |
2611 | compiler->EmitCallToStub(stub); |
2612 | #endif |
2613 | } |
2614 | |
2615 | void FlowGraphCompiler::EmitNativeMove( |
2616 | const compiler::ffi::NativeLocation& destination, |
2617 | const compiler::ffi::NativeLocation& source, |
2618 | TemporaryRegisterAllocator* temp) { |
2619 | const auto& src_payload_type = source.payload_type(); |
2620 | const auto& dst_payload_type = destination.payload_type(); |
2621 | const auto& src_container_type = source.container_type(); |
2622 | const auto& dst_container_type = destination.container_type(); |
2623 | const intptr_t src_payload_size = src_payload_type.SizeInBytes(); |
2624 | const intptr_t dst_payload_size = dst_payload_type.SizeInBytes(); |
2625 | const intptr_t src_container_size = src_container_type.SizeInBytes(); |
2626 | const intptr_t dst_container_size = dst_container_type.SizeInBytes(); |
2627 | |
2628 | // This function does not know how to do larger mem copy moves yet. |
2629 | ASSERT(src_payload_type.IsFundamental()); |
2630 | ASSERT(dst_payload_type.IsFundamental()); |
2631 | |
2632 | // This function does not deal with sign conversions yet. |
2633 | ASSERT(src_payload_type.IsSigned() == dst_payload_type.IsSigned()); |
2634 | |
2635 | // This function does not deal with bit casts yet. |
2636 | ASSERT(src_container_type.IsFloat() == dst_container_type.IsFloat()); |
2637 | ASSERT(src_container_type.IsInt() == dst_container_type.IsInt()); |
2638 | |
2639 | // If the location, payload, and container are equal, we're done. |
2640 | if (source.Equals(destination) && src_payload_type.Equals(dst_payload_type) && |
2641 | src_container_type.Equals(dst_container_type)) { |
2642 | return; |
2643 | } |
2644 | |
2645 | // Solve descrepancies between container size and payload size. |
2646 | if (src_payload_type.IsInt() && dst_payload_type.IsInt() && |
2647 | (src_payload_size != src_container_size || |
2648 | dst_payload_size != dst_container_size)) { |
2649 | if (src_payload_size <= dst_payload_size && |
2650 | src_container_size >= dst_container_size) { |
2651 | // The upper bits of the source are already properly sign or zero |
2652 | // extended, so just copy the required amount of bits. |
2653 | return EmitNativeMove(destination.WithOtherNativeType( |
2654 | dst_container_type, dst_container_type, zone_), |
2655 | source.WithOtherNativeType( |
2656 | dst_container_type, dst_container_type, zone_), |
2657 | temp); |
2658 | } |
2659 | if (src_payload_size >= dst_payload_size && |
2660 | dst_container_size > dst_payload_size) { |
2661 | // The upper bits of the source are not properly sign or zero extended |
2662 | // to be copied to the target, so regard the source as smaller. |
2663 | return EmitNativeMove( |
2664 | destination.WithOtherNativeType(dst_container_type, |
2665 | dst_container_type, zone_), |
2666 | source.WithOtherNativeType(dst_payload_type, dst_payload_type, zone_), |
2667 | temp); |
2668 | } |
2669 | UNREACHABLE(); |
2670 | } |
2671 | ASSERT(src_payload_size == src_container_size); |
2672 | ASSERT(dst_payload_size == dst_container_size); |
2673 | |
2674 | // Split moves that are larger than kWordSize, these require separate |
2675 | // instructions on all architectures. |
2676 | if (compiler::target::kWordSize == 4 && src_container_size == 8 && |
2677 | dst_container_size == 8 && !source.IsFpuRegisters() && |
2678 | !destination.IsFpuRegisters()) { |
2679 | // TODO(40209): If this is stack to stack, we could use FpuTMP. |
2680 | // Test the impact on code size and speed. |
2681 | EmitNativeMove(destination.Split(0, zone_), source.Split(0, zone_), temp); |
2682 | EmitNativeMove(destination.Split(1, zone_), source.Split(1, zone_), temp); |
2683 | return; |
2684 | } |
2685 | |
2686 | // Split moves from stack to stack, none of the architectures provides |
2687 | // memory to memory move instructions. |
2688 | if (source.IsStack() && destination.IsStack()) { |
2689 | Register scratch = TMP; |
2690 | if (TMP == kNoRegister) { |
2691 | scratch = temp->AllocateTemporary(); |
2692 | } |
2693 | const auto& intermediate = |
2694 | *new (zone_) compiler::ffi::NativeRegistersLocation( |
2695 | dst_payload_type, dst_container_type, scratch); |
2696 | EmitNativeMove(intermediate, source, temp); |
2697 | EmitNativeMove(destination, intermediate, temp); |
2698 | if (TMP == kNoRegister) { |
2699 | temp->ReleaseTemporary(); |
2700 | } |
2701 | return; |
2702 | } |
2703 | |
2704 | const bool sign_or_zero_extend = dst_container_size > src_container_size; |
2705 | |
2706 | // No architecture supports sign extending with memory as destination. |
2707 | if (sign_or_zero_extend && destination.IsStack()) { |
2708 | ASSERT(source.IsRegisters()); |
2709 | const auto& intermediate = |
2710 | source.WithOtherNativeType(dst_payload_type, dst_container_type, zone_); |
2711 | EmitNativeMove(intermediate, source, temp); |
2712 | EmitNativeMove(destination, intermediate, temp); |
2713 | return; |
2714 | } |
2715 | |
2716 | #if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) |
2717 | // Arm does not support sign extending from a memory location, x86 does. |
2718 | if (sign_or_zero_extend && source.IsStack()) { |
2719 | ASSERT(destination.IsRegisters()); |
2720 | const auto& intermediate = destination.WithOtherNativeType( |
2721 | src_payload_type, src_container_type, zone_); |
2722 | EmitNativeMove(intermediate, source, temp); |
2723 | EmitNativeMove(destination, intermediate, temp); |
2724 | return; |
2725 | } |
2726 | #endif |
2727 | |
2728 | // If we're not sign extending, and we're moving 8 or 16 bits into a |
2729 | // register, upgrade the move to take upper bits of garbage from the |
2730 | // source location. This is the same as leaving the previous garbage in |
2731 | // there. |
2732 | // |
2733 | // TODO(40210): If our assemblers would support moving 1 and 2 bytes into |
2734 | // registers, this code can be removed. |
2735 | if (!sign_or_zero_extend && destination.IsRegisters() && |
2736 | destination.container_type().SizeInBytes() <= 2) { |
2737 | ASSERT(source.payload_type().IsInt()); |
2738 | return EmitNativeMove(destination.WidenTo4Bytes(zone_), |
2739 | source.WidenTo4Bytes(zone_), temp); |
2740 | } |
2741 | |
2742 | // Do the simple architecture specific moves. |
2743 | EmitNativeMoveArchitecture(destination, source); |
2744 | } |
2745 | |
2746 | // TODO(dartbug.com/36730): Remove this if PairLocations can be converted |
2747 | // into NativeLocations. |
2748 | void FlowGraphCompiler::EmitMoveToNative( |
2749 | const compiler::ffi::NativeLocation& dst, |
2750 | Location src_loc, |
2751 | Representation src_type, |
2752 | TemporaryRegisterAllocator* temp) { |
2753 | if (src_loc.IsPairLocation()) { |
2754 | for (intptr_t i : {0, 1}) { |
2755 | const auto& src_split = compiler::ffi::NativeLocation::FromPairLocation( |
2756 | src_loc, src_type, i, zone_); |
2757 | EmitNativeMove(dst.Split(i, zone_), src_split, temp); |
2758 | } |
2759 | } else { |
2760 | const auto& src = |
2761 | compiler::ffi::NativeLocation::FromLocation(src_loc, src_type, zone_); |
2762 | EmitNativeMove(dst, src, temp); |
2763 | } |
2764 | } |
2765 | |
2766 | // TODO(dartbug.com/36730): Remove this if PairLocations can be converted |
2767 | // into NativeLocations. |
2768 | void FlowGraphCompiler::EmitMoveFromNative( |
2769 | Location dst_loc, |
2770 | Representation dst_type, |
2771 | const compiler::ffi::NativeLocation& src, |
2772 | TemporaryRegisterAllocator* temp) { |
2773 | if (dst_loc.IsPairLocation()) { |
2774 | for (intptr_t i : {0, 1}) { |
2775 | const auto& dest_split = compiler::ffi::NativeLocation::FromPairLocation( |
2776 | dst_loc, dst_type, i, zone_); |
2777 | EmitNativeMove(dest_split, src.Split(i, zone_), temp); |
2778 | } |
2779 | } else { |
2780 | const auto& dest = |
2781 | compiler::ffi::NativeLocation::FromLocation(dst_loc, dst_type, zone_); |
2782 | EmitNativeMove(dest, src, temp); |
2783 | } |
2784 | } |
2785 | |
2786 | void FlowGraphCompiler::EmitMoveConst(const compiler::ffi::NativeLocation& dst, |
2787 | Location src, |
2788 | Representation src_type, |
2789 | TemporaryRegisterAllocator* temp) { |
2790 | ASSERT(src.IsConstant()); |
2791 | const auto& dst_type = dst.payload_type(); |
2792 | if (dst.IsExpressibleAsLocation() && |
2793 | dst_type.IsExpressibleAsRepresentation() && |
2794 | dst_type.AsRepresentationOverApprox(zone_) == src_type) { |
2795 | // We can directly emit the const in the right place and representation. |
2796 | const Location dst_loc = dst.AsLocation(); |
2797 | EmitMove(dst_loc, src, temp); |
2798 | } else { |
2799 | // We need an intermediate location. |
2800 | Location intermediate; |
2801 | if (dst_type.IsInt()) { |
2802 | if (TMP == kNoRegister) { |
2803 | Register scratch = temp->AllocateTemporary(); |
2804 | Location::RegisterLocation(scratch); |
2805 | } else { |
2806 | intermediate = Location::RegisterLocation(TMP); |
2807 | } |
2808 | } else { |
2809 | ASSERT(dst_type.IsFloat()); |
2810 | intermediate = Location::FpuRegisterLocation(FpuTMP); |
2811 | } |
2812 | |
2813 | if (src.IsPairLocation()) { |
2814 | for (intptr_t i : {0, 1}) { |
2815 | const Representation src_type_split = |
2816 | compiler::ffi::NativeType::FromUnboxedRepresentation(src_type, |
2817 | zone_) |
2818 | .Split(i, zone_) |
2819 | .AsRepresentation(); |
2820 | const auto& intermediate_native = |
2821 | compiler::ffi::NativeLocation::FromLocation(intermediate, |
2822 | src_type_split, zone_); |
2823 | EmitMove(intermediate, src.AsPairLocation()->At(i), temp); |
2824 | EmitNativeMove(dst.Split(i, zone_), intermediate_native, temp); |
2825 | } |
2826 | } else { |
2827 | const auto& intermediate_native = |
2828 | compiler::ffi::NativeLocation::FromLocation(intermediate, src_type, |
2829 | zone_); |
2830 | EmitMove(intermediate, src, temp); |
2831 | EmitNativeMove(dst, intermediate_native, temp); |
2832 | } |
2833 | |
2834 | if (dst_type.IsInt() && TMP == kNoRegister) { |
2835 | temp->ReleaseTemporary(); |
2836 | } |
2837 | } |
2838 | return; |
2839 | } |
2840 | |
2841 | // The assignment to loading units here must match that in |
2842 | // AssignLoadingUnitsCodeVisitor, which runs after compilation is done. |
2843 | static intptr_t LoadingUnitOf(Zone* zone, const Function& function) { |
2844 | const Class& cls = Class::Handle(zone, function.Owner()); |
2845 | const Library& lib = Library::Handle(zone, cls.library()); |
2846 | const LoadingUnit& unit = LoadingUnit::Handle(zone, lib.loading_unit()); |
2847 | ASSERT(!unit.IsNull()); |
2848 | return unit.id(); |
2849 | } |
2850 | |
2851 | static intptr_t LoadingUnitOf(Zone* zone, const Code& code) { |
2852 | // No WeakSerializationReference owners here because those are only |
2853 | // introduced during AOT serialization. |
2854 | if (code.IsStubCode() || code.IsTypeTestStubCode()) { |
2855 | return LoadingUnit::kRootId; |
2856 | } else if (code.IsAllocationStubCode()) { |
2857 | const Class& cls = Class::Cast(Object::Handle(zone, code.owner())); |
2858 | const Library& lib = Library::Handle(zone, cls.library()); |
2859 | const LoadingUnit& unit = LoadingUnit::Handle(zone, lib.loading_unit()); |
2860 | ASSERT(!unit.IsNull()); |
2861 | return unit.id(); |
2862 | } else if (code.IsFunctionCode()) { |
2863 | return LoadingUnitOf(zone, |
2864 | Function::Cast(Object::Handle(zone, code.owner()))); |
2865 | } else { |
2866 | UNREACHABLE(); |
2867 | return LoadingUnit::kIllegalId; |
2868 | } |
2869 | } |
2870 | |
2871 | bool FlowGraphCompiler::CanPcRelativeCall(const Function& target) const { |
2872 | return FLAG_precompiled_mode && FLAG_use_bare_instructions && |
2873 | (LoadingUnitOf(zone_, function()) == LoadingUnitOf(zone_, target)); |
2874 | } |
2875 | |
2876 | bool FlowGraphCompiler::CanPcRelativeCall(const Code& target) const { |
2877 | return FLAG_precompiled_mode && FLAG_use_bare_instructions && |
2878 | !target.InVMIsolateHeap() && |
2879 | (LoadingUnitOf(zone_, function()) == LoadingUnitOf(zone_, target)); |
2880 | } |
2881 | |
2882 | bool FlowGraphCompiler::CanPcRelativeCall(const AbstractType& target) const { |
2883 | return FLAG_precompiled_mode && FLAG_use_bare_instructions && |
2884 | !target.InVMIsolateHeap() && |
2885 | (LoadingUnitOf(zone_, function()) == LoadingUnit::kRootId); |
2886 | } |
2887 | |
2888 | #undef __ |
2889 | |
2890 | } // namespace dart |
2891 | |