| // 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 (left_type->IsNull() || left_type->IsNullableSmi() || |
| right_type->IsNull() || right_type->IsNullableSmi()) { |
| 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 { |
| const bool null_aware = |
| left_type->is_nullable() || right_type->is_nullable(); |
| replacement = new (Z) EqualityCompareInstr( |
| instr->source(), op_kind, left_value->CopyWithType(Z), |
| right_value->CopyWithType(Z), kMintCid, DeoptId::kNone, |
| null_aware, 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_X64) && !defined(TARGET_ARCH_ARM64) |
| // 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->IsDouble() && right_type->IsDouble()) { |
| 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: { |
| if (op_kind == Token::kDIV && |
| !FlowGraphCompiler::SupportsHardwareDivision()) { |
| return false; |
| } |
| 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 |