// 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/backend/il.h" #include "vm/bit_vector.h" #include "vm/bootstrap.h" #include "vm/compiler/aot/dispatch_table_generator.h" #include "vm/compiler/backend/code_statistics.h" #include "vm/compiler/backend/constant_propagator.h" #include "vm/compiler/backend/evaluator.h" #include "vm/compiler/backend/flow_graph_compiler.h" #include "vm/compiler/backend/linearscan.h" #include "vm/compiler/backend/locations.h" #include "vm/compiler/backend/loops.h" #include "vm/compiler/backend/range_analysis.h" #include "vm/compiler/ffi/frame_rebase.h" #include "vm/compiler/ffi/marshaller.h" #include "vm/compiler/ffi/native_calling_convention.h" #include "vm/compiler/ffi/native_location.h" #include "vm/compiler/ffi/native_type.h" #include "vm/compiler/frontend/flow_graph_builder.h" #include "vm/compiler/frontend/kernel_translation_helper.h" #include "vm/compiler/jit/compiler.h" #include "vm/compiler/method_recognizer.h" #include "vm/cpu.h" #include "vm/dart_entry.h" #include "vm/object.h" #include "vm/object_store.h" #include "vm/os.h" #include "vm/regexp_assembler_ir.h" #include "vm/resolver.h" #include "vm/runtime_entry.h" #include "vm/scopes.h" #include "vm/stack_frame.h" #include "vm/stub_code.h" #include "vm/symbols.h" #include "vm/type_testing_stubs.h" #include "vm/compiler/backend/il_printer.h" namespace dart { DEFINE_FLAG(bool, propagate_ic_data, true, "Propagate IC data from unoptimized to optimized IC calls."); DEFINE_FLAG(bool, two_args_smi_icd, true, "Generate special IC stubs for two args Smi operations"); class SubclassFinder { public: SubclassFinder(Zone* zone, GrowableArray* cids, bool include_abstract) : array_handles_(zone), class_handles_(zone), cids_(cids), include_abstract_(include_abstract) {} void ScanSubClasses(const Class& klass) { if (include_abstract_ || !klass.is_abstract()) { cids_->Add(klass.id()); } ScopedHandle array(&array_handles_); ScopedHandle subclass(&class_handles_); *array = klass.direct_subclasses(); if (!array->IsNull()) { for (intptr_t i = 0; i < array->Length(); ++i) { *subclass ^= array->At(i); ScanSubClasses(*subclass); } } } void ScanImplementorClasses(const Class& klass) { // An implementor of [klass] is // * the [klass] itself. // * all implementors of the direct subclasses of [klass]. // * all implementors of the direct implementors of [klass]. if (include_abstract_ || !klass.is_abstract()) { cids_->Add(klass.id()); } ScopedHandle array(&array_handles_); ScopedHandle subclass_or_implementor(&class_handles_); *array = klass.direct_subclasses(); if (!array->IsNull()) { for (intptr_t i = 0; i < array->Length(); ++i) { *subclass_or_implementor ^= (*array).At(i); ScanImplementorClasses(*subclass_or_implementor); } } *array = klass.direct_implementors(); if (!array->IsNull()) { for (intptr_t i = 0; i < array->Length(); ++i) { *subclass_or_implementor ^= (*array).At(i); ScanImplementorClasses(*subclass_or_implementor); } } } private: ReusableHandleStack array_handles_; ReusableHandleStack class_handles_; GrowableArray* cids_; const bool include_abstract_; }; const CidRangeVector& HierarchyInfo::SubtypeRangesForClass( const Class& klass, bool include_abstract, bool exclude_null) { ClassTable* table = thread()->isolate_group()->class_table(); const intptr_t cid_count = table->NumCids(); std::unique_ptr* cid_ranges = nullptr; if (include_abstract) { cid_ranges = exclude_null ? &cid_subtype_ranges_abstract_nonnullable_ : &cid_subtype_ranges_abstract_nullable_; } else { cid_ranges = exclude_null ? &cid_subtype_ranges_nonnullable_ : &cid_subtype_ranges_nullable_; } if (*cid_ranges == nullptr) { cid_ranges->reset(new CidRangeVector[cid_count]); } CidRangeVector& ranges = (*cid_ranges)[klass.id()]; if (ranges.length() == 0) { if (!FLAG_precompiled_mode) { BuildRangesForJIT(table, &ranges, klass, /*use_subtype_test=*/true, include_abstract, exclude_null); } else { BuildRangesFor(table, &ranges, klass, /*use_subtype_test=*/true, include_abstract, exclude_null); } } return ranges; } const CidRangeVector& HierarchyInfo::SubclassRangesForClass( const Class& klass) { ClassTable* table = thread()->isolate_group()->class_table(); const intptr_t cid_count = table->NumCids(); if (cid_subclass_ranges_ == nullptr) { cid_subclass_ranges_.reset(new CidRangeVector[cid_count]); } CidRangeVector& ranges = cid_subclass_ranges_[klass.id()]; if (ranges.length() == 0) { if (!FLAG_precompiled_mode) { BuildRangesForJIT(table, &ranges, klass, /*use_subtype_test=*/true, /*include_abstract=*/false, /*exclude_null=*/false); } else { BuildRangesFor(table, &ranges, klass, /*use_subtype_test=*/false, /*include_abstract=*/false, /*exclude_null=*/false); } } return ranges; } // Build the ranges either for: // " as ", or // " is " void HierarchyInfo::BuildRangesFor(ClassTable* table, CidRangeVector* ranges, const Class& klass, bool use_subtype_test, bool include_abstract, bool exclude_null) { Zone* zone = thread()->zone(); ClassTable* class_table = thread()->isolate_group()->class_table(); // Only really used if `use_subtype_test == true`. const Type& dst_type = Type::Handle(zone, Type::RawCast(klass.RareType())); AbstractType& cls_type = AbstractType::Handle(zone); Class& cls = Class::Handle(zone); AbstractType& super_type = AbstractType::Handle(zone); const intptr_t cid_count = table->NumCids(); // Iterate over all cids to find the ones to be included in the ranges. intptr_t start = -1; intptr_t end = -1; for (intptr_t cid = kInstanceCid; cid < cid_count; ++cid) { // Create local zone because deep hierarchies may allocate lots of handles // within one iteration of this loop. StackZone stack_zone(thread()); HANDLESCOPE(thread()); // Some cases are "don't care", i.e., they may or may not be included, // whatever yields the least number of ranges for efficiency. if (!table->HasValidClassAt(cid)) continue; if (cid == kTypeArgumentsCid) continue; if (cid == kVoidCid) continue; if (cid == kDynamicCid) continue; if (cid == kNeverCid) continue; if (cid == kNullCid && !exclude_null) continue; cls = table->At(cid); if (!include_abstract && cls.is_abstract()) continue; if (cls.IsTopLevel()) continue; // We are either interested in [CidRange]es of subclasses or subtypes. bool test_succeeded = false; if (cid == kNullCid) { ASSERT(exclude_null); test_succeeded = false; } else if (use_subtype_test) { cls_type = cls.RareType(); test_succeeded = cls_type.IsSubtypeOf(dst_type, Heap::kNew); } else { while (!cls.IsObjectClass()) { if (cls.ptr() == klass.ptr()) { test_succeeded = true; break; } super_type = cls.super_type(); const intptr_t type_class_id = super_type.type_class_id(); cls = class_table->At(type_class_id); } } if (test_succeeded) { // On success, open a new or continue any open range. if (start == -1) start = cid; end = cid; } else if (start != -1) { // On failure, close any open range from start to end // (the latter is the most recent succesful "do-care" cid). ASSERT(start <= end); CidRange range(start, end); ranges->Add(range); start = -1; end = -1; } } // Construct last range (either close open one, or add invalid). if (start != -1) { ASSERT(start <= end); CidRange range(start, end); ranges->Add(range); } else if (ranges->length() == 0) { CidRange range; ASSERT(range.IsIllegalRange()); ranges->Add(range); } } void HierarchyInfo::BuildRangesForJIT(ClassTable* table, CidRangeVector* ranges, const Class& dst_klass, bool use_subtype_test, bool include_abstract, bool exclude_null) { if (dst_klass.InVMIsolateHeap()) { BuildRangesFor(table, ranges, dst_klass, use_subtype_test, include_abstract, exclude_null); return; } Zone* zone = thread()->zone(); GrowableArray cids; SubclassFinder finder(zone, &cids, include_abstract); { SafepointReadRwLocker ml(thread(), thread()->isolate_group()->program_lock()); if (use_subtype_test) { finder.ScanImplementorClasses(dst_klass); } else { finder.ScanSubClasses(dst_klass); } } // Sort all collected cids. intptr_t* cids_array = cids.data(); qsort(cids_array, cids.length(), sizeof(intptr_t), [](const void* a, const void* b) { // MSAN seems unaware of allocations inside qsort. The linker flag // -fsanitize=memory should give us a MSAN-aware version of libc... MSAN_UNPOISON(static_cast(a), sizeof(intptr_t)); MSAN_UNPOISON(static_cast(b), sizeof(intptr_t)); return static_cast(*static_cast(a) - *static_cast(b)); }); // Build ranges of all the cids. Class& klass = Class::Handle(); intptr_t left_cid = -1; intptr_t last_cid = -1; for (intptr_t i = 0; i < cids.length(); ++i) { if (left_cid == -1) { left_cid = last_cid = cids[i]; } else { const intptr_t current_cid = cids[i]; // Skip duplicates. if (current_cid == last_cid) continue; // Consecutive numbers cids are ok. if (current_cid == (last_cid + 1)) { last_cid = current_cid; } else { // We sorted, after all! RELEASE_ASSERT(last_cid < current_cid); intptr_t j = last_cid + 1; for (; j < current_cid; ++j) { if (table->HasValidClassAt(j)) { klass = table->At(j); if (!klass.IsTopLevel()) { // If we care about abstract classes also, we cannot skip over any // arbitrary abstract class, only those which are subtypes. if (include_abstract) { break; } // If the class is concrete we cannot skip over it. if (!klass.is_abstract()) { break; } } } } if (current_cid == j) { // If there's only abstract cids between [last_cid] and the // [current_cid] then we connect them. last_cid = current_cid; } else { // Finish the current open cid range and start a new one. ranges->Add(CidRange{left_cid, last_cid}); left_cid = last_cid = current_cid; } } } } // If there is an open cid-range which we haven't finished yet, we'll // complete it. if (left_cid != -1) { ranges->Add(CidRange{left_cid, last_cid}); } } bool HierarchyInfo::CanUseSubtypeRangeCheckFor(const AbstractType& type) { ASSERT(type.IsFinalized()); if (!type.IsInstantiated() || !type.IsType() || type.IsDartFunctionType()) { return false; } // The FutureOr type cannot be handled by checking whether the instance is // a subtype of FutureOr and then checking whether the type argument `T` // matches. // // Instead we would need to perform multiple checks: // // instance is Null || instance is T || instance is Future // if (type.IsFutureOrType()) { return false; } Zone* zone = thread()->zone(); const Class& type_class = Class::Handle(zone, type.type_class()); // We can use class id range checks only if we don't have to test type // arguments. // // This is e.g. true for "String" but also for "List". (A type for // which the type arguments vector is filled with "dynamic" is known as a rare // type) if (type_class.IsGeneric()) { // TODO(kustermann): We might want to consider extending this when the type // arguments are not "dynamic" but instantiated-to-bounds. const Type& rare_type = Type::Handle(zone, Type::RawCast(type_class.RareType())); if (!rare_type.IsSubtypeOf(type, Heap::kNew)) { ASSERT(type.arguments() != TypeArguments::null()); return false; } } return true; } bool HierarchyInfo::CanUseGenericSubtypeRangeCheckFor( const AbstractType& type) { ASSERT(type.IsFinalized()); if (!type.IsType() || type.IsDartFunctionType()) { return false; } // The FutureOr type cannot be handled by checking whether the instance is // a subtype of FutureOr and then checking whether the type argument `T` // matches. // // Instead we would need to perform multiple checks: // // instance is Null || instance is T || instance is Future // if (type.IsFutureOrType()) { return false; } // NOTE: We do allow non-instantiated types here (in comparison to // [CanUseSubtypeRangeCheckFor], since we handle type parameters in the type // expression in some cases (see below). Zone* zone = thread()->zone(); const Class& type_class = Class::Handle(zone, type.type_class()); const intptr_t num_type_parameters = type_class.NumTypeParameters(); const intptr_t num_type_arguments = type_class.NumTypeArguments(); // This function should only be called for generic classes. ASSERT(type_class.NumTypeParameters() > 0 && type.arguments() != TypeArguments::null()); // If the type class is implemented the different implementations might have // their type argument vector stored at different offsets and we can therefore // not perform our optimized [CidRange]-based implementation. // // TODO(kustermann): If the class is implemented but all implementations // store the instantator type argument vector at the same offset we can // still do it! if (type_class.is_implemented()) { return false; } const TypeArguments& ta = TypeArguments::Handle(zone, Type::Cast(type).arguments()); ASSERT(ta.Length() == num_type_arguments); // The last [num_type_pararameters] entries in the [TypeArguments] vector [ta] // are the values we have to check against. Ensure we can handle all of them // via [CidRange]-based checks or that it is a type parameter. AbstractType& type_arg = AbstractType::Handle(zone); for (intptr_t i = 0; i < num_type_parameters; ++i) { type_arg = ta.TypeAt(num_type_arguments - num_type_parameters + i); if (!CanUseSubtypeRangeCheckFor(type_arg) && !type_arg.IsTypeParameter()) { return false; } } return true; } bool HierarchyInfo::InstanceOfHasClassRange(const AbstractType& type, intptr_t* lower_limit, intptr_t* upper_limit) { ASSERT(CompilerState::Current().is_aot()); if (type.IsNullable()) { // 'is' test for nullable types should accept null cid in addition to the // class range. In most cases it is not possible to extend class range to // include kNullCid. return false; } if (CanUseSubtypeRangeCheckFor(type)) { const Class& type_class = Class::Handle(thread()->zone(), type.type_class()); const CidRangeVector& ranges = SubtypeRangesForClass(type_class, /*include_abstract=*/false, /*exclude_null=*/true); if (ranges.length() == 1) { const CidRangeValue& range = ranges[0]; if (!range.IsIllegalRange()) { *lower_limit = range.cid_start; *upper_limit = range.cid_end; return true; } } } return false; } // The set of supported non-integer unboxed representations. // Format: (unboxed representations suffix, boxed class type) #define FOR_EACH_NON_INT_BOXED_REPRESENTATION(M) \ M(Double, Double) \ M(Float, Double) \ M(Float32x4, Float32x4) \ M(Float64x2, Float64x2) \ M(Int32x4, Int32x4) #define BOXING_IN_SET_CASE(unboxed, boxed) \ case kUnboxed##unboxed: \ return true; #define BOXING_VALUE_OFFSET_CASE(unboxed, boxed) \ case kUnboxed##unboxed: \ return compiler::target::boxed::value_offset(); #define BOXING_CID_CASE(unboxed, boxed) \ case kUnboxed##unboxed: \ return k##boxed##Cid; bool Boxing::Supports(Representation rep) { if (RepresentationUtils::IsUnboxedInteger(rep)) { return true; } switch (rep) { FOR_EACH_NON_INT_BOXED_REPRESENTATION(BOXING_IN_SET_CASE) default: return false; } } bool Boxing::RequiresAllocation(Representation rep) { if (RepresentationUtils::IsUnboxedInteger(rep)) { return (kBitsPerByte * RepresentationUtils::ValueSize(rep)) > compiler::target::kSmiBits; } return true; } intptr_t Boxing::ValueOffset(Representation rep) { if (RepresentationUtils::IsUnboxedInteger(rep) && Boxing::RequiresAllocation(rep) && RepresentationUtils::ValueSize(rep) <= sizeof(int64_t)) { return compiler::target::Mint::value_offset(); } switch (rep) { FOR_EACH_NON_INT_BOXED_REPRESENTATION(BOXING_VALUE_OFFSET_CASE) default: UNREACHABLE(); return 0; } } // Note that not all boxes require allocation (e.g., Smis). intptr_t Boxing::BoxCid(Representation rep) { if (RepresentationUtils::IsUnboxedInteger(rep)) { if (!Boxing::RequiresAllocation(rep)) { return kSmiCid; } else if (RepresentationUtils::ValueSize(rep) <= sizeof(int64_t)) { return kMintCid; } } switch (rep) { FOR_EACH_NON_INT_BOXED_REPRESENTATION(BOXING_CID_CASE) default: UNREACHABLE(); return kIllegalCid; } } #undef BOXING_CID_CASE #undef BOXING_VALUE_OFFSET_CASE #undef BOXING_IN_SET_CASE #undef FOR_EACH_NON_INT_BOXED_REPRESENTATION #if defined(DEBUG) void Instruction::CheckField(const Field& field) const { ASSERT(field.IsZoneHandle()); ASSERT(!Compiler::IsBackgroundCompilation() || !field.IsOriginal()); } #endif // DEBUG // A value in the constant propagation lattice. // - non-constant sentinel // - a constant (any non-sentinel value) // - unknown sentinel Object& Definition::constant_value() { if (constant_value_ == NULL) { constant_value_ = &Object::ZoneHandle(ConstantPropagator::Unknown()); } return *constant_value_; } Definition* Definition::OriginalDefinition() { Definition* defn = this; Value* unwrapped; while ((unwrapped = defn->RedefinedValue()) != nullptr) { defn = unwrapped->definition(); } return defn; } Value* Definition::RedefinedValue() const { return nullptr; } Value* RedefinitionInstr::RedefinedValue() const { return value(); } Value* AssertAssignableInstr::RedefinedValue() const { return value(); } Value* AssertBooleanInstr::RedefinedValue() const { return value(); } Value* CheckBoundBase::RedefinedValue() const { return index(); } Value* CheckNullInstr::RedefinedValue() const { return value(); } Definition* Definition::OriginalDefinitionIgnoreBoxingAndConstraints() { Definition* def = this; while (true) { Definition* orig; if (def->IsConstraint() || def->IsBox() || def->IsUnbox() || def->IsIntConverter()) { orig = def->InputAt(0)->definition(); } else { orig = def->OriginalDefinition(); } if (orig == def) return def; def = orig; } } bool Definition::IsArrayLength(Definition* def) { if (def != nullptr) { if (auto load = def->OriginalDefinitionIgnoreBoxingAndConstraints() ->AsLoadField()) { return load->IsImmutableLengthLoad(); } } return false; } const ICData* Instruction::GetICData( const ZoneGrowableArray& ic_data_array, intptr_t deopt_id, bool is_static_call) { // The deopt_id can be outside the range of the IC data array for // computations added in the optimizing compiler. ASSERT(deopt_id != DeoptId::kNone); if (deopt_id >= ic_data_array.length()) { return nullptr; } const ICData* result = ic_data_array[deopt_id]; ASSERT(result == nullptr || is_static_call == result->is_static_call()); return result; } uword Instruction::Hash() const { uword result = tag(); for (intptr_t i = 0; i < InputCount(); ++i) { Value* value = InputAt(i); result = CombineHashes(result, value->definition()->ssa_temp_index()); } return FinalizeHash(result, kBitsPerInt32 - 1); } bool Instruction::Equals(const Instruction& other) const { if (tag() != other.tag()) return false; if (InputCount() != other.InputCount()) return false; for (intptr_t i = 0; i < InputCount(); ++i) { if (!InputAt(i)->Equals(*other.InputAt(i))) return false; } return AttributesEqual(other); } void Instruction::Unsupported(FlowGraphCompiler* compiler) { compiler->Bailout(ToCString()); UNREACHABLE(); } bool Value::Equals(const Value& other) const { return definition() == other.definition(); } static int OrderById(CidRange* const* a, CidRange* const* b) { // Negative if 'a' should sort before 'b'. ASSERT((*a)->IsSingleCid()); ASSERT((*b)->IsSingleCid()); return (*a)->cid_start - (*b)->cid_start; } static int OrderByFrequencyThenId(CidRange* const* a, CidRange* const* b) { const TargetInfo* target_info_a = static_cast(*a); const TargetInfo* target_info_b = static_cast(*b); // Negative if 'a' should sort before 'b'. if (target_info_b->count != target_info_a->count) { return (target_info_b->count - target_info_a->count); } else { return (*a)->cid_start - (*b)->cid_start; } } bool Cids::Equals(const Cids& other) const { if (length() != other.length()) return false; for (int i = 0; i < length(); i++) { if (cid_ranges_[i]->cid_start != other.cid_ranges_[i]->cid_start || cid_ranges_[i]->cid_end != other.cid_ranges_[i]->cid_end) { return false; } } return true; } intptr_t Cids::ComputeLowestCid() const { intptr_t min = kIntptrMax; for (intptr_t i = 0; i < cid_ranges_.length(); ++i) { min = Utils::Minimum(min, cid_ranges_[i]->cid_start); } return min; } intptr_t Cids::ComputeHighestCid() const { intptr_t max = -1; for (intptr_t i = 0; i < cid_ranges_.length(); ++i) { max = Utils::Maximum(max, cid_ranges_[i]->cid_end); } return max; } bool Cids::HasClassId(intptr_t cid) const { for (int i = 0; i < length(); i++) { if (cid_ranges_[i]->Contains(cid)) { return true; } } return false; } Cids* Cids::CreateMonomorphic(Zone* zone, intptr_t cid) { Cids* cids = new (zone) Cids(zone); cids->Add(new (zone) CidRange(cid, cid)); return cids; } Cids* Cids::CreateForArgument(Zone* zone, const BinaryFeedback& binary_feedback, int argument_number) { Cids* cids = new (zone) Cids(zone); for (intptr_t i = 0; i < binary_feedback.feedback_.length(); i++) { ASSERT((argument_number == 0) || (argument_number == 1)); const intptr_t cid = argument_number == 0 ? binary_feedback.feedback_[i].first : binary_feedback.feedback_[i].second; cids->Add(new (zone) CidRange(cid, cid)); } if (cids->length() != 0) { cids->Sort(OrderById); // Merge adjacent class id ranges. int dest = 0; for (int src = 1; src < cids->length(); src++) { if (cids->cid_ranges_[dest]->cid_end + 1 >= cids->cid_ranges_[src]->cid_start) { cids->cid_ranges_[dest]->cid_end = cids->cid_ranges_[src]->cid_end; } else { dest++; if (src != dest) cids->cid_ranges_[dest] = cids->cid_ranges_[src]; } } cids->SetLength(dest + 1); } return cids; } static intptr_t Usage(const Function& function) { intptr_t count = function.usage_counter(); if (count < 0) { if (function.HasCode()) { // 'function' is queued for optimized compilation count = FLAG_optimization_counter_threshold; } else { count = 0; } } else if (Code::IsOptimized(function.CurrentCode())) { // 'function' was optimized and stopped counting count = FLAG_optimization_counter_threshold; } return count; } void CallTargets::CreateHelper(Zone* zone, const ICData& ic_data) { Function& dummy = Function::Handle(zone); const intptr_t num_args_tested = ic_data.NumArgsTested(); for (int i = 0, n = ic_data.NumberOfChecks(); i < n; i++) { if (ic_data.GetCountAt(i) == 0) { continue; } intptr_t id = kDynamicCid; if (num_args_tested == 0) { } else if (num_args_tested == 1) { ic_data.GetOneClassCheckAt(i, &id, &dummy); } else { ASSERT(num_args_tested == 2); GrowableArray arg_ids; ic_data.GetCheckAt(i, &arg_ids, &dummy); id = arg_ids[0]; } Function& function = Function::ZoneHandle(zone, ic_data.GetTargetAt(i)); intptr_t count = ic_data.GetCountAt(i); cid_ranges_.Add(new (zone) TargetInfo(id, id, &function, count, ic_data.GetExactnessAt(i))); } if (ic_data.is_megamorphic()) { ASSERT(num_args_tested == 1); // Only 1-arg ICData will turn megamorphic. const String& name = String::Handle(zone, ic_data.target_name()); const Array& descriptor = Array::Handle(zone, ic_data.arguments_descriptor()); Thread* thread = Thread::Current(); const auto& cache = MegamorphicCache::Handle( zone, MegamorphicCacheTable::Lookup(thread, name, descriptor)); { SafepointMutexLocker ml(thread->isolate_group()->type_feedback_mutex()); MegamorphicCacheEntries entries(Array::Handle(zone, cache.buckets())); for (intptr_t i = 0, n = entries.Length(); i < n; i++) { const intptr_t id = Smi::Value(entries[i].Get()); if (id == kIllegalCid) { continue; } Function& function = Function::ZoneHandle(zone); function ^= entries[i].Get(); const intptr_t filled_entry_count = cache.filled_entry_count(); ASSERT(filled_entry_count > 0); cid_ranges_.Add(new (zone) TargetInfo( id, id, &function, Usage(function) / filled_entry_count, StaticTypeExactnessState::NotTracking())); } } } } bool Cids::IsMonomorphic() const { if (length() != 1) return false; return cid_ranges_[0]->IsSingleCid(); } intptr_t Cids::MonomorphicReceiverCid() const { ASSERT(IsMonomorphic()); return cid_ranges_[0]->cid_start; } StaticTypeExactnessState CallTargets::MonomorphicExactness() const { ASSERT(IsMonomorphic()); return TargetAt(0)->exactness; } const char* AssertAssignableInstr::KindToCString(Kind kind) { switch (kind) { #define KIND_CASE(name) \ case k##name: \ return #name; FOR_EACH_ASSERT_ASSIGNABLE_KIND(KIND_CASE) #undef KIND_CASE default: UNREACHABLE(); return nullptr; } } bool AssertAssignableInstr::ParseKind(const char* str, Kind* out) { #define KIND_CASE(name) \ if (strcmp(str, #name) == 0) { \ *out = Kind::k##name; \ return true; \ } FOR_EACH_ASSERT_ASSIGNABLE_KIND(KIND_CASE) #undef KIND_CASE return false; } CheckClassInstr::CheckClassInstr(Value* value, intptr_t deopt_id, const Cids& cids, const InstructionSource& source) : TemplateInstruction(source, deopt_id), cids_(cids), licm_hoisted_(false), is_bit_test_(IsCompactCidRange(cids)), token_pos_(source.token_pos) { // Expected useful check data. const intptr_t number_of_checks = cids.length(); ASSERT(number_of_checks > 0); SetInputAt(0, value); // Otherwise use CheckSmiInstr. ASSERT(number_of_checks != 1 || !cids[0].IsSingleCid() || cids[0].cid_start != kSmiCid); } bool CheckClassInstr::AttributesEqual(const Instruction& other) const { auto const other_check = other.AsCheckClass(); ASSERT(other_check != NULL); return cids().Equals(other_check->cids()); } bool CheckClassInstr::IsDeoptIfNull() const { if (!cids().IsMonomorphic()) { return false; } CompileType* in_type = value()->Type(); const intptr_t cid = cids().MonomorphicReceiverCid(); // Performance check: use CheckSmiInstr instead. ASSERT(cid != kSmiCid); return in_type->is_nullable() && (in_type->ToNullableCid() == cid); } // Null object is a singleton of null-class (except for some sentinel, // transitional temporaries). Instead of checking against the null class only // we can check against null instance instead. bool CheckClassInstr::IsDeoptIfNotNull() const { if (!cids().IsMonomorphic()) { return false; } const intptr_t cid = cids().MonomorphicReceiverCid(); return cid == kNullCid; } bool CheckClassInstr::IsCompactCidRange(const Cids& cids) { const intptr_t number_of_checks = cids.length(); // If there are only two checks, the extra register pressure needed for the // dense-cid-range code is not justified. if (number_of_checks <= 2) return false; // TODO(fschneider): Support smis in dense cid checks. if (cids.HasClassId(kSmiCid)) return false; intptr_t min = cids.ComputeLowestCid(); intptr_t max = cids.ComputeHighestCid(); return (max - min) < compiler::target::kBitsPerWord; } bool CheckClassInstr::IsBitTest() const { return is_bit_test_; } intptr_t CheckClassInstr::ComputeCidMask() const { ASSERT(IsBitTest()); const uintptr_t one = 1; intptr_t min = cids_.ComputeLowestCid(); intptr_t mask = 0; for (intptr_t i = 0; i < cids_.length(); ++i) { uintptr_t run; uintptr_t range = one + cids_[i].Extent(); if (range >= static_cast(compiler::target::kBitsPerWord)) { run = -1; } else { run = (one << range) - 1; } mask |= run << (cids_[i].cid_start - min); } return mask; } bool LoadFieldInstr::IsUnboxedDartFieldLoad() const { return slot().representation() == kTagged && slot().IsDartField() && FlowGraphCompiler::IsUnboxedField(slot().field()); } bool LoadFieldInstr::IsPotentialUnboxedDartFieldLoad() const { return slot().representation() == kTagged && slot().IsDartField() && FlowGraphCompiler::IsPotentialUnboxedField(slot().field()); } Representation LoadFieldInstr::representation() const { if (slot().representation() != kTagged) { return slot().representation(); } else if (IsUnboxedDartFieldLoad()) { const Field& field = slot().field(); const intptr_t cid = field.UnboxedFieldCid(); switch (cid) { case kDoubleCid: return kUnboxedDouble; case kFloat32x4Cid: return kUnboxedFloat32x4; case kFloat64x2Cid: return kUnboxedFloat64x2; default: UNREACHABLE(); break; } } return kTagged; } AllocateUninitializedContextInstr::AllocateUninitializedContextInstr( const InstructionSource& source, intptr_t num_context_variables, intptr_t deopt_id) : TemplateAllocation(source, deopt_id), num_context_variables_(num_context_variables) { // This instruction is not used in AOT for code size reasons. ASSERT(!CompilerState::Current().is_aot()); } LocationSummary* AllocateTypedDataInstr::MakeLocationSummary(Zone* zone, bool opt) const { const intptr_t kNumInputs = 1; const intptr_t kNumTemps = 0; LocationSummary* locs = new (zone) LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kCall); locs->set_in(kLengthPos, Location::RegisterLocation( AllocateTypedDataArrayABI::kLengthReg)); locs->set_out( 0, Location::RegisterLocation(AllocateTypedDataArrayABI::kResultReg)); return locs; } void AllocateTypedDataInstr::EmitNativeCode(FlowGraphCompiler* compiler) { const Code& stub = Code::ZoneHandle( compiler->zone(), StubCode::GetAllocationStubForTypedData(class_id())); compiler->GenerateStubCall(source(), stub, UntaggedPcDescriptors::kOther, locs(), deopt_id()); } bool StoreInstanceFieldInstr::IsUnboxedStore() const { return slot().IsDartField() && FlowGraphCompiler::IsUnboxedField(slot().field()); } bool StoreInstanceFieldInstr::IsPotentialUnboxedStore() const { return slot().IsDartField() && FlowGraphCompiler::IsPotentialUnboxedField(slot().field()); } Representation StoreInstanceFieldInstr::RequiredInputRepresentation( intptr_t index) const { ASSERT((index == 0) || (index == 1)); if ((index == 1) && IsUnboxedStore()) { const Field& field = slot().field(); return FlowGraph::UnboxedFieldRepresentationOf(field); } return kTagged; } Instruction* StoreInstanceFieldInstr::Canonicalize(FlowGraph* flow_graph) { // Dart objects are allocated null-initialized, which means we can eliminate // all initializing stores which store null value. // Context objects can be allocated uninitialized as a performance // optimization in JIT mode - however in AOT mode we always allocate them // null initialized. if (is_initialization_ && (!slot().IsContextSlot() || !instance()->definition()->IsAllocateUninitializedContext()) && value()->BindsToConstantNull()) { return nullptr; } return this; } bool GuardFieldClassInstr::AttributesEqual(const Instruction& other) const { return field().ptr() == other.AsGuardFieldClass()->field().ptr(); } bool GuardFieldLengthInstr::AttributesEqual(const Instruction& other) const { return field().ptr() == other.AsGuardFieldLength()->field().ptr(); } bool GuardFieldTypeInstr::AttributesEqual(const Instruction& other) const { return field().ptr() == other.AsGuardFieldType()->field().ptr(); } Instruction* AssertSubtypeInstr::Canonicalize(FlowGraph* flow_graph) { // If all inputs needed to check instantation are constant, instantiate the // sub and super type and remove the instruction if the subtype test succeeds. if (super_type()->BindsToConstant() && sub_type()->BindsToConstant() && instantiator_type_arguments()->BindsToConstant() && function_type_arguments()->BindsToConstant()) { auto Z = Thread::Current()->zone(); const auto& constant_instantiator_type_args = instantiator_type_arguments()->BoundConstant().IsNull() ? TypeArguments::null_type_arguments() : TypeArguments::Cast( instantiator_type_arguments()->BoundConstant()); const auto& constant_function_type_args = function_type_arguments()->BoundConstant().IsNull() ? TypeArguments::null_type_arguments() : TypeArguments::Cast(function_type_arguments()->BoundConstant()); auto& constant_sub_type = AbstractType::Handle( Z, AbstractType::Cast(sub_type()->BoundConstant()).ptr()); auto& constant_super_type = AbstractType::Handle( Z, AbstractType::Cast(super_type()->BoundConstant()).ptr()); ASSERT(!constant_super_type.IsTypeRef()); ASSERT(!constant_sub_type.IsTypeRef()); if (AbstractType::InstantiateAndTestSubtype( &constant_sub_type, &constant_super_type, constant_instantiator_type_args, constant_function_type_args)) { return nullptr; } } return this; } bool StrictCompareInstr::AttributesEqual(const Instruction& other) const { auto const other_op = other.AsStrictCompare(); ASSERT(other_op != NULL); return ComparisonInstr::AttributesEqual(other) && (needs_number_check() == other_op->needs_number_check()); } bool MathMinMaxInstr::AttributesEqual(const Instruction& other) const { auto const other_op = other.AsMathMinMax(); ASSERT(other_op != NULL); return (op_kind() == other_op->op_kind()) && (result_cid() == other_op->result_cid()); } bool BinaryIntegerOpInstr::AttributesEqual(const Instruction& other) const { ASSERT(other.tag() == tag()); auto const other_op = other.AsBinaryIntegerOp(); return (op_kind() == other_op->op_kind()) && (can_overflow() == other_op->can_overflow()) && (is_truncating() == other_op->is_truncating()); } bool LoadFieldInstr::AttributesEqual(const Instruction& other) const { auto const other_load = other.AsLoadField(); ASSERT(other_load != NULL); return &this->slot_ == &other_load->slot_; } bool LoadStaticFieldInstr::AttributesEqual(const Instruction& other) const { ASSERT(AllowsCSE()); return field().ptr() == other.AsLoadStaticField()->field().ptr(); } bool LoadStaticFieldInstr::IsFieldInitialized(Instance* field_value) const { if (FLAG_fields_may_be_reset) { return false; } // Since new isolates will be spawned, the JITed code cannot depend on whether // global field was initialized when running with --enable-isolate-groups. if (IsolateGroup::AreIsolateGroupsEnabled()) return false; const Field& field = this->field(); Isolate* only_isolate = IsolateGroup::Current()->FirstIsolate(); if (only_isolate == nullptr) { // This can happen if background compiler executes this code but the mutator // is being shutdown and the isolate was already unregistered from the group // (and is trying to stop this BG compiler). if (field_value != nullptr) { *field_value = Object::sentinel().ptr(); } return false; } if (field_value == nullptr) { field_value = &Instance::Handle(); } *field_value = only_isolate->field_table()->At(field.field_id()); return (field_value->ptr() != Object::sentinel().ptr()) && (field_value->ptr() != Object::transition_sentinel().ptr()); } Definition* LoadStaticFieldInstr::Canonicalize(FlowGraph* flow_graph) { // When precompiling, the fact that a field is currently initialized does not // make it safe to omit code that checks if the field needs initialization // because the field will be reset so it starts uninitialized in the process // running the precompiled code. We must be prepared to reinitialize fields. if (calls_initializer() && IsFieldInitialized()) { set_calls_initializer(false); } return this; } ConstantInstr::ConstantInstr(const Object& value, const InstructionSource& source) : TemplateDefinition(source), value_(value), token_pos_(source.token_pos) { // Check that the value is not an incorrect Integer representation. ASSERT(!value.IsMint() || !Smi::IsValid(Mint::Cast(value).AsInt64Value())); // Check that clones of fields are not stored as constants. ASSERT(!value.IsField() || Field::Cast(value).IsOriginal()); // Check that all non-Smi objects are heap allocated and in old space. ASSERT(value.IsSmi() || value.IsOld()); #if defined(DEBUG) // Generally, instances in the flow graph should be canonical. Smis, null // values, and sentinel values are canonical by construction and so we skip // them here. if (!value.IsNull() && !value.IsSmi() && value.IsInstance() && !value.IsCanonical() && (value.ptr() != Object::sentinel().ptr())) { // The only allowed type for which IsCanonical() never answers true is // TypeParameter. (They are treated as canonical due to how they are // created, but there is no way to canonicalize a new TypeParameter // instance containing the same information as an existing instance.) // // Arrays in ConstantInstrs are usually immutable and canonicalized, but // there are at least a couple of cases where one or both is not true: // // * The Arrays created as backing for ArgumentsDescriptors may not be // canonicalized for space reasons when inlined in the IL. However, they // are still immutable. // * The backtracking stack for IRRegExps is put into a ConstantInstr for // immediate use as an argument to the operations on that stack. In this // case, the Array representing it is neither immutable or canonical. // // In addition to complicating the story for Arrays, IRRegExp compilation // also uses other non-canonical values as "constants". For example, the bit // tables used for certain character classes are represented as TypedData, // and so those values are also neither immutable (as there are no immutable // TypedData values) or canonical. // // LibraryPrefixes are also never canonicalized since their equality is // their identity. ASSERT(value.IsTypeParameter() || value.IsArray() || value.IsTypedData() || value.IsLibraryPrefix()); } #endif } bool ConstantInstr::AttributesEqual(const Instruction& other) const { auto const other_constant = other.AsConstant(); ASSERT(other_constant != NULL); return (value().ptr() == other_constant->value().ptr() && representation() == other_constant->representation()); } UnboxedConstantInstr::UnboxedConstantInstr(const Object& value, Representation representation) : ConstantInstr(value), representation_(representation), constant_address_(0) { if (representation_ == kUnboxedDouble) { ASSERT(value.IsDouble()); constant_address_ = FindDoubleConstant(Double::Cast(value).value()); } } // Returns true if the value represents a constant. bool Value::BindsToConstant() const { return definition()->IsConstant(); } // Returns true if the value represents constant null. bool Value::BindsToConstantNull() const { ConstantInstr* constant = definition()->AsConstant(); return (constant != NULL) && constant->value().IsNull(); } const Object& Value::BoundConstant() const { ASSERT(BindsToConstant()); ConstantInstr* constant = definition()->AsConstant(); ASSERT(constant != NULL); return constant->value(); } bool Value::BindsToSmiConstant() const { return BindsToConstant() && BoundConstant().IsSmi(); } intptr_t Value::BoundSmiConstant() const { ASSERT(BindsToSmiConstant()); return Smi::Cast(BoundConstant()).Value(); } GraphEntryInstr::GraphEntryInstr(const ParsedFunction& parsed_function, intptr_t osr_id) : GraphEntryInstr(parsed_function, osr_id, CompilerState::Current().GetNextDeoptId()) {} GraphEntryInstr::GraphEntryInstr(const ParsedFunction& parsed_function, intptr_t osr_id, intptr_t deopt_id) : BlockEntryWithInitialDefs(0, kInvalidTryIndex, deopt_id, /*stack_depth*/ 0), parsed_function_(parsed_function), catch_entries_(), indirect_entries_(), osr_id_(osr_id), entry_count_(0), spill_slot_count_(0), fixed_slot_count_(0) {} ConstantInstr* GraphEntryInstr::constant_null() { ASSERT(initial_definitions()->length() > 0); for (intptr_t i = 0; i < initial_definitions()->length(); ++i) { ConstantInstr* defn = (*initial_definitions())[i]->AsConstant(); if (defn != NULL && defn->value().IsNull()) return defn; } UNREACHABLE(); return NULL; } CatchBlockEntryInstr* GraphEntryInstr::GetCatchEntry(intptr_t index) { // TODO(fschneider): Sort the catch entries by catch_try_index to avoid // searching. for (intptr_t i = 0; i < catch_entries_.length(); ++i) { if (catch_entries_[i]->catch_try_index() == index) return catch_entries_[i]; } return NULL; } bool GraphEntryInstr::IsCompiledForOsr() const { return osr_id_ != Compiler::kNoOSRDeoptId; } // ==== Support for visiting flow graphs. #define DEFINE_ACCEPT(ShortName, Attrs) \ void ShortName##Instr::Accept(FlowGraphVisitor* visitor) { \ visitor->Visit##ShortName(this); \ } FOR_EACH_INSTRUCTION(DEFINE_ACCEPT) #undef DEFINE_ACCEPT void Instruction::SetEnvironment(Environment* deopt_env) { intptr_t use_index = 0; for (Environment::DeepIterator it(deopt_env); !it.Done(); it.Advance()) { Value* use = it.CurrentValue(); use->set_instruction(this); use->set_use_index(use_index++); } env_ = deopt_env; } void Instruction::RemoveEnvironment() { for (Environment::DeepIterator it(env()); !it.Done(); it.Advance()) { it.CurrentValue()->RemoveFromUseList(); } env_ = NULL; } void Instruction::ReplaceInEnvironment(Definition* current, Definition* replacement) { for (Environment::DeepIterator it(env()); !it.Done(); it.Advance()) { Value* use = it.CurrentValue(); if (use->definition() == current) { use->RemoveFromUseList(); use->set_definition(replacement); replacement->AddEnvUse(use); } } } Instruction* Instruction::RemoveFromGraph(bool return_previous) { ASSERT(!IsBlockEntry()); ASSERT(!IsBranch()); ASSERT(!IsThrow()); ASSERT(!IsReturn()); ASSERT(!IsReThrow()); ASSERT(!IsGoto()); ASSERT(previous() != NULL); // We cannot assert that the instruction, if it is a definition, has no // uses. This function is used to remove instructions from the graph and // reinsert them elsewhere (e.g., hoisting). Instruction* prev_instr = previous(); Instruction* next_instr = next(); ASSERT(next_instr != NULL); ASSERT(!next_instr->IsBlockEntry()); prev_instr->LinkTo(next_instr); UnuseAllInputs(); // Reset the successor and previous instruction to indicate that the // instruction is removed from the graph. set_previous(NULL); set_next(NULL); return return_previous ? prev_instr : next_instr; } void Instruction::InsertAfter(Instruction* prev) { ASSERT(previous_ == NULL); ASSERT(next_ == NULL); previous_ = prev; next_ = prev->next_; next_->previous_ = this; previous_->next_ = this; // Update def-use chains whenever instructions are added to the graph // after initial graph construction. for (intptr_t i = InputCount() - 1; i >= 0; --i) { Value* input = InputAt(i); input->definition()->AddInputUse(input); } } Instruction* Instruction::AppendInstruction(Instruction* tail) { LinkTo(tail); // Update def-use chains whenever instructions are added to the graph // after initial graph construction. for (intptr_t i = tail->InputCount() - 1; i >= 0; --i) { Value* input = tail->InputAt(i); input->definition()->AddInputUse(input); } return tail; } BlockEntryInstr* Instruction::GetBlock() { // TODO(fschneider): Implement a faster way to get the block of an // instruction. Instruction* result = previous(); ASSERT(result != nullptr); while (!result->IsBlockEntry()) { result = result->previous(); ASSERT(result != nullptr); } return result->AsBlockEntry(); } void ForwardInstructionIterator::RemoveCurrentFromGraph() { current_ = current_->RemoveFromGraph(true); // Set current_ to previous. } void BackwardInstructionIterator::RemoveCurrentFromGraph() { current_ = current_->RemoveFromGraph(false); // Set current_ to next. } // Default implementation of visiting basic blocks. Can be overridden. void FlowGraphVisitor::VisitBlocks() { ASSERT(current_iterator_ == NULL); for (intptr_t i = 0; i < block_order_->length(); ++i) { BlockEntryInstr* entry = (*block_order_)[i]; entry->Accept(this); ForwardInstructionIterator it(entry); current_iterator_ = ⁢ for (; !it.Done(); it.Advance()) { it.Current()->Accept(this); } current_iterator_ = NULL; } } bool Value::NeedsWriteBarrier() { Value* value = this; do { if (value->Type()->IsNull() || (value->Type()->ToNullableCid() == kSmiCid) || (value->Type()->ToNullableCid() == kBoolCid)) { return false; } // Strictly speaking, the incremental barrier can only be skipped for // immediate objects (Smis) or permanent objects (vm-isolate heap or // image pages). Here we choose to skip the barrier for any constant on // the assumption it will remain reachable through the object pool. if (value->BindsToConstant()) { return false; } // Follow the chain of redefinitions as redefined value could have a more // accurate type (for example, AssertAssignable of Smi to a generic T). value = value->definition()->RedefinedValue(); } while (value != nullptr); return true; } void JoinEntryInstr::AddPredecessor(BlockEntryInstr* predecessor) { // Require the predecessors to be sorted by block_id to make managing // their corresponding phi inputs simpler. intptr_t pred_id = predecessor->block_id(); intptr_t index = 0; while ((index < predecessors_.length()) && (predecessors_[index]->block_id() < pred_id)) { ++index; } #if defined(DEBUG) for (intptr_t i = index; i < predecessors_.length(); ++i) { ASSERT(predecessors_[i]->block_id() != pred_id); } #endif predecessors_.InsertAt(index, predecessor); } intptr_t JoinEntryInstr::IndexOfPredecessor(BlockEntryInstr* pred) const { for (intptr_t i = 0; i < predecessors_.length(); ++i) { if (predecessors_[i] == pred) return i; } return -1; } void Value::AddToList(Value* value, Value** list) { ASSERT(value->next_use() == nullptr); ASSERT(value->previous_use() == nullptr); Value* next = *list; ASSERT(value != next); *list = value; value->set_next_use(next); value->set_previous_use(NULL); if (next != NULL) next->set_previous_use(value); } void Value::RemoveFromUseList() { Definition* def = definition(); Value* next = next_use(); if (this == def->input_use_list()) { def->set_input_use_list(next); if (next != NULL) next->set_previous_use(NULL); } else if (this == def->env_use_list()) { def->set_env_use_list(next); if (next != NULL) next->set_previous_use(NULL); } else if (Value* prev = previous_use()) { prev->set_next_use(next); if (next != NULL) next->set_previous_use(prev); } set_previous_use(NULL); set_next_use(NULL); } // True if the definition has a single input use and is used only in // environments at the same instruction as that input use. bool Definition::HasOnlyUse(Value* use) const { if (!HasOnlyInputUse(use)) { return false; } Instruction* target = use->instruction(); for (Value::Iterator it(env_use_list()); !it.Done(); it.Advance()) { if (it.Current()->instruction() != target) return false; } return true; } bool Definition::HasOnlyInputUse(Value* use) const { return (input_use_list() == use) && (use->next_use() == NULL); } void Definition::ReplaceUsesWith(Definition* other) { ASSERT(other != NULL); ASSERT(this != other); Value* current = NULL; Value* next = input_use_list(); if (next != NULL) { // Change all the definitions. while (next != NULL) { current = next; current->set_definition(other); current->RefineReachingType(other->Type()); next = current->next_use(); } // Concatenate the lists. next = other->input_use_list(); current->set_next_use(next); if (next != NULL) next->set_previous_use(current); other->set_input_use_list(input_use_list()); set_input_use_list(NULL); } // Repeat for environment uses. current = NULL; next = env_use_list(); if (next != NULL) { while (next != NULL) { current = next; current->set_definition(other); current->RefineReachingType(other->Type()); next = current->next_use(); } next = other->env_use_list(); current->set_next_use(next); if (next != NULL) next->set_previous_use(current); other->set_env_use_list(env_use_list()); set_env_use_list(NULL); } } void Instruction::UnuseAllInputs() { for (intptr_t i = InputCount() - 1; i >= 0; --i) { InputAt(i)->RemoveFromUseList(); } for (Environment::DeepIterator it(env()); !it.Done(); it.Advance()) { it.CurrentValue()->RemoveFromUseList(); } } void Instruction::RepairPushArgsInEnvironment() const { // Some calls (e.g. closure calls) have more inputs than actual arguments. // Those extra inputs will be consumed from the stack before the call. const intptr_t after_args_input_count = env()->LazyDeoptPruneCount(); PushArgumentsArray* push_arguments = GetPushArguments(); ASSERT(push_arguments != nullptr); const intptr_t arg_count = ArgumentCount(); ASSERT((arg_count + after_args_input_count) <= env()->Length()); const intptr_t env_base = env()->Length() - arg_count - after_args_input_count; for (intptr_t i = 0; i < arg_count; ++i) { env()->ValueAt(env_base + i)->BindToEnvironment(push_arguments->At(i)); } } void Instruction::InheritDeoptTargetAfter(FlowGraph* flow_graph, Definition* call, Definition* result) { ASSERT(call->env() != NULL); deopt_id_ = DeoptId::ToDeoptAfter(call->deopt_id_); call->env()->DeepCopyAfterTo( flow_graph->zone(), this, call->ArgumentCount(), flow_graph->constant_dead(), result != NULL ? result : flow_graph->constant_dead()); } void Instruction::InheritDeoptTarget(Zone* zone, Instruction* other) { ASSERT(other->env() != NULL); CopyDeoptIdFrom(*other); other->env()->DeepCopyTo(zone, this); } void BranchInstr::InheritDeoptTarget(Zone* zone, Instruction* other) { ASSERT(env() == NULL); Instruction::InheritDeoptTarget(zone, other); comparison()->SetDeoptId(*this); } bool Instruction::IsDominatedBy(Instruction* dom) { BlockEntryInstr* block = GetBlock(); BlockEntryInstr* dom_block = dom->GetBlock(); if (dom->IsPhi()) { dom = dom_block; } if (block == dom_block) { if ((block == dom) || (this == block->last_instruction())) { return true; } if (IsPhi()) { return false; } for (Instruction* curr = dom->next(); curr != NULL; curr = curr->next()) { if (curr == this) return true; } return false; } return dom_block->Dominates(block); } bool Instruction::HasUnmatchedInputRepresentations() const { for (intptr_t i = 0; i < InputCount(); i++) { Definition* input = InputAt(i)->definition(); const Representation input_representation = RequiredInputRepresentation(i); if (input_representation != kNoRepresentation && input_representation != input->representation()) { return true; } } return false; } const intptr_t Instruction::kInstructionAttrs[Instruction::kNumInstructions] = { #define INSTR_ATTRS(type, attrs) InstrAttrs::attrs, FOR_EACH_INSTRUCTION(INSTR_ATTRS) #undef INSTR_ATTRS }; bool Instruction::CanTriggerGC() const { return (kInstructionAttrs[tag()] & InstrAttrs::kNoGC) == 0; } void Definition::ReplaceWithResult(Instruction* replacement, Definition* replacement_for_uses, ForwardInstructionIterator* iterator) { // Record replacement's input uses. for (intptr_t i = replacement->InputCount() - 1; i >= 0; --i) { Value* input = replacement->InputAt(i); input->definition()->AddInputUse(input); } // Take replacement's environment from this definition. ASSERT(replacement->env() == NULL); replacement->SetEnvironment(env()); ClearEnv(); // Replace all uses of this definition with replacement_for_uses. ReplaceUsesWith(replacement_for_uses); // Finally replace this one with the replacement instruction in the graph. previous()->LinkTo(replacement); if ((iterator != NULL) && (this == iterator->Current())) { // Remove through the iterator. replacement->LinkTo(this); iterator->RemoveCurrentFromGraph(); } else { replacement->LinkTo(next()); // Remove this definition's input uses. UnuseAllInputs(); } set_previous(NULL); set_next(NULL); } void Definition::ReplaceWith(Definition* other, ForwardInstructionIterator* iterator) { // Reuse this instruction's SSA name for other. ASSERT(!other->HasSSATemp()); if (HasSSATemp()) { other->set_ssa_temp_index(ssa_temp_index()); } ReplaceWithResult(other, other, iterator); } void BranchInstr::SetComparison(ComparisonInstr* new_comparison) { for (intptr_t i = new_comparison->InputCount() - 1; i >= 0; --i) { Value* input = new_comparison->InputAt(i); input->definition()->AddInputUse(input); input->set_instruction(this); } // There should be no need to copy or unuse an environment. ASSERT(comparison()->env() == NULL); ASSERT(new_comparison->env() == NULL); // Remove the current comparison's input uses. comparison()->UnuseAllInputs(); ASSERT(!new_comparison->HasUses()); comparison_ = new_comparison; } // ==== Postorder graph traversal. static bool IsMarked(BlockEntryInstr* block, GrowableArray* preorder) { // Detect that a block has been visited as part of the current // DiscoverBlocks (we can call DiscoverBlocks multiple times). The block // will be 'marked' by (1) having a preorder number in the range of the // preorder array and (2) being in the preorder array at that index. intptr_t i = block->preorder_number(); return (i >= 0) && (i < preorder->length()) && ((*preorder)[i] == block); } // Base class implementation used for JoinEntry and TargetEntry. bool BlockEntryInstr::DiscoverBlock(BlockEntryInstr* predecessor, GrowableArray* preorder, GrowableArray* parent) { // If this block has a predecessor (i.e., is not the graph entry) we can // assume the preorder array is non-empty. ASSERT((predecessor == NULL) || !preorder->is_empty()); // Blocks with a single predecessor cannot have been reached before. ASSERT(IsJoinEntry() || !IsMarked(this, preorder)); // 1. If the block has already been reached, add current_block as a // basic-block predecessor and we are done. if (IsMarked(this, preorder)) { ASSERT(predecessor != NULL); AddPredecessor(predecessor); return false; } // 2. Otherwise, clear the predecessors which might have been computed on // some earlier call to DiscoverBlocks and record this predecessor. ClearPredecessors(); if (predecessor != NULL) AddPredecessor(predecessor); // 3. The predecessor is the spanning-tree parent. The graph entry has no // parent, indicated by -1. intptr_t parent_number = (predecessor == NULL) ? -1 : predecessor->preorder_number(); parent->Add(parent_number); // 4. Assign the preorder number and add the block entry to the list. set_preorder_number(preorder->length()); preorder->Add(this); // The preorder and parent arrays are indexed by // preorder block number, so they should stay in lockstep. ASSERT(preorder->length() == parent->length()); // 5. Iterate straight-line successors to record assigned variables and // find the last instruction in the block. The graph entry block consists // of only the entry instruction, so that is the last instruction in the // block. Instruction* last = this; for (ForwardInstructionIterator it(this); !it.Done(); it.Advance()) { last = it.Current(); } set_last_instruction(last); if (last->IsGoto()) last->AsGoto()->set_block(this); return true; } void GraphEntryInstr::RelinkToOsrEntry(Zone* zone, intptr_t max_block_id) { ASSERT(osr_id_ != Compiler::kNoOSRDeoptId); BitVector* block_marks = new (zone) BitVector(zone, max_block_id + 1); bool found = FindOsrEntryAndRelink(this, /*parent=*/NULL, block_marks); ASSERT(found); } bool BlockEntryInstr::FindOsrEntryAndRelink(GraphEntryInstr* graph_entry, Instruction* parent, BitVector* block_marks) { const intptr_t osr_id = graph_entry->osr_id(); // Search for the instruction with the OSR id. Use a depth first search // because basic blocks have not been discovered yet. Prune unreachable // blocks by replacing the normal entry with a jump to the block // containing the OSR entry point. // Do not visit blocks more than once. if (block_marks->Contains(block_id())) return false; block_marks->Add(block_id()); // Search this block for the OSR id. Instruction* instr = this; for (ForwardInstructionIterator it(this); !it.Done(); it.Advance()) { instr = it.Current(); if (instr->GetDeoptId() == osr_id) { // Sanity check that we found a stack check instruction. ASSERT(instr->IsCheckStackOverflow()); // Loop stack check checks are always in join blocks so that they can // be the target of a goto. ASSERT(IsJoinEntry()); // The instruction should be the first instruction in the block so // we can simply jump to the beginning of the block. ASSERT(instr->previous() == this); ASSERT(stack_depth() == instr->AsCheckStackOverflow()->stack_depth()); auto normal_entry = graph_entry->normal_entry(); auto osr_entry = new OsrEntryInstr( graph_entry, normal_entry->block_id(), normal_entry->try_index(), normal_entry->deopt_id(), stack_depth()); auto goto_join = new GotoInstr(AsJoinEntry(), CompilerState::Current().GetNextDeoptId()); ASSERT(parent != nullptr); goto_join->CopyDeoptIdFrom(*parent); osr_entry->LinkTo(goto_join); // Remove normal function entries & add osr entry. graph_entry->set_normal_entry(nullptr); graph_entry->set_unchecked_entry(nullptr); graph_entry->set_osr_entry(osr_entry); return true; } } // Recursively search the successors. for (intptr_t i = instr->SuccessorCount() - 1; i >= 0; --i) { if (instr->SuccessorAt(i)->FindOsrEntryAndRelink(graph_entry, instr, block_marks)) { return true; } } return false; } bool BlockEntryInstr::Dominates(BlockEntryInstr* other) const { // TODO(fschneider): Make this faster by e.g. storing dominators for each // block while computing the dominator tree. ASSERT(other != NULL); BlockEntryInstr* current = other; while (current != NULL && current != this) { current = current->dominator(); } return current == this; } BlockEntryInstr* BlockEntryInstr::ImmediateDominator() const { Instruction* last = dominator()->last_instruction(); if ((last->SuccessorCount() == 1) && (last->SuccessorAt(0) == this)) { return dominator(); } return NULL; } bool BlockEntryInstr::IsLoopHeader() const { return loop_info_ != nullptr && loop_info_->header() == this; } intptr_t BlockEntryInstr::NestingDepth() const { return loop_info_ == nullptr ? 0 : loop_info_->NestingDepth(); } // Helper to mutate the graph during inlining. This block should be // replaced with new_block as a predecessor of all of this block's // successors. For each successor, the predecessors will be reordered // to preserve block-order sorting of the predecessors as well as the // phis if the successor is a join. void BlockEntryInstr::ReplaceAsPredecessorWith(BlockEntryInstr* new_block) { // Set the last instruction of the new block to that of the old block. Instruction* last = last_instruction(); new_block->set_last_instruction(last); // For each successor, update the predecessors. for (intptr_t sidx = 0; sidx < last->SuccessorCount(); ++sidx) { // If the successor is a target, update its predecessor. TargetEntryInstr* target = last->SuccessorAt(sidx)->AsTargetEntry(); if (target != NULL) { target->predecessor_ = new_block; continue; } // If the successor is a join, update each predecessor and the phis. JoinEntryInstr* join = last->SuccessorAt(sidx)->AsJoinEntry(); ASSERT(join != NULL); // Find the old predecessor index. intptr_t old_index = join->IndexOfPredecessor(this); intptr_t pred_count = join->PredecessorCount(); ASSERT(old_index >= 0); ASSERT(old_index < pred_count); // Find the new predecessor index while reordering the predecessors. intptr_t new_id = new_block->block_id(); intptr_t new_index = old_index; if (block_id() < new_id) { // Search upwards, bubbling down intermediate predecessors. for (; new_index < pred_count - 1; ++new_index) { if (join->predecessors_[new_index + 1]->block_id() > new_id) break; join->predecessors_[new_index] = join->predecessors_[new_index + 1]; } } else { // Search downwards, bubbling up intermediate predecessors. for (; new_index > 0; --new_index) { if (join->predecessors_[new_index - 1]->block_id() < new_id) break; join->predecessors_[new_index] = join->predecessors_[new_index - 1]; } } join->predecessors_[new_index] = new_block; // If the new and old predecessor index match there is nothing to update. if ((join->phis() == NULL) || (old_index == new_index)) return; // Otherwise, reorder the predecessor uses in each phi. for (PhiIterator it(join); !it.Done(); it.Advance()) { PhiInstr* phi = it.Current(); ASSERT(phi != NULL); ASSERT(pred_count == phi->InputCount()); // Save the predecessor use. Value* pred_use = phi->InputAt(old_index); // Move uses between old and new. intptr_t step = (old_index < new_index) ? 1 : -1; for (intptr_t use_idx = old_index; use_idx != new_index; use_idx += step) { phi->SetInputAt(use_idx, phi->InputAt(use_idx + step)); } // Write the predecessor use. phi->SetInputAt(new_index, pred_use); } } } void BlockEntryInstr::ClearAllInstructions() { JoinEntryInstr* join = this->AsJoinEntry(); if (join != NULL) { for (PhiIterator it(join); !it.Done(); it.Advance()) { it.Current()->UnuseAllInputs(); } } UnuseAllInputs(); for (ForwardInstructionIterator it(this); !it.Done(); it.Advance()) { it.Current()->UnuseAllInputs(); } } PhiInstr* JoinEntryInstr::InsertPhi(intptr_t var_index, intptr_t var_count) { // Lazily initialize the array of phis. // Currently, phis are stored in a sparse array that holds the phi // for variable with index i at position i. // TODO(fschneider): Store phis in a more compact way. if (phis_ == NULL) { phis_ = new ZoneGrowableArray(var_count); for (intptr_t i = 0; i < var_count; i++) { phis_->Add(NULL); } } ASSERT((*phis_)[var_index] == NULL); return (*phis_)[var_index] = new PhiInstr(this, PredecessorCount()); } void JoinEntryInstr::InsertPhi(PhiInstr* phi) { // Lazily initialize the array of phis. if (phis_ == NULL) { phis_ = new ZoneGrowableArray(1); } phis_->Add(phi); } void JoinEntryInstr::RemovePhi(PhiInstr* phi) { ASSERT(phis_ != NULL); for (intptr_t index = 0; index < phis_->length(); ++index) { if (phi == (*phis_)[index]) { (*phis_)[index] = phis_->Last(); phis_->RemoveLast(); return; } } } void JoinEntryInstr::RemoveDeadPhis(Definition* replacement) { if (phis_ == NULL) return; intptr_t to_index = 0; for (intptr_t from_index = 0; from_index < phis_->length(); ++from_index) { PhiInstr* phi = (*phis_)[from_index]; if (phi != NULL) { if (phi->is_alive()) { (*phis_)[to_index++] = phi; for (intptr_t i = phi->InputCount() - 1; i >= 0; --i) { Value* input = phi->InputAt(i); input->definition()->AddInputUse(input); } } else { phi->ReplaceUsesWith(replacement); } } } if (to_index == 0) { phis_ = NULL; } else { phis_->TruncateTo(to_index); } } intptr_t Instruction::SuccessorCount() const { return 0; } BlockEntryInstr* Instruction::SuccessorAt(intptr_t index) const { // Called only if index is in range. Only control-transfer instructions // can have non-zero successor counts and they override this function. UNREACHABLE(); return NULL; } intptr_t GraphEntryInstr::SuccessorCount() const { return (normal_entry() == nullptr ? 0 : 1) + (unchecked_entry() == nullptr ? 0 : 1) + (osr_entry() == nullptr ? 0 : 1) + catch_entries_.length(); } BlockEntryInstr* GraphEntryInstr::SuccessorAt(intptr_t index) const { if (normal_entry() != nullptr) { if (index == 0) return normal_entry_; index--; } if (unchecked_entry() != nullptr) { if (index == 0) return unchecked_entry(); index--; } if (osr_entry() != nullptr) { if (index == 0) return osr_entry(); index--; } return catch_entries_[index]; } intptr_t BranchInstr::SuccessorCount() const { return 2; } BlockEntryInstr* BranchInstr::SuccessorAt(intptr_t index) const { if (index == 0) return true_successor_; if (index == 1) return false_successor_; UNREACHABLE(); return NULL; } intptr_t GotoInstr::SuccessorCount() const { return 1; } BlockEntryInstr* GotoInstr::SuccessorAt(intptr_t index) const { ASSERT(index == 0); return successor(); } void Instruction::Goto(JoinEntryInstr* entry) { LinkTo(new GotoInstr(entry, CompilerState::Current().GetNextDeoptId())); } bool IntConverterInstr::ComputeCanDeoptimize() const { return (to() == kUnboxedInt32) && !is_truncating() && !RangeUtils::Fits(value()->definition()->range(), RangeBoundary::kRangeBoundaryInt32); } bool UnboxInt32Instr::ComputeCanDeoptimize() const { if (SpeculativeModeOfInputs() == kNotSpeculative) { return false; } const intptr_t value_cid = value()->Type()->ToCid(); if (value_cid == kSmiCid) { return (compiler::target::kSmiBits > 32) && !is_truncating() && !RangeUtils::Fits(value()->definition()->range(), RangeBoundary::kRangeBoundaryInt32); } else if (value_cid == kMintCid) { return !is_truncating() && !RangeUtils::Fits(value()->definition()->range(), RangeBoundary::kRangeBoundaryInt32); } else if (is_truncating() && value()->definition()->IsBoxInteger()) { return false; } else if ((compiler::target::kSmiBits < 32) && value()->Type()->IsInt()) { return !RangeUtils::Fits(value()->definition()->range(), RangeBoundary::kRangeBoundaryInt32); } else { return true; } } bool UnboxUint32Instr::ComputeCanDeoptimize() const { ASSERT(is_truncating()); if (SpeculativeModeOfInputs() == kNotSpeculative) { return false; } if ((value()->Type()->ToCid() == kSmiCid) || (value()->Type()->ToCid() == kMintCid)) { return false; } // Check input value's range. Range* value_range = value()->definition()->range(); return !RangeUtils::Fits(value_range, RangeBoundary::kRangeBoundaryInt64); } bool BinaryInt32OpInstr::ComputeCanDeoptimize() const { switch (op_kind()) { case Token::kBIT_AND: case Token::kBIT_OR: case Token::kBIT_XOR: return false; case Token::kSHR: return false; case Token::kUSHR: case Token::kSHL: // Currently only shifts by in range constant are supported, see // BinaryInt32OpInstr::IsSupported. return can_overflow(); case Token::kMOD: { UNREACHABLE(); } default: return can_overflow(); } } bool BinarySmiOpInstr::ComputeCanDeoptimize() const { switch (op_kind()) { case Token::kBIT_AND: case Token::kBIT_OR: case Token::kBIT_XOR: return false; case Token::kSHR: return !RangeUtils::IsPositive(right_range()); case Token::kUSHR: case Token::kSHL: return can_overflow() || !RangeUtils::IsPositive(right_range()); case Token::kMOD: return RangeUtils::CanBeZero(right_range()); case Token::kTRUNCDIV: return RangeUtils::CanBeZero(right_range()) || RangeUtils::Overlaps(right_range(), -1, -1); default: return can_overflow(); } } bool ShiftIntegerOpInstr::IsShiftCountInRange(int64_t max) const { return RangeUtils::IsWithin(shift_range(), 0, max); } bool BinaryIntegerOpInstr::RightIsPowerOfTwoConstant() const { if (!right()->definition()->IsConstant()) return false; const Object& constant = right()->definition()->AsConstant()->value(); if (!constant.IsSmi()) return false; const intptr_t int_value = Smi::Cast(constant).Value(); ASSERT(int_value != kIntptrMin); return Utils::IsPowerOfTwo(Utils::Abs(int_value)); } static intptr_t RepresentationBits(Representation r) { switch (r) { case kTagged: return compiler::target::kSmiBits + 1; case kUnboxedInt32: case kUnboxedUint32: return 32; case kUnboxedInt64: return 64; default: UNREACHABLE(); return 0; } } static int64_t RepresentationMask(Representation r) { return static_cast(static_cast(-1) >> (64 - RepresentationBits(r))); } static Definition* CanonicalizeCommutativeDoubleArithmetic(Token::Kind op, Value* left, Value* right) { int64_t left_value; if (!Evaluator::ToIntegerConstant(left, &left_value)) { return NULL; } // Can't apply 0.0 * x -> 0.0 equivalence to double operation because // 0.0 * NaN is NaN not 0.0. // Can't apply 0.0 + x -> x to double because 0.0 + (-0.0) is 0.0 not -0.0. switch (op) { case Token::kMUL: if (left_value == 1) { if (right->definition()->representation() != kUnboxedDouble) { // Can't yet apply the equivalence because representation selection // did not run yet. We need it to guarantee that right value is // correctly coerced to double. The second canonicalization pass // will apply this equivalence. return NULL; } else { return right->definition(); } } break; default: break; } return NULL; } Definition* DoubleToFloatInstr::Canonicalize(FlowGraph* flow_graph) { #ifdef DEBUG // Must only be used in Float32 StoreIndexedInstr, FloatToDoubleInstr, // Phis introduce by load forwarding, or MaterializeObject for // eliminated Float32 array. ASSERT(env_use_list() == NULL); for (Value* use = input_use_list(); use != NULL; use = use->next_use()) { ASSERT(use->instruction()->IsPhi() || use->instruction()->IsFloatToDouble() || (use->instruction()->IsStoreIndexed() && (use->instruction()->AsStoreIndexed()->class_id() == kTypedDataFloat32ArrayCid)) || (use->instruction()->IsMaterializeObject() && (use->instruction()->AsMaterializeObject()->cls().id() == kTypedDataFloat32ArrayCid))); } #endif if (!HasUses()) return NULL; if (value()->definition()->IsFloatToDouble()) { // F2D(D2F(v)) == v. return value()->definition()->AsFloatToDouble()->value()->definition(); } return this; } Definition* FloatToDoubleInstr::Canonicalize(FlowGraph* flow_graph) { return HasUses() ? this : NULL; } Definition* BinaryDoubleOpInstr::Canonicalize(FlowGraph* flow_graph) { if (!HasUses()) return NULL; Definition* result = NULL; result = CanonicalizeCommutativeDoubleArithmetic(op_kind(), left(), right()); if (result != NULL) { return result; } result = CanonicalizeCommutativeDoubleArithmetic(op_kind(), right(), left()); if (result != NULL) { return result; } if ((op_kind() == Token::kMUL) && (left()->definition() == right()->definition())) { MathUnaryInstr* math_unary = new MathUnaryInstr( MathUnaryInstr::kDoubleSquare, new Value(left()->definition()), DeoptimizationTarget()); flow_graph->InsertBefore(this, math_unary, env(), FlowGraph::kValue); return math_unary; } return this; } Definition* DoubleTestOpInstr::Canonicalize(FlowGraph* flow_graph) { return HasUses() ? this : NULL; } static bool IsCommutative(Token::Kind op) { switch (op) { case Token::kMUL: FALL_THROUGH; case Token::kADD: FALL_THROUGH; case Token::kBIT_AND: FALL_THROUGH; case Token::kBIT_OR: FALL_THROUGH; case Token::kBIT_XOR: return true; default: return false; } } UnaryIntegerOpInstr* UnaryIntegerOpInstr::Make(Representation representation, Token::Kind op_kind, Value* value, intptr_t deopt_id, Range* range) { UnaryIntegerOpInstr* op = NULL; switch (representation) { case kTagged: op = new UnarySmiOpInstr(op_kind, value, deopt_id); break; case kUnboxedInt32: return NULL; case kUnboxedUint32: op = new UnaryUint32OpInstr(op_kind, value, deopt_id); break; case kUnboxedInt64: op = new UnaryInt64OpInstr(op_kind, value, deopt_id); break; default: UNREACHABLE(); return NULL; } if (op == NULL) { return op; } if (!Range::IsUnknown(range)) { op->set_range(*range); } ASSERT(op->representation() == representation); return op; } BinaryIntegerOpInstr* BinaryIntegerOpInstr::Make( Representation representation, Token::Kind op_kind, Value* left, Value* right, intptr_t deopt_id, SpeculativeMode speculative_mode) { BinaryIntegerOpInstr* op = nullptr; Range* right_range = nullptr; switch (op_kind) { case Token::kMOD: case Token::kTRUNCDIV: if (representation != kTagged) break; FALL_THROUGH; case Token::kSHL: case Token::kSHR: case Token::kUSHR: if (auto const const_def = right->definition()->AsConstant()) { right_range = new Range(); const_def->InferRange(nullptr, right_range); } break; default: break; } switch (representation) { case kTagged: op = new BinarySmiOpInstr(op_kind, left, right, deopt_id, right_range); break; case kUnboxedInt32: if (!BinaryInt32OpInstr::IsSupported(op_kind, left, right)) { return nullptr; } op = new BinaryInt32OpInstr(op_kind, left, right, deopt_id); break; case kUnboxedUint32: if ((op_kind == Token::kSHL) || (op_kind == Token::kSHR) || (op_kind == Token::kUSHR)) { if (speculative_mode == kNotSpeculative) { op = new ShiftUint32OpInstr(op_kind, left, right, deopt_id, right_range); } else { op = new SpeculativeShiftUint32OpInstr(op_kind, left, right, deopt_id, right_range); } } else { op = new BinaryUint32OpInstr(op_kind, left, right, deopt_id); } break; case kUnboxedInt64: if ((op_kind == Token::kSHL) || (op_kind == Token::kSHR) || (op_kind == Token::kUSHR)) { if (speculative_mode == kNotSpeculative) { op = new ShiftInt64OpInstr(op_kind, left, right, deopt_id, right_range); } else { op = new SpeculativeShiftInt64OpInstr(op_kind, left, right, deopt_id, right_range); } } else { op = new BinaryInt64OpInstr(op_kind, left, right, deopt_id); } break; default: UNREACHABLE(); return nullptr; } ASSERT(op->representation() == representation); return op; } BinaryIntegerOpInstr* BinaryIntegerOpInstr::Make( Representation representation, Token::Kind op_kind, Value* left, Value* right, intptr_t deopt_id, bool can_overflow, bool is_truncating, Range* range, SpeculativeMode speculative_mode) { BinaryIntegerOpInstr* op = BinaryIntegerOpInstr::Make( representation, op_kind, left, right, deopt_id, speculative_mode); if (op == nullptr) { return nullptr; } if (!Range::IsUnknown(range)) { op->set_range(*range); } op->set_can_overflow(can_overflow); if (is_truncating) { op->mark_truncating(); } return op; } Definition* BinaryIntegerOpInstr::Canonicalize(FlowGraph* flow_graph) { // If both operands are constants evaluate this expression. Might // occur due to load forwarding after constant propagation pass // have already been run. if (left()->BindsToConstant() && right()->BindsToConstant()) { const Integer& result = Integer::Handle(Evaluator::BinaryIntegerEvaluate( left()->BoundConstant(), right()->BoundConstant(), op_kind(), is_truncating(), representation(), Thread::Current())); if (!result.IsNull()) { return flow_graph->TryCreateConstantReplacementFor(this, result); } } if (left()->BindsToConstant() && !right()->BindsToConstant() && IsCommutative(op_kind())) { Value* l = left(); Value* r = right(); SetInputAt(0, r); SetInputAt(1, l); } int64_t rhs; if (!Evaluator::ToIntegerConstant(right(), &rhs)) { return this; } if (is_truncating()) { switch (op_kind()) { case Token::kMUL: case Token::kSUB: case Token::kADD: case Token::kBIT_AND: case Token::kBIT_OR: case Token::kBIT_XOR: rhs = Evaluator::TruncateTo(rhs, representation()); break; default: break; } } switch (op_kind()) { case Token::kMUL: if (rhs == 1) { return left()->definition(); } else if (rhs == 0) { return right()->definition(); } else if (rhs == 2) { const int64_t shift_1 = 1; ConstantInstr* constant_1 = flow_graph->GetConstant(Smi::Handle(Smi::New(shift_1))); BinaryIntegerOpInstr* shift = BinaryIntegerOpInstr::Make( representation(), Token::kSHL, left()->CopyWithType(), new Value(constant_1), GetDeoptId(), can_overflow(), is_truncating(), range(), SpeculativeModeOfInputs()); if (shift != nullptr) { // Assign a range to the shift factor, just in case range // analysis no longer runs after this rewriting. if (auto shift_with_range = shift->AsShiftIntegerOp()) { shift_with_range->set_shift_range( new Range(RangeBoundary::FromConstant(shift_1), RangeBoundary::FromConstant(shift_1))); } flow_graph->InsertBefore(this, shift, env(), FlowGraph::kValue); return shift; } } break; case Token::kADD: if (rhs == 0) { return left()->definition(); } break; case Token::kBIT_AND: if (rhs == 0) { return right()->definition(); } else if (rhs == RepresentationMask(representation())) { return left()->definition(); } break; case Token::kBIT_OR: if (rhs == 0) { return left()->definition(); } else if (rhs == RepresentationMask(representation())) { return right()->definition(); } break; case Token::kBIT_XOR: if (rhs == 0) { return left()->definition(); } else if (rhs == RepresentationMask(representation())) { UnaryIntegerOpInstr* bit_not = UnaryIntegerOpInstr::Make( representation(), Token::kBIT_NOT, left()->CopyWithType(), GetDeoptId(), range()); if (bit_not != NULL) { flow_graph->InsertBefore(this, bit_not, env(), FlowGraph::kValue); return bit_not; } } break; case Token::kSUB: if (rhs == 0) { return left()->definition(); } break; case Token::kTRUNCDIV: if (rhs == 1) { return left()->definition(); } else if (rhs == -1) { UnaryIntegerOpInstr* negation = UnaryIntegerOpInstr::Make( representation(), Token::kNEGATE, left()->CopyWithType(), GetDeoptId(), range()); if (negation != NULL) { flow_graph->InsertBefore(this, negation, env(), FlowGraph::kValue); return negation; } } break; case Token::kMOD: if (std::abs(rhs) == 1) { return flow_graph->TryCreateConstantReplacementFor(this, Object::smi_zero()); } break; case Token::kUSHR: if (rhs >= kBitsPerInt64) { return flow_graph->TryCreateConstantReplacementFor(this, Object::smi_zero()); } FALL_THROUGH; case Token::kSHR: if (rhs == 0) { return left()->definition(); } else if (rhs < 0) { // Instruction will always throw on negative rhs operand. if (!CanDeoptimize()) { // For non-speculative operations (no deopt), let // the code generator deal with throw on slowpath. break; } ASSERT(GetDeoptId() != DeoptId::kNone); DeoptimizeInstr* deopt = new DeoptimizeInstr(ICData::kDeoptBinarySmiOp, GetDeoptId()); flow_graph->InsertBefore(this, deopt, env(), FlowGraph::kEffect); // Replace with zero since it always throws. return flow_graph->TryCreateConstantReplacementFor(this, Object::smi_zero()); } break; case Token::kSHL: { const intptr_t result_bits = RepresentationBits(representation()); if (rhs == 0) { return left()->definition(); } else if ((rhs >= kBitsPerInt64) || ((rhs >= result_bits) && is_truncating())) { return flow_graph->TryCreateConstantReplacementFor(this, Object::smi_zero()); } else if ((rhs < 0) || ((rhs >= result_bits) && !is_truncating())) { // Instruction will always throw on negative rhs operand or // deoptimize on large rhs operand. if (!CanDeoptimize()) { // For non-speculative operations (no deopt), let // the code generator deal with throw on slowpath. break; } ASSERT(GetDeoptId() != DeoptId::kNone); DeoptimizeInstr* deopt = new DeoptimizeInstr(ICData::kDeoptBinarySmiOp, GetDeoptId()); flow_graph->InsertBefore(this, deopt, env(), FlowGraph::kEffect); // Replace with zero since it overshifted or always throws. return flow_graph->TryCreateConstantReplacementFor(this, Object::smi_zero()); } break; } default: break; } return this; } // Optimizations that eliminate or simplify individual instructions. Instruction* Instruction::Canonicalize(FlowGraph* flow_graph) { return this; } Definition* Definition::Canonicalize(FlowGraph* flow_graph) { return this; } Definition* RedefinitionInstr::Canonicalize(FlowGraph* flow_graph) { // Must not remove Redifinitions without uses until LICM, even though // Redefinition might not have any uses itself it can still be dominating // uses of the value it redefines and must serve as a barrier for those // uses. RenameUsesDominatedByRedefinitions would normalize the graph and // route those uses through this redefinition. if (!HasUses() && !flow_graph->is_licm_allowed()) { return NULL; } if ((constrained_type() != nullptr) && Type()->IsEqualTo(value()->Type())) { return value()->definition(); } return this; } Instruction* CheckStackOverflowInstr::Canonicalize(FlowGraph* flow_graph) { switch (kind_) { case kOsrAndPreemption: return this; case kOsrOnly: // Don't need OSR entries in the optimized code. return NULL; } // Switch above exhausts all possibilities but some compilers can't figure // it out. UNREACHABLE(); return this; } bool LoadFieldInstr::IsImmutableLengthLoad() const { switch (slot().kind()) { case Slot::Kind::kArray_length: case Slot::Kind::kTypedDataBase_length: case Slot::Kind::kString_length: case Slot::Kind::kTypeArguments_length: return true; case Slot::Kind::kGrowableObjectArray_length: return false; // Not length loads. case Slot::Kind::kLinkedHashMap_index: case Slot::Kind::kLinkedHashMap_data: case Slot::Kind::kLinkedHashMap_hash_mask: case Slot::Kind::kLinkedHashMap_used_data: case Slot::Kind::kLinkedHashMap_deleted_keys: case Slot::Kind::kArgumentsDescriptor_type_args_len: case Slot::Kind::kArgumentsDescriptor_positional_count: case Slot::Kind::kArgumentsDescriptor_count: case Slot::Kind::kArgumentsDescriptor_size: case Slot::Kind::kArrayElement: case Slot::Kind::kTypeArguments: case Slot::Kind::kTypedDataView_offset_in_bytes: case Slot::Kind::kTypedDataView_data: case Slot::Kind::kGrowableObjectArray_data: case Slot::Kind::kContext_parent: case Slot::Kind::kClosure_context: case Slot::Kind::kClosure_delayed_type_arguments: case Slot::Kind::kClosure_function: case Slot::Kind::kClosure_function_type_arguments: case Slot::Kind::kClosure_instantiator_type_arguments: case Slot::Kind::kClosure_hash: case Slot::Kind::kClosureData_default_type_arguments: case Slot::Kind::kClosureData_default_type_arguments_kind: case Slot::Kind::kCapturedVariable: case Slot::Kind::kDartField: case Slot::Kind::kFunction_data: case Slot::Kind::kFunction_kind_tag: case Slot::Kind::kFunction_packed_fields: case Slot::Kind::kFunction_signature: case Slot::Kind::kFunctionType_packed_fields: case Slot::Kind::kFunctionType_parameter_names: case Slot::Kind::kFunctionType_parameter_types: case Slot::Kind::kFunctionType_type_parameters: case Slot::Kind::kPointerBase_data_field: case Slot::Kind::kType_arguments: case Slot::Kind::kTypeArgumentsIndex: case Slot::Kind::kTypeParameter_bound: case Slot::Kind::kTypeParameter_flags: case Slot::Kind::kTypeParameter_name: case Slot::Kind::kUnhandledException_exception: case Slot::Kind::kUnhandledException_stacktrace: case Slot::Kind::kWeakProperty_key: case Slot::Kind::kWeakProperty_value: return false; } UNREACHABLE(); return false; } bool LoadFieldInstr::IsFixedLengthArrayCid(intptr_t cid) { if (IsTypedDataClassId(cid) || IsExternalTypedDataClassId(cid)) { return true; } switch (cid) { case kArrayCid: case kImmutableArrayCid: case kTypeArgumentsCid: return true; default: return false; } } bool LoadFieldInstr::IsTypedDataViewFactory(const Function& function) { auto kind = function.recognized_kind(); switch (kind) { case MethodRecognizer::kTypedData_ByteDataView_factory: case MethodRecognizer::kTypedData_Int8ArrayView_factory: case MethodRecognizer::kTypedData_Uint8ArrayView_factory: case MethodRecognizer::kTypedData_Uint8ClampedArrayView_factory: case MethodRecognizer::kTypedData_Int16ArrayView_factory: case MethodRecognizer::kTypedData_Uint16ArrayView_factory: case MethodRecognizer::kTypedData_Int32ArrayView_factory: case MethodRecognizer::kTypedData_Uint32ArrayView_factory: case MethodRecognizer::kTypedData_Int64ArrayView_factory: case MethodRecognizer::kTypedData_Uint64ArrayView_factory: case MethodRecognizer::kTypedData_Float32ArrayView_factory: case MethodRecognizer::kTypedData_Float64ArrayView_factory: case MethodRecognizer::kTypedData_Float32x4ArrayView_factory: case MethodRecognizer::kTypedData_Int32x4ArrayView_factory: case MethodRecognizer::kTypedData_Float64x2ArrayView_factory: return true; default: return false; } } Definition* ConstantInstr::Canonicalize(FlowGraph* flow_graph) { return HasUses() ? this : NULL; } // A math unary instruction has a side effect (exception // thrown) if the argument is not a number. // TODO(srdjan): eliminate if has no uses and input is guaranteed to be number. Definition* MathUnaryInstr::Canonicalize(FlowGraph* flow_graph) { return this; } bool LoadFieldInstr::TryEvaluateLoad(const Object& instance, const Slot& field, Object* result) { switch (field.kind()) { case Slot::Kind::kDartField: return TryEvaluateLoad(instance, field.field(), result); case Slot::Kind::kArgumentsDescriptor_type_args_len: if (instance.IsArray() && Array::Cast(instance).IsImmutable()) { ArgumentsDescriptor desc(Array::Cast(instance)); *result = Smi::New(desc.TypeArgsLen()); return true; } return false; case Slot::Kind::kArgumentsDescriptor_count: if (instance.IsArray() && Array::Cast(instance).IsImmutable()) { ArgumentsDescriptor desc(Array::Cast(instance)); *result = Smi::New(desc.Count()); return true; } return false; case Slot::Kind::kArgumentsDescriptor_positional_count: if (instance.IsArray() && Array::Cast(instance).IsImmutable()) { ArgumentsDescriptor desc(Array::Cast(instance)); *result = Smi::New(desc.PositionalCount()); return true; } return false; case Slot::Kind::kArgumentsDescriptor_size: // If a constant arguments descriptor appears, then either it is from // a invocation dispatcher (which always has tagged arguments and so // [host]Size() == [target]Size() == Count()) or the constant should // have the correct Size() in terms of the target architecture if any // spill slots are involved. if (instance.IsArray() && Array::Cast(instance).IsImmutable()) { ArgumentsDescriptor desc(Array::Cast(instance)); *result = Smi::New(desc.Size()); return true; } return false; case Slot::Kind::kTypeArguments_length: if (instance.IsTypeArguments()) { *result = Smi::New(TypeArguments::Cast(instance).Length()); return true; } return false; default: break; } return false; } bool LoadFieldInstr::TryEvaluateLoad(const Object& instance, const Field& field, Object* result) { if (!field.is_final() || !instance.IsInstance()) { return false; } // Check that instance really has the field which we // are trying to load from. Class& cls = Class::Handle(instance.clazz()); while (cls.ptr() != Class::null() && cls.ptr() != field.Owner()) { cls = cls.SuperClass(); } if (cls.ptr() != field.Owner()) { // Failed to find the field in class or its superclasses. return false; } // Object has the field: execute the load. *result = Instance::Cast(instance).GetField(field); return true; } bool LoadFieldInstr::Evaluate(const Object& instance, Object* result) { return TryEvaluateLoad(instance, slot(), result); } Definition* LoadFieldInstr::Canonicalize(FlowGraph* flow_graph) { if (!HasUses() && !calls_initializer()) return nullptr; if (IsImmutableLengthLoad()) { ASSERT(!calls_initializer()); Definition* array = instance()->definition()->OriginalDefinition(); if (StaticCallInstr* call = array->AsStaticCall()) { // For fixed length arrays if the array is the result of a known // constructor call we can replace the length load with the length // argument passed to the constructor. if (call->is_known_list_constructor() && IsFixedLengthArrayCid(call->Type()->ToCid())) { return call->ArgumentAt(1); } else if (call->function().recognized_kind() == MethodRecognizer::kByteDataFactory) { // Similarly, we check for the ByteData constructor and forward its // explicit length argument appropriately. return call->ArgumentAt(1); } else if (IsTypedDataViewFactory(call->function())) { // Typed data view factories all take three arguments (after // the implicit type arguments parameter): // // 1) _TypedList buffer -- the underlying data for the view // 2) int offsetInBytes -- the offset into the buffer to start viewing // 3) int length -- the number of elements in the view // // Here, we forward the third. return call->ArgumentAt(3); } } else if (CreateArrayInstr* create_array = array->AsCreateArray()) { if (slot().kind() == Slot::Kind::kArray_length) { return create_array->num_elements()->definition(); } } else if (AllocateTypedDataInstr* alloc_typed_data = array->AsAllocateTypedData()) { if (slot().kind() == Slot::Kind::kTypedDataBase_length) { return alloc_typed_data->num_elements()->definition(); } } else if (LoadFieldInstr* load_array = array->AsLoadField()) { // For arrays with guarded lengths, replace the length load // with a constant. const Slot& slot = load_array->slot(); if (slot.IsDartField()) { if (slot.field().guarded_list_length() >= 0) { return flow_graph->GetConstant( Smi::Handle(Smi::New(slot.field().guarded_list_length()))); } } } } else if (slot().kind() == Slot::Kind::kTypedDataView_data) { // This case cover the first explicit argument to typed data view // factories, the data (buffer). ASSERT(!calls_initializer()); Definition* array = instance()->definition()->OriginalDefinition(); if (StaticCallInstr* call = array->AsStaticCall()) { if (IsTypedDataViewFactory(call->function())) { return call->ArgumentAt(1); } } } else if (slot().kind() == Slot::Kind::kTypedDataView_offset_in_bytes) { // This case cover the second explicit argument to typed data view // factories, the offset into the buffer. ASSERT(!calls_initializer()); Definition* array = instance()->definition()->OriginalDefinition(); if (StaticCallInstr* call = array->AsStaticCall()) { if (IsTypedDataViewFactory(call->function())) { return call->ArgumentAt(2); } else if (call->function().recognized_kind() == MethodRecognizer::kByteDataFactory) { // A _ByteDataView returned from the ByteData constructor always // has an offset of 0. return flow_graph->GetConstant(Object::smi_zero()); } } } else if (slot().IsTypeArguments()) { ASSERT(!calls_initializer()); Definition* array = instance()->definition()->OriginalDefinition(); if (StaticCallInstr* call = array->AsStaticCall()) { if (call->is_known_list_constructor()) { return call->ArgumentAt(0); } else if (IsTypedDataViewFactory(call->function())) { return flow_graph->constant_null(); } switch (call->function().recognized_kind()) { case MethodRecognizer::kByteDataFactory: case MethodRecognizer::kLinkedHashMap_getData: return flow_graph->constant_null(); default: break; } } else if (CreateArrayInstr* create_array = array->AsCreateArray()) { return create_array->element_type()->definition(); } else if (LoadFieldInstr* load_array = array->AsLoadField()) { const Slot& slot = load_array->slot(); switch (slot.kind()) { case Slot::Kind::kDartField: { // For trivially exact fields we know that type arguments match // static type arguments exactly. const Field& field = slot.field(); if (field.static_type_exactness_state().IsTriviallyExact()) { return flow_graph->GetConstant(TypeArguments::Handle( AbstractType::Handle(field.type()).arguments())); } break; } case Slot::Kind::kLinkedHashMap_data: return flow_graph->constant_null(); default: break; } } } // Try folding away loads from constant objects. if (instance()->BindsToConstant()) { Object& result = Object::Handle(); if (Evaluate(instance()->BoundConstant(), &result)) { if (result.IsSmi() || result.IsOld()) { return flow_graph->GetConstant(result); } } } return this; } Definition* AssertBooleanInstr::Canonicalize(FlowGraph* flow_graph) { if (FLAG_eliminate_type_checks) { if (value()->Type()->ToCid() == kBoolCid) { return value()->definition(); } // In strong mode type is already verified either by static analysis // or runtime checks, so AssertBoolean just ensures that value is not null. if (!value()->Type()->is_nullable()) { return value()->definition(); } } return this; } Definition* AssertAssignableInstr::Canonicalize(FlowGraph* flow_graph) { // We need dst_type() to be a constant AbstractType to perform any // canonicalization. if (!dst_type()->BindsToConstant()) return this; const auto& abs_type = AbstractType::Cast(dst_type()->BoundConstant()); if (abs_type.IsTopTypeForSubtyping() || (FLAG_eliminate_type_checks && value()->Type()->IsAssignableTo(abs_type))) { return value()->definition(); } if (abs_type.IsInstantiated()) { return this; } // For uninstantiated target types: If the instantiator and function // type arguments are constant, instantiate the target type here. // Note: these constant type arguments might not necessarily correspond // to the correct instantiator because AssertAssignable might // be located in the unreachable part of the graph (e.g. // it might be dominated by CheckClass that always fails). // This means that the code below must guard against such possibility. Zone* Z = Thread::Current()->zone(); const TypeArguments* instantiator_type_args = nullptr; const TypeArguments* function_type_args = nullptr; if (instantiator_type_arguments()->BindsToConstant()) { const Object& val = instantiator_type_arguments()->BoundConstant(); instantiator_type_args = (val.ptr() == TypeArguments::null()) ? &TypeArguments::null_type_arguments() : &TypeArguments::Cast(val); } if (function_type_arguments()->BindsToConstant()) { const Object& val = function_type_arguments()->BoundConstant(); function_type_args = (val.ptr() == TypeArguments::null()) ? &TypeArguments::null_type_arguments() : &TypeArguments::Cast(function_type_arguments()->BoundConstant()); } // If instantiator_type_args are not constant try to match the pattern // obj.field.:type_arguments where field's static type exactness state // tells us that all values stored in the field have exact superclass. // In this case we know the prefix of the actual type arguments vector // and can try to instantiate the type using just the prefix. // // Note: TypeParameter::InstantiateFrom returns an error if we try // to instantiate it from a vector that is too short. if (instantiator_type_args == nullptr) { if (LoadFieldInstr* load_type_args = instantiator_type_arguments()->definition()->AsLoadField()) { if (load_type_args->slot().IsTypeArguments()) { if (LoadFieldInstr* load_field = load_type_args->instance() ->definition() ->OriginalDefinition() ->AsLoadField()) { if (load_field->slot().IsDartField() && load_field->slot() .field() .static_type_exactness_state() .IsHasExactSuperClass()) { instantiator_type_args = &TypeArguments::Handle( Z, AbstractType::Handle(Z, load_field->slot().field().type()) .arguments()); } } } } } if ((instantiator_type_args != nullptr) && (function_type_args != nullptr)) { AbstractType& new_dst_type = AbstractType::Handle( Z, abs_type.InstantiateFrom(*instantiator_type_args, *function_type_args, kAllFree, Heap::kOld)); if (new_dst_type.IsNull()) { // Failed instantiation in dead code. return this; } if (new_dst_type.IsTypeRef()) { new_dst_type = TypeRef::Cast(new_dst_type).type(); } new_dst_type = new_dst_type.Canonicalize(Thread::Current(), nullptr); // Successfully instantiated destination type: update the type attached // to this instruction and set type arguments to null because we no // longer need them (the type was instantiated). dst_type()->BindTo(flow_graph->GetConstant(new_dst_type)); instantiator_type_arguments()->BindTo(flow_graph->constant_null()); function_type_arguments()->BindTo(flow_graph->constant_null()); if (new_dst_type.IsTopTypeForSubtyping() || (FLAG_eliminate_type_checks && value()->Type()->IsAssignableTo(new_dst_type))) { return value()->definition(); } } return this; } Definition* InstantiateTypeArgumentsInstr::Canonicalize(FlowGraph* flow_graph) { return HasUses() ? this : NULL; } LocationSummary* DebugStepCheckInstr::MakeLocationSummary(Zone* zone, bool opt) const { const intptr_t kNumInputs = 0; const intptr_t kNumTemps = 0; LocationSummary* locs = new (zone) LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kCall); return locs; } Instruction* DebugStepCheckInstr::Canonicalize(FlowGraph* flow_graph) { return NULL; } Definition* BoxInstr::Canonicalize(FlowGraph* flow_graph) { if (input_use_list() == nullptr) { // Environments can accommodate any representation. No need to box. return value()->definition(); } // Fold away Box(Unbox(v)) if value is known to be of the // right class. UnboxInstr* unbox_defn = value()->definition()->AsUnbox(); if ((unbox_defn != NULL) && (unbox_defn->representation() == from_representation()) && (unbox_defn->value()->Type()->ToCid() == Type()->ToCid())) { return unbox_defn->value()->definition(); } return this; } bool BoxIntegerInstr::ValueFitsSmi() const { Range* range = value()->definition()->range(); return RangeUtils::Fits(range, RangeBoundary::kRangeBoundarySmi); } Definition* BoxIntegerInstr::Canonicalize(FlowGraph* flow_graph) { if (input_use_list() == nullptr) { // Environments can accommodate any representation. No need to box. return value()->definition(); } return this; } Definition* BoxInt64Instr::Canonicalize(FlowGraph* flow_graph) { Definition* replacement = BoxIntegerInstr::Canonicalize(flow_graph); if (replacement != this) { return replacement; } // For all x, box(unbox(x)) = x. if (auto unbox = value()->definition()->AsUnboxInt64()) { if (unbox->SpeculativeModeOfInputs() == kNotSpeculative) { return unbox->value()->definition(); } } else if (auto unbox = value()->definition()->AsUnboxedConstant()) { return flow_graph->GetConstant(unbox->value()); } // Find a more precise box instruction. if (auto conv = value()->definition()->AsIntConverter()) { Definition* replacement; if (conv->from() == kUntagged) { return this; } switch (conv->from()) { case kUnboxedInt32: replacement = new BoxInt32Instr(conv->value()->CopyWithType()); break; case kUnboxedUint32: replacement = new BoxUint32Instr(conv->value()->CopyWithType()); break; default: UNREACHABLE(); break; } flow_graph->InsertBefore(this, replacement, NULL, FlowGraph::kValue); return replacement; } return this; } Definition* UnboxInstr::Canonicalize(FlowGraph* flow_graph) { if (!HasUses() && !CanDeoptimize()) return NULL; // Fold away Unbox(Box(v)). BoxInstr* box_defn = value()->definition()->AsBox(); if ((box_defn != NULL) && (box_defn->from_representation() == representation())) { return box_defn->value()->definition(); } if (representation() == kUnboxedDouble && value()->BindsToConstant()) { const Object& val = value()->BoundConstant(); if (val.IsInteger()) { const Double& double_val = Double::ZoneHandle( flow_graph->zone(), Double::NewCanonical(Integer::Cast(val).AsDoubleValue())); return flow_graph->GetConstant(double_val, kUnboxedDouble); } else if (val.IsDouble()) { return flow_graph->GetConstant(val, kUnboxedDouble); } } return this; } Definition* UnboxIntegerInstr::Canonicalize(FlowGraph* flow_graph) { if (!HasUses() && !CanDeoptimize()) return NULL; // Do not attempt to fold this instruction if we have not matched // input/output representations yet. if (HasUnmatchedInputRepresentations()) { return this; } // Fold away UnboxInteger(BoxInteger(v)). BoxIntegerInstr* box_defn = value()->definition()->AsBoxInteger(); if (box_defn != NULL && !box_defn->HasUnmatchedInputRepresentations()) { Representation from_representation = box_defn->value()->definition()->representation(); if (from_representation == representation()) { return box_defn->value()->definition(); } else { // Only operate on explicit unboxed operands. IntConverterInstr* converter = new IntConverterInstr( from_representation, representation(), box_defn->value()->CopyWithType(), (representation() == kUnboxedInt32) ? GetDeoptId() : DeoptId::kNone); // TODO(vegorov): marking resulting converter as truncating when // unboxing can't deoptimize is a workaround for the missing // deoptimization environment when we insert converter after // EliminateEnvironments and there is a mismatch between predicates // UnboxIntConverterInstr::CanDeoptimize and UnboxInt32::CanDeoptimize. if ((representation() == kUnboxedInt32) && (is_truncating() || !CanDeoptimize())) { converter->mark_truncating(); } flow_graph->InsertBefore(this, converter, env(), FlowGraph::kValue); return converter; } } return this; } Definition* UnboxInt32Instr::Canonicalize(FlowGraph* flow_graph) { Definition* replacement = UnboxIntegerInstr::Canonicalize(flow_graph); if (replacement != this) { return replacement; } ConstantInstr* c = value()->definition()->AsConstant(); if ((c != NULL) && c->value().IsInteger()) { if (!is_truncating()) { // Check that constant fits into 32-bit integer. const int64_t value = Integer::Cast(c->value()).AsInt64Value(); if (!Utils::IsInt(32, value)) { return this; } } return flow_graph->GetConstant(c->value(), kUnboxedInt32); } return this; } Definition* UnboxInt64Instr::Canonicalize(FlowGraph* flow_graph) { Definition* replacement = UnboxIntegerInstr::Canonicalize(flow_graph); if (replacement != this) { return replacement; } // Currently we perform this only on 64-bit architectures. if (compiler::target::kBitsPerWord == 64) { ConstantInstr* c = value()->definition()->AsConstant(); if (c != NULL && c->value().IsInteger()) { return flow_graph->GetConstant(c->value(), kUnboxedInt64); } } return this; } Definition* IntConverterInstr::Canonicalize(FlowGraph* flow_graph) { if (!HasUses()) return NULL; IntConverterInstr* box_defn = value()->definition()->AsIntConverter(); if ((box_defn != NULL) && (box_defn->representation() == from())) { // If the first convertion can erase bits (or deoptimize) we can't // canonicalize it away. auto src_defn = box_defn->value()->definition(); if ((box_defn->from() == kUnboxedInt64) && !Range::Fits(src_defn->range(), box_defn->to())) { return this; } // Otherise it is safe to discard any other conversions from and then back // to the same integer type. if (box_defn->from() == to()) { return src_defn; } // Do not merge conversions where the first starts from Untagged or the // second ends at Untagged, since we expect to see either UnboxedIntPtr // or UnboxedFfiIntPtr as the other type in an Untagged conversion. if ((box_defn->from() == kUntagged) || (to() == kUntagged)) { return this; } IntConverterInstr* converter = new IntConverterInstr( box_defn->from(), representation(), box_defn->value()->CopyWithType(), (to() == kUnboxedInt32) ? GetDeoptId() : DeoptId::kNone); if ((representation() == kUnboxedInt32) && is_truncating()) { converter->mark_truncating(); } flow_graph->InsertBefore(this, converter, env(), FlowGraph::kValue); return converter; } UnboxInt64Instr* unbox_defn = value()->definition()->AsUnboxInt64(); if (unbox_defn != NULL && (from() == kUnboxedInt64) && (to() == kUnboxedInt32) && unbox_defn->HasOnlyInputUse(value())) { // TODO(vegorov): there is a duplication of code between UnboxedIntCoverter // and code path that unboxes Mint into Int32. We should just schedule // these instructions close to each other instead of fusing them. Definition* replacement = new UnboxInt32Instr(is_truncating() ? UnboxInt32Instr::kTruncate : UnboxInt32Instr::kNoTruncation, unbox_defn->value()->CopyWithType(), GetDeoptId()); flow_graph->InsertBefore(this, replacement, env(), FlowGraph::kValue); return replacement; } return this; } // Tests for a FP comparison that cannot be negated // (to preserve NaN semantics). static bool IsFpCompare(ComparisonInstr* comp) { if (comp->IsRelationalOp()) { return comp->operation_cid() == kDoubleCid; } return false; } Definition* BooleanNegateInstr::Canonicalize(FlowGraph* flow_graph) { Definition* defn = value()->definition(); // Convert e.g. !(x > y) into (x <= y) for non-FP x, y. if (defn->IsComparison() && defn->HasOnlyUse(value()) && defn->Type()->ToCid() == kBoolCid) { ComparisonInstr* comp = defn->AsComparison(); if (!IsFpCompare(comp)) { comp->NegateComparison(); return defn; } } return this; } static bool MayBeBoxableNumber(intptr_t cid) { return (cid == kDynamicCid) || (cid == kMintCid) || (cid == kDoubleCid); } static bool MayBeNumber(CompileType* type) { if (type->IsNone()) { return false; } const AbstractType& unwrapped_type = AbstractType::Handle(type->ToAbstractType()->UnwrapFutureOr()); // Note that type 'Number' is a subtype of itself. return unwrapped_type.IsTopTypeForSubtyping() || unwrapped_type.IsObjectType() || unwrapped_type.IsTypeParameter() || unwrapped_type.IsSubtypeOf(Type::Handle(Type::Number()), Heap::kOld); } // Returns a replacement for a strict comparison and signals if the result has // to be negated. static Definition* CanonicalizeStrictCompare(StrictCompareInstr* compare, bool* negated, bool is_branch) { // Use propagated cid and type information to eliminate number checks. // If one of the inputs is not a boxable number (Mint, Double), or // is not a subtype of num, no need for number checks. if (compare->needs_number_check()) { if (!MayBeBoxableNumber(compare->left()->Type()->ToCid()) || !MayBeBoxableNumber(compare->right()->Type()->ToCid())) { compare->set_needs_number_check(false); } else if (!MayBeNumber(compare->left()->Type()) || !MayBeNumber(compare->right()->Type())) { compare->set_needs_number_check(false); } } *negated = false; PassiveObject& constant = PassiveObject::Handle(); Value* other = NULL; if (compare->right()->BindsToConstant()) { constant = compare->right()->BoundConstant().ptr(); other = compare->left(); } else if (compare->left()->BindsToConstant()) { constant = compare->left()->BoundConstant().ptr(); other = compare->right(); } else { return compare; } const bool can_merge = is_branch || (other->Type()->ToCid() == kBoolCid); Definition* other_defn = other->definition(); Token::Kind kind = compare->kind(); // Handle e === true. if ((kind == Token::kEQ_STRICT) && (constant.ptr() == Bool::True().ptr()) && can_merge) { return other_defn; } // Handle e !== false. if ((kind == Token::kNE_STRICT) && (constant.ptr() == Bool::False().ptr()) && can_merge) { return other_defn; } // Handle e !== true. if ((kind == Token::kNE_STRICT) && (constant.ptr() == Bool::True().ptr()) && other_defn->IsComparison() && can_merge && other_defn->HasOnlyUse(other)) { ComparisonInstr* comp = other_defn->AsComparison(); if (!IsFpCompare(comp)) { *negated = true; return other_defn; } } // Handle e === false. if ((kind == Token::kEQ_STRICT) && (constant.ptr() == Bool::False().ptr()) && other_defn->IsComparison() && can_merge && other_defn->HasOnlyUse(other)) { ComparisonInstr* comp = other_defn->AsComparison(); if (!IsFpCompare(comp)) { *negated = true; return other_defn; } } return compare; } static bool BindsToGivenConstant(Value* v, intptr_t expected) { return v->BindsToConstant() && v->BoundConstant().IsSmi() && (Smi::Cast(v->BoundConstant()).Value() == expected); } // Recognize patterns (a & b) == 0 and (a & 2^n) != 2^n. static bool RecognizeTestPattern(Value* left, Value* right, bool* negate) { if (!right->BindsToConstant() || !right->BoundConstant().IsSmi()) { return false; } const intptr_t value = Smi::Cast(right->BoundConstant()).Value(); if ((value != 0) && !Utils::IsPowerOfTwo(value)) { return false; } BinarySmiOpInstr* mask_op = left->definition()->AsBinarySmiOp(); if ((mask_op == NULL) || (mask_op->op_kind() != Token::kBIT_AND) || !mask_op->HasOnlyUse(left)) { return false; } if (value == 0) { // Recognized (a & b) == 0 pattern. *negate = false; return true; } // Recognize if (BindsToGivenConstant(mask_op->left(), value) || BindsToGivenConstant(mask_op->right(), value)) { // Recognized (a & 2^n) == 2^n pattern. It's equivalent to (a & 2^n) != 0 // so we need to negate original comparison. *negate = true; return true; } return false; } Instruction* BranchInstr::Canonicalize(FlowGraph* flow_graph) { Zone* zone = flow_graph->zone(); // Only handle strict-compares. if (comparison()->IsStrictCompare()) { bool negated = false; Definition* replacement = CanonicalizeStrictCompare( comparison()->AsStrictCompare(), &negated, /* is_branch = */ true); if (replacement == comparison()) { return this; } ComparisonInstr* comp = replacement->AsComparison(); if ((comp == NULL) || comp->CanDeoptimize() || comp->HasUnmatchedInputRepresentations()) { return this; } // Replace the comparison if the replacement is used at this branch, // and has exactly one use. Value* use = comp->input_use_list(); if ((use->instruction() == this) && comp->HasOnlyUse(use)) { if (negated) { comp->NegateComparison(); } RemoveEnvironment(); flow_graph->CopyDeoptTarget(this, comp); // Unlink environment from the comparison since it is copied to the // branch instruction. comp->RemoveEnvironment(); comp->RemoveFromGraph(); SetComparison(comp); if (FLAG_trace_optimization) { THR_Print("Merging comparison v%" Pd "\n", comp->ssa_temp_index()); } // Clear the comparison's temp index and ssa temp index since the // value of the comparison is not used outside the branch anymore. ASSERT(comp->input_use_list() == NULL); comp->ClearSSATempIndex(); comp->ClearTempIndex(); } } else if (comparison()->IsEqualityCompare() && comparison()->operation_cid() == kSmiCid) { BinarySmiOpInstr* bit_and = NULL; bool negate = false; if (RecognizeTestPattern(comparison()->left(), comparison()->right(), &negate)) { bit_and = comparison()->left()->definition()->AsBinarySmiOp(); } else if (RecognizeTestPattern(comparison()->right(), comparison()->left(), &negate)) { bit_and = comparison()->right()->definition()->AsBinarySmiOp(); } if (bit_and != NULL) { if (FLAG_trace_optimization) { THR_Print("Merging test smi v%" Pd "\n", bit_and->ssa_temp_index()); } TestSmiInstr* test = new TestSmiInstr( comparison()->source(), negate ? Token::NegateComparison(comparison()->kind()) : comparison()->kind(), bit_and->left()->Copy(zone), bit_and->right()->Copy(zone)); ASSERT(!CanDeoptimize()); RemoveEnvironment(); flow_graph->CopyDeoptTarget(this, bit_and); SetComparison(test); bit_and->RemoveFromGraph(); } } return this; } Definition* StrictCompareInstr::Canonicalize(FlowGraph* flow_graph) { if (!HasUses()) return NULL; bool negated = false; Definition* replacement = CanonicalizeStrictCompare(this, &negated, /* is_branch = */ false); if (negated && replacement->IsComparison()) { ASSERT(replacement != this); replacement->AsComparison()->NegateComparison(); } return replacement; } Definition* EqualityCompareInstr::Canonicalize(FlowGraph* flow_graph) { if (is_null_aware()) { ASSERT(operation_cid() == kMintCid); // Select more efficient instructions based on operand types. CompileType* left_type = left()->Type(); CompileType* right_type = right()->Type(); if (left_type->IsNull() || left_type->IsNullableSmi() || right_type->IsNull() || right_type->IsNullableSmi()) { auto replacement = new StrictCompareInstr( source(), (kind() == Token::kEQ) ? Token::kEQ_STRICT : Token::kNE_STRICT, left()->CopyWithType(), right()->CopyWithType(), /*needs_number_check=*/false, DeoptId::kNone); flow_graph->InsertBefore(this, replacement, env(), FlowGraph::kValue); return replacement; } else { if (!left_type->is_nullable() && !right_type->is_nullable()) { set_null_aware(false); } } } return this; } Instruction* CheckClassInstr::Canonicalize(FlowGraph* flow_graph) { const intptr_t value_cid = value()->Type()->ToCid(); if (value_cid == kDynamicCid) { return this; } return cids().HasClassId(value_cid) ? NULL : this; } Definition* LoadClassIdInstr::Canonicalize(FlowGraph* flow_graph) { // TODO(dartbug.com/40188): Allow this to canonicalize into an untagged // constant and make a subsequent DispatchTableCallInstr canonicalize into a // StaticCall. if (representation() == kUntagged) return this; const intptr_t cid = object()->Type()->ToCid(); if (cid != kDynamicCid) { const auto& smi = Smi::ZoneHandle(flow_graph->zone(), Smi::New(cid)); return flow_graph->GetConstant(smi); } return this; } Instruction* CheckClassIdInstr::Canonicalize(FlowGraph* flow_graph) { if (value()->BindsToConstant()) { const Object& constant_value = value()->BoundConstant(); if (constant_value.IsSmi() && cids_.Contains(Smi::Cast(constant_value).Value())) { return NULL; } } return this; } TestCidsInstr::TestCidsInstr(const InstructionSource& source, Token::Kind kind, Value* value, const ZoneGrowableArray& cid_results, intptr_t deopt_id) : TemplateComparison(source, kind, deopt_id), cid_results_(cid_results), licm_hoisted_(false) { ASSERT((kind == Token::kIS) || (kind == Token::kISNOT)); SetInputAt(0, value); set_operation_cid(kObjectCid); #ifdef DEBUG ASSERT(cid_results[0] == kSmiCid); if (deopt_id == DeoptId::kNone) { // The entry for Smi can be special, but all other entries have // to match in the no-deopt case. for (intptr_t i = 4; i < cid_results.length(); i += 2) { ASSERT(cid_results[i + 1] == cid_results[3]); } } #endif } Definition* TestCidsInstr::Canonicalize(FlowGraph* flow_graph) { CompileType* in_type = left()->Type(); intptr_t cid = in_type->ToCid(); if (cid == kDynamicCid) return this; const ZoneGrowableArray& data = cid_results(); const intptr_t true_result = (kind() == Token::kIS) ? 1 : 0; for (intptr_t i = 0; i < data.length(); i += 2) { if (data[i] == cid) { return (data[i + 1] == true_result) ? flow_graph->GetConstant(Bool::True()) : flow_graph->GetConstant(Bool::False()); } } if (!CanDeoptimize()) { ASSERT(deopt_id() == DeoptId::kNone); return (data[data.length() - 1] == true_result) ? flow_graph->GetConstant(Bool::False()) : flow_graph->GetConstant(Bool::True()); } // TODO(sra): Handle nullable input, possibly canonicalizing to a compare // against `null`. return this; } Instruction* GuardFieldClassInstr::Canonicalize(FlowGraph* flow_graph) { if (field().guarded_cid() == kDynamicCid) { return NULL; // Nothing to guard. } if (field().is_nullable() && value()->Type()->IsNull()) { return NULL; } const intptr_t cid = field().is_nullable() ? value()->Type()->ToNullableCid() : value()->Type()->ToCid(); if (field().guarded_cid() == cid) { return NULL; // Value is guaranteed to have this cid. } return this; } Instruction* GuardFieldLengthInstr::Canonicalize(FlowGraph* flow_graph) { if (!field().needs_length_check()) { return NULL; // Nothing to guard. } const intptr_t expected_length = field().guarded_list_length(); if (expected_length == Field::kUnknownFixedLength) { return this; } // Check if length is statically known. StaticCallInstr* call = value()->definition()->AsStaticCall(); if (call == NULL) { return this; } ConstantInstr* length = NULL; if (call->is_known_list_constructor() && LoadFieldInstr::IsFixedLengthArrayCid(call->Type()->ToCid())) { length = call->ArgumentAt(1)->AsConstant(); } else if (call->function().recognized_kind() == MethodRecognizer::kByteDataFactory) { length = call->ArgumentAt(1)->AsConstant(); } else if (LoadFieldInstr::IsTypedDataViewFactory(call->function())) { length = call->ArgumentAt(3)->AsConstant(); } if ((length != NULL) && length->value().IsSmi() && Smi::Cast(length->value()).Value() == expected_length) { return NULL; // Expected length matched. } return this; } Instruction* GuardFieldTypeInstr::Canonicalize(FlowGraph* flow_graph) { return field().static_type_exactness_state().NeedsFieldGuard() ? this : nullptr; } Instruction* CheckSmiInstr::Canonicalize(FlowGraph* flow_graph) { return (value()->Type()->ToCid() == kSmiCid) ? NULL : this; } Instruction* CheckEitherNonSmiInstr::Canonicalize(FlowGraph* flow_graph) { if ((left()->Type()->ToCid() == kDoubleCid) || (right()->Type()->ToCid() == kDoubleCid)) { return NULL; // Remove from the graph. } return this; } Definition* CheckNullInstr::Canonicalize(FlowGraph* flow_graph) { return (!value()->Type()->is_nullable()) ? value()->definition() : this; } bool CheckNullInstr::AttributesEqual(const Instruction& other) const { auto const other_check = other.AsCheckNull(); ASSERT(other_check != nullptr); return function_name().Equals(other_check->function_name()) && exception_type() == other_check->exception_type(); } BoxInstr* BoxInstr::Create(Representation from, Value* value) { switch (from) { case kUnboxedUint8: return new BoxUint8Instr(value); case kUnboxedInt32: return new BoxInt32Instr(value); case kUnboxedUint32: return new BoxUint32Instr(value); case kUnboxedInt64: return new BoxInt64Instr(value); case kUnboxedDouble: case kUnboxedFloat: case kUnboxedFloat32x4: case kUnboxedFloat64x2: case kUnboxedInt32x4: return new BoxInstr(from, value); default: UNREACHABLE(); return NULL; } } UnboxInstr* UnboxInstr::Create(Representation to, Value* value, intptr_t deopt_id, SpeculativeMode speculative_mode) { switch (to) { case kUnboxedInt32: // We must truncate if we can't deoptimize. return new UnboxInt32Instr( speculative_mode == SpeculativeMode::kNotSpeculative ? UnboxInt32Instr::kTruncate : UnboxInt32Instr::kNoTruncation, value, deopt_id, speculative_mode); case kUnboxedUint32: return new UnboxUint32Instr(value, deopt_id, speculative_mode); case kUnboxedInt64: return new UnboxInt64Instr(value, deopt_id, speculative_mode); case kUnboxedDouble: case kUnboxedFloat: case kUnboxedFloat32x4: case kUnboxedFloat64x2: case kUnboxedInt32x4: ASSERT(FlowGraphCompiler::SupportsUnboxedDoubles()); return new UnboxInstr(to, value, deopt_id, speculative_mode); default: UNREACHABLE(); return NULL; } } bool UnboxInstr::CanConvertSmi() const { switch (representation()) { case kUnboxedDouble: case kUnboxedFloat: case kUnboxedInt32: case kUnboxedInt64: return true; case kUnboxedFloat32x4: case kUnboxedFloat64x2: case kUnboxedInt32x4: return false; default: UNREACHABLE(); return false; } } const BinaryFeedback* BinaryFeedback::Create(Zone* zone, const ICData& ic_data) { BinaryFeedback* result = new (zone) BinaryFeedback(zone); if (ic_data.NumArgsTested() == 2) { for (intptr_t i = 0, n = ic_data.NumberOfChecks(); i < n; i++) { if (ic_data.GetCountAt(i) == 0) { continue; } GrowableArray arg_ids; ic_data.GetClassIdsAt(i, &arg_ids); result->feedback_.Add({arg_ids[0], arg_ids[1]}); } } return result; } const BinaryFeedback* BinaryFeedback::CreateMonomorphic(Zone* zone, intptr_t receiver_cid, intptr_t argument_cid) { BinaryFeedback* result = new (zone) BinaryFeedback(zone); result->feedback_.Add({receiver_cid, argument_cid}); return result; } const CallTargets* CallTargets::CreateMonomorphic(Zone* zone, intptr_t receiver_cid, const Function& target) { CallTargets* targets = new (zone) CallTargets(zone); const intptr_t count = 1; targets->cid_ranges_.Add(new (zone) TargetInfo( receiver_cid, receiver_cid, &Function::ZoneHandle(zone, target.ptr()), count, StaticTypeExactnessState::NotTracking())); return targets; } const CallTargets* CallTargets::Create(Zone* zone, const ICData& ic_data) { CallTargets* targets = new (zone) CallTargets(zone); targets->CreateHelper(zone, ic_data); targets->Sort(OrderById); targets->MergeIntoRanges(); return targets; } const CallTargets* CallTargets::CreateAndExpand(Zone* zone, const ICData& ic_data) { CallTargets& targets = *new (zone) CallTargets(zone); targets.CreateHelper(zone, ic_data); if (targets.is_empty() || targets.IsMonomorphic()) { return &targets; } targets.Sort(OrderById); Array& args_desc_array = Array::Handle(zone, ic_data.arguments_descriptor()); ArgumentsDescriptor args_desc(args_desc_array); String& name = String::Handle(zone, ic_data.target_name()); Function& fn = Function::Handle(zone); intptr_t length = targets.length(); // Merging/extending cid ranges is also done in Cids::CreateAndExpand. // If changing this code, consider also adjusting Cids code. // Spread class-ids to preceding classes where a lookup yields the same // method. A polymorphic target is not really the same method since its // behaviour depends on the receiver class-id, so we don't spread the // class-ids in that case. for (int idx = 0; idx < length; idx++) { int lower_limit_cid = (idx == 0) ? -1 : targets[idx - 1].cid_end; auto target_info = targets.TargetAt(idx); const Function& target = *target_info->target; if (target.is_polymorphic_target()) continue; for (int i = target_info->cid_start - 1; i > lower_limit_cid; i--) { bool class_is_abstract = false; if (FlowGraphCompiler::LookupMethodFor(i, name, args_desc, &fn, &class_is_abstract) && fn.ptr() == target.ptr()) { if (!class_is_abstract) { target_info->cid_start = i; target_info->exactness = StaticTypeExactnessState::NotTracking(); } } else { break; } } } // Spread class-ids to following classes where a lookup yields the same // method. const intptr_t max_cid = IsolateGroup::Current()->class_table()->NumCids(); for (int idx = 0; idx < length; idx++) { int upper_limit_cid = (idx == length - 1) ? max_cid : targets[idx + 1].cid_start; auto target_info = targets.TargetAt(idx); const Function& target = *target_info->target; if (target.is_polymorphic_target()) continue; // The code below makes attempt to avoid spreading class-id range // into a suffix that consists purely of abstract classes to // shorten the range. // However such spreading is beneficial when it allows to // merge to consequtive ranges. intptr_t cid_end_including_abstract = target_info->cid_end; for (int i = target_info->cid_end + 1; i < upper_limit_cid; i++) { bool class_is_abstract = false; if (FlowGraphCompiler::LookupMethodFor(i, name, args_desc, &fn, &class_is_abstract) && fn.ptr() == target.ptr()) { cid_end_including_abstract = i; if (!class_is_abstract) { target_info->cid_end = i; target_info->exactness = StaticTypeExactnessState::NotTracking(); } } else { break; } } // Check if we have a suffix that consists of abstract classes // and expand into it if that would allow us to merge this // range with subsequent range. if ((cid_end_including_abstract > target_info->cid_end) && (idx < length - 1) && ((cid_end_including_abstract + 1) == targets[idx + 1].cid_start) && (target.ptr() == targets.TargetAt(idx + 1)->target->ptr())) { target_info->cid_end = cid_end_including_abstract; target_info->exactness = StaticTypeExactnessState::NotTracking(); } } targets.MergeIntoRanges(); return &targets; } void CallTargets::MergeIntoRanges() { if (length() == 0) { return; // For correctness not performance: must not update length to 1. } // Merge adjacent class id ranges. int dest = 0; // We merge entries that dispatch to the same target, but polymorphic targets // are not really the same target since they depend on the class-id, so we // don't merge them. for (int src = 1; src < length(); src++) { const Function& target = *TargetAt(dest)->target; if (TargetAt(dest)->cid_end + 1 >= TargetAt(src)->cid_start && target.ptr() == TargetAt(src)->target->ptr() && !target.is_polymorphic_target()) { TargetAt(dest)->cid_end = TargetAt(src)->cid_end; TargetAt(dest)->count += TargetAt(src)->count; TargetAt(dest)->exactness = StaticTypeExactnessState::NotTracking(); } else { dest++; if (src != dest) { // Use cid_ranges_ instead of TargetAt when updating the pointer. cid_ranges_[dest] = TargetAt(src); } } } SetLength(dest + 1); Sort(OrderByFrequencyThenId); } void CallTargets::Print() const { for (intptr_t i = 0; i < length(); i++) { THR_Print("cid = [%" Pd ", %" Pd "], count = %" Pd ", target = %s\n", TargetAt(i)->cid_start, TargetAt(i)->cid_end, TargetAt(i)->count, TargetAt(i)->target->ToQualifiedCString()); } } // Shared code generation methods (EmitNativeCode and // MakeLocationSummary). Only assembly code that can be shared across all // architectures can be used. Machine specific register allocation and code // generation is located in intermediate_language_.cc #define __ compiler->assembler()-> LocationSummary* GraphEntryInstr::MakeLocationSummary(Zone* zone, bool optimizing) const { UNREACHABLE(); return NULL; } LocationSummary* JoinEntryInstr::MakeLocationSummary(Zone* zone, bool optimizing) const { UNREACHABLE(); return NULL; } void JoinEntryInstr::EmitNativeCode(FlowGraphCompiler* compiler) { __ Bind(compiler->GetJumpLabel(this)); if (!compiler->is_optimizing()) { compiler->AddCurrentDescriptor(UntaggedPcDescriptors::kDeopt, GetDeoptId(), InstructionSource()); } if (HasParallelMove()) { compiler->parallel_move_resolver()->EmitNativeCode(parallel_move()); } } LocationSummary* TargetEntryInstr::MakeLocationSummary(Zone* zone, bool optimizing) const { UNREACHABLE(); return NULL; } void TargetEntryInstr::EmitNativeCode(FlowGraphCompiler* compiler) { __ Bind(compiler->GetJumpLabel(this)); // TODO(kusterman): Remove duplicate between // {TargetEntryInstr,FunctionEntryInstr}::EmitNativeCode. if (!compiler->is_optimizing()) { if (compiler->NeedsEdgeCounter(this)) { compiler->EmitEdgeCounter(preorder_number()); } // The deoptimization descriptor points after the edge counter code for // uniformity with ARM, where we can reuse pattern matching code that // matches backwards from the end of the pattern. compiler->AddCurrentDescriptor(UntaggedPcDescriptors::kDeopt, GetDeoptId(), InstructionSource()); } if (HasParallelMove()) { if (compiler::Assembler::EmittingComments()) { compiler->EmitComment(parallel_move()); } compiler->parallel_move_resolver()->EmitNativeCode(parallel_move()); } } LocationSummary* FunctionEntryInstr::MakeLocationSummary( Zone* zone, bool optimizing) const { UNREACHABLE(); return NULL; } void FunctionEntryInstr::EmitNativeCode(FlowGraphCompiler* compiler) { #if defined(TARGET_ARCH_X64) // Ensure the start of the monomorphic checked entry is 2-byte aligned (see // also Assembler::MonomorphicCheckedEntry()). if (__ CodeSize() % 2 == 1) { __ nop(); } #endif if (tag() == Instruction::kFunctionEntry) { __ Bind(compiler->GetJumpLabel(this)); } if (this == compiler->flow_graph().graph_entry()->unchecked_entry()) { __ BindUncheckedEntryPoint(); } // In the AOT compiler we want to reduce code size, so generate no // fall-through code in [FlowGraphCompiler::CompileGraph()]. // (As opposed to here where we don't check for the return value of // [Intrinsify]). const Function& function = compiler->parsed_function().function(); if (function.NeedsMonomorphicCheckedEntry(compiler->zone())) { compiler->SpecialStatsBegin(CombinedCodeStatistics::kTagCheckedEntry); if (!FLAG_precompiled_mode) { __ MonomorphicCheckedEntryJIT(); } else { __ MonomorphicCheckedEntryAOT(); } compiler->SpecialStatsEnd(CombinedCodeStatistics::kTagCheckedEntry); } // NOTE: Because of the presence of multiple entry-points, we generate several // times the same intrinsification & frame setup. That's why we cannot rely on // the constant pool being `false` when we come in here. #if defined(TARGET_USES_OBJECT_POOL) __ set_constant_pool_allowed(false); #endif if (compiler->TryIntrinsify() && compiler->skip_body_compilation()) { return; } compiler->EmitPrologue(); #if defined(TARGET_USES_OBJECT_POOL) ASSERT(__ constant_pool_allowed()); #endif if (!compiler->is_optimizing()) { if (compiler->NeedsEdgeCounter(this)) { compiler->EmitEdgeCounter(preorder_number()); } // The deoptimization descriptor points after the edge counter code for // uniformity with ARM, where we can reuse pattern matching code that // matches backwards from the end of the pattern. compiler->AddCurrentDescriptor(UntaggedPcDescriptors::kDeopt, GetDeoptId(), InstructionSource()); } if (HasParallelMove()) { if (compiler::Assembler::EmittingComments()) { compiler->EmitComment(parallel_move()); } compiler->parallel_move_resolver()->EmitNativeCode(parallel_move()); } } LocationSummary* NativeEntryInstr::MakeLocationSummary(Zone* zone, bool optimizing) const { UNREACHABLE(); } void NativeEntryInstr::SaveArguments(FlowGraphCompiler* compiler) const { __ Comment("SaveArguments"); // Save the argument registers, in reverse order. const auto& return_loc = marshaller_.Location(compiler::ffi::kResultIndex); if (return_loc.IsPointerToMemory()) { SaveArgument(compiler, return_loc.AsPointerToMemory().pointer_location()); } for (intptr_t i = marshaller_.num_args(); i-- > 0;) { SaveArgument(compiler, marshaller_.Location(i)); } __ Comment("SaveArgumentsEnd"); } void NativeEntryInstr::SaveArgument( FlowGraphCompiler* compiler, const compiler::ffi::NativeLocation& nloc) const { if (nloc.IsStack()) return; if (nloc.IsRegisters()) { const auto& reg_loc = nloc.WidenTo4Bytes(compiler->zone()).AsRegisters(); const intptr_t num_regs = reg_loc.num_regs(); // Save higher-order component first, so bytes are in little-endian layout // overall. for (intptr_t i = num_regs - 1; i >= 0; i--) { __ PushRegister(reg_loc.reg_at(i)); } } else if (nloc.IsFpuRegisters()) { // TODO(dartbug.com/40469): Reduce code size. __ AddImmediate(SPREG, -8); NoTemporaryAllocator temp_alloc; const auto& dst = compiler::ffi::NativeStackLocation( nloc.payload_type(), nloc.payload_type(), SPREG, 0); compiler->EmitNativeMove(dst, nloc, &temp_alloc); } else if (nloc.IsPointerToMemory()) { const auto& pointer_loc = nloc.AsPointerToMemory().pointer_location(); if (pointer_loc.IsRegisters()) { const auto& regs_loc = pointer_loc.AsRegisters(); ASSERT(regs_loc.num_regs() == 1); __ PushRegister(regs_loc.reg_at(0)); } else { ASSERT(pointer_loc.IsStack()); // It's already on the stack, so we don't have to save it. } } else { ASSERT(nloc.IsMultiple()); const auto& multiple = nloc.AsMultiple(); const intptr_t num = multiple.locations().length(); // Save the argument registers, in reverse order. for (intptr_t i = num; i-- > 0;) { SaveArgument(compiler, *multiple.locations().At(i)); } } } LocationSummary* OsrEntryInstr::MakeLocationSummary(Zone* zone, bool optimizing) const { UNREACHABLE(); return NULL; } void OsrEntryInstr::EmitNativeCode(FlowGraphCompiler* compiler) { ASSERT(!CompilerState::Current().is_aot()); ASSERT(compiler->is_optimizing()); __ Bind(compiler->GetJumpLabel(this)); // NOTE: Because the graph can have multiple entrypoints, we generate several // times the same intrinsification & frame setup. That's why we cannot rely on // the constant pool being `false` when we come in here. #if defined(TARGET_USES_OBJECT_POOL) __ set_constant_pool_allowed(false); #endif compiler->EmitPrologue(); #if defined(TARGET_USES_OBJECT_POOL) ASSERT(__ constant_pool_allowed()); #endif if (HasParallelMove()) { if (compiler::Assembler::EmittingComments()) { compiler->EmitComment(parallel_move()); } compiler->parallel_move_resolver()->EmitNativeCode(parallel_move()); } } void IndirectGotoInstr::ComputeOffsetTable(FlowGraphCompiler* compiler) { ASSERT(SuccessorCount() == offsets_.Length()); intptr_t element_size = offsets_.ElementSizeInBytes(); for (intptr_t i = 0; i < SuccessorCount(); i++) { TargetEntryInstr* target = SuccessorAt(i); auto* label = compiler->GetJumpLabel(target); RELEASE_ASSERT(label != nullptr); RELEASE_ASSERT(label->IsBound()); intptr_t offset = label->Position(); RELEASE_ASSERT(offset > 0); offsets_.SetInt32(i * element_size, offset); } } LocationSummary* IndirectEntryInstr::MakeLocationSummary( Zone* zone, bool optimizing) const { return JoinEntryInstr::MakeLocationSummary(zone, optimizing); } void IndirectEntryInstr::EmitNativeCode(FlowGraphCompiler* compiler) { JoinEntryInstr::EmitNativeCode(compiler); } LocationSummary* LoadStaticFieldInstr::MakeLocationSummary(Zone* zone, bool opt) const { const intptr_t kNumInputs = 0; const intptr_t kNumTemps = 0; LocationSummary* locs = new (zone) LocationSummary( zone, kNumInputs, kNumTemps, calls_initializer() ? LocationSummary::kCall : LocationSummary::kNoCall); locs->set_out(0, calls_initializer() ? Location::RegisterLocation( InitStaticFieldABI::kResultReg) : Location::RequiresRegister()); return locs; } void LoadStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) { const Register result = locs()->out(0).reg(); compiler->used_static_fields().Add(&field()); // Note: static fields ids won't be changed by hot-reload. const intptr_t field_table_offset = compiler::target::Thread::field_table_values_offset(); const intptr_t field_offset = compiler::target::FieldTable::OffsetOf(field()); __ LoadMemoryValue(result, THR, static_cast(field_table_offset)); __ LoadMemoryValue(result, result, static_cast(field_offset)); if (calls_initializer()) { compiler::Label call_runtime, no_call; __ CompareObject(result, Object::sentinel()); if (!field().is_late()) { __ BranchIf(EQUAL, &call_runtime); __ CompareObject(result, Object::transition_sentinel()); } __ BranchIf(NOT_EQUAL, &no_call); __ Bind(&call_runtime); __ LoadObject(InitStaticFieldABI::kFieldReg, Field::ZoneHandle(field().Original())); auto object_store = compiler->isolate_group()->object_store(); const auto& init_static_field_stub = Code::ZoneHandle( compiler->zone(), object_store->init_static_field_stub()); compiler->GenerateStubCall(source(), init_static_field_stub, /*kind=*/UntaggedPcDescriptors::kOther, locs(), deopt_id()); __ Bind(&no_call); } } void LoadFieldInstr::EmitNativeCodeForInitializerCall( FlowGraphCompiler* compiler) { ASSERT(calls_initializer()); if (throw_exception_on_initialization()) { ThrowErrorSlowPathCode* slow_path = new LateInitializationErrorSlowPath(this, compiler->CurrentTryIndex()); compiler->AddSlowPathCode(slow_path); const Register result_reg = locs()->out(0).reg(); __ CompareObject(result_reg, Object::sentinel()); __ BranchIf(EQUAL, slow_path->entry_label()); return; } ASSERT(locs()->in(0).reg() == InitInstanceFieldABI::kInstanceReg); ASSERT(locs()->out(0).reg() == InitInstanceFieldABI::kResultReg); ASSERT(slot().IsDartField()); const Field& field = slot().field(); const Field& original_field = Field::ZoneHandle(field.Original()); compiler::Label no_call; __ CompareObject(InitInstanceFieldABI::kResultReg, Object::sentinel()); __ BranchIf(NOT_EQUAL, &no_call); __ LoadObject(InitInstanceFieldABI::kFieldReg, original_field); auto object_store = compiler->isolate_group()->object_store(); auto& stub = Code::ZoneHandle(compiler->zone()); if (field.needs_load_guard()) { stub = object_store->init_instance_field_stub(); } else if (field.is_late()) { if (!field.has_nontrivial_initializer()) { // Common stub calls runtime which will throw an exception. stub = object_store->init_instance_field_stub(); } else { // Stubs for late field initialization call initializer // function directly, so make sure one is created. original_field.EnsureInitializerFunction(); if (field.is_final()) { stub = object_store->init_late_final_instance_field_stub(); } else { stub = object_store->init_late_instance_field_stub(); } } } else { UNREACHABLE(); } compiler->GenerateStubCall(source(), stub, /*kind=*/UntaggedPcDescriptors::kOther, locs(), deopt_id()); __ Bind(&no_call); } LocationSummary* ThrowInstr::MakeLocationSummary(Zone* zone, bool opt) const { const intptr_t kNumInputs = 1; const intptr_t kNumTemps = 0; LocationSummary* summary = new (zone) LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kCall); summary->set_in(0, Location::RegisterLocation(ThrowABI::kExceptionReg)); return summary; } void ThrowInstr::EmitNativeCode(FlowGraphCompiler* compiler) { auto object_store = compiler->isolate_group()->object_store(); const auto& throw_stub = Code::ZoneHandle(compiler->zone(), object_store->throw_stub()); compiler->GenerateStubCall(source(), throw_stub, /*kind=*/UntaggedPcDescriptors::kOther, locs(), deopt_id()); // Issue(dartbug.com/41353): Right now we have to emit an extra breakpoint // instruction: The ThrowInstr will terminate the current block. The very // next machine code instruction might get a pc descriptor attached with a // different try-index. If we removed this breakpoint instruction, the // runtime might associated this call with the try-index of the next // instruction. __ Breakpoint(); } LocationSummary* ReThrowInstr::MakeLocationSummary(Zone* zone, bool opt) const { const intptr_t kNumInputs = 2; const intptr_t kNumTemps = 0; LocationSummary* summary = new (zone) LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kCall); summary->set_in(0, Location::RegisterLocation(ReThrowABI::kExceptionReg)); summary->set_in(1, Location::RegisterLocation(ReThrowABI::kStackTraceReg)); return summary; } void ReThrowInstr::EmitNativeCode(FlowGraphCompiler* compiler) { auto object_store = compiler->isolate_group()->object_store(); const auto& re_throw_stub = Code::ZoneHandle(compiler->zone(), object_store->re_throw_stub()); compiler->SetNeedsStackTrace(catch_try_index()); compiler->GenerateStubCall(source(), re_throw_stub, /*kind=*/UntaggedPcDescriptors::kOther, locs(), deopt_id()); // Issue(dartbug.com/41353): Right now we have to emit an extra breakpoint // instruction: The ThrowInstr will terminate the current block. The very // next machine code instruction might get a pc descriptor attached with a // different try-index. If we removed this breakpoint instruction, the // runtime might associated this call with the try-index of the next // instruction. __ Breakpoint(); } LocationSummary* AssertBooleanInstr::MakeLocationSummary(Zone* zone, bool opt) const { const intptr_t kNumInputs = 1; const intptr_t kNumTemps = 0; LocationSummary* locs = new (zone) LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kCall); locs->set_in(0, Location::RegisterLocation(AssertBooleanABI::kObjectReg)); locs->set_out(0, Location::RegisterLocation(AssertBooleanABI::kObjectReg)); return locs; } LocationSummary* PhiInstr::MakeLocationSummary(Zone* zone, bool optimizing) const { UNREACHABLE(); return NULL; } void PhiInstr::EmitNativeCode(FlowGraphCompiler* compiler) { UNREACHABLE(); } LocationSummary* RedefinitionInstr::MakeLocationSummary(Zone* zone, bool optimizing) const { UNREACHABLE(); return NULL; } void RedefinitionInstr::EmitNativeCode(FlowGraphCompiler* compiler) { UNREACHABLE(); } LocationSummary* ReachabilityFenceInstr::MakeLocationSummary( Zone* zone, bool optimizing) const { LocationSummary* summary = new (zone) LocationSummary(zone, 1, 0, LocationSummary::ContainsCall::kNoCall); // Keep the parameter alive and reachable, in any location. summary->set_in(0, Location::Any()); return summary; } void ReachabilityFenceInstr::EmitNativeCode(FlowGraphCompiler* compiler) { // No native code, but we rely on the parameter being passed in here so that // it stays alive and reachable. } LocationSummary* ParameterInstr::MakeLocationSummary(Zone* zone, bool optimizing) const { UNREACHABLE(); return NULL; } void ParameterInstr::EmitNativeCode(FlowGraphCompiler* compiler) { UNREACHABLE(); } void NativeParameterInstr::EmitNativeCode(FlowGraphCompiler* compiler) { // The native entry frame has size -kExitLinkSlotFromFp. In order to access // the top of stack from above the entry frame, we add a constant to account // for the two frame pointers and two return addresses of the entry frame. constexpr intptr_t kEntryFramePadding = 4; compiler::ffi::FrameRebase rebase( compiler->zone(), /*old_base=*/SPREG, /*new_base=*/FPREG, (-kExitLinkSlotFromEntryFp + kEntryFramePadding) * compiler::target::kWordSize); const auto& location = marshaller_.NativeLocationOfNativeParameter(def_index_); const auto& src = rebase.Rebase(location.IsPointerToMemory() ? location.AsPointerToMemory().pointer_location() : location); NoTemporaryAllocator no_temp; const Location out_loc = locs()->out(0); const Representation out_rep = representation(); compiler->EmitMoveFromNative(out_loc, out_rep, src, &no_temp); } LocationSummary* NativeParameterInstr::MakeLocationSummary(Zone* zone, bool opt) const { ASSERT(opt); Location output = Location::Any(); if (representation() == kUnboxedInt64 && compiler::target::kWordSize < 8) { output = Location::Pair(Location::RequiresRegister(), Location::RequiresFpuRegister()); } else { output = RegisterKindForResult() == Location::kRegister ? Location::RequiresRegister() : Location::RequiresFpuRegister(); } return LocationSummary::Make(zone, /*num_inputs=*/0, output, LocationSummary::kNoCall); } bool ParallelMoveInstr::IsRedundant() const { for (intptr_t i = 0; i < moves_.length(); i++) { if (!moves_[i]->IsRedundant()) { return false; } } return true; } LocationSummary* ParallelMoveInstr::MakeLocationSummary(Zone* zone, bool optimizing) const { return NULL; } void ParallelMoveInstr::EmitNativeCode(FlowGraphCompiler* compiler) { UNREACHABLE(); } LocationSummary* ConstraintInstr::MakeLocationSummary(Zone* zone, bool optimizing) const { UNREACHABLE(); return NULL; } void ConstraintInstr::EmitNativeCode(FlowGraphCompiler* compiler) { UNREACHABLE(); } LocationSummary* MaterializeObjectInstr::MakeLocationSummary( Zone* zone, bool optimizing) const { UNREACHABLE(); return NULL; } void MaterializeObjectInstr::EmitNativeCode(FlowGraphCompiler* compiler) { UNREACHABLE(); } // This function should be kept in sync with // FlowGraphCompiler::SlowPathEnvironmentFor(). void MaterializeObjectInstr::RemapRegisters(intptr_t* cpu_reg_slots, intptr_t* fpu_reg_slots) { if (registers_remapped_) { return; } registers_remapped_ = true; for (intptr_t i = 0; i < InputCount(); i++) { locations_[i] = LocationRemapForSlowPath( LocationAt(i), InputAt(i)->definition(), cpu_reg_slots, fpu_reg_slots); } } const char* SpecialParameterInstr::KindToCString(SpecialParameterKind k) { switch (k) { #define KIND_CASE(Name) \ case SpecialParameterKind::k##Name: \ return #Name; FOR_EACH_SPECIAL_PARAMETER_KIND(KIND_CASE) #undef KIND_CASE } return nullptr; } bool SpecialParameterInstr::ParseKind(const char* str, SpecialParameterKind* out) { ASSERT(str != nullptr && out != nullptr); #define KIND_CASE(Name) \ if (strcmp(str, #Name) == 0) { \ *out = SpecialParameterKind::k##Name; \ return true; \ } FOR_EACH_SPECIAL_PARAMETER_KIND(KIND_CASE) #undef KIND_CASE return false; } LocationSummary* SpecialParameterInstr::MakeLocationSummary(Zone* zone, bool opt) const { // Only appears in initial definitions, never in normal code. UNREACHABLE(); return NULL; } void SpecialParameterInstr::EmitNativeCode(FlowGraphCompiler* compiler) { // Only appears in initial definitions, never in normal code. UNREACHABLE(); } LocationSummary* MakeTempInstr::MakeLocationSummary(Zone* zone, bool optimizing) const { ASSERT(!optimizing); null_->InitializeLocationSummary(zone, optimizing); return null_->locs(); } void MakeTempInstr::EmitNativeCode(FlowGraphCompiler* compiler) { ASSERT(!compiler->is_optimizing()); null_->EmitNativeCode(compiler); } LocationSummary* DropTempsInstr::MakeLocationSummary(Zone* zone, bool optimizing) const { ASSERT(!optimizing); return (InputCount() == 1) ? LocationSummary::Make(zone, 1, Location::SameAsFirstInput(), LocationSummary::kNoCall) : LocationSummary::Make(zone, 0, Location::NoLocation(), LocationSummary::kNoCall); } void DropTempsInstr::EmitNativeCode(FlowGraphCompiler* compiler) { ASSERT(!compiler->is_optimizing()); // Assert that register assignment is correct. ASSERT((InputCount() == 0) || (locs()->out(0).reg() == locs()->in(0).reg())); __ Drop(num_temps()); } StrictCompareInstr::StrictCompareInstr(const InstructionSource& source, Token::Kind kind, Value* left, Value* right, bool needs_number_check, intptr_t deopt_id) : TemplateComparison(source, kind, deopt_id), needs_number_check_(needs_number_check) { ASSERT((kind == Token::kEQ_STRICT) || (kind == Token::kNE_STRICT)); SetInputAt(0, left); SetInputAt(1, right); } Condition StrictCompareInstr::EmitComparisonCode(FlowGraphCompiler* compiler, BranchLabels labels) { Location left = locs()->in(0); Location right = locs()->in(1); ASSERT(!left.IsConstant() || !right.IsConstant()); Condition true_condition; if (left.IsConstant()) { if (TryEmitBoolTest(compiler, labels, 1, left.constant(), &true_condition)) { return true_condition; } true_condition = EmitComparisonCodeRegConstant( compiler, labels, right.reg(), left.constant()); } else if (right.IsConstant()) { if (TryEmitBoolTest(compiler, labels, 0, right.constant(), &true_condition)) { return true_condition; } true_condition = EmitComparisonCodeRegConstant(compiler, labels, left.reg(), right.constant()); } else { true_condition = compiler->EmitEqualityRegRegCompare( left.reg(), right.reg(), needs_number_check(), source(), deopt_id()); } return true_condition != kInvalidCondition && (kind() != Token::kEQ_STRICT) ? InvertCondition(true_condition) : true_condition; } bool StrictCompareInstr::TryEmitBoolTest(FlowGraphCompiler* compiler, BranchLabels labels, intptr_t input_index, const Object& obj, Condition* true_condition_out) { CompileType* input_type = InputAt(input_index)->Type(); if (input_type->ToCid() == kBoolCid && obj.GetClassId() == kBoolCid) { bool invert = (kind() != Token::kEQ_STRICT) ^ !Bool::Cast(obj).value(); *true_condition_out = compiler->EmitBoolTest(locs()->in(input_index).reg(), labels, invert); return true; } return false; } LocationSummary* LoadClassIdInstr::MakeLocationSummary(Zone* zone, bool opt) const { const intptr_t kNumInputs = 1; return LocationSummary::Make(zone, kNumInputs, Location::RequiresRegister(), LocationSummary::kNoCall); } void LoadClassIdInstr::EmitNativeCode(FlowGraphCompiler* compiler) { const Register object = locs()->in(0).reg(); const Register result = locs()->out(0).reg(); if (input_can_be_smi_ && this->object()->Type()->CanBeSmi()) { if (representation() == kTagged) { __ LoadTaggedClassIdMayBeSmi(result, object); } else { __ LoadClassIdMayBeSmi(result, object); } } else { __ LoadClassId(result, object); if (representation() == kTagged) { __ SmiTag(result); } } } LocationSummary* InstanceCallInstr::MakeLocationSummary(Zone* zone, bool optimizing) const { return MakeCallSummary(zone, this); } static CodePtr TwoArgsSmiOpInlineCacheEntry(Token::Kind kind) { if (!FLAG_two_args_smi_icd) { return Code::null(); } switch (kind) { case Token::kADD: return StubCode::SmiAddInlineCache().ptr(); case Token::kLT: return StubCode::SmiLessInlineCache().ptr(); case Token::kEQ: return StubCode::SmiEqualInlineCache().ptr(); default: return Code::null(); } } bool InstanceCallBaseInstr::CanReceiverBeSmiBasedOnInterfaceTarget( Zone* zone) const { if (!interface_target().IsNull()) { // Note: target_type is fully instantiated rare type (all type parameters // are replaced with dynamic) so checking if Smi is assignable to // it would compute correctly whether or not receiver can be a smi. const AbstractType& target_type = AbstractType::Handle( zone, Class::Handle(zone, interface_target().Owner()).RareType()); if (!CompileType::Smi().IsAssignableTo(target_type)) { return false; } } // In all other cases conservatively assume that the receiver can be a smi. return true; } Representation InstanceCallBaseInstr::RequiredInputRepresentation( intptr_t idx) const { // The first input is the array of types // for generic functions if (type_args_len() > 0) { if (idx == 0) { return kTagged; } idx--; } return FlowGraph::ParameterRepresentationAt(interface_target(), idx); } intptr_t InstanceCallBaseInstr::ArgumentsSize() const { if (interface_target().IsNull()) { return ArgumentCountWithoutTypeArgs() + ((type_args_len() > 0) ? 1 : 0); } return FlowGraph::ParameterOffsetAt(interface_target(), ArgumentCountWithoutTypeArgs(), /*last_slot=*/false) + ((type_args_len() > 0) ? 1 : 0); } Representation InstanceCallBaseInstr::representation() const { return FlowGraph::ReturnRepresentationOf(interface_target()); } void InstanceCallBaseInstr::UpdateReceiverSminess(Zone* zone) { if (CompilerState::Current().is_aot() && !receiver_is_not_smi()) { if (!Receiver()->Type()->CanBeSmi() || !CanReceiverBeSmiBasedOnInterfaceTarget(zone)) { set_receiver_is_not_smi(true); } } } static FunctionPtr FindBinarySmiOp(Zone* zone, const String& name) { const auto& smi_class = Class::Handle(zone, Smi::Class()); auto& smi_op_target = Function::Handle( zone, Resolver::ResolveDynamicAnyArgs(zone, smi_class, name)); #if !defined(DART_PRECOMPILED_RUNTIME) if (smi_op_target.IsNull() && Function::IsDynamicInvocationForwarderName(name)) { const String& demangled = String::Handle( zone, Function::DemangleDynamicInvocationForwarderName(name)); smi_op_target = Resolver::ResolveDynamicAnyArgs(zone, smi_class, demangled); } #endif return smi_op_target.ptr(); } void InstanceCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) { Zone* zone = compiler->zone(); UpdateReceiverSminess(zone); auto& specialized_binary_smi_ic_stub = Code::ZoneHandle(zone); auto& binary_smi_op_target = Function::Handle(zone); if (!receiver_is_not_smi()) { specialized_binary_smi_ic_stub = TwoArgsSmiOpInlineCacheEntry(token_kind()); if (!specialized_binary_smi_ic_stub.IsNull()) { binary_smi_op_target = FindBinarySmiOp(zone, function_name()); } } const ICData* call_ic_data = NULL; if (!FLAG_propagate_ic_data || !compiler->is_optimizing() || (ic_data() == NULL)) { const Array& arguments_descriptor = Array::Handle(zone, GetArgumentsDescriptor()); AbstractType& receivers_static_type = AbstractType::Handle(zone); if (receivers_static_type_ != nullptr) { receivers_static_type = receivers_static_type_->ptr(); } call_ic_data = compiler->GetOrAddInstanceCallICData( deopt_id(), function_name(), arguments_descriptor, checked_argument_count(), receivers_static_type, binary_smi_op_target); } else { call_ic_data = &ICData::ZoneHandle(zone, ic_data()->ptr()); } if (compiler->is_optimizing() && HasICData()) { if (ic_data()->NumberOfUsedChecks() > 0) { const ICData& unary_ic_data = ICData::ZoneHandle(zone, ic_data()->AsUnaryClassChecks()); compiler->GenerateInstanceCall(deopt_id(), source(), locs(), unary_ic_data, entry_kind(), !receiver_is_not_smi()); } else { // Call was not visited yet, use original ICData in order to populate it. compiler->GenerateInstanceCall(deopt_id(), source(), locs(), *call_ic_data, entry_kind(), !receiver_is_not_smi()); } } else { // Unoptimized code. compiler->AddCurrentDescriptor(UntaggedPcDescriptors::kRewind, deopt_id(), source()); // If the ICData contains a (Smi, Smi, ) stub already // we will call the specialized IC Stub that works as a normal IC Stub but // has inlined fast path for the specific Smi operation. bool use_specialized_smi_ic_stub = false; if (!specialized_binary_smi_ic_stub.IsNull() && call_ic_data->NumberOfChecksIs(1)) { GrowableArray class_ids(2); auto& target = Function::Handle(); call_ic_data->GetCheckAt(0, &class_ids, &target); if (class_ids[0] == kSmiCid && class_ids[1] == kSmiCid && target.ptr() == binary_smi_op_target.ptr()) { use_specialized_smi_ic_stub = true; } } if (use_specialized_smi_ic_stub) { ASSERT(ArgumentCount() == 2); compiler->EmitInstanceCallJIT(specialized_binary_smi_ic_stub, *call_ic_data, deopt_id(), source(), locs(), entry_kind()); } else { compiler->GenerateInstanceCall(deopt_id(), source(), locs(), *call_ic_data, entry_kind(), !receiver_is_not_smi()); } } } bool InstanceCallInstr::MatchesCoreName(const String& name) { return Library::IsPrivateCoreLibName(function_name(), name); } FunctionPtr InstanceCallBaseInstr::ResolveForReceiverClass( const Class& cls, bool allow_add /* = true */) { const Array& args_desc_array = Array::Handle(GetArgumentsDescriptor()); ArgumentsDescriptor args_desc(args_desc_array); return Resolver::ResolveDynamicForReceiverClass(cls, function_name(), args_desc, allow_add); } const CallTargets& InstanceCallInstr::Targets() { if (targets_ == nullptr) { Zone* zone = Thread::Current()->zone(); if (HasICData()) { targets_ = CallTargets::CreateAndExpand(zone, *ic_data()); } else { targets_ = new (zone) CallTargets(zone); ASSERT(targets_->is_empty()); } } return *targets_; } const BinaryFeedback& InstanceCallInstr::BinaryFeedback() { if (binary_ == nullptr) { Zone* zone = Thread::Current()->zone(); if (HasICData()) { binary_ = BinaryFeedback::Create(zone, *ic_data()); } else { binary_ = new (zone) class BinaryFeedback(zone); } } return *binary_; } Representation DispatchTableCallInstr::RequiredInputRepresentation( intptr_t idx) const { if (idx == (InputCount() - 1)) { return kUntagged; } // The first input is the array of types // for generic functions if (type_args_len() > 0) { if (idx == 0) { return kTagged; } idx--; } return FlowGraph::ParameterRepresentationAt(interface_target(), idx); } intptr_t DispatchTableCallInstr::ArgumentsSize() const { if (interface_target().IsNull()) { return ArgumentCountWithoutTypeArgs() + ((type_args_len() > 0) ? 1 : 0); } return FlowGraph::ParameterOffsetAt(interface_target(), ArgumentCountWithoutTypeArgs(), /*last_slot=*/false) + ((type_args_len() > 0) ? 1 : 0); } Representation DispatchTableCallInstr::representation() const { return FlowGraph::ReturnRepresentationOf(interface_target()); } DispatchTableCallInstr* DispatchTableCallInstr::FromCall( Zone* zone, const InstanceCallBaseInstr* call, Value* cid, const Function& interface_target, const compiler::TableSelector* selector) { InputsArray* args = new (zone) InputsArray(zone, call->ArgumentCount() + 1); for (intptr_t i = 0; i < call->ArgumentCount(); i++) { args->Add(call->ArgumentValueAt(i)->CopyWithType()); } args->Add(cid); auto dispatch_table_call = new (zone) DispatchTableCallInstr(call->source(), interface_target, selector, args, call->type_args_len(), call->argument_names()); return dispatch_table_call; } LocationSummary* DispatchTableCallInstr::MakeLocationSummary(Zone* zone, bool opt) const { const intptr_t kNumInputs = 1; const intptr_t kNumTemps = 0; LocationSummary* summary = new (zone) LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kCall); summary->set_in( 0, Location::RegisterLocation(DispatchTableNullErrorABI::kClassIdReg)); return MakeCallSummary(zone, this, summary); } void DispatchTableCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) { ASSERT(locs()->in(0).reg() == DispatchTableNullErrorABI::kClassIdReg); Array& arguments_descriptor = Array::ZoneHandle(); if (selector()->requires_args_descriptor) { ArgumentsInfo args_info(type_args_len(), ArgumentCount(), ArgumentsSize(), argument_names()); arguments_descriptor = args_info.ToArgumentsDescriptor(); } compiler->EmitDispatchTableCall(selector()->offset, arguments_descriptor); compiler->EmitCallsiteMetadata(source(), DeoptId::kNone, UntaggedPcDescriptors::kOther, locs()); if (selector()->called_on_null && !selector()->on_null_interface) { Value* receiver = ArgumentValueAt(FirstArgIndex()); if (receiver->Type()->is_nullable()) { const String& function_name = String::ZoneHandle(interface_target().name()); compiler->AddNullCheck(source(), function_name); } } __ Drop(ArgumentsSize()); compiler->AddDispatchTableCallTarget(selector()); } Representation StaticCallInstr::RequiredInputRepresentation( intptr_t idx) const { // The first input is the array of types // for generic functions if (type_args_len() > 0 || function().IsFactory()) { if (idx == 0) { return kTagged; } idx--; } return FlowGraph::ParameterRepresentationAt(function(), idx); } intptr_t StaticCallInstr::ArgumentsSize() const { return FlowGraph::ParameterOffsetAt(function(), ArgumentCountWithoutTypeArgs(), /*last_slot=*/false) + ((type_args_len() > 0) ? 1 : 0); } Representation StaticCallInstr::representation() const { return FlowGraph::ReturnRepresentationOf(function()); } const CallTargets& StaticCallInstr::Targets() { if (targets_ == nullptr) { Zone* zone = Thread::Current()->zone(); if (HasICData()) { targets_ = CallTargets::CreateAndExpand(zone, *ic_data()); } else { targets_ = new (zone) CallTargets(zone); ASSERT(targets_->is_empty()); } } return *targets_; } const BinaryFeedback& StaticCallInstr::BinaryFeedback() { if (binary_ == nullptr) { Zone* zone = Thread::Current()->zone(); if (HasICData()) { binary_ = BinaryFeedback::Create(zone, *ic_data()); } else { binary_ = new (zone) class BinaryFeedback(zone); } } return *binary_; } bool CallTargets::HasSingleRecognizedTarget() const { if (!HasSingleTarget()) return false; return FirstTarget().recognized_kind() != MethodRecognizer::kUnknown; } bool CallTargets::HasSingleTarget() const { if (length() == 0) return false; for (int i = 0; i < length(); i++) { if (TargetAt(i)->target->ptr() != TargetAt(0)->target->ptr()) return false; } return true; } const Function& CallTargets::FirstTarget() const { ASSERT(length() != 0); ASSERT(TargetAt(0)->target->IsZoneHandle()); return *TargetAt(0)->target; } const Function& CallTargets::MostPopularTarget() const { ASSERT(length() != 0); ASSERT(TargetAt(0)->target->IsZoneHandle()); for (int i = 1; i < length(); i++) { ASSERT(TargetAt(i)->count <= TargetAt(0)->count); } return *TargetAt(0)->target; } intptr_t CallTargets::AggregateCallCount() const { intptr_t sum = 0; for (int i = 0; i < length(); i++) { sum += TargetAt(i)->count; } return sum; } bool PolymorphicInstanceCallInstr::HasOnlyDispatcherOrImplicitAccessorTargets() const { const intptr_t len = targets_.length(); Function& target = Function::Handle(); for (intptr_t i = 0; i < len; i++) { target = targets_.TargetAt(i)->target->ptr(); if (!target.IsDispatcherOrImplicitAccessor()) { return false; } } return true; } intptr_t PolymorphicInstanceCallInstr::CallCount() const { return targets().AggregateCallCount(); } LocationSummary* PolymorphicInstanceCallInstr::MakeLocationSummary( Zone* zone, bool optimizing) const { return MakeCallSummary(zone, this); } void PolymorphicInstanceCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) { ArgumentsInfo args_info(type_args_len(), ArgumentCount(), ArgumentsSize(), argument_names()); UpdateReceiverSminess(compiler->zone()); compiler->EmitPolymorphicInstanceCall( this, targets(), args_info, deopt_id(), source(), locs(), complete(), total_call_count(), !receiver_is_not_smi()); } TypePtr PolymorphicInstanceCallInstr::ComputeRuntimeType( const CallTargets& targets) { bool is_string = true; bool is_integer = true; bool is_double = true; bool is_type = true; const intptr_t num_checks = targets.length(); for (intptr_t i = 0; i < num_checks; i++) { ASSERT(targets.TargetAt(i)->target->ptr() == targets.TargetAt(0)->target->ptr()); const intptr_t start = targets[i].cid_start; const intptr_t end = targets[i].cid_end; for (intptr_t cid = start; cid <= end; cid++) { is_string = is_string && IsStringClassId(cid); is_integer = is_integer && IsIntegerClassId(cid); is_double = is_double && (cid == kDoubleCid); is_type = is_type && IsTypeClassId(cid); } } if (is_string) { ASSERT(!is_integer); ASSERT(!is_double); ASSERT(!is_type); return Type::StringType(); } else if (is_integer) { ASSERT(!is_double); ASSERT(!is_type); return Type::IntType(); } else if (is_double) { ASSERT(!is_type); return Type::Double(); } else if (is_type) { return Type::DartTypeType(); } return Type::null(); } Definition* InstanceCallInstr::Canonicalize(FlowGraph* flow_graph) { const intptr_t receiver_cid = Receiver()->Type()->ToCid(); // We could turn cold call sites for known receiver cids into a StaticCall. // However, that keeps the ICData of the InstanceCall from being updated. // This is fine if there is no later deoptimization, but if there is, then // the InstanceCall with the updated ICData for this receiver may then be // better optimized by the compiler. // // TODO(dartbug.com/37291): Allow this optimization, but accumulate affected // InstanceCallInstrs and the corresponding reciever cids during compilation. // After compilation, add receiver checks to the ICData for those call sites. if (Targets().is_empty()) return this; const CallTargets* new_target = FlowGraphCompiler::ResolveCallTargetsForReceiverCid( receiver_cid, String::Handle(flow_graph->zone(), ic_data()->target_name()), Array::Handle(flow_graph->zone(), ic_data()->arguments_descriptor())); if (new_target == NULL) { // No specialization. return this; } ASSERT(new_target->HasSingleTarget()); const Function& target = new_target->FirstTarget(); StaticCallInstr* specialized = StaticCallInstr::FromCall( flow_graph->zone(), this, target, new_target->AggregateCallCount()); flow_graph->InsertBefore(this, specialized, env(), FlowGraph::kValue); return specialized; } Definition* DispatchTableCallInstr::Canonicalize(FlowGraph* flow_graph) { // TODO(dartbug.com/40188): Allow this to canonicalize into a StaticCall when // when input class id is constant; return this; } Definition* PolymorphicInstanceCallInstr::Canonicalize(FlowGraph* flow_graph) { if (!IsSureToCallSingleRecognizedTarget()) { return this; } const Function& target = targets().FirstTarget(); if (target.recognized_kind() == MethodRecognizer::kObjectRuntimeType) { const AbstractType& type = AbstractType::Handle(ComputeRuntimeType(targets_)); if (!type.IsNull()) { return flow_graph->GetConstant(type); } } return this; } bool PolymorphicInstanceCallInstr::IsSureToCallSingleRecognizedTarget() const { if (CompilerState::Current().is_aot() && !complete()) return false; return targets_.HasSingleRecognizedTarget(); } bool StaticCallInstr::InitResultType(Zone* zone) { const intptr_t list_cid = FactoryRecognizer::GetResultCidOfListFactory( zone, function(), ArgumentCount()); if (list_cid != kDynamicCid) { SetResultType(zone, CompileType::FromCid(list_cid)); set_is_known_list_constructor(true); return true; } else if (function().has_pragma()) { const intptr_t recognized_cid = MethodRecognizer::ResultCidFromPragma(function()); if (recognized_cid != kDynamicCid) { SetResultType(zone, CompileType::FromCid(recognized_cid)); return true; } } return false; } Definition* StaticCallInstr::Canonicalize(FlowGraph* flow_graph) { if (!CompilerState::Current().is_aot()) { return this; } if (function().recognized_kind() == MethodRecognizer::kObjectRuntimeType) { if (input_use_list() == NULL) { // This function has only environment uses. In precompiled mode it is // fine to remove it - because we will never deoptimize. return flow_graph->constant_dead(); } } return this; } LocationSummary* StaticCallInstr::MakeLocationSummary(Zone* zone, bool optimizing) const { return MakeCallSummary(zone, this); } void StaticCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) { Zone* zone = compiler->zone(); const ICData* call_ic_data = NULL; if (!FLAG_propagate_ic_data || !compiler->is_optimizing() || (ic_data() == NULL)) { const Array& arguments_descriptor = Array::Handle(zone, GetArgumentsDescriptor()); const int num_args_checked = MethodRecognizer::NumArgsCheckedForStaticCall(function()); call_ic_data = compiler->GetOrAddStaticCallICData( deopt_id(), function(), arguments_descriptor, num_args_checked, rebind_rule_); } else { call_ic_data = &ICData::ZoneHandle(ic_data()->ptr()); } ArgumentsInfo args_info(type_args_len(), ArgumentCount(), ArgumentsSize(), argument_names()); compiler->GenerateStaticCall(deopt_id(), source(), function(), args_info, locs(), *call_ic_data, rebind_rule_, entry_kind()); if (function().IsFactory()) { TypeUsageInfo* type_usage_info = compiler->thread()->type_usage_info(); if (type_usage_info != nullptr) { const Class& klass = Class::Handle(function().Owner()); RegisterTypeArgumentsUse(compiler->function(), type_usage_info, klass, ArgumentAt(0)); } } } intptr_t AssertAssignableInstr::statistics_tag() const { switch (kind_) { case kParameterCheck: return CombinedCodeStatistics::kTagAssertAssignableParameterCheck; case kInsertedByFrontend: return CombinedCodeStatistics::kTagAssertAssignableInsertedByFrontend; case kFromSource: return CombinedCodeStatistics::kTagAssertAssignableFromSource; case kUnknown: break; } return tag(); } void AssertAssignableInstr::EmitNativeCode(FlowGraphCompiler* compiler) { compiler->GenerateAssertAssignable(value()->Type(), source(), deopt_id(), dst_name(), locs()); ASSERT(locs()->in(kInstancePos).reg() == locs()->out(0).reg()); } LocationSummary* AssertSubtypeInstr::MakeLocationSummary(Zone* zone, bool opt) const { const intptr_t kNumInputs = 5; const intptr_t kNumTemps = 0; LocationSummary* summary = new (zone) LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kCall); summary->set_in(kInstantiatorTAVPos, Location::RegisterLocation( AssertSubtypeABI::kInstantiatorTypeArgumentsReg)); summary->set_in( kFunctionTAVPos, Location::RegisterLocation(AssertSubtypeABI::kFunctionTypeArgumentsReg)); summary->set_in(kSubTypePos, Location::RegisterLocation(AssertSubtypeABI::kSubTypeReg)); summary->set_in(kSuperTypePos, Location::RegisterLocation(AssertSubtypeABI::kSuperTypeReg)); summary->set_in(kDstNamePos, Location::RegisterLocation(AssertSubtypeABI::kDstNameReg)); return summary; } void AssertSubtypeInstr::EmitNativeCode(FlowGraphCompiler* compiler) { compiler->GenerateStubCall(source(), StubCode::AssertSubtype(), UntaggedPcDescriptors::kOther, locs(), deopt_id()); } LocationSummary* InstantiateTypeInstr::MakeLocationSummary(Zone* zone, bool opt) const { const intptr_t kNumInputs = 2; const intptr_t kNumTemps = 0; LocationSummary* locs = new (zone) LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kCall); locs->set_in(0, Location::RegisterLocation( InstantiateTypeABI::kInstantiatorTypeArgumentsReg)); locs->set_in(1, Location::RegisterLocation( InstantiateTypeABI::kFunctionTypeArgumentsReg)); locs->set_out(0, Location::RegisterLocation(InstantiateTypeABI::kResultTypeReg)); return locs; } void InstantiateTypeInstr::EmitNativeCode(FlowGraphCompiler* compiler) { __ LoadObject(InstantiateTypeABI::kTypeReg, type()); compiler->GenerateStubCall(source(), StubCode::InstantiateType(), UntaggedPcDescriptors::kOther, locs(), deopt_id(), env()); } LocationSummary* InstantiateTypeArgumentsInstr::MakeLocationSummary( Zone* zone, bool opt) const { const intptr_t kNumInputs = 3; const intptr_t kNumTemps = 0; LocationSummary* locs = new (zone) LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kCall); locs->set_in(0, Location::RegisterLocation( InstantiationABI::kInstantiatorTypeArgumentsReg)); locs->set_in(1, Location::RegisterLocation( InstantiationABI::kFunctionTypeArgumentsReg)); locs->set_in(2, Location::RegisterLocation( InstantiationABI::kUninstantiatedTypeArgumentsReg)); locs->set_out( 0, Location::RegisterLocation(InstantiationABI::kResultTypeArgumentsReg)); return locs; } void InstantiateTypeArgumentsInstr::EmitNativeCode( FlowGraphCompiler* compiler) { // We should never try and instantiate a TAV known at compile time to be null, // so we can use a null value below for the dynamic case. ASSERT(!type_arguments()->BindsToConstant() || !type_arguments()->BoundConstant().IsNull()); const auto& type_args = type_arguments()->BindsToConstant() ? TypeArguments::Cast(type_arguments()->BoundConstant()) : Object::null_type_arguments(); const intptr_t len = type_args.Length(); const bool can_function_type_args_be_null = function_type_arguments()->CanBe(Object::null_object()); compiler::Label type_arguments_instantiated; if (type_args.IsNull()) { // Currently we only create dynamic InstantiateTypeArguments instructions // in cases where we know the type argument is uninstantiated at runtime, // so there are no extra checks needed to call the stub successfully. } else if (type_args.IsRawWhenInstantiatedFromRaw(len) && can_function_type_args_be_null) { // If both the instantiator and function type arguments are null and if the // type argument vector instantiated from null becomes a vector of dynamic, // then use null as the type arguments. compiler::Label non_null_type_args; __ LoadObject(InstantiationABI::kResultTypeArgumentsReg, Object::null_object()); __ CompareRegisters(InstantiationABI::kInstantiatorTypeArgumentsReg, InstantiationABI::kResultTypeArgumentsReg); if (!function_type_arguments()->BindsToConstant()) { __ BranchIf(NOT_EQUAL, &non_null_type_args, compiler::AssemblerBase::kNearJump); __ CompareRegisters(InstantiationABI::kFunctionTypeArgumentsReg, InstantiationABI::kResultTypeArgumentsReg); } __ BranchIf(EQUAL, &type_arguments_instantiated, compiler::AssemblerBase::kNearJump); __ Bind(&non_null_type_args); } compiler->GenerateStubCall(source(), GetStub(), UntaggedPcDescriptors::kOther, locs(), deopt_id()); __ Bind(&type_arguments_instantiated); } LocationSummary* DeoptimizeInstr::MakeLocationSummary(Zone* zone, bool opt) const { return new (zone) LocationSummary(zone, 0, 0, LocationSummary::kNoCall); } void DeoptimizeInstr::EmitNativeCode(FlowGraphCompiler* compiler) { __ Jump(compiler->AddDeoptStub(deopt_id(), deopt_reason_)); } void CheckClassInstr::EmitNativeCode(FlowGraphCompiler* compiler) { compiler::Label* deopt = compiler->AddDeoptStub(deopt_id(), ICData::kDeoptCheckClass, licm_hoisted_ ? ICData::kHoisted : 0); if (IsNullCheck()) { EmitNullCheck(compiler, deopt); return; } ASSERT(!cids_.IsMonomorphic() || !cids_.HasClassId(kSmiCid)); Register value = locs()->in(0).reg(); Register temp = locs()->temp(0).reg(); compiler::Label is_ok; __ BranchIfSmi(value, cids_.HasClassId(kSmiCid) ? &is_ok : deopt); __ LoadClassId(temp, value); if (IsBitTest()) { intptr_t min = cids_.ComputeLowestCid(); intptr_t max = cids_.ComputeHighestCid(); EmitBitTest(compiler, min, max, ComputeCidMask(), deopt); } else { const intptr_t num_checks = cids_.length(); const bool use_near_jump = num_checks < 5; int bias = 0; for (intptr_t i = 0; i < num_checks; i++) { intptr_t cid_start = cids_[i].cid_start; intptr_t cid_end = cids_[i].cid_end; if (cid_start == kSmiCid && cid_end == kSmiCid) { continue; // We already handled Smi above. } if (cid_start == kSmiCid) cid_start++; if (cid_end == kSmiCid) cid_end--; const bool is_last = (i == num_checks - 1) || (i == num_checks - 2 && cids_[i + 1].cid_start == kSmiCid && cids_[i + 1].cid_end == kSmiCid); bias = EmitCheckCid(compiler, bias, cid_start, cid_end, is_last, &is_ok, deopt, use_near_jump); } } __ Bind(&is_ok); } LocationSummary* GenericCheckBoundInstr::MakeLocationSummary(Zone* zone, bool opt) const { const intptr_t kNumInputs = 2; const intptr_t kNumTemps = 0; LocationSummary* locs = new (zone) LocationSummary( zone, kNumInputs, kNumTemps, UseSharedSlowPathStub(opt) ? LocationSummary::kCallOnSharedSlowPath : LocationSummary::kCallOnSlowPath); locs->set_in(kLengthPos, Location::RegisterLocation(RangeErrorABI::kLengthReg)); locs->set_in(kIndexPos, Location::RegisterLocation(RangeErrorABI::kIndexReg)); return locs; } void GenericCheckBoundInstr::EmitNativeCode(FlowGraphCompiler* compiler) { ASSERT(representation() == RequiredInputRepresentation(kIndexPos)); ASSERT(representation() == RequiredInputRepresentation(kLengthPos)); RangeErrorSlowPath* slow_path = new RangeErrorSlowPath(this, compiler->CurrentTryIndex()); compiler->AddSlowPathCode(slow_path); Location length_loc = locs()->in(kLengthPos); Location index_loc = locs()->in(kIndexPos); Register length = length_loc.reg(); Register index = index_loc.reg(); const intptr_t index_cid = this->index()->Type()->ToCid(); // The length comes from one of our variable-sized heap objects (e.g. typed // data array) and is therefore guaranteed to be in the positive Smi range. if (representation() == kTagged) { if (index_cid != kSmiCid) { __ BranchIfNotSmi(index, slow_path->entry_label()); } __ CompareObjectRegisters(index, length); } else { ASSERT(representation() == kUnboxedInt64); __ CompareRegisters(index, length); } __ BranchIf(UNSIGNED_GREATER_EQUAL, slow_path->entry_label()); } LocationSummary* CheckNullInstr::MakeLocationSummary(Zone* zone, bool opt) const { const intptr_t kNumInputs = 1; const intptr_t kNumTemps = 0; LocationSummary* locs = new (zone) LocationSummary( zone, kNumInputs, kNumTemps, UseSharedSlowPathStub(opt) ? LocationSummary::kCallOnSharedSlowPath : LocationSummary::kCallOnSlowPath); locs->set_in(0, Location::RequiresRegister()); return locs; } void CheckNullInstr::AddMetadataForRuntimeCall(CheckNullInstr* check_null, FlowGraphCompiler* compiler) { compiler->AddNullCheck(check_null->source(), check_null->function_name()); } void RangeErrorSlowPath::EmitSharedStubCall(FlowGraphCompiler* compiler, bool save_fpu_registers) { #if defined(TARGET_ARCH_IA32) UNREACHABLE(); #else auto object_store = compiler->isolate_group()->object_store(); const auto& stub = Code::ZoneHandle( compiler->zone(), save_fpu_registers ? object_store->range_error_stub_with_fpu_regs_stub() : object_store->range_error_stub_without_fpu_regs_stub()); compiler->EmitCallToStub(stub); #endif } void UnboxInstr::EmitLoadFromBoxWithDeopt(FlowGraphCompiler* compiler) { const intptr_t box_cid = BoxCid(); ASSERT(box_cid != kSmiCid); // Should never reach here with Smi-able ints. const Register box = locs()->in(0).reg(); const Register temp = (locs()->temp_count() > 0) ? locs()->temp(0).reg() : kNoRegister; compiler::Label* deopt = compiler->AddDeoptStub(GetDeoptId(), ICData::kDeoptUnbox); compiler::Label is_smi; if ((value()->Type()->ToNullableCid() == box_cid) && value()->Type()->is_nullable()) { __ CompareObject(box, Object::null_object()); __ BranchIf(EQUAL, deopt); } else { __ BranchIfSmi(box, CanConvertSmi() ? &is_smi : deopt); __ CompareClassId(box, box_cid, temp); __ BranchIf(NOT_EQUAL, deopt); } EmitLoadFromBox(compiler); if (is_smi.IsLinked()) { compiler::Label done; __ Jump(&done); __ Bind(&is_smi); EmitSmiConversion(compiler); __ Bind(&done); } } void UnboxInstr::EmitNativeCode(FlowGraphCompiler* compiler) { if (SpeculativeModeOfInputs() == kNotSpeculative) { if (BoxCid() == kSmiCid) { // Since the representation fits in a Smi, we can extract it directly. ASSERT_EQUAL(value()->Type()->ToCid(), kSmiCid); return EmitSmiConversion(compiler); } switch (representation()) { case kUnboxedDouble: case kUnboxedFloat: case kUnboxedFloat32x4: case kUnboxedFloat64x2: case kUnboxedInt32x4: EmitLoadFromBox(compiler); break; case kUnboxedInt32: EmitLoadInt32FromBoxOrSmi(compiler); break; case kUnboxedInt64: { if (value()->Type()->ToCid() == kSmiCid) { // Smi -> int64 conversion is more efficient than // handling arbitrary smi/mint. EmitSmiConversion(compiler); } else { EmitLoadInt64FromBoxOrSmi(compiler); } break; } default: UNREACHABLE(); break; } } else { ASSERT(SpeculativeModeOfInputs() == kGuardInputs); const intptr_t value_cid = value()->Type()->ToCid(); const intptr_t box_cid = BoxCid(); if (box_cid == kSmiCid || (CanConvertSmi() && (value_cid == kSmiCid))) { ASSERT_EQUAL(value_cid, kSmiCid); EmitSmiConversion(compiler); } else if (representation() == kUnboxedInt32 && value()->Type()->IsInt()) { EmitLoadInt32FromBoxOrSmi(compiler); } else if (representation() == kUnboxedInt64 && value()->Type()->IsInt()) { EmitLoadInt64FromBoxOrSmi(compiler); } else if (value_cid == box_cid) { EmitLoadFromBox(compiler); } else { ASSERT(CanDeoptimize()); EmitLoadFromBoxWithDeopt(compiler); } } } Environment* Environment::From(Zone* zone, const GrowableArray& definitions, intptr_t fixed_parameter_count, intptr_t lazy_deopt_pruning_count, const ParsedFunction& parsed_function) { Environment* env = new (zone) Environment(definitions.length(), fixed_parameter_count, lazy_deopt_pruning_count, parsed_function, NULL); for (intptr_t i = 0; i < definitions.length(); ++i) { env->values_.Add(new (zone) Value(definitions[i])); } return env; } void Environment::PushValue(Value* value) { values_.Add(value); } Environment* Environment::DeepCopy(Zone* zone, intptr_t length) const { ASSERT(length <= values_.length()); Environment* copy = new (zone) Environment( length, fixed_parameter_count_, LazyDeoptPruneCount(), parsed_function_, (outer_ == NULL) ? NULL : outer_->DeepCopy(zone)); copy->SetDeoptId(DeoptIdBits::decode(bitfield_)); if (locations_ != NULL) { Location* new_locations = zone->Alloc(length); copy->set_locations(new_locations); } for (intptr_t i = 0; i < length; ++i) { copy->values_.Add(values_[i]->CopyWithType(zone)); if (locations_ != NULL) { copy->locations_[i] = locations_[i].Copy(); } } return copy; } // Copies the environment and updates the environment use lists. void Environment::DeepCopyTo(Zone* zone, Instruction* instr) const { for (Environment::DeepIterator it(instr->env()); !it.Done(); it.Advance()) { it.CurrentValue()->RemoveFromUseList(); } Environment* copy = DeepCopy(zone); instr->SetEnvironment(copy); for (Environment::DeepIterator it(copy); !it.Done(); it.Advance()) { Value* value = it.CurrentValue(); value->definition()->AddEnvUse(value); } } void Environment::DeepCopyAfterTo(Zone* zone, Instruction* instr, intptr_t argc, Definition* dead, Definition* result) const { for (Environment::DeepIterator it(instr->env()); !it.Done(); it.Advance()) { it.CurrentValue()->RemoveFromUseList(); } Environment* copy = DeepCopy(zone, values_.length() - argc - LazyDeoptPruneCount()); copy->SetLazyDeoptPruneCount(0); for (intptr_t i = 0; i < argc; i++) { copy->values_.Add(new (zone) Value(dead)); } copy->values_.Add(new (zone) Value(result)); instr->SetEnvironment(copy); for (Environment::DeepIterator it(copy); !it.Done(); it.Advance()) { Value* value = it.CurrentValue(); value->definition()->AddEnvUse(value); } } // Copies the environment as outer on an inlined instruction and updates the // environment use lists. void Environment::DeepCopyToOuter(Zone* zone, Instruction* instr, intptr_t outer_deopt_id) const { // Create a deep copy removing caller arguments from the environment. ASSERT(this != NULL); ASSERT(instr->env()->outer() == NULL); intptr_t argument_count = instr->env()->fixed_parameter_count(); Environment* outer = DeepCopy(zone, values_.length() - argument_count - LazyDeoptPruneCount()); outer->SetDeoptId(outer_deopt_id); outer->SetLazyDeoptPruneCount(0); instr->env()->outer_ = outer; intptr_t use_index = instr->env()->Length(); // Start index after inner. for (Environment::DeepIterator it(outer); !it.Done(); it.Advance()) { Value* value = it.CurrentValue(); value->set_instruction(instr); value->set_use_index(use_index++); value->definition()->AddEnvUse(value); } } ComparisonInstr* DoubleTestOpInstr::CopyWithNewOperands(Value* new_left, Value* new_right) { UNREACHABLE(); return NULL; } ComparisonInstr* EqualityCompareInstr::CopyWithNewOperands(Value* new_left, Value* new_right) { return new EqualityCompareInstr(source(), kind(), new_left, new_right, operation_cid(), deopt_id(), is_null_aware(), speculative_mode_); } ComparisonInstr* RelationalOpInstr::CopyWithNewOperands(Value* new_left, Value* new_right) { return new RelationalOpInstr(source(), kind(), new_left, new_right, operation_cid(), deopt_id(), SpeculativeModeOfInputs()); } ComparisonInstr* StrictCompareInstr::CopyWithNewOperands(Value* new_left, Value* new_right) { return new StrictCompareInstr(source(), kind(), new_left, new_right, needs_number_check(), DeoptId::kNone); } ComparisonInstr* TestSmiInstr::CopyWithNewOperands(Value* new_left, Value* new_right) { return new TestSmiInstr(source(), kind(), new_left, new_right); } ComparisonInstr* TestCidsInstr::CopyWithNewOperands(Value* new_left, Value* new_right) { return new TestCidsInstr(source(), kind(), new_left, cid_results(), deopt_id()); } bool TestCidsInstr::AttributesEqual(const Instruction& other) const { auto const other_instr = other.AsTestCids(); if (!ComparisonInstr::AttributesEqual(other)) { return false; } if (cid_results().length() != other_instr->cid_results().length()) { return false; } for (intptr_t i = 0; i < cid_results().length(); i++) { if (cid_results()[i] != other_instr->cid_results()[i]) { return false; } } return true; } bool IfThenElseInstr::Supports(ComparisonInstr* comparison, Value* v1, Value* v2) { bool is_smi_result = v1->BindsToSmiConstant() && v2->BindsToSmiConstant(); if (comparison->IsStrictCompare()) { // Strict comparison with number checks calls a stub and is not supported // by if-conversion. return is_smi_result && !comparison->AsStrictCompare()->needs_number_check(); } if (comparison->operation_cid() != kSmiCid) { // Non-smi comparisons are not supported by if-conversion. return false; } return is_smi_result; } bool PhiInstr::IsRedundant() const { ASSERT(InputCount() > 1); Definition* first = InputAt(0)->definition(); for (intptr_t i = 1; i < InputCount(); ++i) { Definition* def = InputAt(i)->definition(); if (def != first) return false; } return true; } Definition* PhiInstr::GetReplacementForRedundantPhi() const { Definition* first = InputAt(0)->definition(); if (InputCount() == 1) { return first; } ASSERT(InputCount() > 1); Definition* first_origin = first->OriginalDefinition(); bool look_for_redefinition = false; for (intptr_t i = 1; i < InputCount(); ++i) { Definition* def = InputAt(i)->definition(); if ((def != first) && (def != this)) { Definition* origin = def->OriginalDefinition(); if ((origin != first_origin) && (origin != this)) return nullptr; look_for_redefinition = true; } } if (look_for_redefinition) { // Find the most specific redefinition which is common for all inputs // (the longest common chain). Definition* redef = first; for (intptr_t i = 1, n = InputCount(); redef != first_origin && i < n;) { Value* value = InputAt(i); bool found = false; do { Definition* def = value->definition(); if ((def == redef) || (def == this)) { found = true; break; } value = def->RedefinedValue(); } while (value != nullptr); if (found) { ++i; } else { ASSERT(redef != first_origin); redef = redef->RedefinedValue()->definition(); } } return redef; } else { return first; } } Definition* PhiInstr::Canonicalize(FlowGraph* flow_graph) { Definition* replacement = GetReplacementForRedundantPhi(); return (replacement != nullptr) ? replacement : this; } // Removes current phi from graph and sets current to previous phi. void PhiIterator::RemoveCurrentFromGraph() { Current()->UnuseAllInputs(); (*phis_)[index_] = phis_->Last(); phis_->RemoveLast(); --index_; } Instruction* CheckConditionInstr::Canonicalize(FlowGraph* graph) { if (StrictCompareInstr* strict_compare = comparison()->AsStrictCompare()) { if ((InputAt(0)->definition()->OriginalDefinition() == InputAt(1)->definition()->OriginalDefinition()) && strict_compare->kind() == Token::kEQ_STRICT) { return nullptr; } } return this; } bool CheckArrayBoundInstr::IsFixedLengthArrayType(intptr_t cid) { return LoadFieldInstr::IsFixedLengthArrayCid(cid); } Definition* CheckBoundBase::Canonicalize(FlowGraph* flow_graph) { return IsRedundant() ? index()->definition() : this; } intptr_t CheckArrayBoundInstr::LengthOffsetFor(intptr_t class_id) { if (IsTypedDataClassId(class_id) || IsTypedDataViewClassId(class_id) || IsExternalTypedDataClassId(class_id)) { return compiler::target::TypedDataBase::length_offset(); } switch (class_id) { case kGrowableObjectArrayCid: return compiler::target::GrowableObjectArray::length_offset(); case kOneByteStringCid: case kTwoByteStringCid: return compiler::target::String::length_offset(); case kArrayCid: case kImmutableArrayCid: return compiler::target::Array::length_offset(); default: UNREACHABLE(); return -1; } } const Function& StringInterpolateInstr::CallFunction() const { if (function_.IsNull()) { const int kTypeArgsLen = 0; const int kNumberOfArguments = 1; const Array& kNoArgumentNames = Object::null_array(); const Class& cls = Class::Handle(Library::LookupCoreClass(Symbols::StringBase())); ASSERT(!cls.IsNull()); function_ = Resolver::ResolveStatic( cls, Library::PrivateCoreLibName(Symbols::Interpolate()), kTypeArgsLen, kNumberOfArguments, kNoArgumentNames); } ASSERT(!function_.IsNull()); return function_; } // Replace StringInterpolateInstr with a constant string if all inputs are // constant of [string, number, boolean, null]. // Leave the CreateArrayInstr and StoreIndexedInstr in the stream in case // deoptimization occurs. Definition* StringInterpolateInstr::Canonicalize(FlowGraph* flow_graph) { // The following graph structure is generated by the graph builder: // v2 <- CreateArray(v0) // StoreIndexed(v2, v3, v4) -- v3:constant index, v4: value. // .. // v8 <- StringInterpolate(v2) // Don't compile-time fold when optimizing the interpolation function itself. if (flow_graph->function().ptr() == CallFunction().ptr()) { return this; } CreateArrayInstr* create_array = value()->definition()->AsCreateArray(); if (create_array == nullptr) { // Do not try to fold interpolate if array is an OSR argument. ASSERT(flow_graph->IsCompiledForOsr()); ASSERT(value()->definition()->IsPhi() || value()->definition()->IsParameter()); return this; } // Check if the string interpolation has only constant inputs. Value* num_elements = create_array->num_elements(); if (!num_elements->BindsToConstant() || !num_elements->BoundConstant().IsSmi()) { return this; } const intptr_t length = Smi::Cast(num_elements->BoundConstant()).Value(); Thread* thread = Thread::Current(); Zone* zone = thread->zone(); GrowableHandlePtrArray pieces(zone, length); for (intptr_t i = 0; i < length; i++) { pieces.Add(Object::null_string()); } for (Value::Iterator it(create_array->input_use_list()); !it.Done(); it.Advance()) { Instruction* curr = it.Current()->instruction(); if (curr == this) continue; StoreIndexedInstr* store = curr->AsStoreIndexed(); if (store == nullptr || !store->index()->BindsToConstant() || !store->index()->BoundConstant().IsSmi()) { return this; } intptr_t store_index = Smi::Cast(store->index()->BoundConstant()).Value(); ASSERT(store_index < length); ASSERT(store != NULL); if (store->value()->definition()->IsConstant()) { ASSERT(store->index()->BindsToConstant()); const Object& obj = store->value()->definition()->AsConstant()->value(); // TODO(srdjan): Verify if any other types should be converted as well. if (obj.IsString()) { pieces.SetAt(store_index, String::Cast(obj)); } else if (obj.IsSmi()) { const char* cstr = obj.ToCString(); pieces.SetAt(store_index, String::Handle(zone, String::New(cstr, Heap::kOld))); } else if (obj.IsBool()) { pieces.SetAt(store_index, Bool::Cast(obj).value() ? Symbols::True() : Symbols::False()); } else if (obj.IsNull()) { pieces.SetAt(store_index, Symbols::null()); } else { return this; } } else { return this; } } const String& concatenated = String::ZoneHandle(zone, Symbols::FromConcatAll(thread, pieces)); return flow_graph->GetConstant(concatenated); } static AlignmentType StrengthenAlignment(intptr_t cid, AlignmentType alignment) { switch (cid) { case kTypedDataInt8ArrayCid: case kTypedDataUint8ArrayCid: case kTypedDataUint8ClampedArrayCid: case kExternalTypedDataUint8ArrayCid: case kExternalTypedDataUint8ClampedArrayCid: case kOneByteStringCid: case kExternalOneByteStringCid: // Don't need to worry about alignment for accessing bytes. return kAlignedAccess; case kTypedDataFloat64x2ArrayCid: case kTypedDataInt32x4ArrayCid: case kTypedDataFloat32x4ArrayCid: // TODO(rmacnak): Investigate alignment requirements of floating point // loads. return kAlignedAccess; } return alignment; } LoadIndexedInstr::LoadIndexedInstr(Value* array, Value* index, bool index_unboxed, intptr_t index_scale, intptr_t class_id, AlignmentType alignment, intptr_t deopt_id, const InstructionSource& source, CompileType* result_type) : TemplateDefinition(source, deopt_id), index_unboxed_(index_unboxed), index_scale_(index_scale), class_id_(class_id), alignment_(StrengthenAlignment(class_id, alignment)), token_pos_(source.token_pos), result_type_(result_type) { SetInputAt(0, array); SetInputAt(1, index); } Definition* LoadIndexedInstr::Canonicalize(FlowGraph* flow_graph) { auto Z = flow_graph->zone(); if (auto box = index()->definition()->AsBoxInt64()) { // TODO(dartbug.com/39432): Make LoadIndexed fully suport unboxed indices. if (!box->ComputeCanDeoptimize() && compiler::target::kWordSize == 8) { auto load = new (Z) LoadIndexedInstr( array()->CopyWithType(Z), box->value()->CopyWithType(Z), /*index_unboxed=*/true, index_scale(), class_id(), alignment_, GetDeoptId(), source(), result_type_); flow_graph->InsertBefore(this, load, env(), FlowGraph::kValue); return load; } } return this; } Representation LoadIndexedInstr::RepresentationOfArrayElement( intptr_t array_cid) { switch (array_cid) { case kImmutableArrayCid: case kTypeArgumentsCid: return kTagged; case kExternalOneByteStringCid: case kExternalTwoByteStringCid: return kUnboxedIntPtr; default: return StoreIndexedInstr::RepresentationOfArrayElement(array_cid); } } StoreIndexedInstr::StoreIndexedInstr(Value* array, Value* index, Value* value, StoreBarrierType emit_store_barrier, bool index_unboxed, intptr_t index_scale, intptr_t class_id, AlignmentType alignment, intptr_t deopt_id, const InstructionSource& source, SpeculativeMode speculative_mode) : TemplateInstruction(source, deopt_id), emit_store_barrier_(emit_store_barrier), index_unboxed_(index_unboxed), index_scale_(index_scale), class_id_(class_id), alignment_(StrengthenAlignment(class_id, alignment)), token_pos_(source.token_pos), speculative_mode_(speculative_mode) { SetInputAt(kArrayPos, array); SetInputAt(kIndexPos, index); SetInputAt(kValuePos, value); } Instruction* StoreIndexedInstr::Canonicalize(FlowGraph* flow_graph) { auto Z = flow_graph->zone(); if (auto box = index()->definition()->AsBoxInt64()) { // TODO(dartbug.com/39432): Make StoreIndexed fully suport unboxed indices. if (!box->ComputeCanDeoptimize() && compiler::target::kWordSize == 8) { auto store = new (Z) StoreIndexedInstr( array()->CopyWithType(Z), box->value()->CopyWithType(Z), value()->CopyWithType(Z), emit_store_barrier_, /*index_unboxed=*/true, index_scale(), class_id(), alignment_, GetDeoptId(), source(), speculative_mode_); flow_graph->InsertBefore(this, store, env(), FlowGraph::kEffect); return nullptr; } } return this; } Representation StoreIndexedInstr::RepresentationOfArrayElement( intptr_t array_cid) { switch (array_cid) { case kArrayCid: return kTagged; case kOneByteStringCid: case kTwoByteStringCid: case kTypedDataInt8ArrayCid: case kTypedDataInt16ArrayCid: case kTypedDataUint8ArrayCid: case kTypedDataUint8ClampedArrayCid: case kTypedDataUint16ArrayCid: case kExternalTypedDataUint8ArrayCid: case kExternalTypedDataUint8ClampedArrayCid: return kUnboxedIntPtr; case kTypedDataInt32ArrayCid: return kUnboxedInt32; case kTypedDataUint32ArrayCid: return kUnboxedUint32; case kTypedDataInt64ArrayCid: case kTypedDataUint64ArrayCid: return kUnboxedInt64; case kTypedDataFloat32ArrayCid: case kTypedDataFloat64ArrayCid: return kUnboxedDouble; case kTypedDataInt32x4ArrayCid: return kUnboxedInt32x4; case kTypedDataFloat32x4ArrayCid: return kUnboxedFloat32x4; case kTypedDataFloat64x2ArrayCid: return kUnboxedFloat64x2; default: UNREACHABLE(); return kTagged; } } Representation StoreIndexedInstr::RequiredInputRepresentation( intptr_t idx) const { // Array can be a Dart object or a pointer to external data. if (idx == 0) return kNoRepresentation; // Flexible input representation. if (idx == 1) { if (index_unboxed_) { #if defined(TARGET_ARCH_IS_64_BIT) return kUnboxedInt64; #else // TODO(dartbug.com/39432): kUnboxedInt32 || kUnboxedUint32 on 32-bit // architectures. return kNoRepresentation; // Index can be any unboxed representation. #endif } else { return kTagged; // Index is a smi. } } ASSERT(idx == 2); return RepresentationOfArrayElement(class_id()); } bool Utf8ScanInstr::IsScanFlagsUnboxed() const { return FlowGraphCompiler::IsUnboxedField(scan_flags_field_.field()); } InvokeMathCFunctionInstr::InvokeMathCFunctionInstr( ZoneGrowableArray* inputs, intptr_t deopt_id, MethodRecognizer::Kind recognized_kind, const InstructionSource& source) : PureDefinition(source, deopt_id), inputs_(inputs), recognized_kind_(recognized_kind), token_pos_(source.token_pos) { ASSERT(inputs_->length() == ArgumentCountFor(recognized_kind_)); for (intptr_t i = 0; i < inputs_->length(); ++i) { ASSERT((*inputs)[i] != NULL); (*inputs)[i]->set_instruction(this); (*inputs)[i]->set_use_index(i); } } intptr_t InvokeMathCFunctionInstr::ArgumentCountFor( MethodRecognizer::Kind kind) { switch (kind) { case MethodRecognizer::kDoubleTruncate: case MethodRecognizer::kDoubleFloor: case MethodRecognizer::kDoubleCeil: { ASSERT(!TargetCPUFeatures::double_truncate_round_supported()); return 1; } case MethodRecognizer::kDoubleRound: case MethodRecognizer::kMathAtan: case MethodRecognizer::kMathTan: case MethodRecognizer::kMathAcos: case MethodRecognizer::kMathAsin: case MethodRecognizer::kMathSin: case MethodRecognizer::kMathCos: case MethodRecognizer::kMathExp: case MethodRecognizer::kMathLog: return 1; case MethodRecognizer::kDoubleMod: case MethodRecognizer::kMathDoublePow: case MethodRecognizer::kMathAtan2: return 2; default: UNREACHABLE(); } return 0; } const RuntimeEntry& InvokeMathCFunctionInstr::TargetFunction() const { switch (recognized_kind_) { case MethodRecognizer::kDoubleTruncate: return kLibcTruncRuntimeEntry; case MethodRecognizer::kDoubleRound: return kLibcRoundRuntimeEntry; case MethodRecognizer::kDoubleFloor: return kLibcFloorRuntimeEntry; case MethodRecognizer::kDoubleCeil: return kLibcCeilRuntimeEntry; case MethodRecognizer::kMathDoublePow: return kLibcPowRuntimeEntry; case MethodRecognizer::kDoubleMod: return kDartModuloRuntimeEntry; case MethodRecognizer::kMathTan: return kLibcTanRuntimeEntry; case MethodRecognizer::kMathAsin: return kLibcAsinRuntimeEntry; case MethodRecognizer::kMathSin: return kLibcSinRuntimeEntry; case MethodRecognizer::kMathCos: return kLibcCosRuntimeEntry; case MethodRecognizer::kMathAcos: return kLibcAcosRuntimeEntry; case MethodRecognizer::kMathAtan: return kLibcAtanRuntimeEntry; case MethodRecognizer::kMathAtan2: return kLibcAtan2RuntimeEntry; case MethodRecognizer::kMathExp: return kLibcExpRuntimeEntry; case MethodRecognizer::kMathLog: return kLibcLogRuntimeEntry; default: UNREACHABLE(); } return kLibcPowRuntimeEntry; } const char* MathUnaryInstr::KindToCString(MathUnaryKind kind) { switch (kind) { case kIllegal: return "illegal"; case kSqrt: return "sqrt"; case kDoubleSquare: return "double-square"; } UNREACHABLE(); return ""; } TruncDivModInstr::TruncDivModInstr(Value* lhs, Value* rhs, intptr_t deopt_id) : TemplateDefinition(deopt_id) { SetInputAt(0, lhs); SetInputAt(1, rhs); } intptr_t TruncDivModInstr::OutputIndexOf(Token::Kind token) { switch (token) { case Token::kTRUNCDIV: return 0; case Token::kMOD: return 1; default: UNIMPLEMENTED(); return -1; } } LocationSummary* NativeCallInstr::MakeLocationSummary(Zone* zone, bool optimizing) const { return MakeCallSummary(zone, this); } void NativeCallInstr::SetupNative() { if (link_lazily()) { // Resolution will happen during NativeEntry::LinkNativeCall. return; } Zone* zone = Thread::Current()->zone(); const Class& cls = Class::Handle(zone, function().Owner()); const Library& library = Library::Handle(zone, cls.library()); Dart_NativeEntryResolver resolver = library.native_entry_resolver(); bool is_bootstrap_native = Bootstrap::IsBootstrapResolver(resolver); set_is_bootstrap_native(is_bootstrap_native); const int num_params = NativeArguments::ParameterCountForResolution(function()); bool auto_setup_scope = true; NativeFunction native_function = NativeEntry::ResolveNative( library, native_name(), num_params, &auto_setup_scope); if (native_function == NULL) { if (has_inlining_id()) { UNIMPLEMENTED(); } Report::MessageF(Report::kError, Script::Handle(function().script()), function().token_pos(), Report::AtLocation, "native function '%s' (%" Pd " arguments) cannot be found", native_name().ToCString(), function().NumParameters()); } set_is_auto_scope(auto_setup_scope); set_native_c_function(native_function); } #if !defined(TARGET_ARCH_ARM) && !defined(TARGET_ARCH_ARM64) LocationSummary* BitCastInstr::MakeLocationSummary(Zone* zone, bool opt) const { UNREACHABLE(); } void BitCastInstr::EmitNativeCode(FlowGraphCompiler* compiler) { UNREACHABLE(); } #endif // !defined(TARGET_ARCH_ARM) && !defined(TARGET_ARCH_ARM64) Representation FfiCallInstr::RequiredInputRepresentation(intptr_t idx) const { if (idx < TargetAddressIndex()) { return marshaller_.RepInFfiCall(idx); } else if (idx == TargetAddressIndex()) { return kUnboxedFfiIntPtr; } else { ASSERT(idx == TypedDataIndex()); return kTagged; } } #define Z zone_ LocationSummary* FfiCallInstr::MakeLocationSummary(Zone* zone, bool is_optimizing) const { // The temporary register needs to be callee-saved and not an argument // register. ASSERT(((1 << CallingConventions::kFfiAnyNonAbiRegister) & CallingConventions::kArgumentRegisters) == 0); constexpr intptr_t kNumTemps = 2; LocationSummary* summary = new (zone) LocationSummary(zone, /*num_inputs=*/InputCount(), /*num_temps=*/kNumTemps, LocationSummary::kCall); const Register temp0 = CallingConventions::kSecondNonArgumentRegister; const Register temp1 = CallingConventions::kFfiAnyNonAbiRegister; ASSERT(temp0 != temp1); summary->set_temp(0, Location::RegisterLocation(temp0)); summary->set_temp(1, Location::RegisterLocation(temp1)); summary->set_in(TargetAddressIndex(), Location::RegisterLocation( CallingConventions::kFirstNonArgumentRegister)); for (intptr_t i = 0, n = marshaller_.NumDefinitions(); i < n; ++i) { summary->set_in(i, marshaller_.LocInFfiCall(i)); } if (marshaller_.PassTypedData()) { // The register allocator already preserves this value across the call on // a stack slot, so we'll use the spilled value directly. summary->set_in(TypedDataIndex(), Location::RequiresStackSlot()); // We don't care about return location, but we need to pass a register. summary->set_out( 0, Location::RegisterLocation(CallingConventions::kReturnReg)); } else { summary->set_out(0, marshaller_.LocInFfiCall(compiler::ffi::kResultIndex)); } return summary; } void FfiCallInstr::EmitParamMoves(FlowGraphCompiler* compiler) { if (compiler::Assembler::EmittingComments()) { __ Comment("EmitParamMoves"); } const Register saved_fp = locs()->temp(0).reg(); const Register temp = locs()->temp(1).reg(); // Moves for return pointer. const auto& return_location = marshaller_.Location(compiler::ffi::kResultIndex); if (return_location.IsPointerToMemory()) { const auto& pointer_location = return_location.AsPointerToMemory().pointer_location(); const auto& pointer_register = pointer_location.IsRegisters() ? pointer_location.AsRegisters().reg_at(0) : temp; __ MoveRegister(pointer_register, SPREG); __ AddImmediate(pointer_register, marshaller_.PassByPointerStackOffset( compiler::ffi::kResultIndex)); if (pointer_location.IsStack()) { const auto& pointer_stack = pointer_location.AsStack(); __ StoreMemoryValue(pointer_register, pointer_stack.base_register(), pointer_stack.offset_in_bytes()); } } // Moves for arguments. compiler::ffi::FrameRebase rebase(zone_, /*old_base=*/FPREG, /*new_base=*/saved_fp, /*stack_delta=*/0); intptr_t def_index = 0; for (intptr_t arg_index = 0; arg_index < marshaller_.num_args(); arg_index++) { const intptr_t num_defs = marshaller_.NumDefinitions(arg_index); const auto& arg_target = marshaller_.Location(arg_index); // First deal with moving all individual definitions passed in to the // FfiCall to the right native location based on calling convention. for (intptr_t i = 0; i < num_defs; i++) { const Location origin = rebase.Rebase(locs()->in(def_index)); const Representation origin_rep = RequiredInputRepresentation(def_index) == kTagged ? kUnboxedFfiIntPtr // When arg_target.IsPointerToMemory(). : RequiredInputRepresentation(def_index); // Find the native location where this individual definition should be // moved to. const auto& def_target = arg_target.payload_type().IsPrimitive() ? arg_target : arg_target.IsMultiple() ? *arg_target.AsMultiple().locations()[i] : arg_target.IsPointerToMemory() ? arg_target.AsPointerToMemory().pointer_location() : /*arg_target.IsStack()*/ arg_target.Split( zone_, num_defs, i); ConstantTemporaryAllocator temp_alloc(temp); if (origin.IsConstant()) { compiler->EmitMoveConst(def_target, origin, origin_rep, &temp_alloc); } else { compiler->EmitMoveToNative(def_target, origin, origin_rep, &temp_alloc); } def_index++; } // Then make sure that any pointers passed through the calling convention // actually have a copy of the struct. // Note that the step above has already moved the pointer into the expected // native location. if (arg_target.IsPointerToMemory()) { NoTemporaryAllocator temp_alloc; const auto& pointer_loc = arg_target.AsPointerToMemory().pointer_location(); // TypedData/Pointer data pointed to in temp. const auto& dst = compiler::ffi::NativeRegistersLocation( zone_, pointer_loc.payload_type(), pointer_loc.container_type(), temp); compiler->EmitNativeMove(dst, pointer_loc, &temp_alloc); __ LoadField( temp, compiler::FieldAddress( temp, compiler::target::TypedDataBase::data_field_offset())); // Copy chuncks. const intptr_t sp_offset = marshaller_.PassByPointerStackOffset(arg_index); // Struct size is rounded up to a multiple of target::kWordSize. // This is safe because we do the same rounding when we allocate the // space on the stack. for (intptr_t i = 0; i < arg_target.payload_type().SizeInBytes(); i += compiler::target::kWordSize) { __ LoadMemoryValue(TMP, temp, i); __ StoreMemoryValue(TMP, SPREG, i + sp_offset); } // Store the stack address in the argument location. __ MoveRegister(temp, SPREG); __ AddImmediate(temp, sp_offset); const auto& src = compiler::ffi::NativeRegistersLocation( zone_, pointer_loc.payload_type(), pointer_loc.container_type(), temp); compiler->EmitNativeMove(pointer_loc, src, &temp_alloc); } } if (compiler::Assembler::EmittingComments()) { __ Comment("EmitParamMovesEnd"); } } void FfiCallInstr::EmitReturnMoves(FlowGraphCompiler* compiler) { __ Comment("EmitReturnMoves"); const auto& returnLocation = marshaller_.Location(compiler::ffi::kResultIndex); if (returnLocation.payload_type().IsVoid()) { return; } NoTemporaryAllocator no_temp; if (returnLocation.IsRegisters() || returnLocation.IsFpuRegisters()) { const auto& src = returnLocation; const Location dst_loc = locs()->out(0); const Representation dst_type = representation(); compiler->EmitMoveFromNative(dst_loc, dst_type, src, &no_temp); } else if (returnLocation.IsPointerToMemory() || returnLocation.IsMultiple()) { ASSERT(returnLocation.payload_type().IsCompound()); ASSERT(marshaller_.PassTypedData()); const Register temp0 = TMP != kNoRegister ? TMP : locs()->temp(0).reg(); const Register temp1 = locs()->temp(1).reg(); ASSERT(temp0 != temp1); // Get the typed data pointer which we have pinned to a stack slot. const Location typed_data_loc = locs()->in(TypedDataIndex()); ASSERT(typed_data_loc.IsStackSlot()); ASSERT(typed_data_loc.base_reg() == FPREG); __ LoadMemoryValue(temp0, FPREG, 0); __ LoadMemoryValue(temp0, temp0, typed_data_loc.ToStackSlotOffset()); __ LoadField( temp0, compiler::FieldAddress( temp0, compiler::target::TypedDataBase::data_field_offset())); if (returnLocation.IsPointerToMemory()) { // Copy blocks from the stack location to TypedData. // Struct size is rounded up to a multiple of target::kWordSize. // This is safe because we do the same rounding when we allocate the // TypedData in IL. const intptr_t sp_offset = marshaller_.PassByPointerStackOffset(compiler::ffi::kResultIndex); for (intptr_t i = 0; i < marshaller_.TypedDataSizeInBytes(); i += compiler::target::kWordSize) { __ LoadMemoryValue(temp1, SPREG, i + sp_offset); __ StoreMemoryValue(temp1, temp0, i); } } else { ASSERT(returnLocation.IsMultiple()); // Copy to the struct from the native locations. const auto& multiple = marshaller_.Location(compiler::ffi::kResultIndex).AsMultiple(); int offset_in_bytes = 0; for (int i = 0; i < multiple.locations().length(); i++) { const auto& src = *multiple.locations().At(i); const auto& dst = compiler::ffi::NativeStackLocation( src.payload_type(), src.container_type(), temp0, offset_in_bytes); compiler->EmitNativeMove(dst, src, &no_temp); offset_in_bytes += src.payload_type().SizeInBytes(); } } } else { UNREACHABLE(); } __ Comment("EmitReturnMovesEnd"); } static Location FirstArgumentLocation() { #ifdef TARGET_ARCH_IA32 return Location::StackSlot(0, SPREG); #else return Location::RegisterLocation(CallingConventions::ArgumentRegisters[0]); #endif } LocationSummary* EnterHandleScopeInstr::MakeLocationSummary( Zone* zone, bool is_optimizing) const { LocationSummary* summary = new (zone) LocationSummary(zone, /*num_inputs=*/0, /*num_temps=*/0, LocationSummary::kCall); summary->set_out(0, Location::RegisterLocation(CallingConventions::kReturnReg)); return summary; } void EnterHandleScopeInstr::EmitNativeCode(FlowGraphCompiler* compiler) { ASSERT(kEnterHandleScopeRuntimeEntry.is_leaf()); if (kind_ == Kind::kGetTopHandleScope) { __ LoadMemoryValue(CallingConventions::kReturnReg, THR, compiler::target::Thread::api_top_scope_offset()); return; } Location arg_loc = FirstArgumentLocation(); __ EnterCFrame(arg_loc.IsRegister() ? 0 : compiler::target::kWordSize); NoTemporaryAllocator no_temp; compiler->EmitMove(arg_loc, Location::RegisterLocation(THR), &no_temp); __ CallCFunction( compiler::Address(THR, compiler::target::Thread::OffsetFromThread( &kEnterHandleScopeRuntimeEntry))); __ LeaveCFrame(); } LocationSummary* ExitHandleScopeInstr::MakeLocationSummary( Zone* zone, bool is_optimizing) const { LocationSummary* summary = new (zone) LocationSummary(zone, /*num_inputs=*/0, /*num_temps=*/0, LocationSummary::kCall); return summary; } void ExitHandleScopeInstr::EmitNativeCode(FlowGraphCompiler* compiler) { ASSERT(kEnterHandleScopeRuntimeEntry.is_leaf()); Location arg_loc = FirstArgumentLocation(); __ EnterCFrame(arg_loc.IsRegister() ? 0 : compiler::target::kWordSize); NoTemporaryAllocator no_temp; compiler->EmitMove(arg_loc, Location::RegisterLocation(THR), &no_temp); __ CallCFunction( compiler::Address(THR, compiler::target::Thread::OffsetFromThread( &kExitHandleScopeRuntimeEntry))); __ LeaveCFrame(); } LocationSummary* AllocateHandleInstr::MakeLocationSummary( Zone* zone, bool is_optimizing) const { LocationSummary* summary = new (zone) LocationSummary(zone, /*num_inputs=*/1, /*num_temps=*/0, LocationSummary::kCall); Location arg_loc = FirstArgumentLocation(); // Assign input to a register that does not conflict with anything if // argument is passed on the stack. const Register scope_reg = arg_loc.IsStackSlot() ? CallingConventions::kSecondNonArgumentRegister : arg_loc.reg(); summary->set_in(kScope, Location::RegisterLocation(scope_reg)); summary->set_out(0, Location::RegisterLocation(CallingConventions::kReturnReg)); return summary; } Representation AllocateHandleInstr::RequiredInputRepresentation( intptr_t idx) const { ASSERT(idx == kScope); return kUnboxedIntPtr; } void AllocateHandleInstr::EmitNativeCode(FlowGraphCompiler* compiler) { ASSERT(kEnterHandleScopeRuntimeEntry.is_leaf()); Location arg_loc = FirstArgumentLocation(); __ EnterCFrame(arg_loc.IsRegister() ? 0 : compiler::target::kWordSize); if (arg_loc.IsStackSlot()) { NoTemporaryAllocator no_temp; compiler->EmitMove(arg_loc, locs()->in(kScope), &no_temp); } __ CallCFunction( compiler::Address(THR, compiler::target::Thread::OffsetFromThread( &kAllocateHandleRuntimeEntry))); __ LeaveCFrame(); } LocationSummary* RawStoreFieldInstr::MakeLocationSummary( Zone* zone, bool is_optimizing) const { LocationSummary* summary = new (zone) LocationSummary(zone, /*num_inputs=*/2, /*num_temps=*/0, LocationSummary::kNoCall); summary->set_in(kBase, Location::RequiresRegister()); summary->set_in(kValue, Location::RequiresRegister()); return summary; } Representation RawStoreFieldInstr::RequiredInputRepresentation( intptr_t idx) const { switch (idx) { case kBase: return kUntagged; case kValue: return kTagged; default: break; } UNREACHABLE(); } void RawStoreFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) { const Register base_reg = locs()->in(kBase).reg(); const Register value_reg = locs()->in(kValue).reg(); compiler->assembler()->StoreMemoryValue(value_reg, base_reg, offset_); } void NativeReturnInstr::EmitReturnMoves(FlowGraphCompiler* compiler) { const auto& dst1 = marshaller_.Location(compiler::ffi::kResultIndex); if (dst1.payload_type().IsVoid()) { return; } if (dst1.IsMultiple()) { Register typed_data_reg = locs()->in(0).reg(); // Load the data pointer out of the TypedData/Pointer. __ LoadField(typed_data_reg, compiler::FieldAddress( typed_data_reg, compiler::target::TypedDataBase::data_field_offset())); const auto& multiple = dst1.AsMultiple(); int offset_in_bytes = 0; for (intptr_t i = 0; i < multiple.locations().length(); i++) { const auto& dst = *multiple.locations().At(i); ASSERT(!dst.IsRegisters() || dst.AsRegisters().reg_at(0) != typed_data_reg); const auto& src = compiler::ffi::NativeStackLocation( dst.payload_type(), dst.container_type(), typed_data_reg, offset_in_bytes); NoTemporaryAllocator no_temp; compiler->EmitNativeMove(dst, src, &no_temp); offset_in_bytes += dst.payload_type().SizeInBytes(); } return; } const auto& dst = dst1.IsPointerToMemory() ? dst1.AsPointerToMemory().pointer_return_location() : dst1; const Location src_loc = locs()->in(0); const Representation src_type = RequiredInputRepresentation(0); NoTemporaryAllocator no_temp; compiler->EmitMoveToNative(dst, src_loc, src_type, &no_temp); } LocationSummary* NativeReturnInstr::MakeLocationSummary(Zone* zone, bool opt) const { const intptr_t kNumInputs = 1; const intptr_t kNumTemps = 0; LocationSummary* locs = new (zone) LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kNoCall); ASSERT(marshaller_.NumReturnDefinitions() == 1); const auto& native_loc = marshaller_.Location(compiler::ffi::kResultIndex); const auto& native_return_loc = native_loc.IsPointerToMemory() ? native_loc.AsPointerToMemory().pointer_return_location() : native_loc; if (native_loc.IsMultiple()) { // We pass in a typed data for easy copying in machine code. // Can be any register which does not conflict with return registers. Register typed_data_reg = CallingConventions::kSecondNonArgumentRegister; ASSERT(typed_data_reg != CallingConventions::kReturnReg); ASSERT(typed_data_reg != CallingConventions::kSecondReturnReg); locs->set_in(0, Location::RegisterLocation(typed_data_reg)); } else { locs->set_in(0, native_return_loc.AsLocation()); } return locs; } #undef Z Representation FfiCallInstr::representation() const { if (marshaller_.PassTypedData()) { // Don't care, we're discarding the value. return kTagged; } return marshaller_.RepInFfiCall(compiler::ffi::kResultIndex); } // SIMD SimdOpInstr::Kind SimdOpInstr::KindForOperator(MethodRecognizer::Kind kind) { switch (kind) { case MethodRecognizer::kFloat32x4Mul: return SimdOpInstr::kFloat32x4Mul; case MethodRecognizer::kFloat32x4Div: return SimdOpInstr::kFloat32x4Div; case MethodRecognizer::kFloat32x4Add: return SimdOpInstr::kFloat32x4Add; case MethodRecognizer::kFloat32x4Sub: return SimdOpInstr::kFloat32x4Sub; case MethodRecognizer::kFloat64x2Mul: return SimdOpInstr::kFloat64x2Mul; case MethodRecognizer::kFloat64x2Div: return SimdOpInstr::kFloat64x2Div; case MethodRecognizer::kFloat64x2Add: return SimdOpInstr::kFloat64x2Add; case MethodRecognizer::kFloat64x2Sub: return SimdOpInstr::kFloat64x2Sub; default: break; } UNREACHABLE(); return SimdOpInstr::kIllegalSimdOp; } SimdOpInstr* SimdOpInstr::CreateFromCall(Zone* zone, MethodRecognizer::Kind kind, Definition* receiver, Instruction* call, intptr_t mask /* = 0 */) { SimdOpInstr* op; switch (kind) { case MethodRecognizer::kFloat32x4Mul: case MethodRecognizer::kFloat32x4Div: case MethodRecognizer::kFloat32x4Add: case MethodRecognizer::kFloat32x4Sub: case MethodRecognizer::kFloat64x2Mul: case MethodRecognizer::kFloat64x2Div: case MethodRecognizer::kFloat64x2Add: case MethodRecognizer::kFloat64x2Sub: op = new (zone) SimdOpInstr(KindForOperator(kind), call->deopt_id()); break; default: op = new (zone) SimdOpInstr(KindForMethod(kind), call->deopt_id()); break; } if (receiver != nullptr) { op->SetInputAt(0, new (zone) Value(receiver)); } for (intptr_t i = (receiver != nullptr ? 1 : 0); i < op->InputCount(); i++) { op->SetInputAt(i, call->ArgumentValueAt(i)->CopyWithType(zone)); } if (op->HasMask()) { op->set_mask(mask); } ASSERT(call->ArgumentCount() == (op->InputCount() + (op->HasMask() ? 1 : 0))); return op; } SimdOpInstr* SimdOpInstr::CreateFromFactoryCall(Zone* zone, MethodRecognizer::Kind kind, Instruction* call) { SimdOpInstr* op = new (zone) SimdOpInstr(KindForMethod(kind), call->deopt_id()); for (intptr_t i = 0; i < op->InputCount(); i++) { // Note: ArgumentAt(0) is type arguments which we don't need. op->SetInputAt(i, call->ArgumentValueAt(i + 1)->CopyWithType(zone)); } ASSERT(call->ArgumentCount() == (op->InputCount() + 1)); return op; } SimdOpInstr::Kind SimdOpInstr::KindForOperator(intptr_t cid, Token::Kind op) { switch (cid) { case kFloat32x4Cid: switch (op) { case Token::kADD: return kFloat32x4Add; case Token::kSUB: return kFloat32x4Sub; case Token::kMUL: return kFloat32x4Mul; case Token::kDIV: return kFloat32x4Div; default: break; } break; case kFloat64x2Cid: switch (op) { case Token::kADD: return kFloat64x2Add; case Token::kSUB: return kFloat64x2Sub; case Token::kMUL: return kFloat64x2Mul; case Token::kDIV: return kFloat64x2Div; default: break; } break; case kInt32x4Cid: switch (op) { case Token::kADD: return kInt32x4Add; case Token::kSUB: return kInt32x4Sub; case Token::kBIT_AND: return kInt32x4BitAnd; case Token::kBIT_OR: return kInt32x4BitOr; case Token::kBIT_XOR: return kInt32x4BitXor; default: break; } break; } UNREACHABLE(); return kIllegalSimdOp; } SimdOpInstr::Kind SimdOpInstr::KindForMethod(MethodRecognizer::Kind kind) { switch (kind) { #define CASE_METHOD(Arity, Mask, Name, ...) \ case MethodRecognizer::k##Name: \ return k##Name; #define CASE_BINARY_OP(Arity, Mask, Name, Args, Result) SIMD_OP_LIST(CASE_METHOD, CASE_BINARY_OP) #undef CASE_METHOD #undef CASE_BINARY_OP default: break; } FATAL1("Not a SIMD method: %s", MethodRecognizer::KindToCString(kind)); return kIllegalSimdOp; } // Methods InputCount(), representation(), RequiredInputRepresentation() and // HasMask() are using an array of SimdOpInfo structures representing all // necessary information about the instruction. struct SimdOpInfo { uint8_t arity; bool has_mask; Representation output; Representation inputs[4]; }; // Make representaion from type name used by SIMD_OP_LIST. #define REP(T) (kUnboxed##T) static const Representation kUnboxedBool = kTagged; static const Representation kUnboxedInt8 = kUnboxedInt32; #define ENCODE_INPUTS_0() #define ENCODE_INPUTS_1(In0) REP(In0) #define ENCODE_INPUTS_2(In0, In1) REP(In0), REP(In1) #define ENCODE_INPUTS_3(In0, In1, In2) REP(In0), REP(In1), REP(In2) #define ENCODE_INPUTS_4(In0, In1, In2, In3) \ REP(In0), REP(In1), REP(In2), REP(In3) // Helpers for correct interpretation of the Mask field in the SIMD_OP_LIST. #define HAS_MASK true #define HAS__ false // Define the metadata array. static const SimdOpInfo simd_op_information[] = { #define PP_APPLY(M, Args) M Args #define CASE(Arity, Mask, Name, Args, Result) \ {Arity, HAS_##Mask, REP(Result), {PP_APPLY(ENCODE_INPUTS_##Arity, Args)}}, SIMD_OP_LIST(CASE, CASE) #undef CASE #undef PP_APPLY }; // Undef all auxiliary macros. #undef ENCODE_INFORMATION #undef HAS__ #undef HAS_MASK #undef ENCODE_INPUTS_0 #undef ENCODE_INPUTS_1 #undef ENCODE_INPUTS_2 #undef ENCODE_INPUTS_3 #undef ENCODE_INPUTS_4 #undef REP intptr_t SimdOpInstr::InputCount() const { return simd_op_information[kind()].arity; } Representation SimdOpInstr::representation() const { return simd_op_information[kind()].output; } Representation SimdOpInstr::RequiredInputRepresentation(intptr_t idx) const { ASSERT(0 <= idx && idx < InputCount()); return simd_op_information[kind()].inputs[idx]; } bool SimdOpInstr::HasMask() const { return simd_op_information[kind()].has_mask; } #undef __ } // namespace dart