4bf6de784c
This change extends EqualityCompare IL instruction to handle case when operands are nullable ints. Unlike CheckedSmiComparison, EqualityCompare performs comparison without making a dynamic call on a slow path. Dynamic call in CheckedSmiComparison is a problem because it is not present in the original program and not properly tracked in TFA and precompiler, so precompiler is not able to drop Code objects (needed for dynamic calls) reliably. This change removes the remaining case when CheckedSmiComparison instruction was used. The cleanup will follow in a separate CL. TEST=ci Issue: https://github.com/dart-lang/sdk/issues/32166 Issue: https://github.com/dart-lang/sdk/issues/44852 Change-Id: I8114a2616becf632462d4cb3bfc255691f24e1ef Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/193447 Reviewed-by: Ryan Macnak <rmacnak@google.com> Commit-Queue: Alexander Markov <alexmarkov@google.com>
1211 lines
44 KiB
C++
1211 lines
44 KiB
C++
// 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) {
|
|
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 == kNeverCid) 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) {
|
|
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
|