/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "intrinsics.h" #include "art_field-inl.h" #include "art_method-inl.h" #include "class_linker.h" #include "driver/compiler_driver.h" #include "driver/compiler_options.h" #include "invoke_type.h" #include "mirror/dex_cache-inl.h" #include "nodes.h" #include "scoped_thread_state_change-inl.h" #include "thread-inl.h" #include "utils.h" namespace art { // Function that returns whether an intrinsic is static/direct or virtual. static inline InvokeType GetIntrinsicInvokeType(Intrinsics i) { switch (i) { case Intrinsics::kNone: return kInterface; // Non-sensical for intrinsic. #define OPTIMIZING_INTRINSICS(Name, IsStatic, NeedsEnvironmentOrCache, SideEffects, Exceptions, ...) \ case Intrinsics::k ## Name: \ return IsStatic; #include "intrinsics_list.h" INTRINSICS_LIST(OPTIMIZING_INTRINSICS) #undef INTRINSICS_LIST #undef OPTIMIZING_INTRINSICS } return kInterface; } // Function that returns whether an intrinsic needs an environment or not. static inline IntrinsicNeedsEnvironmentOrCache NeedsEnvironmentOrCache(Intrinsics i) { switch (i) { case Intrinsics::kNone: return kNeedsEnvironmentOrCache; // Non-sensical for intrinsic. #define OPTIMIZING_INTRINSICS(Name, IsStatic, NeedsEnvironmentOrCache, SideEffects, Exceptions, ...) \ case Intrinsics::k ## Name: \ return NeedsEnvironmentOrCache; #include "intrinsics_list.h" INTRINSICS_LIST(OPTIMIZING_INTRINSICS) #undef INTRINSICS_LIST #undef OPTIMIZING_INTRINSICS } return kNeedsEnvironmentOrCache; } // Function that returns whether an intrinsic has side effects. static inline IntrinsicSideEffects GetSideEffects(Intrinsics i) { switch (i) { case Intrinsics::kNone: return kAllSideEffects; #define OPTIMIZING_INTRINSICS(Name, IsStatic, NeedsEnvironmentOrCache, SideEffects, Exceptions, ...) \ case Intrinsics::k ## Name: \ return SideEffects; #include "intrinsics_list.h" INTRINSICS_LIST(OPTIMIZING_INTRINSICS) #undef INTRINSICS_LIST #undef OPTIMIZING_INTRINSICS } return kAllSideEffects; } // Function that returns whether an intrinsic can throw exceptions. static inline IntrinsicExceptions GetExceptions(Intrinsics i) { switch (i) { case Intrinsics::kNone: return kCanThrow; #define OPTIMIZING_INTRINSICS(Name, IsStatic, NeedsEnvironmentOrCache, SideEffects, Exceptions, ...) \ case Intrinsics::k ## Name: \ return Exceptions; #include "intrinsics_list.h" INTRINSICS_LIST(OPTIMIZING_INTRINSICS) #undef INTRINSICS_LIST #undef OPTIMIZING_INTRINSICS } return kCanThrow; } static bool CheckInvokeType(Intrinsics intrinsic, HInvoke* invoke) { // Whenever the intrinsic is marked as static, report an error if we find an InvokeVirtual. // // Whenever the intrinsic is marked as direct and we find an InvokeVirtual, a devirtualization // failure occured. We might be in a situation where we have inlined a method that calls an // intrinsic, but that method is in a different dex file on which we do not have a // verified_method that would have helped the compiler driver sharpen the call. In that case, // make sure that the intrinsic is actually for some final method (or in a final class), as // otherwise the intrinsics setup is broken. // // For the last direction, we have intrinsics for virtual functions that will perform a check // inline. If the precise type is known, however, the instruction will be sharpened to an // InvokeStaticOrDirect. InvokeType intrinsic_type = GetIntrinsicInvokeType(intrinsic); InvokeType invoke_type = invoke->GetInvokeType(); switch (intrinsic_type) { case kStatic: return (invoke_type == kStatic); case kDirect: if (invoke_type == kDirect) { return true; } if (invoke_type == kVirtual) { ArtMethod* art_method = invoke->GetResolvedMethod(); ScopedObjectAccess soa(Thread::Current()); return (art_method->IsFinal() || art_method->GetDeclaringClass()->IsFinal()); } return false; case kVirtual: // Call might be devirtualized. return (invoke_type == kVirtual || invoke_type == kDirect); default: return false; } } void IntrinsicsRecognizer::Run() { ScopedObjectAccess soa(Thread::Current()); for (HBasicBlock* block : graph_->GetReversePostOrder()) { for (HInstructionIterator inst_it(block->GetInstructions()); !inst_it.Done(); inst_it.Advance()) { HInstruction* inst = inst_it.Current(); if (inst->IsInvoke()) { HInvoke* invoke = inst->AsInvoke(); ArtMethod* art_method = invoke->GetResolvedMethod(); if (art_method != nullptr && art_method->IsIntrinsic()) { Intrinsics intrinsic = static_cast<Intrinsics>(art_method->GetIntrinsic()); if (!CheckInvokeType(intrinsic, invoke)) { LOG(WARNING) << "Found an intrinsic with unexpected invoke type: " << intrinsic << " for " << art_method->PrettyMethod() << invoke->DebugName(); } else { invoke->SetIntrinsic(intrinsic, NeedsEnvironmentOrCache(intrinsic), GetSideEffects(intrinsic), GetExceptions(intrinsic)); MaybeRecordStat(MethodCompilationStat::kIntrinsicRecognized); } } } } } } std::ostream& operator<<(std::ostream& os, const Intrinsics& intrinsic) { switch (intrinsic) { case Intrinsics::kNone: os << "None"; break; #define OPTIMIZING_INTRINSICS(Name, IsStatic, NeedsEnvironmentOrCache, SideEffects, Exceptions, ...) \ case Intrinsics::k ## Name: \ os << # Name; \ break; #include "intrinsics_list.h" INTRINSICS_LIST(OPTIMIZING_INTRINSICS) #undef STATIC_INTRINSICS_LIST #undef VIRTUAL_INTRINSICS_LIST #undef OPTIMIZING_INTRINSICS } return os; } void IntrinsicVisitor::ComputeIntegerValueOfLocations(HInvoke* invoke, CodeGenerator* codegen, Location return_location, Location first_argument_location) { if (Runtime::Current()->IsAotCompiler()) { if (codegen->GetCompilerOptions().IsBootImage() || codegen->GetCompilerOptions().GetCompilePic()) { // TODO(ngeoffray): Support boot image compilation. return; } } IntegerValueOfInfo info = ComputeIntegerValueOfInfo(); // Most common case is that we have found all we needed (classes are initialized // and in the boot image). Bail if not. if (info.integer_cache == nullptr || info.integer == nullptr || info.cache == nullptr || info.value_offset == 0 || // low and high cannot be 0, per the spec. info.low == 0 || info.high == 0) { LOG(INFO) << "Integer.valueOf will not be optimized"; return; } // The intrinsic will call if it needs to allocate a j.l.Integer. LocationSummary* locations = new (invoke->GetBlock()->GetGraph()->GetArena()) LocationSummary( invoke, LocationSummary::kCallOnMainOnly, kIntrinsified); if (!invoke->InputAt(0)->IsConstant()) { locations->SetInAt(0, Location::RequiresRegister()); } locations->AddTemp(first_argument_location); locations->SetOut(return_location); } IntrinsicVisitor::IntegerValueOfInfo IntrinsicVisitor::ComputeIntegerValueOfInfo() { // Note that we could cache all of the data looked up here. but there's no good // location for it. We don't want to add it to WellKnownClasses, to avoid creating global // jni values. Adding it as state to the compiler singleton seems like wrong // separation of concerns. // The need for this data should be pretty rare though. // The most common case is that the classes are in the boot image and initialized, // which is easy to generate code for. We bail if not. Thread* self = Thread::Current(); ScopedObjectAccess soa(self); Runtime* runtime = Runtime::Current(); ClassLinker* class_linker = runtime->GetClassLinker(); gc::Heap* heap = runtime->GetHeap(); IntegerValueOfInfo info; info.integer_cache = class_linker->FindSystemClass(self, "Ljava/lang/Integer$IntegerCache;"); if (info.integer_cache == nullptr) { self->ClearException(); return info; } if (!heap->ObjectIsInBootImageSpace(info.integer_cache) || !info.integer_cache->IsInitialized()) { // Optimization only works if the class is initialized and in the boot image. return info; } info.integer = class_linker->FindSystemClass(self, "Ljava/lang/Integer;"); if (info.integer == nullptr) { self->ClearException(); return info; } if (!heap->ObjectIsInBootImageSpace(info.integer) || !info.integer->IsInitialized()) { // Optimization only works if the class is initialized and in the boot image. return info; } ArtField* field = info.integer_cache->FindDeclaredStaticField("cache", "[Ljava/lang/Integer;"); if (field == nullptr) { return info; } info.cache = static_cast<mirror::ObjectArray<mirror::Object>*>( field->GetObject(info.integer_cache).Ptr()); if (info.cache == nullptr) { return info; } if (!heap->ObjectIsInBootImageSpace(info.cache)) { // Optimization only works if the object is in the boot image. return info; } field = info.integer->FindDeclaredInstanceField("value", "I"); if (field == nullptr) { return info; } info.value_offset = field->GetOffset().Int32Value(); field = info.integer_cache->FindDeclaredStaticField("low", "I"); if (field == nullptr) { return info; } info.low = field->GetInt(info.integer_cache); field = info.integer_cache->FindDeclaredStaticField("high", "I"); if (field == nullptr) { return info; } info.high = field->GetInt(info.integer_cache); DCHECK_EQ(info.cache->GetLength(), info.high - info.low + 1); return info; } } // namespace art