/* * 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_method.h" #include "class_linker.h" #include "dex/quick/dex_file_method_inliner.h" #include "dex/quick/dex_file_to_method_inliner_map.h" #include "driver/compiler_driver.h" #include "invoke_type.h" #include "mirror/dex_cache-inl.h" #include "nodes.h" #include "quick/inline_method_analyser.h" #include "scoped_thread_state_change.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 Primitive::Type GetType(uint64_t data, bool is_op_size) { if (is_op_size) { switch (static_cast<OpSize>(data)) { case kSignedByte: return Primitive::kPrimByte; case kSignedHalf: return Primitive::kPrimShort; case k32: return Primitive::kPrimInt; case k64: return Primitive::kPrimLong; default: LOG(FATAL) << "Unknown/unsupported op size " << data; UNREACHABLE(); } } else { if ((data & kIntrinsicFlagIsLong) != 0) { return Primitive::kPrimLong; } if ((data & kIntrinsicFlagIsObject) != 0) { return Primitive::kPrimNot; } return Primitive::kPrimInt; } } static Intrinsics GetIntrinsic(InlineMethod method) { switch (method.opcode) { // Floating-point conversions. case kIntrinsicDoubleCvt: return ((method.d.data & kIntrinsicFlagToFloatingPoint) == 0) ? Intrinsics::kDoubleDoubleToRawLongBits : Intrinsics::kDoubleLongBitsToDouble; case kIntrinsicFloatCvt: return ((method.d.data & kIntrinsicFlagToFloatingPoint) == 0) ? Intrinsics::kFloatFloatToRawIntBits : Intrinsics::kFloatIntBitsToFloat; case kIntrinsicFloat2Int: return Intrinsics::kFloatFloatToIntBits; case kIntrinsicDouble2Long: return Intrinsics::kDoubleDoubleToLongBits; // Floating-point tests. case kIntrinsicFloatIsInfinite: return Intrinsics::kFloatIsInfinite; case kIntrinsicDoubleIsInfinite: return Intrinsics::kDoubleIsInfinite; case kIntrinsicFloatIsNaN: return Intrinsics::kFloatIsNaN; case kIntrinsicDoubleIsNaN: return Intrinsics::kDoubleIsNaN; // Bit manipulations. case kIntrinsicReverseBits: switch (GetType(method.d.data, true)) { case Primitive::kPrimInt: return Intrinsics::kIntegerReverse; case Primitive::kPrimLong: return Intrinsics::kLongReverse; default: LOG(FATAL) << "Unknown/unsupported op size " << method.d.data; UNREACHABLE(); } case kIntrinsicReverseBytes: switch (GetType(method.d.data, true)) { case Primitive::kPrimShort: return Intrinsics::kShortReverseBytes; case Primitive::kPrimInt: return Intrinsics::kIntegerReverseBytes; case Primitive::kPrimLong: return Intrinsics::kLongReverseBytes; default: LOG(FATAL) << "Unknown/unsupported op size " << method.d.data; UNREACHABLE(); } case kIntrinsicRotateRight: switch (GetType(method.d.data, true)) { case Primitive::kPrimInt: return Intrinsics::kIntegerRotateRight; case Primitive::kPrimLong: return Intrinsics::kLongRotateRight; default: LOG(FATAL) << "Unknown/unsupported op size " << method.d.data; UNREACHABLE(); } case kIntrinsicRotateLeft: switch (GetType(method.d.data, true)) { case Primitive::kPrimInt: return Intrinsics::kIntegerRotateLeft; case Primitive::kPrimLong: return Intrinsics::kLongRotateLeft; default: LOG(FATAL) << "Unknown/unsupported op size " << method.d.data; UNREACHABLE(); } // Misc data processing. case kIntrinsicBitCount: switch (GetType(method.d.data, true)) { case Primitive::kPrimInt: return Intrinsics::kIntegerBitCount; case Primitive::kPrimLong: return Intrinsics::kLongBitCount; default: LOG(FATAL) << "Unknown/unsupported op size " << method.d.data; UNREACHABLE(); } case kIntrinsicCompare: switch (GetType(method.d.data, true)) { case Primitive::kPrimInt: return Intrinsics::kIntegerCompare; case Primitive::kPrimLong: return Intrinsics::kLongCompare; default: LOG(FATAL) << "Unknown/unsupported op size " << method.d.data; UNREACHABLE(); } case kIntrinsicHighestOneBit: switch (GetType(method.d.data, true)) { case Primitive::kPrimInt: return Intrinsics::kIntegerHighestOneBit; case Primitive::kPrimLong: return Intrinsics::kLongHighestOneBit; default: LOG(FATAL) << "Unknown/unsupported op size " << method.d.data; UNREACHABLE(); } case kIntrinsicLowestOneBit: switch (GetType(method.d.data, true)) { case Primitive::kPrimInt: return Intrinsics::kIntegerLowestOneBit; case Primitive::kPrimLong: return Intrinsics::kLongLowestOneBit; default: LOG(FATAL) << "Unknown/unsupported op size " << method.d.data; UNREACHABLE(); } case kIntrinsicNumberOfLeadingZeros: switch (GetType(method.d.data, true)) { case Primitive::kPrimInt: return Intrinsics::kIntegerNumberOfLeadingZeros; case Primitive::kPrimLong: return Intrinsics::kLongNumberOfLeadingZeros; default: LOG(FATAL) << "Unknown/unsupported op size " << method.d.data; UNREACHABLE(); } case kIntrinsicNumberOfTrailingZeros: switch (GetType(method.d.data, true)) { case Primitive::kPrimInt: return Intrinsics::kIntegerNumberOfTrailingZeros; case Primitive::kPrimLong: return Intrinsics::kLongNumberOfTrailingZeros; default: LOG(FATAL) << "Unknown/unsupported op size " << method.d.data; UNREACHABLE(); } case kIntrinsicSignum: switch (GetType(method.d.data, true)) { case Primitive::kPrimInt: return Intrinsics::kIntegerSignum; case Primitive::kPrimLong: return Intrinsics::kLongSignum; default: LOG(FATAL) << "Unknown/unsupported op size " << method.d.data; UNREACHABLE(); } // Abs. case kIntrinsicAbsDouble: return Intrinsics::kMathAbsDouble; case kIntrinsicAbsFloat: return Intrinsics::kMathAbsFloat; case kIntrinsicAbsInt: return Intrinsics::kMathAbsInt; case kIntrinsicAbsLong: return Intrinsics::kMathAbsLong; // Min/max. case kIntrinsicMinMaxDouble: return ((method.d.data & kIntrinsicFlagMin) == 0) ? Intrinsics::kMathMaxDoubleDouble : Intrinsics::kMathMinDoubleDouble; case kIntrinsicMinMaxFloat: return ((method.d.data & kIntrinsicFlagMin) == 0) ? Intrinsics::kMathMaxFloatFloat : Intrinsics::kMathMinFloatFloat; case kIntrinsicMinMaxInt: return ((method.d.data & kIntrinsicFlagMin) == 0) ? Intrinsics::kMathMaxIntInt : Intrinsics::kMathMinIntInt; case kIntrinsicMinMaxLong: return ((method.d.data & kIntrinsicFlagMin) == 0) ? Intrinsics::kMathMaxLongLong : Intrinsics::kMathMinLongLong; // More math builtins. case kIntrinsicCos: return Intrinsics::kMathCos; case kIntrinsicSin: return Intrinsics::kMathSin; case kIntrinsicAcos: return Intrinsics::kMathAcos; case kIntrinsicAsin: return Intrinsics::kMathAsin; case kIntrinsicAtan: return Intrinsics::kMathAtan; case kIntrinsicAtan2: return Intrinsics::kMathAtan2; case kIntrinsicCbrt: return Intrinsics::kMathCbrt; case kIntrinsicCosh: return Intrinsics::kMathCosh; case kIntrinsicExp: return Intrinsics::kMathExp; case kIntrinsicExpm1: return Intrinsics::kMathExpm1; case kIntrinsicHypot: return Intrinsics::kMathHypot; case kIntrinsicLog: return Intrinsics::kMathLog; case kIntrinsicLog10: return Intrinsics::kMathLog10; case kIntrinsicNextAfter: return Intrinsics::kMathNextAfter; case kIntrinsicSinh: return Intrinsics::kMathSinh; case kIntrinsicTan: return Intrinsics::kMathTan; case kIntrinsicTanh: return Intrinsics::kMathTanh; // Misc math. case kIntrinsicSqrt: return Intrinsics::kMathSqrt; case kIntrinsicCeil: return Intrinsics::kMathCeil; case kIntrinsicFloor: return Intrinsics::kMathFloor; case kIntrinsicRint: return Intrinsics::kMathRint; case kIntrinsicRoundDouble: return Intrinsics::kMathRoundDouble; case kIntrinsicRoundFloat: return Intrinsics::kMathRoundFloat; // System.arraycopy. case kIntrinsicSystemArrayCopyCharArray: return Intrinsics::kSystemArrayCopyChar; case kIntrinsicSystemArrayCopy: return Intrinsics::kSystemArrayCopy; // Thread.currentThread. case kIntrinsicCurrentThread: return Intrinsics::kThreadCurrentThread; // Memory.peek. case kIntrinsicPeek: switch (GetType(method.d.data, true)) { case Primitive::kPrimByte: return Intrinsics::kMemoryPeekByte; case Primitive::kPrimShort: return Intrinsics::kMemoryPeekShortNative; case Primitive::kPrimInt: return Intrinsics::kMemoryPeekIntNative; case Primitive::kPrimLong: return Intrinsics::kMemoryPeekLongNative; default: LOG(FATAL) << "Unknown/unsupported op size " << method.d.data; UNREACHABLE(); } // Memory.poke. case kIntrinsicPoke: switch (GetType(method.d.data, true)) { case Primitive::kPrimByte: return Intrinsics::kMemoryPokeByte; case Primitive::kPrimShort: return Intrinsics::kMemoryPokeShortNative; case Primitive::kPrimInt: return Intrinsics::kMemoryPokeIntNative; case Primitive::kPrimLong: return Intrinsics::kMemoryPokeLongNative; default: LOG(FATAL) << "Unknown/unsupported op size " << method.d.data; UNREACHABLE(); } // String. case kIntrinsicCharAt: return Intrinsics::kStringCharAt; case kIntrinsicCompareTo: return Intrinsics::kStringCompareTo; case kIntrinsicEquals: return Intrinsics::kStringEquals; case kIntrinsicGetCharsNoCheck: return Intrinsics::kStringGetCharsNoCheck; case kIntrinsicIsEmptyOrLength: // The inliner can handle these two cases - and this is the preferred approach // since after inlining the call is no longer visible (as opposed to waiting // until codegen to handle intrinsic). return Intrinsics::kNone; case kIntrinsicIndexOf: return ((method.d.data & kIntrinsicFlagBase0) == 0) ? Intrinsics::kStringIndexOfAfter : Intrinsics::kStringIndexOf; case kIntrinsicNewStringFromBytes: return Intrinsics::kStringNewStringFromBytes; case kIntrinsicNewStringFromChars: return Intrinsics::kStringNewStringFromChars; case kIntrinsicNewStringFromString: return Intrinsics::kStringNewStringFromString; case kIntrinsicCas: switch (GetType(method.d.data, false)) { case Primitive::kPrimNot: return Intrinsics::kUnsafeCASObject; case Primitive::kPrimInt: return Intrinsics::kUnsafeCASInt; case Primitive::kPrimLong: return Intrinsics::kUnsafeCASLong; default: LOG(FATAL) << "Unknown/unsupported op size " << method.d.data; UNREACHABLE(); } case kIntrinsicUnsafeGet: { const bool is_volatile = (method.d.data & kIntrinsicFlagIsVolatile); switch (GetType(method.d.data, false)) { case Primitive::kPrimInt: return is_volatile ? Intrinsics::kUnsafeGetVolatile : Intrinsics::kUnsafeGet; case Primitive::kPrimLong: return is_volatile ? Intrinsics::kUnsafeGetLongVolatile : Intrinsics::kUnsafeGetLong; case Primitive::kPrimNot: return is_volatile ? Intrinsics::kUnsafeGetObjectVolatile : Intrinsics::kUnsafeGetObject; default: LOG(FATAL) << "Unknown/unsupported op size " << method.d.data; UNREACHABLE(); } } case kIntrinsicUnsafePut: { enum Sync { kNoSync, kVolatile, kOrdered }; const Sync sync = ((method.d.data & kIntrinsicFlagIsVolatile) != 0) ? kVolatile : ((method.d.data & kIntrinsicFlagIsOrdered) != 0) ? kOrdered : kNoSync; switch (GetType(method.d.data, false)) { case Primitive::kPrimInt: switch (sync) { case kNoSync: return Intrinsics::kUnsafePut; case kVolatile: return Intrinsics::kUnsafePutVolatile; case kOrdered: return Intrinsics::kUnsafePutOrdered; } break; case Primitive::kPrimLong: switch (sync) { case kNoSync: return Intrinsics::kUnsafePutLong; case kVolatile: return Intrinsics::kUnsafePutLongVolatile; case kOrdered: return Intrinsics::kUnsafePutLongOrdered; } break; case Primitive::kPrimNot: switch (sync) { case kNoSync: return Intrinsics::kUnsafePutObject; case kVolatile: return Intrinsics::kUnsafePutObjectVolatile; case kOrdered: return Intrinsics::kUnsafePutObjectOrdered; } break; default: LOG(FATAL) << "Unknown/unsupported op size " << method.d.data; UNREACHABLE(); } break; } // 1.8. case kIntrinsicUnsafeGetAndAddInt: return Intrinsics::kUnsafeGetAndAddInt; case kIntrinsicUnsafeGetAndAddLong: return Intrinsics::kUnsafeGetAndAddLong; case kIntrinsicUnsafeGetAndSetInt: return Intrinsics::kUnsafeGetAndSetInt; case kIntrinsicUnsafeGetAndSetLong: return Intrinsics::kUnsafeGetAndSetLong; case kIntrinsicUnsafeGetAndSetObject: return Intrinsics::kUnsafeGetAndSetObject; case kIntrinsicUnsafeLoadFence: return Intrinsics::kUnsafeLoadFence; case kIntrinsicUnsafeStoreFence: return Intrinsics::kUnsafeStoreFence; case kIntrinsicUnsafeFullFence: return Intrinsics::kUnsafeFullFence; // Virtual cases. case kIntrinsicReferenceGetReferent: return Intrinsics::kReferenceGetReferent; // Quick inliner cases. Remove after refactoring. They are here so that we can use the // compiler to warn on missing cases. case kInlineOpNop: case kInlineOpReturnArg: case kInlineOpNonWideConst: case kInlineOpIGet: case kInlineOpIPut: case kInlineOpConstructor: return Intrinsics::kNone; // String init cases, not intrinsics. case kInlineStringInit: return Intrinsics::kNone; // No default case to make the compiler warn on missing cases. } return Intrinsics::kNone; } static bool CheckInvokeType(Intrinsics intrinsic, HInvoke* invoke, const DexFile& dex_file) { // The DexFileMethodInliner should have checked whether the methods are agreeing with // what we expect, i.e., static methods are called as such. Add another check here for // our expectations: // // 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->IsInvokeStaticOrDirect() ? invoke->AsInvokeStaticOrDirect()->GetOptimizedInvokeType() : invoke->IsInvokeVirtual() ? kVirtual : kSuper; switch (intrinsic_type) { case kStatic: return (invoke_type == kStatic); case kDirect: if (invoke_type == kDirect) { return true; } if (invoke_type == kVirtual) { ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); ScopedObjectAccess soa(Thread::Current()); ArtMethod* art_method = class_linker->FindDexCache(soa.Self(), dex_file)->GetResolvedMethod( invoke->GetDexMethodIndex(), class_linker->GetImagePointerSize()); return art_method != nullptr && (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; } } // TODO: Refactor DexFileMethodInliner and have something nicer than InlineMethod. void IntrinsicsRecognizer::Run() { for (HReversePostOrderIterator it(*graph_); !it.Done(); it.Advance()) { HBasicBlock* block = it.Current(); for (HInstructionIterator inst_it(block->GetInstructions()); !inst_it.Done(); inst_it.Advance()) { HInstruction* inst = inst_it.Current(); if (inst->IsInvoke()) { HInvoke* invoke = inst->AsInvoke(); InlineMethod method; const DexFile& dex_file = invoke->GetDexFile(); DexFileMethodInliner* inliner = driver_->GetMethodInlinerMap()->GetMethodInliner(&dex_file); DCHECK(inliner != nullptr); if (inliner->IsIntrinsic(invoke->GetDexMethodIndex(), &method)) { Intrinsics intrinsic = GetIntrinsic(method); if (intrinsic != Intrinsics::kNone) { if (!CheckInvokeType(intrinsic, invoke, dex_file)) { LOG(WARNING) << "Found an intrinsic with unexpected invoke type: " << intrinsic << " for " << PrettyMethod(invoke->GetDexMethodIndex(), invoke->GetDexFile()) << 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; } } // namespace art