| // Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file | 
 | // for details. All rights reserved. Use of this source code is governed by a | 
 | // BSD-style license that can be found in the LICENSE file. | 
 |  | 
 | #include "vm/compiler/aot/aot_call_specializer.h" | 
 |  | 
 | #include "vm/bit_vector.h" | 
 | #include "vm/compiler/aot/precompiler.h" | 
 | #include "vm/compiler/backend/branch_optimizer.h" | 
 | #include "vm/compiler/backend/flow_graph_compiler.h" | 
 | #include "vm/compiler/backend/il.h" | 
 | #include "vm/compiler/backend/il_printer.h" | 
 | #include "vm/compiler/backend/inliner.h" | 
 | #include "vm/compiler/backend/range_analysis.h" | 
 | #include "vm/compiler/cha.h" | 
 | #include "vm/compiler/compiler_state.h" | 
 | #include "vm/compiler/frontend/flow_graph_builder.h" | 
 | #include "vm/compiler/jit/compiler.h" | 
 | #include "vm/compiler/jit/jit_call_specializer.h" | 
 | #include "vm/cpu.h" | 
 | #include "vm/dart_entry.h" | 
 | #include "vm/exceptions.h" | 
 | #include "vm/hash_map.h" | 
 | #include "vm/object.h" | 
 | #include "vm/object_store.h" | 
 | #include "vm/parser.h" | 
 | #include "vm/resolver.h" | 
 | #include "vm/scopes.h" | 
 | #include "vm/stack_frame.h" | 
 | #include "vm/symbols.h" | 
 |  | 
 | namespace dart { | 
 |  | 
 | DEFINE_FLAG(int, | 
 |             max_exhaustive_polymorphic_checks, | 
 |             5, | 
 |             "If a call receiver is known to be of at most this many classes, " | 
 |             "generate exhaustive class tests instead of a megamorphic call"); | 
 |  | 
 | // Quick access to the current isolate and zone. | 
 | #define IG (isolate_group()) | 
 | #define Z (zone()) | 
 |  | 
 | #ifdef DART_PRECOMPILER | 
 |  | 
 | // Returns named function that is a unique dynamic target, i.e., | 
 | // - the target is identified by its name alone, since it occurs only once. | 
 | // - target's class has no subclasses, and neither is subclassed, i.e., | 
 | //   the receiver type can be only the function's class. | 
 | // Returns Function::null() if there is no unique dynamic target for | 
 | // given 'fname'. 'fname' must be a symbol. | 
 | static void GetUniqueDynamicTarget(IsolateGroup* isolate_group, | 
 |                                    const String& fname, | 
 |                                    Object* function) { | 
 |   UniqueFunctionsMap functions_map( | 
 |       isolate_group->object_store()->unique_dynamic_targets()); | 
 |   ASSERT(fname.IsSymbol()); | 
 |   *function = functions_map.GetOrNull(fname); | 
 |   ASSERT(functions_map.Release().ptr() == | 
 |          isolate_group->object_store()->unique_dynamic_targets()); | 
 | } | 
 |  | 
 | AotCallSpecializer::AotCallSpecializer( | 
 |     Precompiler* precompiler, | 
 |     FlowGraph* flow_graph, | 
 |     SpeculativeInliningPolicy* speculative_policy) | 
 |     : CallSpecializer(flow_graph, | 
 |                       speculative_policy, | 
 |                       /* should_clone_fields=*/false), | 
 |       precompiler_(precompiler), | 
 |       has_unique_no_such_method_(false) { | 
 |   Function& target_function = Function::Handle(); | 
 |   if (isolate_group()->object_store()->unique_dynamic_targets() != | 
 |       Array::null()) { | 
 |     GetUniqueDynamicTarget(isolate_group(), Symbols::NoSuchMethod(), | 
 |                            &target_function); | 
 |     has_unique_no_such_method_ = !target_function.IsNull(); | 
 |   } | 
 | } | 
 |  | 
 | bool AotCallSpecializer::TryCreateICDataForUniqueTarget( | 
 |     InstanceCallInstr* call) { | 
 |   if (isolate_group()->object_store()->unique_dynamic_targets() == | 
 |       Array::null()) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   // Check if the target is unique. | 
 |   Function& target_function = Function::Handle(Z); | 
 |   GetUniqueDynamicTarget(isolate_group(), call->function_name(), | 
 |                          &target_function); | 
 |  | 
 |   if (target_function.IsNull()) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   // Calls passing named arguments and calls to a function taking named | 
 |   // arguments must be resolved/checked at runtime. | 
 |   // Calls passing a type argument vector and calls to a generic function must | 
 |   // be resolved/checked at runtime. | 
 |   if (target_function.HasOptionalNamedParameters() || | 
 |       target_function.IsGeneric() || | 
 |       !target_function.AreValidArgumentCounts( | 
 |           call->type_args_len(), call->ArgumentCountWithoutTypeArgs(), | 
 |           call->argument_names().IsNull() ? 0 : call->argument_names().Length(), | 
 |           /* error_message = */ NULL)) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   const Class& cls = Class::Handle(Z, target_function.Owner()); | 
 |   if (CHA::IsImplemented(cls) || CHA::HasSubclasses(cls)) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   call->SetTargets( | 
 |       CallTargets::CreateMonomorphic(Z, cls.id(), target_function)); | 
 |   ASSERT(call->Targets().IsMonomorphic()); | 
 |  | 
 |   // If we know that the only noSuchMethod is Object.noSuchMethod then | 
 |   // this call is guaranteed to either succeed or throw. | 
 |   if (has_unique_no_such_method_) { | 
 |     call->set_has_unique_selector(true); | 
 |  | 
 |     // Add redefinition of the receiver to prevent code motion across | 
 |     // this call. | 
 |     const intptr_t receiver_index = call->FirstArgIndex(); | 
 |     RedefinitionInstr* redefinition = new (Z) | 
 |         RedefinitionInstr(new (Z) Value(call->ArgumentAt(receiver_index))); | 
 |     redefinition->set_ssa_temp_index(flow_graph()->alloc_ssa_temp_index()); | 
 |     if (FlowGraph::NeedsPairLocation(redefinition->representation())) { | 
 |       flow_graph()->alloc_ssa_temp_index(); | 
 |     } | 
 |     redefinition->InsertAfter(call); | 
 |     // Replace all uses of the receiver dominated by this call. | 
 |     FlowGraph::RenameDominatedUses(call->ArgumentAt(receiver_index), | 
 |                                    redefinition, redefinition); | 
 |     if (!redefinition->HasUses()) { | 
 |       redefinition->RemoveFromGraph(); | 
 |     } | 
 |   } | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | bool AotCallSpecializer::TryCreateICData(InstanceCallInstr* call) { | 
 |   if (TryCreateICDataForUniqueTarget(call)) { | 
 |     return true; | 
 |   } | 
 |  | 
 |   return CallSpecializer::TryCreateICData(call); | 
 | } | 
 |  | 
 | bool AotCallSpecializer::RecognizeRuntimeTypeGetter(InstanceCallInstr* call) { | 
 |   if ((precompiler_ == NULL) || !precompiler_->get_runtime_type_is_unique()) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   if (call->function_name().ptr() != Symbols::GetRuntimeType().ptr()) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   // There is only a single function Object.get:runtimeType that can be invoked | 
 |   // by this call. Convert dynamic invocation to a static one. | 
 |   const Class& cls = Class::Handle(Z, IG->object_store()->object_class()); | 
 |   const Function& function = | 
 |       Function::Handle(Z, call->ResolveForReceiverClass(cls)); | 
 |   ASSERT(!function.IsNull()); | 
 |   const Function& target = Function::ZoneHandle(Z, function.ptr()); | 
 |   StaticCallInstr* static_call = | 
 |       StaticCallInstr::FromCall(Z, call, target, call->CallCount()); | 
 |   // Since the result is either a Type or a FunctionType, we cannot pin it. | 
 |   call->ReplaceWith(static_call, current_iterator()); | 
 |   return true; | 
 | } | 
 |  | 
 | static bool IsGetRuntimeType(Definition* defn) { | 
 |   StaticCallInstr* call = defn->AsStaticCall(); | 
 |   return (call != NULL) && (call->function().recognized_kind() == | 
 |                             MethodRecognizer::kObjectRuntimeType); | 
 | } | 
 |  | 
 | // Recognize a.runtimeType == b.runtimeType and fold it into | 
 | // Object._haveSameRuntimeType(a, b). | 
 | // Note: this optimization is not speculative. | 
 | bool AotCallSpecializer::TryReplaceWithHaveSameRuntimeType( | 
 |     TemplateDartCall<0>* call) { | 
 |   ASSERT((call->IsInstanceCall() && | 
 |           (call->AsInstanceCall()->ic_data()->NumArgsTested() == 2)) || | 
 |          call->IsStaticCall()); | 
 |   ASSERT(call->type_args_len() == 0); | 
 |   ASSERT(call->ArgumentCount() == 2); | 
 |  | 
 |   Definition* left = call->ArgumentAt(0); | 
 |   Definition* right = call->ArgumentAt(1); | 
 |  | 
 |   if (IsGetRuntimeType(left) && left->input_use_list()->IsSingleUse() && | 
 |       IsGetRuntimeType(right) && right->input_use_list()->IsSingleUse()) { | 
 |     const Class& cls = Class::Handle(Z, IG->object_store()->object_class()); | 
 |     const Function& have_same_runtime_type = Function::ZoneHandle( | 
 |         Z, | 
 |         cls.LookupStaticFunctionAllowPrivate(Symbols::HaveSameRuntimeType())); | 
 |     ASSERT(!have_same_runtime_type.IsNull()); | 
 |  | 
 |     InputsArray* args = new (Z) InputsArray(Z, 2); | 
 |     args->Add(left->ArgumentValueAt(0)->CopyWithType(Z)); | 
 |     args->Add(right->ArgumentValueAt(0)->CopyWithType(Z)); | 
 |     const intptr_t kTypeArgsLen = 0; | 
 |     StaticCallInstr* static_call = new (Z) StaticCallInstr( | 
 |         call->source(), have_same_runtime_type, kTypeArgsLen, | 
 |         Object::null_array(),  // argument_names | 
 |         args, call->deopt_id(), call->CallCount(), ICData::kOptimized); | 
 |     static_call->SetResultType(Z, CompileType::FromCid(kBoolCid)); | 
 |     ReplaceCall(call, static_call); | 
 |     // ReplaceCall moved environment from 'call' to 'static_call'. | 
 |     // Update arguments of 'static_call' in the environment. | 
 |     Environment* env = static_call->env(); | 
 |     env->ValueAt(env->Length() - 2) | 
 |         ->BindToEnvironment(static_call->ArgumentAt(0)); | 
 |     env->ValueAt(env->Length() - 1) | 
 |         ->BindToEnvironment(static_call->ArgumentAt(1)); | 
 |     return true; | 
 |   } | 
 |  | 
 |   return false; | 
 | } | 
 |  | 
 | bool AotCallSpecializer::TryInlineFieldAccess(InstanceCallInstr* call) { | 
 |   const Token::Kind op_kind = call->token_kind(); | 
 |   if ((op_kind == Token::kGET) && TryInlineInstanceGetter(call)) { | 
 |     return true; | 
 |   } | 
 |   if ((op_kind == Token::kSET) && TryInlineInstanceSetter(call)) { | 
 |     return true; | 
 |   } | 
 |   return false; | 
 | } | 
 |  | 
 | bool AotCallSpecializer::TryInlineFieldAccess(StaticCallInstr* call) { | 
 |   if (call->function().IsImplicitGetterFunction()) { | 
 |     Field& field = Field::ZoneHandle(call->function().accessor_field()); | 
 |     if (field.is_late()) { | 
 |       // TODO(dartbug.com/40447): Inline implicit getters for late fields. | 
 |       return false; | 
 |     } | 
 |     if (should_clone_fields_) { | 
 |       field = field.CloneFromOriginal(); | 
 |     } | 
 |     InlineImplicitInstanceGetter(call, field); | 
 |     return true; | 
 |   } | 
 |  | 
 |   return false; | 
 | } | 
 |  | 
 | bool AotCallSpecializer::IsSupportedIntOperandForStaticDoubleOp( | 
 |     CompileType* operand_type) { | 
 |   if (operand_type->IsNullableInt()) { | 
 |     if (operand_type->ToNullableCid() == kSmiCid) { | 
 |       return true; | 
 |     } | 
 |  | 
 |     if (FlowGraphCompiler::CanConvertInt64ToDouble()) { | 
 |       return true; | 
 |     } | 
 |   } | 
 |  | 
 |   return false; | 
 | } | 
 |  | 
 | Value* AotCallSpecializer::PrepareStaticOpInput(Value* input, | 
 |                                                 intptr_t cid, | 
 |                                                 Instruction* call) { | 
 |   ASSERT((cid == kDoubleCid) || (cid == kMintCid)); | 
 |  | 
 |   if (input->Type()->is_nullable()) { | 
 |     const String& function_name = | 
 |         (call->IsInstanceCall() | 
 |              ? call->AsInstanceCall()->function_name() | 
 |              : String::ZoneHandle(Z, call->AsStaticCall()->function().name())); | 
 |     AddCheckNull(input, function_name, call->deopt_id(), call->env(), call); | 
 |   } | 
 |  | 
 |   input = input->CopyWithType(Z); | 
 |  | 
 |   if (cid == kDoubleCid && input->Type()->IsNullableInt()) { | 
 |     Definition* conversion = NULL; | 
 |  | 
 |     if (input->Type()->ToNullableCid() == kSmiCid) { | 
 |       conversion = new (Z) SmiToDoubleInstr(input, call->source()); | 
 |     } else if (FlowGraphCompiler::CanConvertInt64ToDouble()) { | 
 |       conversion = new (Z) Int64ToDoubleInstr(input, DeoptId::kNone, | 
 |                                               Instruction::kNotSpeculative); | 
 |     } else { | 
 |       UNREACHABLE(); | 
 |     } | 
 |  | 
 |     if (FLAG_trace_strong_mode_types) { | 
 |       THR_Print("[Strong mode] Inserted %s\n", conversion->ToCString()); | 
 |     } | 
 |     InsertBefore(call, conversion, /* env = */ NULL, FlowGraph::kValue); | 
 |     return new (Z) Value(conversion); | 
 |   } | 
 |  | 
 |   return input; | 
 | } | 
 |  | 
 | CompileType AotCallSpecializer::BuildStrengthenedReceiverType(Value* input, | 
 |                                                               intptr_t cid) { | 
 |   CompileType* old_type = input->Type(); | 
 |   CompileType* refined_type = old_type; | 
 |  | 
 |   CompileType type = CompileType::None(); | 
 |   if (cid == kSmiCid) { | 
 |     type = CompileType::NullableSmi(); | 
 |     refined_type = CompileType::ComputeRefinedType(old_type, &type); | 
 |   } else if (cid == kMintCid) { | 
 |     type = CompileType::NullableMint(); | 
 |     refined_type = CompileType::ComputeRefinedType(old_type, &type); | 
 |   } else if (cid == kIntegerCid && !input->Type()->IsNullableInt()) { | 
 |     type = CompileType::NullableInt(); | 
 |     refined_type = CompileType::ComputeRefinedType(old_type, &type); | 
 |   } else if (cid == kDoubleCid && !input->Type()->IsNullableDouble()) { | 
 |     type = CompileType::NullableDouble(); | 
 |     refined_type = CompileType::ComputeRefinedType(old_type, &type); | 
 |   } | 
 |  | 
 |   if (refined_type != old_type) { | 
 |     return *refined_type; | 
 |   } | 
 |   return CompileType::None(); | 
 | } | 
 |  | 
 | // After replacing a call with a specialized instruction, make sure to | 
 | // update types at all uses, as specialized instruction can provide a more | 
 | // specific type. | 
 | static void RefineUseTypes(Definition* instr) { | 
 |   CompileType* new_type = instr->Type(); | 
 |   for (Value::Iterator it(instr->input_use_list()); !it.Done(); it.Advance()) { | 
 |     it.Current()->RefineReachingType(new_type); | 
 |   } | 
 | } | 
 |  | 
 | bool AotCallSpecializer::TryOptimizeInstanceCallUsingStaticTypes( | 
 |     InstanceCallInstr* instr) { | 
 |   const Token::Kind op_kind = instr->token_kind(); | 
 |   return TryOptimizeIntegerOperation(instr, op_kind) || | 
 |          TryOptimizeDoubleOperation(instr, op_kind); | 
 | } | 
 |  | 
 | bool AotCallSpecializer::TryOptimizeStaticCallUsingStaticTypes( | 
 |     StaticCallInstr* instr) { | 
 |   const String& name = String::Handle(Z, instr->function().name()); | 
 |   const Token::Kind op_kind = MethodTokenRecognizer::RecognizeTokenKind(name); | 
 |  | 
 |   if (op_kind == Token::kEQ && TryReplaceWithHaveSameRuntimeType(instr)) { | 
 |     return true; | 
 |   } | 
 |  | 
 |   // We only specialize instance methods for int/double operations. | 
 |   const auto& target = instr->function(); | 
 |   if (!target.IsDynamicFunction()) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   // For de-virtualized instance calls, we strengthen the type here manually | 
 |   // because it might not be attached to the receiver. | 
 |   // See http://dartbug.com/35179 for preserving the receiver type information. | 
 |   const Class& owner = Class::Handle(Z, target.Owner()); | 
 |   const intptr_t cid = owner.id(); | 
 |   if (cid == kSmiCid || cid == kMintCid || cid == kIntegerCid || | 
 |       cid == kDoubleCid) { | 
 |     // Sometimes TFA de-virtualizes instance calls to static calls.  In such | 
 |     // cases the VM might have a looser type on the receiver, so we explicitly | 
 |     // tighten it (this is safe since it was proven that the receiver is either | 
 |     // null or will end up with that target). | 
 |     const intptr_t receiver_index = instr->FirstArgIndex(); | 
 |     const intptr_t argument_count = instr->ArgumentCountWithoutTypeArgs(); | 
 |     if (argument_count >= 1) { | 
 |       auto receiver_value = instr->ArgumentValueAt(receiver_index); | 
 |       auto receiver = receiver_value->definition(); | 
 |       auto type = BuildStrengthenedReceiverType(receiver_value, cid); | 
 |       if (!type.IsNone()) { | 
 |         auto redefinition = | 
 |             flow_graph()->EnsureRedefinition(instr->previous(), receiver, type); | 
 |         if (redefinition != nullptr) { | 
 |           RefineUseTypes(redefinition); | 
 |         } | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   return TryOptimizeIntegerOperation(instr, op_kind) || | 
 |          TryOptimizeDoubleOperation(instr, op_kind); | 
 | } | 
 |  | 
 | // Modulo against a constant power-of-two can be optimized into a mask. | 
 | // x % y -> x & (|y| - 1)  for smi masks only | 
 | Definition* AotCallSpecializer::TryOptimizeMod(TemplateDartCall<0>* instr, | 
 |                                                Token::Kind op_kind, | 
 |                                                Value* left_value, | 
 |                                                Value* right_value) { | 
 |   if (!right_value->BindsToConstant()) { | 
 |     return nullptr; | 
 |   } | 
 |  | 
 |   const Object& rhs = right_value->BoundConstant(); | 
 |   const int64_t value = Integer::Cast(rhs).AsInt64Value();  // smi and mint | 
 |   if (value == kMinInt64) { | 
 |     return nullptr;  // non-smi mask | 
 |   } | 
 |   const int64_t modulus = Utils::Abs(value); | 
 |   if (!Utils::IsPowerOfTwo(modulus) || !compiler::target::IsSmi(modulus - 1)) { | 
 |     return nullptr; | 
 |   } | 
 |  | 
 |   left_value = PrepareStaticOpInput(left_value, kMintCid, instr); | 
 |  | 
 | #if defined(TARGET_ARCH_ARM) | 
 |   Definition* right_definition = new (Z) UnboxedConstantInstr( | 
 |       Smi::ZoneHandle(Z, Smi::New(modulus - 1)), kUnboxedInt32); | 
 |   InsertBefore(instr, right_definition, /*env=*/NULL, FlowGraph::kValue); | 
 |   right_definition = new (Z) | 
 |       IntConverterInstr(kUnboxedInt32, kUnboxedInt64, | 
 |                         new (Z) Value(right_definition), DeoptId::kNone); | 
 | #else | 
 |   Definition* right_definition = new (Z) UnboxedConstantInstr( | 
 |       Smi::ZoneHandle(Z, Smi::New(modulus - 1)), kUnboxedInt64); | 
 | #endif | 
 |   if (modulus == 1) return right_definition; | 
 |   InsertBefore(instr, right_definition, /*env=*/NULL, FlowGraph::kValue); | 
 |   right_value = new (Z) Value(right_definition); | 
 |   return new (Z) | 
 |       BinaryInt64OpInstr(Token::kBIT_AND, left_value, right_value, | 
 |                          DeoptId::kNone, Instruction::kNotSpeculative); | 
 | } | 
 |  | 
 | bool AotCallSpecializer::TryOptimizeIntegerOperation(TemplateDartCall<0>* instr, | 
 |                                                      Token::Kind op_kind) { | 
 |   if (instr->type_args_len() != 0) { | 
 |     // Arithmetic operations don't have type arguments. | 
 |     return false; | 
 |   } | 
 |  | 
 |   Definition* replacement = NULL; | 
 |   if (instr->ArgumentCount() == 2) { | 
 |     Value* left_value = instr->ArgumentValueAt(0); | 
 |     Value* right_value = instr->ArgumentValueAt(1); | 
 |     CompileType* left_type = left_value->Type(); | 
 |     CompileType* right_type = right_value->Type(); | 
 |  | 
 |     bool has_nullable_int_args = | 
 |         left_type->IsNullableInt() && right_type->IsNullableInt(); | 
 |  | 
 |     if (auto* call = instr->AsInstanceCall()) { | 
 |       if (!call->CanReceiverBeSmiBasedOnInterfaceTarget(zone())) { | 
 |         has_nullable_int_args = false; | 
 |       } | 
 |     } | 
 |  | 
 |     // We only support binary operations if both operands are nullable integers | 
 |     // or when we can use a cheap strict comparison operation. | 
 |     if (!has_nullable_int_args) { | 
 |       return false; | 
 |     } | 
 |  | 
 |     switch (op_kind) { | 
 |       case Token::kEQ: | 
 |       case Token::kNE: { | 
 |         // If both arguments are nullable Smi or one of the arguments is | 
 |         // a null or Smi and the other argument is nullable then emit | 
 |         // StrictCompare (all arguments are going to be boxed anyway). | 
 |         // Otherwise prefer EqualityCompare to avoid redundant boxing. | 
 |         const bool left_is_null_or_smi = | 
 |             left_type->IsNull() || left_type->IsNullableSmi(); | 
 |         const bool right_is_null_or_smi = | 
 |             right_type->IsNull() || right_type->IsNullableSmi(); | 
 |         const bool both_are_nullable_smis = | 
 |             left_type->IsNullableSmi() && right_type->IsNullableSmi(); | 
 |         const bool either_can_be_null = | 
 |             left_type->is_nullable() || right_type->is_nullable(); | 
 |         if (both_are_nullable_smis || | 
 |             ((left_is_null_or_smi || right_is_null_or_smi) && | 
 |              either_can_be_null)) { | 
 |           replacement = new (Z) StrictCompareInstr( | 
 |               instr->source(), | 
 |               (op_kind == Token::kEQ) ? Token::kEQ_STRICT : Token::kNE_STRICT, | 
 |               left_value->CopyWithType(Z), right_value->CopyWithType(Z), | 
 |               /*needs_number_check=*/false, DeoptId::kNone); | 
 |         } else { | 
 |           replacement = new (Z) EqualityCompareInstr( | 
 |               instr->source(), op_kind, left_value->CopyWithType(Z), | 
 |               right_value->CopyWithType(Z), kMintCid, DeoptId::kNone, | 
 |               /*null_aware=*/either_can_be_null, Instruction::kNotSpeculative); | 
 |         } | 
 |         break; | 
 |       } | 
 |       case Token::kLT: | 
 |       case Token::kLTE: | 
 |       case Token::kGT: | 
 |       case Token::kGTE: | 
 |         left_value = PrepareStaticOpInput(left_value, kMintCid, instr); | 
 |         right_value = PrepareStaticOpInput(right_value, kMintCid, instr); | 
 |         replacement = new (Z) RelationalOpInstr( | 
 |             instr->source(), op_kind, left_value, right_value, kMintCid, | 
 |             DeoptId::kNone, Instruction::kNotSpeculative); | 
 |         break; | 
 |       case Token::kMOD: | 
 |         replacement = TryOptimizeMod(instr, op_kind, left_value, right_value); | 
 |         if (replacement != nullptr) break; | 
 |         FALL_THROUGH; | 
 |       case Token::kTRUNCDIV: | 
 | #if !defined(TARGET_ARCH_IS_64_BIT) | 
 |         // TODO(ajcbik): 32-bit archs too? | 
 |         break; | 
 | #else | 
 |         FALL_THROUGH; | 
 | #endif | 
 |       case Token::kSHL: | 
 |         FALL_THROUGH; | 
 |       case Token::kSHR: | 
 |         FALL_THROUGH; | 
 |       case Token::kUSHR: | 
 |         FALL_THROUGH; | 
 |       case Token::kBIT_OR: | 
 |         FALL_THROUGH; | 
 |       case Token::kBIT_XOR: | 
 |         FALL_THROUGH; | 
 |       case Token::kBIT_AND: | 
 |         FALL_THROUGH; | 
 |       case Token::kADD: | 
 |         FALL_THROUGH; | 
 |       case Token::kSUB: | 
 |         FALL_THROUGH; | 
 |       case Token::kMUL: { | 
 |         if (op_kind == Token::kSHL || op_kind == Token::kSHR || | 
 |             op_kind == Token::kUSHR) { | 
 |           left_value = PrepareStaticOpInput(left_value, kMintCid, instr); | 
 |           right_value = PrepareStaticOpInput(right_value, kMintCid, instr); | 
 |           replacement = new (Z) ShiftInt64OpInstr(op_kind, left_value, | 
 |                                                   right_value, DeoptId::kNone); | 
 |         } else { | 
 |           left_value = PrepareStaticOpInput(left_value, kMintCid, instr); | 
 |           right_value = PrepareStaticOpInput(right_value, kMintCid, instr); | 
 |           replacement = new (Z) | 
 |               BinaryInt64OpInstr(op_kind, left_value, right_value, | 
 |                                  DeoptId::kNone, Instruction::kNotSpeculative); | 
 |         } | 
 |         break; | 
 |       } | 
 |  | 
 |       default: | 
 |         break; | 
 |     } | 
 |   } else if (instr->ArgumentCount() == 1) { | 
 |     Value* left_value = instr->ArgumentValueAt(0); | 
 |     CompileType* left_type = left_value->Type(); | 
 |  | 
 |     // We only support unary operations on nullable integers. | 
 |     if (!left_type->IsNullableInt()) { | 
 |       return false; | 
 |     } | 
 |  | 
 |     if (op_kind == Token::kNEGATE || op_kind == Token::kBIT_NOT) { | 
 |       left_value = PrepareStaticOpInput(left_value, kMintCid, instr); | 
 |       replacement = new (Z) UnaryInt64OpInstr( | 
 |           op_kind, left_value, DeoptId::kNone, Instruction::kNotSpeculative); | 
 |     } | 
 |   } | 
 |  | 
 |   if (replacement != nullptr && !replacement->ComputeCanDeoptimize()) { | 
 |     if (FLAG_trace_strong_mode_types) { | 
 |       THR_Print("[Strong mode] Optimization: replacing %s with %s\n", | 
 |                 instr->ToCString(), replacement->ToCString()); | 
 |     } | 
 |     ReplaceCall(instr, replacement); | 
 |     RefineUseTypes(replacement); | 
 |     return true; | 
 |   } | 
 |  | 
 |   return false; | 
 | } | 
 |  | 
 | bool AotCallSpecializer::TryOptimizeDoubleOperation(TemplateDartCall<0>* instr, | 
 |                                                     Token::Kind op_kind) { | 
 |   if (instr->type_args_len() != 0) { | 
 |     // Arithmetic operations don't have type arguments. | 
 |     return false; | 
 |   } | 
 |  | 
 |   if (!FlowGraphCompiler::SupportsUnboxedDoubles()) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   Definition* replacement = NULL; | 
 |  | 
 |   if (instr->ArgumentCount() == 2) { | 
 |     Value* left_value = instr->ArgumentValueAt(0); | 
 |     Value* right_value = instr->ArgumentValueAt(1); | 
 |     CompileType* left_type = left_value->Type(); | 
 |     CompileType* right_type = right_value->Type(); | 
 |  | 
 |     if (!left_type->IsNullableDouble() && | 
 |         !IsSupportedIntOperandForStaticDoubleOp(left_type)) { | 
 |       return false; | 
 |     } | 
 |     if (!right_type->IsNullableDouble() && | 
 |         !IsSupportedIntOperandForStaticDoubleOp(right_type)) { | 
 |       return false; | 
 |     } | 
 |  | 
 |     switch (op_kind) { | 
 |       case Token::kEQ: | 
 |         FALL_THROUGH; | 
 |       case Token::kNE: { | 
 |         // TODO(dartbug.com/32166): Support EQ, NE for nullable doubles. | 
 |         // (requires null-aware comparison instruction). | 
 |         if (!left_type->is_nullable() && !right_type->is_nullable()) { | 
 |           left_value = PrepareStaticOpInput(left_value, kDoubleCid, instr); | 
 |           right_value = PrepareStaticOpInput(right_value, kDoubleCid, instr); | 
 |           replacement = new (Z) EqualityCompareInstr( | 
 |               instr->source(), op_kind, left_value, right_value, kDoubleCid, | 
 |               DeoptId::kNone, /*null_aware=*/false, | 
 |               Instruction::kNotSpeculative); | 
 |           break; | 
 |         } | 
 |         break; | 
 |       } | 
 |       case Token::kLT: | 
 |         FALL_THROUGH; | 
 |       case Token::kLTE: | 
 |         FALL_THROUGH; | 
 |       case Token::kGT: | 
 |         FALL_THROUGH; | 
 |       case Token::kGTE: { | 
 |         left_value = PrepareStaticOpInput(left_value, kDoubleCid, instr); | 
 |         right_value = PrepareStaticOpInput(right_value, kDoubleCid, instr); | 
 |         replacement = new (Z) RelationalOpInstr( | 
 |             instr->source(), op_kind, left_value, right_value, kDoubleCid, | 
 |             DeoptId::kNone, Instruction::kNotSpeculative); | 
 |         break; | 
 |       } | 
 |       case Token::kADD: | 
 |         FALL_THROUGH; | 
 |       case Token::kSUB: | 
 |         FALL_THROUGH; | 
 |       case Token::kMUL: | 
 |         FALL_THROUGH; | 
 |       case Token::kDIV: { | 
 |         left_value = PrepareStaticOpInput(left_value, kDoubleCid, instr); | 
 |         right_value = PrepareStaticOpInput(right_value, kDoubleCid, instr); | 
 |         replacement = new (Z) BinaryDoubleOpInstr( | 
 |             op_kind, left_value, right_value, DeoptId::kNone, instr->source(), | 
 |             Instruction::kNotSpeculative); | 
 |         break; | 
 |       } | 
 |  | 
 |       case Token::kBIT_OR: | 
 |         FALL_THROUGH; | 
 |       case Token::kBIT_XOR: | 
 |         FALL_THROUGH; | 
 |       case Token::kBIT_AND: | 
 |         FALL_THROUGH; | 
 |       case Token::kMOD: | 
 |         FALL_THROUGH; | 
 |       case Token::kTRUNCDIV: | 
 |         FALL_THROUGH; | 
 |       default: | 
 |         break; | 
 |     } | 
 |   } else if (instr->ArgumentCount() == 1) { | 
 |     Value* left_value = instr->ArgumentValueAt(0); | 
 |     CompileType* left_type = left_value->Type(); | 
 |  | 
 |     // We only support unary operations on nullable doubles. | 
 |     if (!left_type->IsNullableDouble()) { | 
 |       return false; | 
 |     } | 
 |  | 
 |     if (op_kind == Token::kNEGATE) { | 
 |       left_value = PrepareStaticOpInput(left_value, kDoubleCid, instr); | 
 |       replacement = new (Z) | 
 |           UnaryDoubleOpInstr(Token::kNEGATE, left_value, instr->deopt_id(), | 
 |                              Instruction::kNotSpeculative); | 
 |     } | 
 |   } | 
 |  | 
 |   if (replacement != NULL && !replacement->ComputeCanDeoptimize()) { | 
 |     if (FLAG_trace_strong_mode_types) { | 
 |       THR_Print("[Strong mode] Optimization: replacing %s with %s\n", | 
 |                 instr->ToCString(), replacement->ToCString()); | 
 |     } | 
 |     ReplaceCall(instr, replacement); | 
 |     RefineUseTypes(replacement); | 
 |     return true; | 
 |   } | 
 |  | 
 |   return false; | 
 | } | 
 |  | 
 | static void EnsureICData(Zone* zone, | 
 |                          const Function& function, | 
 |                          InstanceCallInstr* call) { | 
 |   if (!call->HasICData()) { | 
 |     const Array& arguments_descriptor = | 
 |         Array::Handle(zone, call->GetArgumentsDescriptor()); | 
 |     const ICData& ic_data = ICData::ZoneHandle( | 
 |         zone, ICData::New(function, call->function_name(), arguments_descriptor, | 
 |                           call->deopt_id(), call->checked_argument_count(), | 
 |                           ICData::kInstance)); | 
 |     call->set_ic_data(&ic_data); | 
 |   } | 
 | } | 
 |  | 
 | // Tries to optimize instance call by replacing it with a faster instruction | 
 | // (e.g, binary op, field load, ..). | 
 | // TODO(dartbug.com/30635) Evaluate how much this can be shared with | 
 | // JitCallSpecializer. | 
 | void AotCallSpecializer::VisitInstanceCall(InstanceCallInstr* instr) { | 
 |   // Type test is special as it always gets converted into inlined code. | 
 |   const Token::Kind op_kind = instr->token_kind(); | 
 |   if (Token::IsTypeTestOperator(op_kind)) { | 
 |     ReplaceWithInstanceOf(instr); | 
 |     return; | 
 |   } | 
 |  | 
 |   if (TryInlineFieldAccess(instr)) { | 
 |     return; | 
 |   } | 
 |  | 
 |   if (RecognizeRuntimeTypeGetter(instr)) { | 
 |     return; | 
 |   } | 
 |  | 
 |   if ((op_kind == Token::kEQ) && TryReplaceWithHaveSameRuntimeType(instr)) { | 
 |     return; | 
 |   } | 
 |  | 
 |   const CallTargets& targets = instr->Targets(); | 
 |   const intptr_t receiver_idx = instr->FirstArgIndex(); | 
 |  | 
 |   if (TryOptimizeInstanceCallUsingStaticTypes(instr)) { | 
 |     return; | 
 |   } | 
 |  | 
 |   bool has_one_target = targets.HasSingleTarget(); | 
 |   if (has_one_target) { | 
 |     // Check if the single target is a polymorphic target, if it is, | 
 |     // we don't have one target. | 
 |     const Function& target = targets.FirstTarget(); | 
 |     has_one_target = !target.is_polymorphic_target(); | 
 |   } | 
 |  | 
 |   if (has_one_target) { | 
 |     const Function& target = targets.FirstTarget(); | 
 |     UntaggedFunction::Kind function_kind = target.kind(); | 
 |     if (flow_graph()->CheckForInstanceCall(instr, function_kind) == | 
 |         FlowGraph::ToCheck::kNoCheck) { | 
 |       StaticCallInstr* call = StaticCallInstr::FromCall( | 
 |           Z, instr, target, targets.AggregateCallCount()); | 
 |       instr->ReplaceWith(call, current_iterator()); | 
 |       return; | 
 |     } | 
 |   } | 
 |  | 
 |   // No IC data checks. Try resolve target using the propagated cid. | 
 |   const intptr_t receiver_cid = | 
 |       instr->ArgumentValueAt(receiver_idx)->Type()->ToCid(); | 
 |   if (receiver_cid != kDynamicCid && receiver_cid != kSentinelCid) { | 
 |     const Class& receiver_class = | 
 |         Class::Handle(Z, isolate_group()->class_table()->At(receiver_cid)); | 
 |     const Function& function = | 
 |         Function::Handle(Z, instr->ResolveForReceiverClass(receiver_class)); | 
 |     if (!function.IsNull()) { | 
 |       const Function& target = Function::ZoneHandle(Z, function.ptr()); | 
 |       StaticCallInstr* call = | 
 |           StaticCallInstr::FromCall(Z, instr, target, instr->CallCount()); | 
 |       instr->ReplaceWith(call, current_iterator()); | 
 |       return; | 
 |     } | 
 |   } | 
 |  | 
 |   // Check for x == y, where x has type T?, there are no subtypes of T, and | 
 |   // T does not override ==. Replace with StrictCompare. | 
 |   if (instr->token_kind() == Token::kEQ || instr->token_kind() == Token::kNE) { | 
 |     GrowableArray<intptr_t> class_ids(6); | 
 |     if (instr->ArgumentValueAt(receiver_idx)->Type()->Specialize(&class_ids)) { | 
 |       bool is_object_eq = true; | 
 |       for (intptr_t i = 0; i < class_ids.length(); i++) { | 
 |         const intptr_t cid = class_ids[i]; | 
 |         // Skip sentinel cid. It may appear in the unreachable code after | 
 |         // inlining a method which doesn't return. | 
 |         if (cid == kSentinelCid) continue; | 
 |         const Class& cls = | 
 |             Class::Handle(Z, isolate_group()->class_table()->At(cid)); | 
 |         const Function& target = | 
 |             Function::Handle(Z, instr->ResolveForReceiverClass(cls)); | 
 |         if (target.recognized_kind() != MethodRecognizer::kObjectEquals) { | 
 |           is_object_eq = false; | 
 |           break; | 
 |         } | 
 |       } | 
 |       if (is_object_eq) { | 
 |         auto* replacement = new (Z) StrictCompareInstr( | 
 |             instr->source(), | 
 |             (instr->token_kind() == Token::kEQ) ? Token::kEQ_STRICT | 
 |                                                 : Token::kNE_STRICT, | 
 |             instr->ArgumentValueAt(0)->CopyWithType(Z), | 
 |             instr->ArgumentValueAt(1)->CopyWithType(Z), | 
 |             /*needs_number_check=*/false, DeoptId::kNone); | 
 |         ReplaceCall(instr, replacement); | 
 |         RefineUseTypes(replacement); | 
 |         return; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   Definition* callee_receiver = instr->ArgumentAt(receiver_idx); | 
 |   const Function& function = flow_graph()->function(); | 
 |   Class& receiver_class = Class::Handle(Z); | 
 |  | 
 |   if (function.IsDynamicFunction() && | 
 |       flow_graph()->IsReceiver(callee_receiver)) { | 
 |     // Call receiver is method receiver. | 
 |     receiver_class = function.Owner(); | 
 |   } else { | 
 |     // Check if we have an non-nullable compile type for the receiver. | 
 |     CompileType* type = instr->ArgumentAt(receiver_idx)->Type(); | 
 |     if (type->ToAbstractType()->IsType() && | 
 |         !type->ToAbstractType()->IsDynamicType() && !type->is_nullable()) { | 
 |       receiver_class = type->ToAbstractType()->type_class(); | 
 |       if (receiver_class.is_implemented()) { | 
 |         receiver_class = Class::null(); | 
 |       } | 
 |     } | 
 |   } | 
 |   if (!receiver_class.IsNull()) { | 
 |     GrowableArray<intptr_t> class_ids(6); | 
 |     if (thread()->compiler_state().cha().ConcreteSubclasses(receiver_class, | 
 |                                                             &class_ids)) { | 
 |       // First check if all subclasses end up calling the same method. | 
 |       // If this is the case we will replace instance call with a direct | 
 |       // static call. | 
 |       // Otherwise we will try to create ICData that contains all possible | 
 |       // targets with appropriate checks. | 
 |       Function& single_target = Function::Handle(Z); | 
 |       ICData& ic_data = ICData::Handle(Z); | 
 |       const Array& args_desc_array = | 
 |           Array::Handle(Z, instr->GetArgumentsDescriptor()); | 
 |       Function& target = Function::Handle(Z); | 
 |       Class& cls = Class::Handle(Z); | 
 |       for (intptr_t i = 0; i < class_ids.length(); i++) { | 
 |         const intptr_t cid = class_ids[i]; | 
 |         cls = isolate_group()->class_table()->At(cid); | 
 |         target = instr->ResolveForReceiverClass(cls); | 
 |         ASSERT(target.IsNull() || !target.IsInvokeFieldDispatcher()); | 
 |         if (target.IsNull()) { | 
 |           single_target = Function::null(); | 
 |           ic_data = ICData::null(); | 
 |           break; | 
 |         } else if (ic_data.IsNull()) { | 
 |           // First we are trying to compute a single target for all subclasses. | 
 |           if (single_target.IsNull()) { | 
 |             ASSERT(i == 0); | 
 |             single_target = target.ptr(); | 
 |             continue; | 
 |           } else if (single_target.ptr() == target.ptr()) { | 
 |             continue; | 
 |           } | 
 |  | 
 |           // The call does not resolve to a single target within the hierarchy. | 
 |           // If we have too many subclasses abort the optimization. | 
 |           if (class_ids.length() > FLAG_max_exhaustive_polymorphic_checks) { | 
 |             single_target = Function::null(); | 
 |             break; | 
 |           } | 
 |  | 
 |           // Create an ICData and map all previously seen classes (< i) to | 
 |           // the computed single_target. | 
 |           ic_data = ICData::New(function, instr->function_name(), | 
 |                                 args_desc_array, DeoptId::kNone, | 
 |                                 /* args_tested = */ 1, ICData::kOptimized); | 
 |           for (intptr_t j = 0; j < i; j++) { | 
 |             ic_data.AddReceiverCheck(class_ids[j], single_target); | 
 |           } | 
 |  | 
 |           single_target = Function::null(); | 
 |         } | 
 |  | 
 |         ASSERT(ic_data.ptr() != ICData::null()); | 
 |         ASSERT(single_target.ptr() == Function::null()); | 
 |         ic_data.AddReceiverCheck(cid, target); | 
 |       } | 
 |  | 
 |       if (single_target.ptr() != Function::null()) { | 
 |         // If this is a getter or setter invocation try inlining it right away | 
 |         // instead of replacing it with a static call. | 
 |         if ((op_kind == Token::kGET) || (op_kind == Token::kSET)) { | 
 |           // Create fake IC data with the resolved target. | 
 |           const ICData& ic_data = ICData::Handle( | 
 |               ICData::New(flow_graph()->function(), instr->function_name(), | 
 |                           args_desc_array, DeoptId::kNone, | 
 |                           /* args_tested = */ 1, ICData::kOptimized)); | 
 |           cls = single_target.Owner(); | 
 |           ic_data.AddReceiverCheck(cls.id(), single_target); | 
 |           instr->set_ic_data(&ic_data); | 
 |  | 
 |           if (TryInlineFieldAccess(instr)) { | 
 |             return; | 
 |           } | 
 |         } | 
 |  | 
 |         // We have computed that there is only a single target for this call | 
 |         // within the whole hierarchy. Replace InstanceCall with StaticCall. | 
 |         const Function& target = Function::ZoneHandle(Z, single_target.ptr()); | 
 |         StaticCallInstr* call = | 
 |             StaticCallInstr::FromCall(Z, instr, target, instr->CallCount()); | 
 |         instr->ReplaceWith(call, current_iterator()); | 
 |         return; | 
 |       } else if ((ic_data.ptr() != ICData::null()) && | 
 |                  !ic_data.NumberOfChecksIs(0)) { | 
 |         const CallTargets* targets = CallTargets::Create(Z, ic_data); | 
 |         ASSERT(!targets->is_empty()); | 
 |         PolymorphicInstanceCallInstr* call = | 
 |             PolymorphicInstanceCallInstr::FromCall(Z, instr, *targets, | 
 |                                                    /* complete = */ true); | 
 |         instr->ReplaceWith(call, current_iterator()); | 
 |         return; | 
 |       } | 
 |     } | 
 |  | 
 |     // Detect if o.m(...) is a call through a getter and expand it | 
 |     // into o.get:m().call(...). | 
 |     if (TryExpandCallThroughGetter(receiver_class, instr)) { | 
 |       return; | 
 |     } | 
 |   } | 
 |  | 
 |   // More than one target. Generate generic polymorphic call without | 
 |   // deoptimization. | 
 |   if (targets.length() > 0) { | 
 |     ASSERT(!FLAG_polymorphic_with_deopt); | 
 |     // OK to use checks with PolymorphicInstanceCallInstr since no | 
 |     // deoptimization is allowed. | 
 |     PolymorphicInstanceCallInstr* call = | 
 |         PolymorphicInstanceCallInstr::FromCall(Z, instr, targets, | 
 |                                                /* complete = */ false); | 
 |     instr->ReplaceWith(call, current_iterator()); | 
 |     return; | 
 |   } | 
 | } | 
 |  | 
 | void AotCallSpecializer::VisitStaticCall(StaticCallInstr* instr) { | 
 |   if (TryInlineFieldAccess(instr)) { | 
 |     return; | 
 |   } | 
 |   CallSpecializer::VisitStaticCall(instr); | 
 | } | 
 |  | 
 | bool AotCallSpecializer::TryExpandCallThroughGetter(const Class& receiver_class, | 
 |                                                     InstanceCallInstr* call) { | 
 |   // If it's an accessor call it can't be a call through getter. | 
 |   if (call->token_kind() == Token::kGET || call->token_kind() == Token::kSET) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   // Ignore callsites like f.call() for now. Those need to be handled | 
 |   // specially if f is a closure. | 
 |   if (call->function_name().ptr() == Symbols::Call().ptr()) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   Function& target = Function::Handle(Z); | 
 |  | 
 |   const String& getter_name = String::ZoneHandle( | 
 |       Z, Symbols::LookupFromGet(thread(), call->function_name())); | 
 |   if (getter_name.IsNull()) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   const Array& args_desc_array = Array::Handle( | 
 |       Z, | 
 |       ArgumentsDescriptor::NewBoxed(/*type_args_len=*/0, /*num_arguments=*/1)); | 
 |   ArgumentsDescriptor args_desc(args_desc_array); | 
 |   target = Resolver::ResolveDynamicForReceiverClass( | 
 |       receiver_class, getter_name, args_desc, /*allow_add=*/false); | 
 |   if (target.ptr() == Function::null() || target.IsMethodExtractor()) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   // We found a getter with the same name as the method this | 
 |   // call tries to invoke. This implies call through getter | 
 |   // because methods can't override getters. Build | 
 |   // o.get:m().call(...) sequence and replace o.m(...) invocation. | 
 |  | 
 |   const intptr_t receiver_idx = call->type_args_len() > 0 ? 1 : 0; | 
 |  | 
 |   InputsArray* get_arguments = new (Z) InputsArray(Z, 1); | 
 |   get_arguments->Add(call->ArgumentValueAt(receiver_idx)->CopyWithType(Z)); | 
 |   InstanceCallInstr* invoke_get = new (Z) | 
 |       InstanceCallInstr(call->source(), getter_name, Token::kGET, get_arguments, | 
 |                         /*type_args_len=*/0, | 
 |                         /*argument_names=*/Object::empty_array(), | 
 |                         /*checked_argument_count=*/1, | 
 |                         thread()->compiler_state().GetNextDeoptId()); | 
 |  | 
 |   // Arguments to the .call() are the same as arguments to the | 
 |   // original call (including type arguments), but receiver | 
 |   // is replaced with the result of the get. | 
 |   InputsArray* call_arguments = new (Z) InputsArray(Z, call->ArgumentCount()); | 
 |   if (call->type_args_len() > 0) { | 
 |     call_arguments->Add(call->ArgumentValueAt(0)->CopyWithType(Z)); | 
 |   } | 
 |   call_arguments->Add(new (Z) Value(invoke_get)); | 
 |   for (intptr_t i = receiver_idx + 1; i < call->ArgumentCount(); i++) { | 
 |     call_arguments->Add(call->ArgumentValueAt(i)->CopyWithType(Z)); | 
 |   } | 
 |  | 
 |   InstanceCallInstr* invoke_call = new (Z) InstanceCallInstr( | 
 |       call->source(), Symbols::Call(), Token::kILLEGAL, call_arguments, | 
 |       call->type_args_len(), call->argument_names(), | 
 |       /*checked_argument_count=*/1, | 
 |       thread()->compiler_state().GetNextDeoptId()); | 
 |  | 
 |   // Create environment and insert 'invoke_get'. | 
 |   Environment* get_env = | 
 |       call->env()->DeepCopy(Z, call->env()->Length() - call->ArgumentCount()); | 
 |   for (intptr_t i = 0, n = invoke_get->ArgumentCount(); i < n; i++) { | 
 |     get_env->PushValue(new (Z) Value(invoke_get->ArgumentAt(i))); | 
 |   } | 
 |   InsertBefore(call, invoke_get, get_env, FlowGraph::kValue); | 
 |  | 
 |   // Replace original call with .call(...) invocation. | 
 |   call->ReplaceWith(invoke_call, current_iterator()); | 
 |  | 
 |   // ReplaceWith moved environment from 'call' to 'invoke_call'. | 
 |   // Update receiver argument in the environment. | 
 |   Environment* invoke_env = invoke_call->env(); | 
 |   invoke_env | 
 |       ->ValueAt(invoke_env->Length() - invoke_call->ArgumentCount() + | 
 |                 receiver_idx) | 
 |       ->BindToEnvironment(invoke_get); | 
 |  | 
 |   // AOT compiler expects all calls to have an ICData. | 
 |   EnsureICData(Z, flow_graph()->function(), invoke_get); | 
 |   EnsureICData(Z, flow_graph()->function(), invoke_call); | 
 |  | 
 |   // Specialize newly inserted calls. | 
 |   TryCreateICData(invoke_get); | 
 |   VisitInstanceCall(invoke_get); | 
 |   TryCreateICData(invoke_call); | 
 |   VisitInstanceCall(invoke_call); | 
 |  | 
 |   // Success. | 
 |   return true; | 
 | } | 
 |  | 
 | void AotCallSpecializer::VisitPolymorphicInstanceCall( | 
 |     PolymorphicInstanceCallInstr* call) { | 
 |   const intptr_t receiver_idx = call->type_args_len() > 0 ? 1 : 0; | 
 |   const intptr_t receiver_cid = | 
 |       call->ArgumentValueAt(receiver_idx)->Type()->ToCid(); | 
 |   if (receiver_cid != kDynamicCid && receiver_cid != kSentinelCid) { | 
 |     const Class& receiver_class = | 
 |         Class::Handle(Z, isolate_group()->class_table()->At(receiver_cid)); | 
 |     const Function& function = | 
 |         Function::ZoneHandle(Z, call->ResolveForReceiverClass(receiver_class)); | 
 |     if (!function.IsNull()) { | 
 |       // Only one target. Replace by static call. | 
 |       StaticCallInstr* new_call = | 
 |           StaticCallInstr::FromCall(Z, call, function, call->CallCount()); | 
 |       call->ReplaceWith(new_call, current_iterator()); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | bool AotCallSpecializer::TryReplaceInstanceOfWithRangeCheck( | 
 |     InstanceCallInstr* call, | 
 |     const AbstractType& type) { | 
 |   if (precompiler_ == NULL) { | 
 |     // Loading not complete, can't do CHA yet. | 
 |     return false; | 
 |   } | 
 |  | 
 |   HierarchyInfo* hi = thread()->hierarchy_info(); | 
 |   if (hi == NULL) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   intptr_t lower_limit, upper_limit; | 
 |   if (!hi->InstanceOfHasClassRange(type, &lower_limit, &upper_limit)) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   Definition* left = call->ArgumentAt(0); | 
 |  | 
 |   // left.instanceof(type) => | 
 |   //     _classRangeCheck(left.cid, lower_limit, upper_limit) | 
 |   LoadClassIdInstr* left_cid = new (Z) LoadClassIdInstr(new (Z) Value(left)); | 
 |   InsertBefore(call, left_cid, NULL, FlowGraph::kValue); | 
 |   ConstantInstr* lower_cid = | 
 |       flow_graph()->GetConstant(Smi::Handle(Z, Smi::New(lower_limit))); | 
 |  | 
 |   if (lower_limit == upper_limit) { | 
 |     StrictCompareInstr* check_cid = new (Z) | 
 |         StrictCompareInstr(call->source(), Token::kEQ_STRICT, | 
 |                            new (Z) Value(left_cid), new (Z) Value(lower_cid), | 
 |                            /* number_check = */ false, DeoptId::kNone); | 
 |     ReplaceCall(call, check_cid); | 
 |     return true; | 
 |   } | 
 |  | 
 |   ConstantInstr* upper_cid = | 
 |       flow_graph()->GetConstant(Smi::Handle(Z, Smi::New(upper_limit))); | 
 |  | 
 |   InputsArray* args = new (Z) InputsArray(Z, 3); | 
 |   args->Add(new (Z) Value(left_cid)); | 
 |   args->Add(new (Z) Value(lower_cid)); | 
 |   args->Add(new (Z) Value(upper_cid)); | 
 |  | 
 |   const Library& dart_internal = Library::Handle(Z, Library::InternalLibrary()); | 
 |   const String& target_name = Symbols::_classRangeCheck(); | 
 |   const Function& target = Function::ZoneHandle( | 
 |       Z, dart_internal.LookupFunctionAllowPrivate(target_name)); | 
 |   ASSERT(!target.IsNull()); | 
 |   ASSERT(target.IsRecognized()); | 
 |   ASSERT(FlowGraphInliner::FunctionHasPreferInlinePragma(target)); | 
 |  | 
 |   const intptr_t kTypeArgsLen = 0; | 
 |   StaticCallInstr* new_call = new (Z) StaticCallInstr( | 
 |       call->source(), target, kTypeArgsLen, | 
 |       Object::null_array(),  // argument_names | 
 |       args, call->deopt_id(), call->CallCount(), ICData::kOptimized); | 
 |   Environment* copy = | 
 |       call->env()->DeepCopy(Z, call->env()->Length() - call->ArgumentCount()); | 
 |   for (intptr_t i = 0; i < args->length(); ++i) { | 
 |     copy->PushValue(new (Z) Value(new_call->ArgumentAt(i))); | 
 |   } | 
 |   call->RemoveEnvironment(); | 
 |   ReplaceCall(call, new_call); | 
 |   copy->DeepCopyTo(Z, new_call); | 
 |   return true; | 
 | } | 
 |  | 
 | void AotCallSpecializer::ReplaceInstanceCallsWithDispatchTableCalls() { | 
 |   ASSERT(current_iterator_ == nullptr); | 
 |   for (BlockIterator block_it = flow_graph()->reverse_postorder_iterator(); | 
 |        !block_it.Done(); block_it.Advance()) { | 
 |     ForwardInstructionIterator it(block_it.Current()); | 
 |     current_iterator_ = ⁢ | 
 |     for (; !it.Done(); it.Advance()) { | 
 |       Instruction* instr = it.Current(); | 
 |       if (auto call = instr->AsInstanceCall()) { | 
 |         TryReplaceWithDispatchTableCall(call); | 
 |       } else if (auto call = instr->AsPolymorphicInstanceCall()) { | 
 |         TryReplaceWithDispatchTableCall(call); | 
 |       } | 
 |     } | 
 |     current_iterator_ = nullptr; | 
 |   } | 
 | } | 
 |  | 
 | const Function& AotCallSpecializer::InterfaceTargetForTableDispatch( | 
 |     InstanceCallBaseInstr* call) { | 
 |   const Function& interface_target = call->interface_target(); | 
 |   if (!interface_target.IsNull()) { | 
 |     return interface_target; | 
 |   } | 
 |  | 
 |   // Dynamic call or tearoff. | 
 |   const Function& tearoff_interface_target = call->tearoff_interface_target(); | 
 |   if (!tearoff_interface_target.IsNull()) { | 
 |     // Tearoff. | 
 |     return Function::ZoneHandle( | 
 |         Z, tearoff_interface_target.GetMethodExtractor(call->function_name())); | 
 |   } | 
 |  | 
 |   // Dynamic call. | 
 |   return Function::null_function(); | 
 | } | 
 |  | 
 | void AotCallSpecializer::TryReplaceWithDispatchTableCall( | 
 |     InstanceCallBaseInstr* call) { | 
 |   const Function& interface_target = InterfaceTargetForTableDispatch(call); | 
 |   if (interface_target.IsNull()) { | 
 |     // Dynamic call. | 
 |     return; | 
 |   } | 
 |  | 
 |   Value* receiver = call->ArgumentValueAt(call->FirstArgIndex()); | 
 |   const compiler::TableSelector* selector = | 
 |       precompiler_->selector_map()->GetSelector(interface_target); | 
 |  | 
 |   if (selector == nullptr) { | 
 |     // Target functions were removed by tree shaking. This call is dead code, | 
 |     // or the receiver is always null. | 
 | #if defined(DEBUG) | 
 |     AddCheckNull(receiver->CopyWithType(Z), call->function_name(), | 
 |                  DeoptId::kNone, call->env(), call); | 
 |     StopInstr* stop = new (Z) StopInstr("Dead instance call executed."); | 
 |     InsertBefore(call, stop, call->env(), FlowGraph::kEffect); | 
 | #endif | 
 |     return; | 
 |   } | 
 |  | 
 |   const bool receiver_can_be_smi = | 
 |       call->CanReceiverBeSmiBasedOnInterfaceTarget(zone()); | 
 |   auto load_cid = new (Z) LoadClassIdInstr(receiver->CopyWithType(Z), kUntagged, | 
 |                                            receiver_can_be_smi); | 
 |   InsertBefore(call, load_cid, call->env(), FlowGraph::kValue); | 
 |  | 
 |   auto dispatch_table_call = DispatchTableCallInstr::FromCall( | 
 |       Z, call, new (Z) Value(load_cid), interface_target, selector); | 
 |   call->ReplaceWith(dispatch_table_call, current_iterator()); | 
 | } | 
 |  | 
 | #endif  // DART_PRECOMPILER | 
 |  | 
 | }  // namespace dart |