diff options
551 files changed, 18919 insertions, 7943 deletions
diff --git a/Android.mk b/Android.mk index 25796a0179..bb1334a05d 100644 --- a/Android.mk +++ b/Android.mk @@ -93,6 +93,7 @@ include $(art_path)/tools/ahat/Android.mk include $(art_path)/tools/dexfuzz/Android.mk include $(art_path)/tools/dmtracedump/Android.mk include $(art_path)/sigchainlib/Android.mk +include $(art_path)/libart_fake/Android.mk # ART_HOST_DEPENDENCIES depends on Android.executable.mk above for ART_HOST_EXECUTABLES diff --git a/benchmark/jni-perf/src/JniPerfBenchmark.java b/benchmark/jni-perf/src/JniPerfBenchmark.java index b1b21ce0ba..1e7cc2bf46 100644 --- a/benchmark/jni-perf/src/JniPerfBenchmark.java +++ b/benchmark/jni-perf/src/JniPerfBenchmark.java @@ -14,9 +14,7 @@ * limitations under the License. */ -import com.google.caliper.SimpleBenchmark; - -public class JniPerfBenchmark extends SimpleBenchmark { +public class JniPerfBenchmark { private static final String MSG = "ABCDE"; native void perfJniEmptyCall(); diff --git a/benchmark/jobject-benchmark/src/JObjectBenchmark.java b/benchmark/jobject-benchmark/src/JObjectBenchmark.java index f4c059c58b..90a53b3995 100644 --- a/benchmark/jobject-benchmark/src/JObjectBenchmark.java +++ b/benchmark/jobject-benchmark/src/JObjectBenchmark.java @@ -14,9 +14,7 @@ * limitations under the License. */ -import com.google.caliper.SimpleBenchmark; - -public class JObjectBenchmark extends SimpleBenchmark { +public class JObjectBenchmark { public JObjectBenchmark() { // Make sure to link methods before benchmark starts. System.loadLibrary("artbenchmark"); diff --git a/benchmark/scoped-primitive-array/src/ScopedPrimitiveArrayBenchmark.java b/benchmark/scoped-primitive-array/src/ScopedPrimitiveArrayBenchmark.java index be276fe48c..0ad9c36950 100644 --- a/benchmark/scoped-primitive-array/src/ScopedPrimitiveArrayBenchmark.java +++ b/benchmark/scoped-primitive-array/src/ScopedPrimitiveArrayBenchmark.java @@ -14,9 +14,7 @@ * limitations under the License. */ -import com.google.caliper.SimpleBenchmark; - -public class ScopedPrimitiveArrayBenchmark extends SimpleBenchmark { +public class ScopedPrimitiveArrayBenchmark { // Measure adds the first and last element of the array by using ScopedPrimitiveArray. static native long measureByteArray(int reps, byte[] arr); static native long measureShortArray(int reps, short[] arr); diff --git a/build/Android.common_build.mk b/build/Android.common_build.mk index 2294ddbd55..0235a308f8 100644 --- a/build/Android.common_build.mk +++ b/build/Android.common_build.mk @@ -363,7 +363,21 @@ ART_HOST_ASFLAGS += $(art_asflags) ifndef LIBART_IMG_TARGET_BASE_ADDRESS $(error LIBART_IMG_TARGET_BASE_ADDRESS unset) endif -ART_TARGET_CFLAGS += $(art_cflags) -DART_TARGET -DART_BASE_ADDRESS=$(LIBART_IMG_TARGET_BASE_ADDRESS) + +ART_TARGET_CFLAGS += $(art_cflags) -DART_TARGET \ + -DART_BASE_ADDRESS=$(LIBART_IMG_TARGET_BASE_ADDRESS) \ + +ifeq ($(ART_TARGET_LINUX),true) +# Setting ART_TARGET_LINUX to true compiles art/ assuming that the target device +# will be running linux rather than android. +ART_TARGET_CFLAGS += -DART_TARGET_LINUX +else +# The ART_TARGET_ANDROID macro is passed to target builds, which check +# against it instead of against __ANDROID__ (which is provided by target +# toolchains). +ART_TARGET_CFLAGS += -DART_TARGET_ANDROID +endif + ART_TARGET_CFLAGS += $(art_target_cflags) ART_TARGET_ASFLAGS += $(art_asflags) diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk index 8309bd854f..f73657cb0d 100644 --- a/build/Android.gtest.mk +++ b/build/Android.gtest.mk @@ -172,6 +172,7 @@ RUNTIME_GTEST_COMMON_SRC_FILES := \ runtime/arch/x86/instruction_set_features_x86_test.cc \ runtime/arch/x86_64/instruction_set_features_x86_64_test.cc \ runtime/barrier_test.cc \ + runtime/base/arena_allocator_test.cc \ runtime/base/bit_field_test.cc \ runtime/base/bit_utils_test.cc \ runtime/base/bit_vector_test.cc \ @@ -269,14 +270,17 @@ COMPILER_GTEST_COMMON_SRC_FILES := \ compiler/optimizing/nodes_test.cc \ compiler/optimizing/parallel_move_test.cc \ compiler/optimizing/pretty_printer_test.cc \ + compiler/optimizing/reference_type_propagation_test.cc \ compiler/optimizing/side_effects_test.cc \ compiler/optimizing/ssa_test.cc \ compiler/optimizing/stack_map_test.cc \ compiler/optimizing/suspend_check_test.cc \ - compiler/utils/arena_allocator_test.cc \ compiler/utils/dedupe_set_test.cc \ + compiler/utils/intrusive_forward_list_test.cc \ compiler/utils/swap_space_test.cc \ compiler/utils/test_dex_file_builder_test.cc \ + compiler/utils/transform_array_ref_test.cc \ + compiler/utils/transform_iterator_test.cc \ COMPILER_GTEST_COMMON_SRC_FILES_all := \ compiler/jni/jni_cfi_test.cc \ @@ -648,11 +652,11 @@ endef # define-art-gtest ifeq ($(ART_BUILD_TARGET),true) $(foreach file,$(RUNTIME_GTEST_TARGET_SRC_FILES), $(eval $(call define-art-gtest,target,$(file),,libbacktrace))) - $(foreach file,$(COMPILER_GTEST_TARGET_SRC_FILES), $(eval $(call define-art-gtest,target,$(file),art/compiler,libartd-compiler libbacktrace))) + $(foreach file,$(COMPILER_GTEST_TARGET_SRC_FILES), $(eval $(call define-art-gtest,target,$(file),art/compiler,libartd-compiler libbacktrace libnativeloader))) endif ifeq ($(ART_BUILD_HOST),true) $(foreach file,$(RUNTIME_GTEST_HOST_SRC_FILES), $(eval $(call define-art-gtest,host,$(file),,libbacktrace))) - $(foreach file,$(COMPILER_GTEST_HOST_SRC_FILES), $(eval $(call define-art-gtest,host,$(file),art/compiler,libartd-compiler libbacktrace))) + $(foreach file,$(COMPILER_GTEST_HOST_SRC_FILES), $(eval $(call define-art-gtest,host,$(file),art/compiler,libartd-compiler libbacktrace libnativeloader))) endif # Used outside the art project to get a list of the current tests diff --git a/cmdline/cmdline_parser_test.cc b/cmdline/cmdline_parser_test.cc index 81b854e9c3..7c53e01c47 100644 --- a/cmdline/cmdline_parser_test.cc +++ b/cmdline/cmdline_parser_test.cc @@ -461,8 +461,8 @@ TEST_F(CmdlineParserTest, TestJitOptions) { * Test successes */ { - EXPECT_SINGLE_PARSE_VALUE(true, "-Xusejit:true", M::UseJIT); - EXPECT_SINGLE_PARSE_VALUE(false, "-Xusejit:false", M::UseJIT); + EXPECT_SINGLE_PARSE_VALUE(true, "-Xusejit:true", M::UseJitCompilation); + EXPECT_SINGLE_PARSE_VALUE(false, "-Xusejit:false", M::UseJitCompilation); } { EXPECT_SINGLE_PARSE_VALUE( diff --git a/cmdline/cmdline_types.h b/cmdline/cmdline_types.h index c0a00cce70..4797540c35 100644 --- a/cmdline/cmdline_types.h +++ b/cmdline/cmdline_types.h @@ -620,6 +620,8 @@ struct CmdlineType<LogVerbosity> : CmdlineTypeParser<LogVerbosity> { log_verbosity.verifier = true; } else if (verbose_options[j] == "image") { log_verbosity.image = true; + } else if (verbose_options[j] == "systrace-locks") { + log_verbosity.systrace_lock_logging = true; } else { return Result::Usage(std::string("Unknown -verbose option ") + verbose_options[j]); } diff --git a/compiler/cfi_test.h b/compiler/cfi_test.h index 230cb9aeea..f8b7460935 100644 --- a/compiler/cfi_test.h +++ b/compiler/cfi_test.h @@ -55,7 +55,9 @@ class CFITest : public dwarf::DwarfTest { kCFIFormat, 0, &debug_frame_data_, &debug_frame_patches); ReformatCfi(Objdump(false, "-W"), &lines); // Pretty-print assembly. - auto* opts = new DisassemblerOptions(false, actual_asm.data(), true); + const uint8_t* asm_base = actual_asm.data(); + const uint8_t* asm_end = asm_base + actual_asm.size(); + auto* opts = new DisassemblerOptions(false, asm_base, asm_end, true); std::unique_ptr<Disassembler> disasm(Disassembler::Create(isa, opts)); std::stringstream stream; const uint8_t* base = actual_asm.data() + (isa == kThumb2 ? 1 : 0); diff --git a/compiler/common_compiler_test.cc b/compiler/common_compiler_test.cc index f75a252df2..bf29e1c31d 100644 --- a/compiler/common_compiler_test.cc +++ b/compiler/common_compiler_test.cc @@ -180,6 +180,7 @@ void CommonCompilerTest::CreateCompilerDriver(Compiler::Kind kind, isa, instruction_set_features_.get(), /* boot_image */ true, + /* app_image */ false, GetImageClasses(), GetCompiledClasses(), GetCompiledMethods(), diff --git a/compiler/debug/dwarf/dwarf_test.cc b/compiler/debug/dwarf/dwarf_test.cc index 2ba3af5e10..866bf4394d 100644 --- a/compiler/debug/dwarf/dwarf_test.cc +++ b/compiler/debug/dwarf/dwarf_test.cc @@ -27,7 +27,7 @@ namespace art { namespace dwarf { // Run the tests only on host since we need objdump. -#ifndef __ANDROID__ +#ifndef ART_TARGET_ANDROID constexpr CFIFormat kCFIFormat = DW_DEBUG_FRAME_FORMAT; @@ -341,7 +341,7 @@ TEST_F(DwarfTest, DebugInfo) { CheckObjdumpOutput(is64bit, "-W"); } -#endif // __ANDROID__ +#endif // ART_TARGET_ANDROID } // namespace dwarf } // namespace art diff --git a/compiler/dex/quick/dex_file_method_inliner.cc b/compiler/dex/quick/dex_file_method_inliner.cc index 4a98342bfc..951b07585c 100644 --- a/compiler/dex/quick/dex_file_method_inliner.cc +++ b/compiler/dex/quick/dex_file_method_inliner.cc @@ -606,13 +606,13 @@ const DexFileMethodInliner::IntrinsicDef DexFileMethodInliner::kIntrinsicMethods INTRINSIC(SunMiscUnsafe, Get ## type, ObjectJ_ ## code, kIntrinsicUnsafeGet, \ type_flags), \ INTRINSIC(SunMiscUnsafe, Get ## type ## Volatile, ObjectJ_ ## code, kIntrinsicUnsafeGet, \ - type_flags | kIntrinsicFlagIsVolatile), \ + (type_flags) | kIntrinsicFlagIsVolatile), \ INTRINSIC(SunMiscUnsafe, Put ## type, ObjectJ ## code ## _V, kIntrinsicUnsafePut, \ type_flags), \ INTRINSIC(SunMiscUnsafe, Put ## type ## Volatile, ObjectJ ## code ## _V, kIntrinsicUnsafePut, \ - type_flags | kIntrinsicFlagIsVolatile), \ + (type_flags) | kIntrinsicFlagIsVolatile), \ INTRINSIC(SunMiscUnsafe, PutOrdered ## type, ObjectJ ## code ## _V, kIntrinsicUnsafePut, \ - type_flags | kIntrinsicFlagIsOrdered) + (type_flags) | kIntrinsicFlagIsOrdered) UNSAFE_GET_PUT(Int, I, kIntrinsicFlagNone), UNSAFE_GET_PUT(Long, J, kIntrinsicFlagIsLong), diff --git a/compiler/dex/verification_results.cc b/compiler/dex/verification_results.cc index 1491a183f1..d87762d417 100644 --- a/compiler/dex/verification_results.cc +++ b/compiler/dex/verification_results.cc @@ -60,7 +60,7 @@ void VerificationResults::ProcessVerifiedMethod(verifier::MethodVerifier* method // TODO: Investigate why are we doing the work again for this method and try to avoid it. LOG(WARNING) << "Method processed more than once: " << PrettyMethod(ref.dex_method_index, *ref.dex_file); - if (!Runtime::Current()->UseJit()) { + if (!Runtime::Current()->UseJitCompilation()) { DCHECK_EQ(it->second->GetDevirtMap().size(), verified_method->GetDevirtMap().size()); DCHECK_EQ(it->second->GetSafeCastSet().size(), verified_method->GetSafeCastSet().size()); } @@ -80,15 +80,6 @@ const VerifiedMethod* VerificationResults::GetVerifiedMethod(MethodReference ref return (it != verified_methods_.end()) ? it->second : nullptr; } -void VerificationResults::RemoveVerifiedMethod(MethodReference ref) { - WriterMutexLock mu(Thread::Current(), verified_methods_lock_); - auto it = verified_methods_.find(ref); - if (it != verified_methods_.end()) { - delete it->second; - verified_methods_.erase(it); - } -} - void VerificationResults::AddRejectedClass(ClassReference ref) { { WriterMutexLock mu(Thread::Current(), rejected_classes_lock_); @@ -104,7 +95,7 @@ bool VerificationResults::IsClassRejected(ClassReference ref) { bool VerificationResults::IsCandidateForCompilation(MethodReference&, const uint32_t access_flags) { - if (!compiler_options_->IsCompilationEnabled()) { + if (!compiler_options_->IsBytecodeCompilationEnabled()) { return false; } // Don't compile class initializers unless kEverything. diff --git a/compiler/dex/verification_results.h b/compiler/dex/verification_results.h index da80bf07db..1af11a8114 100644 --- a/compiler/dex/verification_results.h +++ b/compiler/dex/verification_results.h @@ -48,7 +48,6 @@ class VerificationResults { const VerifiedMethod* GetVerifiedMethod(MethodReference ref) REQUIRES(!verified_methods_lock_); - void RemoveVerifiedMethod(MethodReference ref) REQUIRES(!verified_methods_lock_); void AddRejectedClass(ClassReference ref) REQUIRES(!rejected_classes_lock_); bool IsClassRejected(ClassReference ref) REQUIRES(!rejected_classes_lock_); diff --git a/compiler/dex/verified_method.cc b/compiler/dex/verified_method.cc index 5c0253c29e..bace014713 100644 --- a/compiler/dex/verified_method.cc +++ b/compiler/dex/verified_method.cc @@ -54,7 +54,8 @@ const VerifiedMethod* VerifiedMethod::Create(verifier::MethodVerifier* method_ve } // Only need dequicken info for JIT so far. - if (Runtime::Current()->UseJit() && !verified_method->GenerateDequickenMap(method_verifier)) { + if (Runtime::Current()->UseJitCompilation() && + !verified_method->GenerateDequickenMap(method_verifier)) { return nullptr; } } @@ -72,7 +73,7 @@ const MethodReference* VerifiedMethod::GetDevirtTarget(uint32_t dex_pc) const { } const DexFileReference* VerifiedMethod::GetDequickenIndex(uint32_t dex_pc) const { - DCHECK(Runtime::Current()->UseJit()); + DCHECK(Runtime::Current()->UseJitCompilation()); auto it = dequicken_map_.find(dex_pc); return (it != dequicken_map_.end()) ? &it->second : nullptr; } diff --git a/compiler/driver/compiled_method_storage_test.cc b/compiler/driver/compiled_method_storage_test.cc index 9e0c22c68c..6863f42d11 100644 --- a/compiler/driver/compiled_method_storage_test.cc +++ b/compiler/driver/compiled_method_storage_test.cc @@ -36,6 +36,7 @@ TEST(CompiledMethodStorage, Deduplicate) { /* instruction_set_ */ kNone, /* instruction_set_features */ nullptr, /* boot_image */ false, + /* app_image */ false, /* image_classes */ nullptr, /* compiled_classes */ nullptr, /* compiled_methods */ nullptr, diff --git a/compiler/driver/compiler_driver-inl.h b/compiler/driver/compiler_driver-inl.h index 3cb63e7082..94f5acc2b6 100644 --- a/compiler/driver/compiler_driver-inl.h +++ b/compiler/driver/compiler_driver-inl.h @@ -390,9 +390,8 @@ inline int CompilerDriver::IsFastInvoke( *devirt_target->dex_file, devirt_target->dex_method_index, dex_cache, class_loader, nullptr, kVirtual); } else { - auto target_dex_cache(hs.NewHandle(class_linker->RegisterDexFile( - *devirt_target->dex_file, - class_linker->GetOrCreateAllocatorForClassLoader(class_loader.Get())))); + auto target_dex_cache(hs.NewHandle(class_linker->RegisterDexFile(*devirt_target->dex_file, + class_loader.Get()))); called_method = class_linker->ResolveMethod<ClassLinker::kNoICCECheckForCache>( *devirt_target->dex_file, devirt_target->dex_method_index, target_dex_cache, class_loader, nullptr, kVirtual); diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc index 8bdff21c70..a4b48892fb 100644 --- a/compiler/driver/compiler_driver.cc +++ b/compiler/driver/compiler_driver.cc @@ -26,6 +26,7 @@ #include "art_field-inl.h" #include "art_method-inl.h" +#include "base/bit_vector.h" #include "base/stl_util.h" #include "base/systrace.h" #include "base/time_utils.h" @@ -66,6 +67,7 @@ #include "thread_pool.h" #include "trampolines/trampoline_compiler.h" #include "transaction.h" +#include "utils/array_ref.h" #include "utils/dex_cache_arrays_layout-inl.h" #include "utils/swap_space.h" #include "verifier/method_verifier.h" @@ -333,6 +335,24 @@ class CompilerDriver::AOTCompilationStats { DISALLOW_COPY_AND_ASSIGN(AOTCompilationStats); }; +class CompilerDriver::DexFileMethodSet { + public: + explicit DexFileMethodSet(const DexFile& dex_file) + : dex_file_(dex_file), + method_indexes_(dex_file.NumMethodIds(), false, Allocator::GetMallocAllocator()) { + } + DexFileMethodSet(DexFileMethodSet&& other) = default; + + const DexFile& GetDexFile() const { return dex_file_; } + + BitVector& GetMethodIndexes() { return method_indexes_; } + const BitVector& GetMethodIndexes() const { return method_indexes_; } + + private: + const DexFile& dex_file_; + BitVector method_indexes_; +}; + CompilerDriver::CompilerDriver( const CompilerOptions* compiler_options, VerificationResults* verification_results, @@ -341,6 +361,7 @@ CompilerDriver::CompilerDriver( InstructionSet instruction_set, const InstructionSetFeatures* instruction_set_features, bool boot_image, + bool app_image, std::unordered_set<std::string>* image_classes, std::unordered_set<std::string>* compiled_classes, std::unordered_set<std::string>* compiled_methods, @@ -363,6 +384,7 @@ CompilerDriver::CompilerDriver( compiled_methods_(MethodTable::key_compare()), non_relative_linker_patch_count_(0u), boot_image_(boot_image), + app_image_(app_image), image_classes_(image_classes), classes_to_compile_(compiled_classes), methods_to_compile_(compiled_methods), @@ -377,7 +399,10 @@ CompilerDriver::CompilerDriver( dex_files_for_oat_file_(nullptr), compiled_method_storage_(swap_fd), profile_compilation_info_(profile_compilation_info), - max_arena_alloc_(0) { + max_arena_alloc_(0), + dex_to_dex_references_lock_("dex-to-dex references lock"), + dex_to_dex_references_(), + current_dex_to_dex_methods_(nullptr) { DCHECK(compiler_options_ != nullptr); DCHECK(method_inliner_map_ != nullptr); @@ -473,7 +498,7 @@ static optimizer::DexToDexCompilationLevel GetDexToDexCompilationLevel( const DexFile& dex_file, const DexFile::ClassDef& class_def) SHARED_REQUIRES(Locks::mutator_lock_) { auto* const runtime = Runtime::Current(); - if (runtime->UseJit() || driver.GetCompilerOptions().VerifyAtRuntime()) { + if (runtime->UseJitCompilation() || driver.GetCompilerOptions().VerifyAtRuntime()) { // Verify at runtime shouldn't dex to dex since we didn't resolve of verify. return optimizer::DexToDexCompilationLevel::kDontDexToDexCompile; } @@ -550,9 +575,31 @@ static void CompileMethod(Thread* self, uint64_t start_ns = kTimeCompileMethod ? NanoTime() : 0; MethodReference method_ref(&dex_file, method_idx); - if ((access_flags & kAccNative) != 0) { - // Are we interpreting only and have support for generic JNI down calls? - if (!driver->GetCompilerOptions().IsCompilationEnabled() && + if (driver->GetCurrentDexToDexMethods() != nullptr) { + // This is the second pass when we dex-to-dex compile previously marked methods. + // TODO: Refactor the compilation to avoid having to distinguish the two passes + // here. That should be done on a higher level. http://b/29089975 + if (driver->GetCurrentDexToDexMethods()->IsBitSet(method_idx)) { + const VerifiedMethod* verified_method = + driver->GetVerificationResults()->GetVerifiedMethod(method_ref); + // Do not optimize if a VerifiedMethod is missing. SafeCast elision, + // for example, relies on it. + compiled_method = optimizer::ArtCompileDEX( + driver, + code_item, + access_flags, + invoke_type, + class_def_idx, + method_idx, + class_loader, + dex_file, + (verified_method != nullptr) + ? dex_to_dex_compilation_level + : optimizer::DexToDexCompilationLevel::kRequired); + } + } else if ((access_flags & kAccNative) != 0) { + // Are we extracting only and have support for generic JNI down calls? + if (!driver->GetCompilerOptions().IsJniCompilationEnabled() && InstructionSetHasGenericJniStub(driver->GetInstructionSet())) { // Leaving this empty will trigger the generic JNI version } else { @@ -586,21 +633,9 @@ static void CompileMethod(Thread* self, } if (compiled_method == nullptr && dex_to_dex_compilation_level != optimizer::DexToDexCompilationLevel::kDontDexToDexCompile) { + DCHECK(!Runtime::Current()->UseJitCompilation()); // TODO: add a command-line option to disable DEX-to-DEX compilation ? - // Do not optimize if a VerifiedMethod is missing. SafeCast elision, for example, relies on - // it. - compiled_method = optimizer::ArtCompileDEX( - driver, - code_item, - access_flags, - invoke_type, - class_def_idx, - method_idx, - class_loader, - dex_file, - (verified_method != nullptr) - ? dex_to_dex_compilation_level - : optimizer::DexToDexCompilationLevel::kRequired); + driver->MarkForDexToDexCompilation(self, method_ref); } } if (kTimeCompileMethod) { @@ -626,12 +661,6 @@ static void CompileMethod(Thread* self, driver->AddCompiledMethod(method_ref, compiled_method, non_relative_linker_patch_count); } - // Done compiling, delete the verified method to reduce native memory usage. Do not delete in - // optimizing compiler, which may need the verified method again for inlining. - if (driver->GetCompilerKind() != Compiler::kOptimizing) { - driver->GetVerificationResults()->RemoveVerifiedMethod(method_ref); - } - if (self->IsExceptionPending()) { ScopedObjectAccess soa(self); LOG(FATAL) << "Unexpected exception compiling: " << PrettyMethod(method_idx, dex_file) << "\n" @@ -678,6 +707,7 @@ void CompilerDriver::CompileOne(Thread* self, ArtMethod* method, TimingLogger* t *dex_file, dex_file->GetClassDef(class_def_idx)); + DCHECK(current_dex_to_dex_methods_ == nullptr); CompileMethod(self, this, code_item, @@ -691,6 +721,34 @@ void CompilerDriver::CompileOne(Thread* self, ArtMethod* method, TimingLogger* t true, dex_cache); + ArrayRef<DexFileMethodSet> dex_to_dex_references; + { + // From this point on, we shall not modify dex_to_dex_references_, so + // just grab a reference to it that we use without holding the mutex. + MutexLock lock(Thread::Current(), dex_to_dex_references_lock_); + dex_to_dex_references = ArrayRef<DexFileMethodSet>(dex_to_dex_references_); + } + if (!dex_to_dex_references.empty()) { + DCHECK_EQ(dex_to_dex_references.size(), 1u); + DCHECK(&dex_to_dex_references[0].GetDexFile() == dex_file); + current_dex_to_dex_methods_ = &dex_to_dex_references.front().GetMethodIndexes(); + DCHECK(current_dex_to_dex_methods_->IsBitSet(method_idx)); + DCHECK_EQ(current_dex_to_dex_methods_->NumSetBits(), 1u); + CompileMethod(self, + this, + code_item, + access_flags, + invoke_type, + class_def_idx, + method_idx, + jclass_loader, + *dex_file, + dex_to_dex_compilation_level, + true, + dex_cache); + current_dex_to_dex_methods_ = nullptr; + } + FreeThreadPools(); self->GetJniEnv()->DeleteGlobalRef(jclass_loader); @@ -945,7 +1003,7 @@ bool CompilerDriver::ShouldVerifyClassBasedOnProfile(const DexFile& dex_file, class ResolveCatchBlockExceptionsClassVisitor : public ClassVisitor { public: - ResolveCatchBlockExceptionsClassVisitor( + explicit ResolveCatchBlockExceptionsClassVisitor( std::set<std::pair<uint16_t, const DexFile*>>& exceptions_to_resolve) : exceptions_to_resolve_(exceptions_to_resolve) {} @@ -1055,9 +1113,8 @@ void CompilerDriver::LoadImageClasses(TimingLogger* timings) { uint16_t exception_type_idx = exception_type.first; const DexFile* dex_file = exception_type.second; StackHandleScope<2> hs2(self); - Handle<mirror::DexCache> dex_cache(hs2.NewHandle(class_linker->RegisterDexFile( - *dex_file, - Runtime::Current()->GetLinearAlloc()))); + Handle<mirror::DexCache> dex_cache(hs2.NewHandle(class_linker->RegisterDexFile(*dex_file, + nullptr))); Handle<mirror::Class> klass(hs2.NewHandle( class_linker->ResolveType(*dex_file, exception_type_idx, @@ -1268,7 +1325,7 @@ void CompilerDriver::UpdateImageClasses(TimingLogger* timings) { bool CompilerDriver::CanAssumeClassIsLoaded(mirror::Class* klass) { Runtime* runtime = Runtime::Current(); if (!runtime->IsAotCompiler()) { - DCHECK(runtime->UseJit()); + DCHECK(runtime->UseJitCompilation()); // Having the klass reference here implies that the klass is already loaded. return true; } @@ -1283,14 +1340,24 @@ bool CompilerDriver::CanAssumeClassIsLoaded(mirror::Class* klass) { return IsImageClass(descriptor); } -bool CompilerDriver::CanAssumeTypeIsPresentInDexCache(const DexFile& dex_file, uint32_t type_idx) { +void CompilerDriver::MarkForDexToDexCompilation(Thread* self, const MethodReference& method_ref) { + MutexLock lock(self, dex_to_dex_references_lock_); + // Since we're compiling one dex file at a time, we need to look for the + // current dex file entry only at the end of dex_to_dex_references_. + if (dex_to_dex_references_.empty() || + &dex_to_dex_references_.back().GetDexFile() != method_ref.dex_file) { + dex_to_dex_references_.emplace_back(*method_ref.dex_file); + } + dex_to_dex_references_.back().GetMethodIndexes().SetBit(method_ref.dex_method_index); +} + +bool CompilerDriver::CanAssumeTypeIsPresentInDexCache(Handle<mirror::DexCache> dex_cache, + uint32_t type_idx) { bool result = false; if ((IsBootImage() && - IsImageClass(dex_file.StringDataByIdx(dex_file.GetTypeId(type_idx).descriptor_idx_))) || - Runtime::Current()->UseJit()) { - ScopedObjectAccess soa(Thread::Current()); - mirror::DexCache* dex_cache = Runtime::Current()->GetClassLinker()->FindDexCache( - soa.Self(), dex_file, false); + IsImageClass(dex_cache->GetDexFile()->StringDataByIdx( + dex_cache->GetDexFile()->GetTypeId(type_idx).descriptor_idx_))) || + Runtime::Current()->UseJitCompilation()) { mirror::Class* resolved_class = dex_cache->GetResolvedType(type_idx); result = (resolved_class != nullptr); } @@ -1308,7 +1375,7 @@ bool CompilerDriver::CanAssumeStringIsPresentInDexCache(const DexFile& dex_file, // See also Compiler::ResolveDexFile bool result = false; - if (IsBootImage() || Runtime::Current()->UseJit()) { + if (IsBootImage() || Runtime::Current()->UseJitCompilation()) { ScopedObjectAccess soa(Thread::Current()); StackHandleScope<1> hs(soa.Self()); ClassLinker* const class_linker = Runtime::Current()->GetClassLinker(); @@ -1320,7 +1387,7 @@ bool CompilerDriver::CanAssumeStringIsPresentInDexCache(const DexFile& dex_file, result = true; } else { // Just check whether the dex cache already has the string. - DCHECK(Runtime::Current()->UseJit()); + DCHECK(Runtime::Current()->UseJitCompilation()); result = (dex_cache->GetResolvedString(string_idx) != nullptr); } } @@ -1332,32 +1399,16 @@ bool CompilerDriver::CanAssumeStringIsPresentInDexCache(const DexFile& dex_file, return result; } -bool CompilerDriver::CanAccessTypeWithoutChecks(uint32_t referrer_idx, const DexFile& dex_file, - uint32_t type_idx, - bool* type_known_final, bool* type_known_abstract, - bool* equals_referrers_class) { - if (type_known_final != nullptr) { - *type_known_final = false; - } - if (type_known_abstract != nullptr) { - *type_known_abstract = false; - } - if (equals_referrers_class != nullptr) { - *equals_referrers_class = false; - } - ScopedObjectAccess soa(Thread::Current()); - mirror::DexCache* dex_cache = Runtime::Current()->GetClassLinker()->FindDexCache( - soa.Self(), dex_file, false); +bool CompilerDriver::CanAccessTypeWithoutChecks(uint32_t referrer_idx, + Handle<mirror::DexCache> dex_cache, + uint32_t type_idx) { // Get type from dex cache assuming it was populated by the verifier mirror::Class* resolved_class = dex_cache->GetResolvedType(type_idx); if (resolved_class == nullptr) { stats_->TypeNeedsAccessCheck(); return false; // Unknown class needs access checks. } - const DexFile::MethodId& method_id = dex_file.GetMethodId(referrer_idx); - if (equals_referrers_class != nullptr) { - *equals_referrers_class = (method_id.class_idx_ == type_idx); - } + const DexFile::MethodId& method_id = dex_cache->GetDexFile()->GetMethodId(referrer_idx); bool is_accessible = resolved_class->IsPublic(); // Public classes are always accessible. if (!is_accessible) { mirror::Class* referrer_class = dex_cache->GetResolvedType(method_id.class_idx_); @@ -1371,12 +1422,6 @@ bool CompilerDriver::CanAccessTypeWithoutChecks(uint32_t referrer_idx, const Dex } if (is_accessible) { stats_->TypeDoesntNeedAccessCheck(); - if (type_known_final != nullptr) { - *type_known_final = resolved_class->IsFinal() && !resolved_class->IsArrayClass(); - } - if (type_known_abstract != nullptr) { - *type_known_abstract = resolved_class->IsAbstract() && !resolved_class->IsArrayClass(); - } } else { stats_->TypeNeedsAccessCheck(); } @@ -1384,12 +1429,9 @@ bool CompilerDriver::CanAccessTypeWithoutChecks(uint32_t referrer_idx, const Dex } bool CompilerDriver::CanAccessInstantiableTypeWithoutChecks(uint32_t referrer_idx, - const DexFile& dex_file, + Handle<mirror::DexCache> dex_cache, uint32_t type_idx, bool* finalizable) { - ScopedObjectAccess soa(Thread::Current()); - mirror::DexCache* dex_cache = Runtime::Current()->GetClassLinker()->FindDexCache( - soa.Self(), dex_file, false); // Get type from dex cache assuming it was populated by the verifier. mirror::Class* resolved_class = dex_cache->GetResolvedType(type_idx); if (resolved_class == nullptr) { @@ -1399,7 +1441,7 @@ bool CompilerDriver::CanAccessInstantiableTypeWithoutChecks(uint32_t referrer_id return false; // Unknown class needs access checks. } *finalizable = resolved_class->IsFinalizable(); - const DexFile::MethodId& method_id = dex_file.GetMethodId(referrer_idx); + const DexFile::MethodId& method_id = dex_cache->GetDexFile()->GetMethodId(referrer_idx); bool is_accessible = resolved_class->IsPublic(); // Public classes are always accessible. if (!is_accessible) { mirror::Class* referrer_class = dex_cache->GetResolvedType(method_id.class_idx_); @@ -1453,7 +1495,7 @@ bool CompilerDriver::CanEmbedTypeInCode(const DexFile& dex_file, uint32_t type_i } else { return false; } - } else if (runtime->UseJit() && !heap->IsMovableObject(resolved_class)) { + } else if (runtime->UseJitCompilation() && !heap->IsMovableObject(resolved_class)) { *is_type_initialized = resolved_class->IsInitialized(); // If the class may move around, then don't embed it as a direct pointer. *use_direct_type_ptr = true; @@ -1587,53 +1629,6 @@ bool CompilerDriver::ComputeInstanceFieldInfo(uint32_t field_idx, const DexCompi } } -bool CompilerDriver::ComputeStaticFieldInfo(uint32_t field_idx, const DexCompilationUnit* mUnit, - bool is_put, MemberOffset* field_offset, - uint32_t* storage_index, bool* is_referrers_class, - bool* is_volatile, bool* is_initialized, - Primitive::Type* type) { - ScopedObjectAccess soa(Thread::Current()); - // Try to resolve the field and compiling method's class. - ArtField* resolved_field; - mirror::Class* referrer_class; - Handle<mirror::DexCache> dex_cache(mUnit->GetDexCache()); - { - StackHandleScope<1> hs(soa.Self()); - Handle<mirror::ClassLoader> class_loader_handle( - hs.NewHandle(soa.Decode<mirror::ClassLoader*>(mUnit->GetClassLoader()))); - resolved_field = - ResolveField(soa, dex_cache, class_loader_handle, mUnit, field_idx, true); - referrer_class = resolved_field != nullptr - ? ResolveCompilingMethodsClass(soa, dex_cache, class_loader_handle, mUnit) : nullptr; - } - bool result = false; - if (resolved_field != nullptr && referrer_class != nullptr) { - *is_volatile = IsFieldVolatile(resolved_field); - std::pair<bool, bool> fast_path = IsFastStaticField( - dex_cache.Get(), referrer_class, resolved_field, field_idx, storage_index); - result = is_put ? fast_path.second : fast_path.first; - } - if (result) { - *field_offset = GetFieldOffset(resolved_field); - *is_referrers_class = IsStaticFieldInReferrerClass(referrer_class, resolved_field); - // *is_referrers_class == true implies no worrying about class initialization. - *is_initialized = (*is_referrers_class) || - (IsStaticFieldsClassInitialized(referrer_class, resolved_field) && - CanAssumeTypeIsPresentInDexCache(*mUnit->GetDexFile(), *storage_index)); - *type = resolved_field->GetTypeAsPrimitiveType(); - } else { - // Conservative defaults. - *is_volatile = true; - *field_offset = MemberOffset(static_cast<size_t>(-1)); - *storage_index = -1; - *is_referrers_class = false; - *is_initialized = false; - *type = Primitive::kPrimVoid; - } - ProcessedStaticField(result, *is_referrers_class); - return result; -} - void CompilerDriver::GetCodeAndMethodForDirectCall(InvokeType* type, InvokeType sharp_type, bool no_guarantee_of_dex_cache_entry, const mirror::Class* referrer_class, @@ -1677,7 +1672,7 @@ void CompilerDriver::GetCodeAndMethodForDirectCall(InvokeType* type, InvokeType } } } - if (runtime->UseJit()) { + if (runtime->UseJitCompilation()) { // If we are the JIT, then don't allow a direct call to the interpreter bridge since this will // never be updated even after we compile the method. if (cl->IsQuickToInterpreterBridge( @@ -1709,7 +1704,7 @@ void CompilerDriver::GetCodeAndMethodForDirectCall(InvokeType* type, InvokeType bool must_use_direct_pointers = false; mirror::DexCache* dex_cache = declaring_class->GetDexCache(); if (target_method->dex_file == dex_cache->GetDexFile() && - !(runtime->UseJit() && dex_cache->GetResolvedMethod( + !(runtime->UseJitCompilation() && dex_cache->GetResolvedMethod( method->GetDexMethodIndex(), pointer_size) == nullptr)) { target_method->dex_method_index = method->GetDexMethodIndex(); } else { @@ -1746,7 +1741,7 @@ void CompilerDriver::GetCodeAndMethodForDirectCall(InvokeType* type, InvokeType break; } } - if (method_in_image || compiling_boot || runtime->UseJit()) { + if (method_in_image || compiling_boot || runtime->UseJitCompilation()) { // We know we must be able to get to the method in the image, so use that pointer. // In the case where we are the JIT, we can always use direct pointers since we know where // the method and its code are / will be. We don't sharpen to interpreter bridge since we @@ -2160,7 +2155,7 @@ class ResolveTypeVisitor : public CompilationVisitor { hs.NewHandle(soa.Decode<mirror::ClassLoader*>(manager_->GetClassLoader()))); Handle<mirror::DexCache> dex_cache(hs.NewHandle(class_linker->RegisterDexFile( dex_file, - class_linker->GetOrCreateAllocatorForClassLoader(class_loader.Get())))); + class_loader.Get()))); mirror::Class* klass = class_linker->ResolveType(dex_file, type_idx, dex_cache, class_loader); if (klass == nullptr) { @@ -2513,9 +2508,12 @@ void CompilerDriver::InitializeClasses(jobject jni_class_loader, context.ForAll(0, dex_file.NumClassDefs(), &visitor, init_thread_count); } -class InitializeArrayClassVisitor : public ClassVisitor { +class InitializeArrayClassesAndCreateConflictTablesVisitor : public ClassVisitor { public: virtual bool operator()(mirror::Class* klass) OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_) { + if (Runtime::Current()->GetHeap()->ObjectIsInBootImageSpace(klass)) { + return true; + } if (klass->IsArrayClass()) { StackHandleScope<1> hs(Thread::Current()); Runtime::Current()->GetClassLinker()->EnsureInitialized(hs.Self(), @@ -2523,6 +2521,10 @@ class InitializeArrayClassVisitor : public ClassVisitor { true, true); } + // Create the conflict tables. + if (!klass->IsTemp() && klass->ShouldHaveEmbeddedImtAndVTable()) { + Runtime::Current()->GetClassLinker()->FillIMTAndConflictTables(klass); + } return true; } }; @@ -2535,13 +2537,15 @@ void CompilerDriver::InitializeClasses(jobject class_loader, CHECK(dex_file != nullptr); InitializeClasses(class_loader, *dex_file, dex_files, timings); } - { + if (boot_image_ || app_image_) { // Make sure that we call EnsureIntiailized on all the array classes to call // SetVerificationAttempted so that the access flags are set. If we do not do this they get // changed at runtime resulting in more dirty image pages. + // Also create conflict tables. + // Only useful if we are compiling an image (image_classes_ is not null). ScopedObjectAccess soa(Thread::Current()); - InitializeArrayClassVisitor visitor; - Runtime::Current()->GetClassLinker()->VisitClasses(&visitor); + InitializeArrayClassesAndCreateConflictTablesVisitor visitor; + Runtime::Current()->GetClassLinker()->VisitClassesWithoutClassesLock(&visitor); } if (IsBootImage()) { // Prune garbage objects created during aborted transactions. @@ -2558,8 +2562,9 @@ void CompilerDriver::Compile(jobject class_loader, ? "null" : profile_compilation_info_->DumpInfo(&dex_files)); } - for (size_t i = 0; i != dex_files.size(); ++i) { - const DexFile* dex_file = dex_files[i]; + + DCHECK(current_dex_to_dex_methods_ == nullptr); + for (const DexFile* dex_file : dex_files) { CHECK(dex_file != nullptr); CompileDexFile(class_loader, *dex_file, @@ -2572,6 +2577,25 @@ void CompilerDriver::Compile(jobject class_loader, max_arena_alloc_ = std::max(arena_alloc, max_arena_alloc_); Runtime::Current()->ReclaimArenaPoolMemory(); } + + ArrayRef<DexFileMethodSet> dex_to_dex_references; + { + // From this point on, we shall not modify dex_to_dex_references_, so + // just grab a reference to it that we use without holding the mutex. + MutexLock lock(Thread::Current(), dex_to_dex_references_lock_); + dex_to_dex_references = ArrayRef<DexFileMethodSet>(dex_to_dex_references_); + } + for (const auto& method_set : dex_to_dex_references) { + current_dex_to_dex_methods_ = &method_set.GetMethodIndexes(); + CompileDexFile(class_loader, + method_set.GetDexFile(), + dex_files, + parallel_thread_pool_.get(), + parallel_thread_count_, + timings); + } + current_dex_to_dex_methods_ = nullptr; + VLOG(compiler) << "Compile: " << GetMemoryUsageString(false); } diff --git a/compiler/driver/compiler_driver.h b/compiler/driver/compiler_driver.h index 4308eac4af..2dd46514e7 100644 --- a/compiler/driver/compiler_driver.h +++ b/compiler/driver/compiler_driver.h @@ -52,6 +52,7 @@ namespace verifier { class MethodVerifier; } // namespace verifier +class BitVector; class CompiledClass; class CompiledMethod; class CompilerOptions; @@ -92,6 +93,7 @@ class CompilerDriver { InstructionSet instruction_set, const InstructionSetFeatures* instruction_set_features, bool boot_image, + bool app_image, std::unordered_set<std::string>* image_classes, std::unordered_set<std::string>* compiled_classes, std::unordered_set<std::string>* compiled_methods, @@ -119,12 +121,12 @@ class CompilerDriver { void CompileAll(jobject class_loader, const std::vector<const DexFile*>& dex_files, TimingLogger* timings) - REQUIRES(!Locks::mutator_lock_, !compiled_classes_lock_); + REQUIRES(!Locks::mutator_lock_, !compiled_classes_lock_, !dex_to_dex_references_lock_); // Compile a single Method. void CompileOne(Thread* self, ArtMethod* method, TimingLogger* timings) SHARED_REQUIRES(Locks::mutator_lock_) - REQUIRES(!compiled_methods_lock_, !compiled_classes_lock_); + REQUIRES(!compiled_methods_lock_, !compiled_classes_lock_, !dex_to_dex_references_lock_); VerificationResults* GetVerificationResults() const { DCHECK(Runtime::Current()->IsAotCompiler()); @@ -195,25 +197,26 @@ class CompilerDriver { // Callbacks from compiler to see what runtime checks must be generated. - bool CanAssumeTypeIsPresentInDexCache(const DexFile& dex_file, uint32_t type_idx); + bool CanAssumeTypeIsPresentInDexCache(Handle<mirror::DexCache> dex_cache, + uint32_t type_idx) + SHARED_REQUIRES(Locks::mutator_lock_); bool CanAssumeStringIsPresentInDexCache(const DexFile& dex_file, uint32_t string_idx) REQUIRES(!Locks::mutator_lock_); // Are runtime access checks necessary in the compiled code? - bool CanAccessTypeWithoutChecks(uint32_t referrer_idx, const DexFile& dex_file, - uint32_t type_idx, bool* type_known_final = nullptr, - bool* type_known_abstract = nullptr, - bool* equals_referrers_class = nullptr) - REQUIRES(!Locks::mutator_lock_); + bool CanAccessTypeWithoutChecks(uint32_t referrer_idx, + Handle<mirror::DexCache> dex_cache, + uint32_t type_idx) + SHARED_REQUIRES(Locks::mutator_lock_); // Are runtime access and instantiable checks necessary in the code? // out_is_finalizable is set to whether the type is finalizable. bool CanAccessInstantiableTypeWithoutChecks(uint32_t referrer_idx, - const DexFile& dex_file, + Handle<mirror::DexCache> dex_cache, uint32_t type_idx, bool* out_is_finalizable) - REQUIRES(!Locks::mutator_lock_); + SHARED_REQUIRES(Locks::mutator_lock_); bool CanEmbedTypeInCode(const DexFile& dex_file, uint32_t type_idx, bool* is_type_initialized, bool* use_direct_type_ptr, @@ -368,14 +371,6 @@ class CompilerDriver { SHARED_REQUIRES(Locks::mutator_lock_); - // Can we fastpath static field access? Computes field's offset, volatility and whether the - // field is within the referrer (which can avoid checking class initialization). - bool ComputeStaticFieldInfo(uint32_t field_idx, const DexCompilationUnit* mUnit, bool is_put, - MemberOffset* field_offset, uint32_t* storage_index, - bool* is_referrers_class, bool* is_volatile, bool* is_initialized, - Primitive::Type* type) - REQUIRES(!Locks::mutator_lock_); - // Can we fastpath a interface, super class or virtual method call? Computes method's vtable // index. bool ComputeInvokeInfo(const DexCompilationUnit* mUnit, const uint32_t dex_pc, @@ -481,6 +476,13 @@ class CompilerDriver { return true; } + void MarkForDexToDexCompilation(Thread* self, const MethodReference& method_ref) + REQUIRES(!dex_to_dex_references_lock_); + + const BitVector* GetCurrentDexToDexMethods() const { + return current_dex_to_dex_methods_; + } + private: // Return whether the declaring class of `resolved_member` is // available to `referrer_class` for read or write access using two @@ -607,7 +609,7 @@ class CompilerDriver { void Compile(jobject class_loader, const std::vector<const DexFile*>& dex_files, - TimingLogger* timings); + TimingLogger* timings) REQUIRES(!dex_to_dex_references_lock_); void CompileDexFile(jobject class_loader, const DexFile& dex_file, const std::vector<const DexFile*>& dex_files, @@ -659,6 +661,7 @@ class CompilerDriver { size_t non_relative_linker_patch_count_ GUARDED_BY(compiled_methods_lock_); const bool boot_image_; + const bool app_image_; // If image_ is true, specifies the classes that will be included in the image. // Note if image_classes_ is null, all classes are included in the image. @@ -707,6 +710,16 @@ class CompilerDriver { const ProfileCompilationInfo* const profile_compilation_info_; size_t max_arena_alloc_; + + // Data for delaying dex-to-dex compilation. + Mutex dex_to_dex_references_lock_; + // In the first phase, dex_to_dex_references_ collects methods for dex-to-dex compilation. + class DexFileMethodSet; + std::vector<DexFileMethodSet> dex_to_dex_references_ GUARDED_BY(dex_to_dex_references_lock_); + // In the second phase, current_dex_to_dex_methods_ points to the BitVector with method + // indexes for dex-to-dex compilation in the current dex file. + const BitVector* current_dex_to_dex_methods_; + friend class CompileClassVisitor; DISALLOW_COPY_AND_ASSIGN(CompilerDriver); }; diff --git a/compiler/driver/compiler_options.cc b/compiler/driver/compiler_options.cc index 1bd4c3ad80..f20dba34a6 100644 --- a/compiler/driver/compiler_options.cc +++ b/compiler/driver/compiler_options.cc @@ -21,7 +21,7 @@ namespace art { CompilerOptions::CompilerOptions() - : compiler_filter_(kDefaultCompilerFilter), + : compiler_filter_(CompilerFilter::kDefaultCompilerFilter), huge_method_threshold_(kDefaultHugeMethodThreshold), large_method_threshold_(kDefaultLargeMethodThreshold), small_method_threshold_(kDefaultSmallMethodThreshold), diff --git a/compiler/driver/compiler_options.h b/compiler/driver/compiler_options.h index c67ab6ef14..60b700ad91 100644 --- a/compiler/driver/compiler_options.h +++ b/compiler/driver/compiler_options.h @@ -31,7 +31,6 @@ namespace art { class CompilerOptions FINAL { public: // Guide heuristics to determine whether to compile method if profile data not available. - static const CompilerFilter::Filter kDefaultCompilerFilter = CompilerFilter::kSpeed; static const size_t kDefaultHugeMethodThreshold = 10000; static const size_t kDefaultLargeMethodThreshold = 600; static const size_t kDefaultSmallMethodThreshold = 60; @@ -89,8 +88,12 @@ class CompilerOptions FINAL { return compiler_filter_ == CompilerFilter::kVerifyAtRuntime; } - bool IsCompilationEnabled() const { - return CompilerFilter::IsCompilationEnabled(compiler_filter_); + bool IsBytecodeCompilationEnabled() const { + return CompilerFilter::IsBytecodeCompilationEnabled(compiler_filter_); + } + + bool IsJniCompilationEnabled() const { + return CompilerFilter::IsJniCompilationEnabled(compiler_filter_); } bool IsVerificationEnabled() const { diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc index 8bb462c667..da10568475 100644 --- a/compiler/image_writer.cc +++ b/compiler/image_writer.cc @@ -437,6 +437,9 @@ void ImageWriter::PrepareDexCacheArraySlots() { continue; } const DexFile* dex_file = dex_cache->GetDexFile(); + CHECK(dex_file_oat_index_map_.find(dex_file) != dex_file_oat_index_map_.end()) + << "Dex cache should have been pruned " << dex_file->GetLocation() + << "; possibly in class path"; DexCacheArraysLayout layout(target_ptr_size_, dex_file); DCHECK(layout.Valid()); size_t oat_index = GetOatIndexForDexCache(dex_cache); @@ -653,8 +656,7 @@ bool ImageWriter::AllocMemory() { for (ImageInfo& image_info : image_infos_) { ImageSection unused_sections[ImageHeader::kSectionCount]; const size_t length = RoundUp( - image_info.CreateImageSections(target_ptr_size_, unused_sections), - kPageSize); + image_info.CreateImageSections(unused_sections), kPageSize); std::string error_msg; image_info.image_.reset(MemMap::MapAnonymous("image writer image", @@ -840,6 +842,10 @@ void ImageWriter::PruneNonImageClasses() { ClassLinker* class_linker = runtime->GetClassLinker(); Thread* self = Thread::Current(); + // Clear class table strong roots so that dex caches can get pruned. We require pruning the class + // path dex caches. + class_linker->ClearClassTableStrongRoots(); + // Make a list of classes we would like to prune. NonImageClassesVisitor visitor(this); class_linker->VisitClasses(&visitor); @@ -1029,6 +1035,9 @@ ObjectArray<Object>* ImageWriter::CreateImageRoots(size_t oat_index) const { for (const ClassLinker::DexCacheData& data : class_linker->GetDexCachesData()) { mirror::DexCache* dex_cache = down_cast<mirror::DexCache*>(self->DecodeJObject(data.weak_root)); + if (dex_cache == nullptr) { + continue; + } const DexFile* dex_file = dex_cache->GetDexFile(); if (!IsInBootImage(dex_cache)) { dex_cache_count += image_dex_files.find(dex_file) != image_dex_files.end() ? 1u : 0u; @@ -1045,6 +1054,9 @@ ObjectArray<Object>* ImageWriter::CreateImageRoots(size_t oat_index) const { for (const ClassLinker::DexCacheData& data : class_linker->GetDexCachesData()) { mirror::DexCache* dex_cache = down_cast<mirror::DexCache*>(self->DecodeJObject(data.weak_root)); + if (dex_cache == nullptr) { + continue; + } const DexFile* dex_file = dex_cache->GetDexFile(); if (!IsInBootImage(dex_cache)) { non_image_dex_caches += image_dex_files.find(dex_file) != image_dex_files.end() ? 1u : 0u; @@ -1056,6 +1068,9 @@ ObjectArray<Object>* ImageWriter::CreateImageRoots(size_t oat_index) const { for (const ClassLinker::DexCacheData& data : class_linker->GetDexCachesData()) { mirror::DexCache* dex_cache = down_cast<mirror::DexCache*>(self->DecodeJObject(data.weak_root)); + if (dex_cache == nullptr) { + continue; + } const DexFile* dex_file = dex_cache->GetDexFile(); if (!IsInBootImage(dex_cache) && image_dex_files.find(dex_file) != image_dex_files.end()) { dex_caches->Set<false>(i, dex_cache); @@ -1215,6 +1230,19 @@ void ImageWriter::WalkFieldsInOrder(mirror::Object* obj) { } (any_dirty ? dirty_methods_ : clean_methods_) += num_methods; } + // Assign offsets for all runtime methods in the IMT since these may hold conflict tables + // live. + if (as_klass->ShouldHaveEmbeddedImtAndVTable()) { + for (size_t i = 0; i < mirror::Class::kImtSize; ++i) { + ArtMethod* imt_method = as_klass->GetEmbeddedImTableEntry(i, target_ptr_size_); + DCHECK(imt_method != nullptr); + if (imt_method->IsRuntimeMethod() && + !IsInBootImage(imt_method) && + !NativeRelocationAssigned(imt_method)) { + AssignMethodOffset(imt_method, kNativeObjectRelocationTypeRuntimeMethod, oat_index); + } + } + } } else if (h_obj->IsObjectArray()) { // Walk elements of an object array. int32_t length = h_obj->AsObjectArray<mirror::Object>()->GetLength(); @@ -1237,13 +1265,37 @@ void ImageWriter::WalkFieldsInOrder(mirror::Object* obj) { } } +bool ImageWriter::NativeRelocationAssigned(void* ptr) const { + return native_object_relocations_.find(ptr) != native_object_relocations_.end(); +} + +void ImageWriter::TryAssignConflictTableOffset(ImtConflictTable* table, size_t oat_index) { + // No offset, or already assigned. + if (table == nullptr || NativeRelocationAssigned(table)) { + return; + } + CHECK(!IsInBootImage(table)); + // If the method is a conflict method we also want to assign the conflict table offset. + ImageInfo& image_info = GetImageInfo(oat_index); + const size_t size = table->ComputeSize(target_ptr_size_); + native_object_relocations_.emplace( + table, + NativeObjectRelocation { + oat_index, + image_info.bin_slot_sizes_[kBinIMTConflictTable], + kNativeObjectRelocationTypeIMTConflictTable}); + image_info.bin_slot_sizes_[kBinIMTConflictTable] += size; +} + void ImageWriter::AssignMethodOffset(ArtMethod* method, NativeObjectRelocationType type, size_t oat_index) { DCHECK(!IsInBootImage(method)); - auto it = native_object_relocations_.find(method); - CHECK(it == native_object_relocations_.end()) << "Method " << method << " already assigned " + CHECK(!NativeRelocationAssigned(method)) << "Method " << method << " already assigned " << PrettyMethod(method); + if (method->IsRuntimeMethod()) { + TryAssignConflictTableOffset(method->GetImtConflictTable(target_ptr_size_), oat_index); + } ImageInfo& image_info = GetImageInfo(oat_index); size_t& offset = image_info.bin_slot_sizes_[BinTypeForNativeRelocationType(type)]; native_object_relocations_.emplace(method, NativeObjectRelocation { oat_index, offset, type }); @@ -1292,8 +1344,7 @@ void ImageWriter::CalculateNewObjectOffsets() { // know where image_roots is going to end up image_objects_offset_begin_ = RoundUp(sizeof(ImageHeader), kObjectAlignment); // 64-bit-alignment - // Clear any pre-existing monitors which may have been in the monitor words, assign bin slots. - heap->VisitObjects(WalkFieldsCallback, this); + const size_t method_alignment = ArtMethod::Alignment(target_ptr_size_); // Write the image runtime methods. image_methods_[ImageHeader::kResolutionMethod] = runtime->GetResolutionMethod(); image_methods_[ImageHeader::kImtConflictMethod] = runtime->GetImtConflictMethod(); @@ -1303,31 +1354,19 @@ void ImageWriter::CalculateNewObjectOffsets() { runtime->GetCalleeSaveMethod(Runtime::kRefsOnly); image_methods_[ImageHeader::kRefsAndArgsSaveMethod] = runtime->GetCalleeSaveMethod(Runtime::kRefsAndArgs); - - // Add room for fake length prefixed array for holding the image methods. - const auto image_method_type = kNativeObjectRelocationTypeArtMethodArrayClean; - auto it = native_object_relocations_.find(&image_method_array_); - CHECK(it == native_object_relocations_.end()); - ImageInfo& default_image_info = GetImageInfo(GetDefaultOatIndex()); - size_t& offset = - default_image_info.bin_slot_sizes_[BinTypeForNativeRelocationType(image_method_type)]; - if (!compile_app_image_) { - native_object_relocations_.emplace(&image_method_array_, - NativeObjectRelocation { GetDefaultOatIndex(), offset, image_method_type }); - } - size_t method_alignment = ArtMethod::Alignment(target_ptr_size_); - const size_t array_size = LengthPrefixedArray<ArtMethod>::ComputeSize( - 0, ArtMethod::Size(target_ptr_size_), method_alignment); - CHECK_ALIGNED_PARAM(array_size, method_alignment); - offset += array_size; + // Visit image methods first to have the main runtime methods in the first image. for (auto* m : image_methods_) { CHECK(m != nullptr); CHECK(m->IsRuntimeMethod()); DCHECK_EQ(compile_app_image_, IsInBootImage(m)) << "Trampolines should be in boot image"; if (!IsInBootImage(m)) { - AssignMethodOffset(m, kNativeObjectRelocationTypeArtMethodClean, GetDefaultOatIndex()); + AssignMethodOffset(m, kNativeObjectRelocationTypeRuntimeMethod, GetDefaultOatIndex()); } } + + // Clear any pre-existing monitors which may have been in the monitor words, assign bin slots. + heap->VisitObjects(WalkFieldsCallback, this); + // Calculate size of the dex cache arrays slot and prepare offsets. PrepareDexCacheArraySlots(); @@ -1346,15 +1385,22 @@ void ImageWriter::CalculateNewObjectOffsets() { for (ImageInfo& image_info : image_infos_) { size_t bin_offset = image_objects_offset_begin_; for (size_t i = 0; i != kBinSize; ++i) { + switch (i) { + case kBinArtMethodClean: + case kBinArtMethodDirty: { + bin_offset = RoundUp(bin_offset, method_alignment); + break; + } + case kBinIMTConflictTable: { + bin_offset = RoundUp(bin_offset, target_ptr_size_); + break; + } + default: { + // Normal alignment. + } + } image_info.bin_slot_offsets_[i] = bin_offset; bin_offset += image_info.bin_slot_sizes_[i]; - if (i == kBinArtField) { - static_assert(kBinArtField + 1 == kBinArtMethodClean, "Methods follow fields."); - static_assert(alignof(ArtField) == 4u, "ArtField alignment is 4."); - DCHECK_ALIGNED(bin_offset, 4u); - DCHECK(method_alignment == 4u || method_alignment == 8u); - bin_offset = RoundUp(bin_offset, method_alignment); - } } // NOTE: There may be additional padding between the bin slots and the intern table. DCHECK_EQ(image_info.image_end_, @@ -1367,9 +1413,7 @@ void ImageWriter::CalculateNewObjectOffsets() { image_info.image_begin_ = global_image_begin_ + image_offset; image_info.image_offset_ = image_offset; ImageSection unused_sections[ImageHeader::kSectionCount]; - image_info.image_size_ = RoundUp( - image_info.CreateImageSections(target_ptr_size_, unused_sections), - kPageSize); + image_info.image_size_ = RoundUp(image_info.CreateImageSections(unused_sections), kPageSize); // There should be no gaps until the next image. image_offset += image_info.image_size_; } @@ -1396,42 +1440,52 @@ void ImageWriter::CalculateNewObjectOffsets() { // Note that image_info.image_end_ is left at end of used mirror object section. } -size_t ImageWriter::ImageInfo::CreateImageSections(size_t target_ptr_size, - ImageSection* out_sections) const { +size_t ImageWriter::ImageInfo::CreateImageSections(ImageSection* out_sections) const { DCHECK(out_sections != nullptr); + + // Do not round up any sections here that are represented by the bins since it will break + // offsets. + // Objects section - auto* objects_section = &out_sections[ImageHeader::kSectionObjects]; + ImageSection* objects_section = &out_sections[ImageHeader::kSectionObjects]; *objects_section = ImageSection(0u, image_end_); - size_t cur_pos = objects_section->End(); + // Add field section. - auto* field_section = &out_sections[ImageHeader::kSectionArtFields]; - *field_section = ImageSection(cur_pos, bin_slot_sizes_[kBinArtField]); + ImageSection* field_section = &out_sections[ImageHeader::kSectionArtFields]; + *field_section = ImageSection(bin_slot_offsets_[kBinArtField], bin_slot_sizes_[kBinArtField]); CHECK_EQ(bin_slot_offsets_[kBinArtField], field_section->Offset()); - cur_pos = field_section->End(); - // Round up to the alignment the required by the method section. - cur_pos = RoundUp(cur_pos, ArtMethod::Alignment(target_ptr_size)); + // Add method section. - auto* methods_section = &out_sections[ImageHeader::kSectionArtMethods]; - *methods_section = ImageSection(cur_pos, - bin_slot_sizes_[kBinArtMethodClean] + - bin_slot_sizes_[kBinArtMethodDirty]); - CHECK_EQ(bin_slot_offsets_[kBinArtMethodClean], methods_section->Offset()); - cur_pos = methods_section->End(); + ImageSection* methods_section = &out_sections[ImageHeader::kSectionArtMethods]; + *methods_section = ImageSection( + bin_slot_offsets_[kBinArtMethodClean], + bin_slot_sizes_[kBinArtMethodClean] + bin_slot_sizes_[kBinArtMethodDirty]); + + // Conflict tables section. + ImageSection* imt_conflict_tables_section = &out_sections[ImageHeader::kSectionIMTConflictTables]; + *imt_conflict_tables_section = ImageSection(bin_slot_offsets_[kBinIMTConflictTable], + bin_slot_sizes_[kBinIMTConflictTable]); + + // Runtime methods section. + ImageSection* runtime_methods_section = &out_sections[ImageHeader::kSectionRuntimeMethods]; + *runtime_methods_section = ImageSection(bin_slot_offsets_[kBinRuntimeMethod], + bin_slot_sizes_[kBinRuntimeMethod]); + // Add dex cache arrays section. - auto* dex_cache_arrays_section = &out_sections[ImageHeader::kSectionDexCacheArrays]; - *dex_cache_arrays_section = ImageSection(cur_pos, bin_slot_sizes_[kBinDexCacheArray]); - CHECK_EQ(bin_slot_offsets_[kBinDexCacheArray], dex_cache_arrays_section->Offset()); - cur_pos = dex_cache_arrays_section->End(); + ImageSection* dex_cache_arrays_section = &out_sections[ImageHeader::kSectionDexCacheArrays]; + *dex_cache_arrays_section = ImageSection(bin_slot_offsets_[kBinDexCacheArray], + bin_slot_sizes_[kBinDexCacheArray]); + // Round up to the alignment the string table expects. See HashSet::WriteToMemory. - cur_pos = RoundUp(cur_pos, sizeof(uint64_t)); + size_t cur_pos = RoundUp(dex_cache_arrays_section->End(), sizeof(uint64_t)); // Calculate the size of the interned strings. - auto* interned_strings_section = &out_sections[ImageHeader::kSectionInternedStrings]; + ImageSection* interned_strings_section = &out_sections[ImageHeader::kSectionInternedStrings]; *interned_strings_section = ImageSection(cur_pos, intern_table_bytes_); cur_pos = interned_strings_section->End(); // Round up to the alignment the class table expects. See HashSet::WriteToMemory. cur_pos = RoundUp(cur_pos, sizeof(uint64_t)); // Calculate the size of the class table section. - auto* class_table_section = &out_sections[ImageHeader::kSectionClassTable]; + ImageSection* class_table_section = &out_sections[ImageHeader::kSectionClassTable]; *class_table_section = ImageSection(cur_pos, class_table_bytes_); cur_pos = class_table_section->End(); // Image end goes right before the start of the image bitmap. @@ -1446,7 +1500,7 @@ void ImageWriter::CreateHeader(size_t oat_index) { // Create the image sections. ImageSection sections[ImageHeader::kSectionCount]; - const size_t image_end = image_info.CreateImageSections(target_ptr_size_, sections); + const size_t image_end = image_info.CreateImageSections(sections); // Finally bitmap section. const size_t bitmap_bytes = image_info.image_bitmap_->Size(); @@ -1531,8 +1585,20 @@ class FixupRootVisitor : public RootVisitor { ImageWriter* const image_writer_; }; +void ImageWriter::CopyAndFixupImtConflictTable(ImtConflictTable* orig, ImtConflictTable* copy) { + const size_t count = orig->NumEntries(target_ptr_size_); + for (size_t i = 0; i < count; ++i) { + ArtMethod* interface_method = orig->GetInterfaceMethod(i, target_ptr_size_); + ArtMethod* implementation_method = orig->GetImplementationMethod(i, target_ptr_size_); + copy->SetInterfaceMethod(i, target_ptr_size_, NativeLocationInImage(interface_method)); + copy->SetImplementationMethod(i, + target_ptr_size_, + NativeLocationInImage(implementation_method)); + } +} + void ImageWriter::CopyAndFixupNativeData(size_t oat_index) { - ImageInfo& image_info = GetImageInfo(oat_index); + const ImageInfo& image_info = GetImageInfo(oat_index); // Copy ArtFields and methods to their locations and update the array for convenience. for (auto& pair : native_object_relocations_) { NativeObjectRelocation& relocation = pair.second; @@ -1550,6 +1616,7 @@ void ImageWriter::CopyAndFixupNativeData(size_t oat_index) { GetImageAddress(reinterpret_cast<ArtField*>(pair.first)->GetDeclaringClass())); break; } + case kNativeObjectRelocationTypeRuntimeMethod: case kNativeObjectRelocationTypeArtMethodClean: case kNativeObjectRelocationTypeArtMethodDirty: { CopyAndFixupMethod(reinterpret_cast<ArtMethod*>(pair.first), @@ -1575,26 +1642,22 @@ void ImageWriter::CopyAndFixupNativeData(size_t oat_index) { case kNativeObjectRelocationTypeDexCacheArray: // Nothing to copy here, everything is done in FixupDexCache(). break; + case kNativeObjectRelocationTypeIMTConflictTable: { + auto* orig_table = reinterpret_cast<ImtConflictTable*>(pair.first); + CopyAndFixupImtConflictTable( + orig_table, + new(dest)ImtConflictTable(orig_table->NumEntries(target_ptr_size_), target_ptr_size_)); + break; + } } } // Fixup the image method roots. auto* image_header = reinterpret_cast<ImageHeader*>(image_info.image_->Begin()); - const ImageSection& methods_section = image_header->GetMethodsSection(); for (size_t i = 0; i < ImageHeader::kImageMethodsCount; ++i) { ArtMethod* method = image_methods_[i]; CHECK(method != nullptr); - // Only place runtime methods in the image of the default oat file. - if (method->IsRuntimeMethod() && oat_index != GetDefaultOatIndex()) { - continue; - } if (!IsInBootImage(method)) { - auto it = native_object_relocations_.find(method); - CHECK(it != native_object_relocations_.end()) << "No forwarding for " << PrettyMethod(method); - NativeObjectRelocation& relocation = it->second; - CHECK(methods_section.Contains(relocation.offset)) << relocation.offset << " not in " - << methods_section; - CHECK(relocation.IsArtMethodRelocation()) << relocation.type; - method = reinterpret_cast<ArtMethod*>(global_image_begin_ + it->second.offset); + method = NativeLocationInImage(method); } image_header->SetImageMethod(static_cast<ImageHeader::ImageMethod>(i), method); } @@ -2057,24 +2120,28 @@ void ImageWriter::CopyAndFixupMethod(ArtMethod* orig, // The resolution method has a special trampoline to call. Runtime* runtime = Runtime::Current(); - if (UNLIKELY(orig == runtime->GetResolutionMethod())) { - copy->SetEntryPointFromQuickCompiledCodePtrSize( - GetOatAddress(kOatAddressQuickResolutionTrampoline), target_ptr_size_); - } else if (UNLIKELY(orig == runtime->GetImtConflictMethod() || - orig == runtime->GetImtUnimplementedMethod())) { - copy->SetEntryPointFromQuickCompiledCodePtrSize( - GetOatAddress(kOatAddressQuickIMTConflictTrampoline), target_ptr_size_); - } else if (UNLIKELY(orig->IsRuntimeMethod())) { - bool found_one = false; - for (size_t i = 0; i < static_cast<size_t>(Runtime::kLastCalleeSaveType); ++i) { - auto idx = static_cast<Runtime::CalleeSaveType>(i); - if (runtime->HasCalleeSaveMethod(idx) && runtime->GetCalleeSaveMethod(idx) == orig) { - found_one = true; - break; + if (orig->IsRuntimeMethod()) { + ImtConflictTable* orig_table = orig->GetImtConflictTable(target_ptr_size_); + if (orig_table != nullptr) { + // Special IMT conflict method, normal IMT conflict method or unimplemented IMT method. + copy->SetEntryPointFromQuickCompiledCodePtrSize( + GetOatAddress(kOatAddressQuickIMTConflictTrampoline), target_ptr_size_); + copy->SetImtConflictTable(NativeLocationInImage(orig_table), target_ptr_size_); + } else if (UNLIKELY(orig == runtime->GetResolutionMethod())) { + copy->SetEntryPointFromQuickCompiledCodePtrSize( + GetOatAddress(kOatAddressQuickResolutionTrampoline), target_ptr_size_); + } else { + bool found_one = false; + for (size_t i = 0; i < static_cast<size_t>(Runtime::kLastCalleeSaveType); ++i) { + auto idx = static_cast<Runtime::CalleeSaveType>(i); + if (runtime->HasCalleeSaveMethod(idx) && runtime->GetCalleeSaveMethod(idx) == orig) { + found_one = true; + break; + } } + CHECK(found_one) << "Expected to find callee save method but got " << PrettyMethod(orig); + CHECK(copy->IsRuntimeMethod()); } - CHECK(found_one) << "Expected to find callee save method but got " << PrettyMethod(orig); - CHECK(copy->IsRuntimeMethod()); } else { // We assume all methods have code. If they don't currently then we set them to the use the // resolution trampoline. Abstract methods never have code and so we need to make sure their @@ -2141,6 +2208,10 @@ ImageWriter::Bin ImageWriter::BinTypeForNativeRelocationType(NativeObjectRelocat return kBinArtMethodDirty; case kNativeObjectRelocationTypeDexCacheArray: return kBinDexCacheArray; + case kNativeObjectRelocationTypeRuntimeMethod: + return kBinRuntimeMethod; + case kNativeObjectRelocationTypeIMTConflictTable: + return kBinIMTConflictTable; } UNREACHABLE(); } @@ -2242,7 +2313,6 @@ ImageWriter::ImageWriter( compile_app_image_(compile_app_image), target_ptr_size_(InstructionSetPointerSize(compiler_driver_.GetInstructionSet())), image_infos_(oat_filenames.size()), - image_method_array_(ImageHeader::kImageMethodsCount), dirty_methods_(0u), clean_methods_(0u), image_storage_mode_(image_storage_mode), diff --git a/compiler/image_writer.h b/compiler/image_writer.h index 0cb6aea9b2..51976c511f 100644 --- a/compiler/image_writer.h +++ b/compiler/image_writer.h @@ -169,6 +169,10 @@ class ImageWriter FINAL { // ArtMethods may be dirty if the class has native methods or a declaring class that isn't // initialized. kBinArtMethodDirty, + // Conflict tables (clean). + kBinIMTConflictTable, + // Runtime methods (always clean, do not have a length prefix array). + kBinRuntimeMethod, // Dex cache arrays have a special slot for PC-relative addressing. Since they are // huge, and as such their dirtiness is not important for the clean/dirty separation, // we arbitrarily keep them at the end of the native data. @@ -186,6 +190,8 @@ class ImageWriter FINAL { kNativeObjectRelocationTypeArtMethodArrayClean, kNativeObjectRelocationTypeArtMethodDirty, kNativeObjectRelocationTypeArtMethodArrayDirty, + kNativeObjectRelocationTypeRuntimeMethod, + kNativeObjectRelocationTypeIMTConflictTable, kNativeObjectRelocationTypeDexCacheArray, }; friend std::ostream& operator<<(std::ostream& stream, const NativeObjectRelocationType& type); @@ -240,7 +246,7 @@ class ImageWriter FINAL { // Create the image sections into the out sections variable, returns the size of the image // excluding the bitmap. - size_t CreateImageSections(size_t target_ptr_size, ImageSection* out_sections) const; + size_t CreateImageSections(ImageSection* out_sections) const; std::unique_ptr<MemMap> image_; // Memory mapped for generating the image. @@ -395,6 +401,8 @@ class ImageWriter FINAL { void CopyAndFixupObject(mirror::Object* obj) SHARED_REQUIRES(Locks::mutator_lock_); void CopyAndFixupMethod(ArtMethod* orig, ArtMethod* copy, const ImageInfo& image_info) SHARED_REQUIRES(Locks::mutator_lock_); + void CopyAndFixupImtConflictTable(ImtConflictTable* orig, ImtConflictTable* copy) + SHARED_REQUIRES(Locks::mutator_lock_); void FixupClass(mirror::Class* orig, mirror::Class* copy) SHARED_REQUIRES(Locks::mutator_lock_); void FixupObject(mirror::Object* orig, mirror::Object* copy) @@ -425,6 +433,11 @@ class ImageWriter FINAL { size_t oat_index) SHARED_REQUIRES(Locks::mutator_lock_); + // Assign the offset for an IMT conflict table. Does nothing if the table already has a native + // relocation. + void TryAssignConflictTableOffset(ImtConflictTable* table, size_t oat_index) + SHARED_REQUIRES(Locks::mutator_lock_); + // Return true if klass is loaded by the boot class loader but not in the boot image. bool IsBootClassLoaderNonImageClass(mirror::Class* klass) SHARED_REQUIRES(Locks::mutator_lock_); @@ -481,6 +494,9 @@ class ImageWriter FINAL { // remove duplicates in the multi image and app image case. mirror::String* FindInternedString(mirror::String* string) SHARED_REQUIRES(Locks::mutator_lock_); + // Return true if there already exists a native allocation for an object. + bool NativeRelocationAssigned(void* ptr) const; + const CompilerDriver& compiler_driver_; // Beginning target image address for the first image. @@ -517,16 +533,14 @@ class ImageWriter FINAL { bool IsArtMethodRelocation() const { return type == kNativeObjectRelocationTypeArtMethodClean || - type == kNativeObjectRelocationTypeArtMethodDirty; + type == kNativeObjectRelocationTypeArtMethodDirty || + type == kNativeObjectRelocationTypeRuntimeMethod; } }; std::unordered_map<void*, NativeObjectRelocation> native_object_relocations_; // Runtime ArtMethods which aren't reachable from any Class but need to be copied into the image. ArtMethod* image_methods_[ImageHeader::kImageMethodsCount]; - // Fake length prefixed array for image methods. This array does not contain the actual - // ArtMethods. We only use it for the header and relocation addresses. - LengthPrefixedArray<ArtMethod> image_method_array_; // Counters for measurements, used for logging only. uint64_t dirty_methods_; diff --git a/compiler/jit/jit_compiler.cc b/compiler/jit/jit_compiler.cc index c8dfc93a04..178533849b 100644 --- a/compiler/jit/jit_compiler.cc +++ b/compiler/jit/jit_compiler.cc @@ -88,7 +88,7 @@ NO_RETURN static void Usage(const char* fmt, ...) { JitCompiler::JitCompiler() { compiler_options_.reset(new CompilerOptions( - CompilerOptions::kDefaultCompilerFilter, + CompilerFilter::kDefaultCompilerFilter, CompilerOptions::kDefaultHugeMethodThreshold, CompilerOptions::kDefaultLargeMethodThreshold, CompilerOptions::kDefaultSmallMethodThreshold, @@ -155,7 +155,8 @@ JitCompiler::JitCompiler() { Compiler::kOptimizing, instruction_set, instruction_set_features_.get(), - /* image */ false, + /* boot_image */ false, + /* app_image */ false, /* image_classes */ nullptr, /* compiled_classes */ nullptr, /* compiled_methods */ nullptr, @@ -171,7 +172,7 @@ JitCompiler::JitCompiler() { size_t thread_count = compiler_driver_->GetThreadCount(); if (compiler_options_->GetGenerateDebugInfo()) { -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID const char* prefix = "/data/misc/trace"; #else const char* prefix = "/tmp"; diff --git a/compiler/jni/jni_cfi_test.cc b/compiler/jni/jni_cfi_test.cc index 05c85e027a..3526802d6c 100644 --- a/compiler/jni/jni_cfi_test.cc +++ b/compiler/jni/jni_cfi_test.cc @@ -29,7 +29,7 @@ namespace art { // Run the tests only on host. -#ifndef __ANDROID__ +#ifndef ART_TARGET_ANDROID class JNICFITest : public CFITest { public: @@ -52,7 +52,7 @@ class JNICFITest : public CFITest { std::unique_ptr<ManagedRuntimeCallingConvention> mr_conv( ManagedRuntimeCallingConvention::Create(&arena, is_static, is_synchronized, shorty, isa)); const int frame_size(jni_conv->FrameSize()); - const std::vector<ManagedRegister>& callee_save_regs = jni_conv->CalleeSaveRegisters(); + ArrayRef<const ManagedRegister> callee_save_regs = jni_conv->CalleeSaveRegisters(); // Assemble the method. std::unique_ptr<Assembler> jni_asm(Assembler::Create(&arena, isa)); @@ -94,6 +94,6 @@ TEST_ISA(kX86_64) TEST_ISA(kMips) TEST_ISA(kMips64) -#endif // __ANDROID__ +#endif // ART_TARGET_ANDROID } // namespace art diff --git a/compiler/jni/jni_compiler_test.cc b/compiler/jni/jni_compiler_test.cc index cf836a9c9f..c4c2399ccd 100644 --- a/compiler/jni/jni_compiler_test.cc +++ b/compiler/jni/jni_compiler_test.cc @@ -31,6 +31,7 @@ #include "mirror/object_array-inl.h" #include "mirror/object-inl.h" #include "mirror/stack_trace_element.h" +#include "nativeloader/native_loader.h" #include "runtime.h" #include "ScopedLocalRef.h" #include "scoped_thread_state_change.h" @@ -53,6 +54,11 @@ class JniCompilerTest : public CommonCompilerTest { check_generic_jni_ = false; } + void TearDown() OVERRIDE { + android::ResetNativeLoader(); + CommonCompilerTest::TearDown(); + } + void SetCheckGenericJni(bool generic) { check_generic_jni_ = generic; } @@ -92,11 +98,13 @@ class JniCompilerTest : public CommonCompilerTest { CompileForTest(class_loader_, direct, method_name, method_sig); // Start runtime. Thread::Current()->TransitionFromSuspendedToRunnable(); + android::InitializeNativeLoader(); bool started = runtime_->Start(); CHECK(started); } // JNI operations after runtime start. env_ = Thread::Current()->GetJniEnv(); + library_search_path_ = env_->NewStringUTF(""); jklass_ = env_->FindClass("MyClassNatives"); ASSERT_TRUE(jklass_ != nullptr) << method_name << " " << method_sig; @@ -168,6 +176,7 @@ class JniCompilerTest : public CommonCompilerTest { void StackArgsSignExtendedMips64Impl(); JNIEnv* env_; + jstring library_search_path_; jmethodID jmethod_; bool check_generic_jni_; }; @@ -220,7 +229,7 @@ void JniCompilerTest::CompileAndRunIntMethodThroughStubImpl() { std::string reason; ASSERT_TRUE(Runtime::Current()->GetJavaVM()-> - LoadNativeLibrary(env_, "", class_loader_, nullptr, &reason)) + LoadNativeLibrary(env_, "", class_loader_, library_search_path_, &reason)) << reason; jint result = env_->CallNonvirtualIntMethod(jobj_, jklass_, jmethod_, 24); @@ -235,7 +244,7 @@ void JniCompilerTest::CompileAndRunStaticIntMethodThroughStubImpl() { std::string reason; ASSERT_TRUE(Runtime::Current()->GetJavaVM()-> - LoadNativeLibrary(env_, "", class_loader_, nullptr, &reason)) + LoadNativeLibrary(env_, "", class_loader_, library_search_path_, &reason)) << reason; jint result = env_->CallStaticIntMethod(jklass_, jmethod_, 42); diff --git a/compiler/jni/quick/arm/calling_convention_arm.cc b/compiler/jni/quick/arm/calling_convention_arm.cc index 9d2732aa2b..29411f0236 100644 --- a/compiler/jni/quick/arm/calling_convention_arm.cc +++ b/compiler/jni/quick/arm/calling_convention_arm.cc @@ -31,10 +31,6 @@ static const SRegister kHFSArgumentRegisters[] = { S0, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, S13, S14, S15 }; -static const SRegister kHFSCalleeSaveRegisters[] = { - S16, S17, S18, S19, S20, S21, S22, S23, S24, S25, S26, S27, S28, S29, S30, S31 -}; - static const DRegister kHFDArgumentRegisters[] = { D0, D1, D2, D3, D4, D5, D6, D7 }; @@ -42,6 +38,57 @@ static const DRegister kHFDArgumentRegisters[] = { static_assert(arraysize(kHFDArgumentRegisters) * 2 == arraysize(kHFSArgumentRegisters), "ks d argument registers mismatch"); +static constexpr ManagedRegister kCalleeSaveRegisters[] = { + // Core registers. + ArmManagedRegister::FromCoreRegister(R5), + ArmManagedRegister::FromCoreRegister(R6), + ArmManagedRegister::FromCoreRegister(R7), + ArmManagedRegister::FromCoreRegister(R8), + ArmManagedRegister::FromCoreRegister(R10), + ArmManagedRegister::FromCoreRegister(R11), + // Hard float registers. + ArmManagedRegister::FromSRegister(S16), + ArmManagedRegister::FromSRegister(S17), + ArmManagedRegister::FromSRegister(S18), + ArmManagedRegister::FromSRegister(S19), + ArmManagedRegister::FromSRegister(S20), + ArmManagedRegister::FromSRegister(S21), + ArmManagedRegister::FromSRegister(S22), + ArmManagedRegister::FromSRegister(S23), + ArmManagedRegister::FromSRegister(S24), + ArmManagedRegister::FromSRegister(S25), + ArmManagedRegister::FromSRegister(S26), + ArmManagedRegister::FromSRegister(S27), + ArmManagedRegister::FromSRegister(S28), + ArmManagedRegister::FromSRegister(S29), + ArmManagedRegister::FromSRegister(S30), + ArmManagedRegister::FromSRegister(S31) +}; + +static constexpr uint32_t CalculateCoreCalleeSpillMask() { + // LR is a special callee save which is not reported by CalleeSaveRegisters(). + uint32_t result = 1 << LR; + for (auto&& r : kCalleeSaveRegisters) { + if (r.AsArm().IsCoreRegister()) { + result |= (1 << r.AsArm().AsCoreRegister()); + } + } + return result; +} + +static constexpr uint32_t CalculateFpCalleeSpillMask() { + uint32_t result = 0; + for (auto&& r : kCalleeSaveRegisters) { + if (r.AsArm().IsSRegister()) { + result |= (1 << r.AsArm().AsSRegister()); + } + } + return result; +} + +static constexpr uint32_t kCoreCalleeSpillMask = CalculateCoreCalleeSpillMask(); +static constexpr uint32_t kFpCalleeSpillMask = CalculateFpCalleeSpillMask(); + // Calling convention ManagedRegister ArmManagedRuntimeCallingConvention::InterproceduralScratchRegister() { @@ -223,32 +270,15 @@ ArmJniCallingConvention::ArmJniCallingConvention(bool is_static, bool is_synchro cur_reg++; // bump the iterator for every argument } padding_ = padding; - - callee_save_regs_.push_back(ArmManagedRegister::FromCoreRegister(R5)); - callee_save_regs_.push_back(ArmManagedRegister::FromCoreRegister(R6)); - callee_save_regs_.push_back(ArmManagedRegister::FromCoreRegister(R7)); - callee_save_regs_.push_back(ArmManagedRegister::FromCoreRegister(R8)); - callee_save_regs_.push_back(ArmManagedRegister::FromCoreRegister(R10)); - callee_save_regs_.push_back(ArmManagedRegister::FromCoreRegister(R11)); - - for (size_t i = 0; i < arraysize(kHFSCalleeSaveRegisters); ++i) { - callee_save_regs_.push_back(ArmManagedRegister::FromSRegister(kHFSCalleeSaveRegisters[i])); - } } uint32_t ArmJniCallingConvention::CoreSpillMask() const { // Compute spill mask to agree with callee saves initialized in the constructor - uint32_t result = 0; - result = 1 << R5 | 1 << R6 | 1 << R7 | 1 << R8 | 1 << R10 | 1 << R11 | 1 << LR; - return result; + return kCoreCalleeSpillMask; } uint32_t ArmJniCallingConvention::FpSpillMask() const { - uint32_t result = 0; - for (size_t i = 0; i < arraysize(kHFSCalleeSaveRegisters); ++i) { - result |= (1 << kHFSCalleeSaveRegisters[i]); - } - return result; + return kFpCalleeSpillMask; } ManagedRegister ArmJniCallingConvention::ReturnScratchRegister() const { @@ -269,6 +299,10 @@ size_t ArmJniCallingConvention::OutArgSize() { kStackAlignment); } +ArrayRef<const ManagedRegister> ArmJniCallingConvention::CalleeSaveRegisters() const { + return ArrayRef<const ManagedRegister>(kCalleeSaveRegisters); +} + // JniCallingConvention ABI follows AAPCS where longs and doubles must occur // in even register numbers and stack slots void ArmJniCallingConvention::Next() { diff --git a/compiler/jni/quick/arm/calling_convention_arm.h b/compiler/jni/quick/arm/calling_convention_arm.h index 35b50937e9..157880ba90 100644 --- a/compiler/jni/quick/arm/calling_convention_arm.h +++ b/compiler/jni/quick/arm/calling_convention_arm.h @@ -58,9 +58,7 @@ class ArmJniCallingConvention FINAL : public JniCallingConvention { void Next() OVERRIDE; // Override default behavior for AAPCS size_t FrameSize() OVERRIDE; size_t OutArgSize() OVERRIDE; - const std::vector<ManagedRegister>& CalleeSaveRegisters() const OVERRIDE { - return callee_save_regs_; - } + ArrayRef<const ManagedRegister> CalleeSaveRegisters() const OVERRIDE; ManagedRegister ReturnScratchRegister() const OVERRIDE; uint32_t CoreSpillMask() const OVERRIDE; uint32_t FpSpillMask() const OVERRIDE; @@ -78,9 +76,6 @@ class ArmJniCallingConvention FINAL : public JniCallingConvention { size_t NumberOfOutgoingStackArgs() OVERRIDE; private: - // TODO: these values aren't unique and can be shared amongst instances - std::vector<ManagedRegister> callee_save_regs_; - // Padding to ensure longs and doubles are not split in AAPCS size_t padding_; diff --git a/compiler/jni/quick/arm64/calling_convention_arm64.cc b/compiler/jni/quick/arm64/calling_convention_arm64.cc index 9aef10e8f4..ab56c1c04e 100644 --- a/compiler/jni/quick/arm64/calling_convention_arm64.cc +++ b/compiler/jni/quick/arm64/calling_convention_arm64.cc @@ -38,10 +38,65 @@ static const SRegister kSArgumentRegisters[] = { S0, S1, S2, S3, S4, S5, S6, S7 }; -static const DRegister kDCalleeSaveRegisters[] = { - D8, D9, D10, D11, D12, D13, D14, D15 +static constexpr ManagedRegister kCalleeSaveRegisters[] = { + // Core registers. + // Note: The native jni function may call to some VM runtime functions which may suspend + // or trigger GC. And the jni method frame will become top quick frame in those cases. + // So we need to satisfy GC to save LR and callee-save registers which is similar to + // CalleeSaveMethod(RefOnly) frame. + // Jni function is the native function which the java code wants to call. + // Jni method is the method that is compiled by jni compiler. + // Call chain: managed code(java) --> jni method --> jni function. + // Thread register(X19) is saved on stack. + Arm64ManagedRegister::FromXRegister(X19), + Arm64ManagedRegister::FromXRegister(X20), + Arm64ManagedRegister::FromXRegister(X21), + Arm64ManagedRegister::FromXRegister(X22), + Arm64ManagedRegister::FromXRegister(X23), + Arm64ManagedRegister::FromXRegister(X24), + Arm64ManagedRegister::FromXRegister(X25), + Arm64ManagedRegister::FromXRegister(X26), + Arm64ManagedRegister::FromXRegister(X27), + Arm64ManagedRegister::FromXRegister(X28), + Arm64ManagedRegister::FromXRegister(X29), + Arm64ManagedRegister::FromXRegister(LR), + // Hard float registers. + // Considering the case, java_method_1 --> jni method --> jni function --> java_method_2, + // we may break on java_method_2 and we still need to find out the values of DEX registers + // in java_method_1. So all callee-saves(in managed code) need to be saved. + Arm64ManagedRegister::FromDRegister(D8), + Arm64ManagedRegister::FromDRegister(D9), + Arm64ManagedRegister::FromDRegister(D10), + Arm64ManagedRegister::FromDRegister(D11), + Arm64ManagedRegister::FromDRegister(D12), + Arm64ManagedRegister::FromDRegister(D13), + Arm64ManagedRegister::FromDRegister(D14), + Arm64ManagedRegister::FromDRegister(D15), }; +static constexpr uint32_t CalculateCoreCalleeSpillMask() { + uint32_t result = 0u; + for (auto&& r : kCalleeSaveRegisters) { + if (r.AsArm64().IsXRegister()) { + result |= (1 << r.AsArm64().AsXRegister()); + } + } + return result; +} + +static constexpr uint32_t CalculateFpCalleeSpillMask() { + uint32_t result = 0; + for (auto&& r : kCalleeSaveRegisters) { + if (r.AsArm64().IsDRegister()) { + result |= (1 << r.AsArm64().AsDRegister()); + } + } + return result; +} + +static constexpr uint32_t kCoreCalleeSpillMask = CalculateCoreCalleeSpillMask(); +static constexpr uint32_t kFpCalleeSpillMask = CalculateFpCalleeSpillMask(); + // Calling convention ManagedRegister Arm64ManagedRuntimeCallingConvention::InterproceduralScratchRegister() { return Arm64ManagedRegister::FromXRegister(X20); // saved on entry restored on exit @@ -157,47 +212,14 @@ const ManagedRegisterEntrySpills& Arm64ManagedRuntimeCallingConvention::EntrySpi Arm64JniCallingConvention::Arm64JniCallingConvention(bool is_static, bool is_synchronized, const char* shorty) : JniCallingConvention(is_static, is_synchronized, shorty, kFramePointerSize) { - uint32_t core_spill_mask = CoreSpillMask(); - DCHECK_EQ(XZR, kNumberOfXRegisters - 1); // Exclude XZR from the loop (avoid 1 << 32). - for (int x_reg = 0; x_reg < kNumberOfXRegisters - 1; ++x_reg) { - if (((1 << x_reg) & core_spill_mask) != 0) { - callee_save_regs_.push_back( - Arm64ManagedRegister::FromXRegister(static_cast<XRegister>(x_reg))); - } - } - - uint32_t fp_spill_mask = FpSpillMask(); - for (int d_reg = 0; d_reg < kNumberOfDRegisters; ++d_reg) { - if (((1 << d_reg) & fp_spill_mask) != 0) { - callee_save_regs_.push_back( - Arm64ManagedRegister::FromDRegister(static_cast<DRegister>(d_reg))); - } - } } uint32_t Arm64JniCallingConvention::CoreSpillMask() const { - // Compute spill mask to agree with callee saves initialized in the constructor. - // Note: The native jni function may call to some VM runtime functions which may suspend - // or trigger GC. And the jni method frame will become top quick frame in those cases. - // So we need to satisfy GC to save LR and callee-save registers which is similar to - // CalleeSaveMethod(RefOnly) frame. - // Jni function is the native function which the java code wants to call. - // Jni method is the method that compiled by jni compiler. - // Call chain: managed code(java) --> jni method --> jni function. - // Thread register(X19) is saved on stack. - return 1 << X19 | 1 << X20 | 1 << X21 | 1 << X22 | 1 << X23 | 1 << X24 | - 1 << X25 | 1 << X26 | 1 << X27 | 1 << X28 | 1 << X29 | 1 << LR; + return kCoreCalleeSpillMask; } uint32_t Arm64JniCallingConvention::FpSpillMask() const { - // Considering the case, java_method_1 --> jni method --> jni function --> java_method_2, we may - // break on java_method_2 and we still need to find out the values of DEX registers in - // java_method_1. So all callee-saves(in managed code) need to be saved. - uint32_t result = 0; - for (size_t i = 0; i < arraysize(kDCalleeSaveRegisters); ++i) { - result |= (1 << kDCalleeSaveRegisters[i]); - } - return result; + return kFpCalleeSpillMask; } ManagedRegister Arm64JniCallingConvention::ReturnScratchRegister() const { @@ -218,6 +240,10 @@ size_t Arm64JniCallingConvention::OutArgSize() { return RoundUp(NumberOfOutgoingStackArgs() * kFramePointerSize, kStackAlignment); } +ArrayRef<const ManagedRegister> Arm64JniCallingConvention::CalleeSaveRegisters() const { + return ArrayRef<const ManagedRegister>(kCalleeSaveRegisters); +} + bool Arm64JniCallingConvention::IsCurrentParamInRegister() { if (IsCurrentParamAFloatOrDouble()) { return (itr_float_and_doubles_ < 8); diff --git a/compiler/jni/quick/arm64/calling_convention_arm64.h b/compiler/jni/quick/arm64/calling_convention_arm64.h index 37c92b2034..337e881fb7 100644 --- a/compiler/jni/quick/arm64/calling_convention_arm64.h +++ b/compiler/jni/quick/arm64/calling_convention_arm64.h @@ -57,9 +57,7 @@ class Arm64JniCallingConvention FINAL : public JniCallingConvention { // JNI calling convention size_t FrameSize() OVERRIDE; size_t OutArgSize() OVERRIDE; - const std::vector<ManagedRegister>& CalleeSaveRegisters() const OVERRIDE { - return callee_save_regs_; - } + ArrayRef<const ManagedRegister> CalleeSaveRegisters() const OVERRIDE; ManagedRegister ReturnScratchRegister() const OVERRIDE; uint32_t CoreSpillMask() const OVERRIDE; uint32_t FpSpillMask() const OVERRIDE; @@ -77,9 +75,6 @@ class Arm64JniCallingConvention FINAL : public JniCallingConvention { size_t NumberOfOutgoingStackArgs() OVERRIDE; private: - // TODO: these values aren't unique and can be shared amongst instances - std::vector<ManagedRegister> callee_save_regs_; - DISALLOW_COPY_AND_ASSIGN(Arm64JniCallingConvention); }; diff --git a/compiler/jni/quick/calling_convention.h b/compiler/jni/quick/calling_convention.h index 2c4b15ca31..e8f738d4d7 100644 --- a/compiler/jni/quick/calling_convention.h +++ b/compiler/jni/quick/calling_convention.h @@ -17,12 +17,11 @@ #ifndef ART_COMPILER_JNI_QUICK_CALLING_CONVENTION_H_ #define ART_COMPILER_JNI_QUICK_CALLING_CONVENTION_H_ -#include <vector> - #include "base/arena_object.h" #include "handle_scope.h" #include "primitive.h" #include "thread.h" +#include "utils/array_ref.h" #include "utils/managed_register.h" namespace art { @@ -301,7 +300,7 @@ class JniCallingConvention : public CallingConvention { virtual bool RequiresSmallResultTypeExtension() const = 0; // Callee save registers to spill prior to native code (which may clobber) - virtual const std::vector<ManagedRegister>& CalleeSaveRegisters() const = 0; + virtual ArrayRef<const ManagedRegister> CalleeSaveRegisters() const = 0; // Spill mask values virtual uint32_t CoreSpillMask() const = 0; diff --git a/compiler/jni/quick/jni_compiler.cc b/compiler/jni/quick/jni_compiler.cc index 27714b8055..4311a34494 100644 --- a/compiler/jni/quick/jni_compiler.cc +++ b/compiler/jni/quick/jni_compiler.cc @@ -112,7 +112,7 @@ CompiledMethod* ArtJniCompileMethodInternal(CompilerDriver* driver, // 1. Build the frame saving all callee saves const size_t frame_size(main_jni_conv->FrameSize()); - const std::vector<ManagedRegister>& callee_save_regs = main_jni_conv->CalleeSaveRegisters(); + ArrayRef<const ManagedRegister> callee_save_regs = main_jni_conv->CalleeSaveRegisters(); __ BuildFrame(frame_size, mr_conv->MethodRegister(), callee_save_regs, mr_conv->EntrySpills()); DCHECK_EQ(jni_asm->cfi().GetCurrentCFAOffset(), static_cast<int>(frame_size)); diff --git a/compiler/jni/quick/mips/calling_convention_mips.cc b/compiler/jni/quick/mips/calling_convention_mips.cc index 2d31a9881e..3d4d140b73 100644 --- a/compiler/jni/quick/mips/calling_convention_mips.cc +++ b/compiler/jni/quick/mips/calling_convention_mips.cc @@ -27,6 +27,32 @@ static const Register kCoreArgumentRegisters[] = { A0, A1, A2, A3 }; static const FRegister kFArgumentRegisters[] = { F12, F14 }; static const DRegister kDArgumentRegisters[] = { D6, D7 }; +static constexpr ManagedRegister kCalleeSaveRegisters[] = { + // Core registers. + MipsManagedRegister::FromCoreRegister(S2), + MipsManagedRegister::FromCoreRegister(S3), + MipsManagedRegister::FromCoreRegister(S4), + MipsManagedRegister::FromCoreRegister(S5), + MipsManagedRegister::FromCoreRegister(S6), + MipsManagedRegister::FromCoreRegister(S7), + MipsManagedRegister::FromCoreRegister(FP), + // No hard float callee saves. +}; + +static constexpr uint32_t CalculateCoreCalleeSpillMask() { + // RA is a special callee save which is not reported by CalleeSaveRegisters(). + uint32_t result = 1 << RA; + for (auto&& r : kCalleeSaveRegisters) { + if (r.AsMips().IsCoreRegister()) { + result |= (1 << r.AsMips().AsCoreRegister()); + } + } + return result; +} + +static constexpr uint32_t kCoreCalleeSpillMask = CalculateCoreCalleeSpillMask(); +static constexpr uint32_t kFpCalleeSpillMask = 0u; + // Calling convention ManagedRegister MipsManagedRuntimeCallingConvention::InterproceduralScratchRegister() { return MipsManagedRegister::FromCoreRegister(T9); @@ -161,21 +187,14 @@ MipsJniCallingConvention::MipsJniCallingConvention(bool is_static, bool is_synch cur_reg++; // bump the iterator for every argument } padding_ = padding; - - callee_save_regs_.push_back(MipsManagedRegister::FromCoreRegister(S2)); - callee_save_regs_.push_back(MipsManagedRegister::FromCoreRegister(S3)); - callee_save_regs_.push_back(MipsManagedRegister::FromCoreRegister(S4)); - callee_save_regs_.push_back(MipsManagedRegister::FromCoreRegister(S5)); - callee_save_regs_.push_back(MipsManagedRegister::FromCoreRegister(S6)); - callee_save_regs_.push_back(MipsManagedRegister::FromCoreRegister(S7)); - callee_save_regs_.push_back(MipsManagedRegister::FromCoreRegister(FP)); } uint32_t MipsJniCallingConvention::CoreSpillMask() const { - // Compute spill mask to agree with callee saves initialized in the constructor - uint32_t result = 0; - result = 1 << S2 | 1 << S3 | 1 << S4 | 1 << S5 | 1 << S6 | 1 << S7 | 1 << FP | 1 << RA; - return result; + return kCoreCalleeSpillMask; +} + +uint32_t MipsJniCallingConvention::FpSpillMask() const { + return kFpCalleeSpillMask; } ManagedRegister MipsJniCallingConvention::ReturnScratchRegister() const { @@ -196,6 +215,10 @@ size_t MipsJniCallingConvention::OutArgSize() { return RoundUp(NumberOfOutgoingStackArgs() * kFramePointerSize + padding_, kStackAlignment); } +ArrayRef<const ManagedRegister> MipsJniCallingConvention::CalleeSaveRegisters() const { + return ArrayRef<const ManagedRegister>(kCalleeSaveRegisters); +} + // JniCallingConvention ABI follows AAPCS where longs and doubles must occur // in even register numbers and stack slots void MipsJniCallingConvention::Next() { diff --git a/compiler/jni/quick/mips/calling_convention_mips.h b/compiler/jni/quick/mips/calling_convention_mips.h index dc45432410..5c128b0343 100644 --- a/compiler/jni/quick/mips/calling_convention_mips.h +++ b/compiler/jni/quick/mips/calling_convention_mips.h @@ -58,14 +58,10 @@ class MipsJniCallingConvention FINAL : public JniCallingConvention { void Next() OVERRIDE; // Override default behavior for AAPCS size_t FrameSize() OVERRIDE; size_t OutArgSize() OVERRIDE; - const std::vector<ManagedRegister>& CalleeSaveRegisters() const OVERRIDE { - return callee_save_regs_; - } + ArrayRef<const ManagedRegister> CalleeSaveRegisters() const OVERRIDE; ManagedRegister ReturnScratchRegister() const OVERRIDE; uint32_t CoreSpillMask() const OVERRIDE; - uint32_t FpSpillMask() const OVERRIDE { - return 0; // Floats aren't spilled in JNI down call - } + uint32_t FpSpillMask() const OVERRIDE; bool IsCurrentParamInRegister() OVERRIDE; bool IsCurrentParamOnStack() OVERRIDE; ManagedRegister CurrentParamRegister() OVERRIDE; @@ -80,9 +76,6 @@ class MipsJniCallingConvention FINAL : public JniCallingConvention { size_t NumberOfOutgoingStackArgs() OVERRIDE; private: - // TODO: these values aren't unique and can be shared amongst instances - std::vector<ManagedRegister> callee_save_regs_; - // Padding to ensure longs and doubles are not split in AAPCS size_t padding_; diff --git a/compiler/jni/quick/mips64/calling_convention_mips64.cc b/compiler/jni/quick/mips64/calling_convention_mips64.cc index 807d740b4d..f2e1da8254 100644 --- a/compiler/jni/quick/mips64/calling_convention_mips64.cc +++ b/compiler/jni/quick/mips64/calling_convention_mips64.cc @@ -31,6 +31,33 @@ static const FpuRegister kFpuArgumentRegisters[] = { F12, F13, F14, F15, F16, F17, F18, F19 }; +static constexpr ManagedRegister kCalleeSaveRegisters[] = { + // Core registers. + Mips64ManagedRegister::FromGpuRegister(S2), + Mips64ManagedRegister::FromGpuRegister(S3), + Mips64ManagedRegister::FromGpuRegister(S4), + Mips64ManagedRegister::FromGpuRegister(S5), + Mips64ManagedRegister::FromGpuRegister(S6), + Mips64ManagedRegister::FromGpuRegister(S7), + Mips64ManagedRegister::FromGpuRegister(GP), + Mips64ManagedRegister::FromGpuRegister(S8), + // No hard float callee saves. +}; + +static constexpr uint32_t CalculateCoreCalleeSpillMask() { + // RA is a special callee save which is not reported by CalleeSaveRegisters(). + uint32_t result = 1 << RA; + for (auto&& r : kCalleeSaveRegisters) { + if (r.AsMips64().IsGpuRegister()) { + result |= (1 << r.AsMips64().AsGpuRegister()); + } + } + return result; +} + +static constexpr uint32_t kCoreCalleeSpillMask = CalculateCoreCalleeSpillMask(); +static constexpr uint32_t kFpCalleeSpillMask = 0u; + // Calling convention ManagedRegister Mips64ManagedRuntimeCallingConvention::InterproceduralScratchRegister() { return Mips64ManagedRegister::FromGpuRegister(T9); @@ -126,22 +153,14 @@ const ManagedRegisterEntrySpills& Mips64ManagedRuntimeCallingConvention::EntrySp Mips64JniCallingConvention::Mips64JniCallingConvention(bool is_static, bool is_synchronized, const char* shorty) : JniCallingConvention(is_static, is_synchronized, shorty, kFramePointerSize) { - callee_save_regs_.push_back(Mips64ManagedRegister::FromGpuRegister(S2)); - callee_save_regs_.push_back(Mips64ManagedRegister::FromGpuRegister(S3)); - callee_save_regs_.push_back(Mips64ManagedRegister::FromGpuRegister(S4)); - callee_save_regs_.push_back(Mips64ManagedRegister::FromGpuRegister(S5)); - callee_save_regs_.push_back(Mips64ManagedRegister::FromGpuRegister(S6)); - callee_save_regs_.push_back(Mips64ManagedRegister::FromGpuRegister(S7)); - callee_save_regs_.push_back(Mips64ManagedRegister::FromGpuRegister(GP)); - callee_save_regs_.push_back(Mips64ManagedRegister::FromGpuRegister(S8)); } uint32_t Mips64JniCallingConvention::CoreSpillMask() const { - // Compute spill mask to agree with callee saves initialized in the constructor - uint32_t result = 0; - result = 1 << S2 | 1 << S3 | 1 << S4 | 1 << S5 | 1 << S6 | 1 << S7 | 1 << GP | 1 << S8 | 1 << RA; - DCHECK_EQ(static_cast<size_t>(POPCOUNT(result)), callee_save_regs_.size() + 1); - return result; + return kCoreCalleeSpillMask; +} + +uint32_t Mips64JniCallingConvention::FpSpillMask() const { + return kFpCalleeSpillMask; } ManagedRegister Mips64JniCallingConvention::ReturnScratchRegister() const { @@ -162,6 +181,10 @@ size_t Mips64JniCallingConvention::OutArgSize() { return RoundUp(NumberOfOutgoingStackArgs() * kFramePointerSize, kStackAlignment); } +ArrayRef<const ManagedRegister> Mips64JniCallingConvention::CalleeSaveRegisters() const { + return ArrayRef<const ManagedRegister>(kCalleeSaveRegisters); +} + bool Mips64JniCallingConvention::IsCurrentParamInRegister() { return itr_args_ < 8; } diff --git a/compiler/jni/quick/mips64/calling_convention_mips64.h b/compiler/jni/quick/mips64/calling_convention_mips64.h index 3d6aab7399..99ea3cd66c 100644 --- a/compiler/jni/quick/mips64/calling_convention_mips64.h +++ b/compiler/jni/quick/mips64/calling_convention_mips64.h @@ -57,14 +57,10 @@ class Mips64JniCallingConvention FINAL : public JniCallingConvention { // JNI calling convention size_t FrameSize() OVERRIDE; size_t OutArgSize() OVERRIDE; - const std::vector<ManagedRegister>& CalleeSaveRegisters() const OVERRIDE { - return callee_save_regs_; - } + ArrayRef<const ManagedRegister> CalleeSaveRegisters() const OVERRIDE; ManagedRegister ReturnScratchRegister() const OVERRIDE; uint32_t CoreSpillMask() const OVERRIDE; - uint32_t FpSpillMask() const OVERRIDE { - return 0; // Floats aren't spilled in JNI down call - } + uint32_t FpSpillMask() const OVERRIDE; bool IsCurrentParamInRegister() OVERRIDE; bool IsCurrentParamOnStack() OVERRIDE; ManagedRegister CurrentParamRegister() OVERRIDE; @@ -79,9 +75,6 @@ class Mips64JniCallingConvention FINAL : public JniCallingConvention { size_t NumberOfOutgoingStackArgs() OVERRIDE; private: - // TODO: these values aren't unique and can be shared amongst instances - std::vector<ManagedRegister> callee_save_regs_; - DISALLOW_COPY_AND_ASSIGN(Mips64JniCallingConvention); }; diff --git a/compiler/jni/quick/x86/calling_convention_x86.cc b/compiler/jni/quick/x86/calling_convention_x86.cc index 322caca41f..22c7cd04e8 100644 --- a/compiler/jni/quick/x86/calling_convention_x86.cc +++ b/compiler/jni/quick/x86/calling_convention_x86.cc @@ -23,6 +23,28 @@ namespace art { namespace x86 { +static constexpr ManagedRegister kCalleeSaveRegisters[] = { + // Core registers. + X86ManagedRegister::FromCpuRegister(EBP), + X86ManagedRegister::FromCpuRegister(ESI), + X86ManagedRegister::FromCpuRegister(EDI), + // No hard float callee saves. +}; + +static constexpr uint32_t CalculateCoreCalleeSpillMask() { + // The spilled PC gets a special marker. + uint32_t result = 1 << kNumberOfCpuRegisters; + for (auto&& r : kCalleeSaveRegisters) { + if (r.AsX86().IsCpuRegister()) { + result |= (1 << r.AsX86().AsCpuRegister()); + } + } + return result; +} + +static constexpr uint32_t kCoreCalleeSpillMask = CalculateCoreCalleeSpillMask(); +static constexpr uint32_t kFpCalleeSpillMask = 0u; + // Calling convention ManagedRegister X86ManagedRuntimeCallingConvention::InterproceduralScratchRegister() { @@ -169,13 +191,14 @@ const ManagedRegisterEntrySpills& X86ManagedRuntimeCallingConvention::EntrySpill X86JniCallingConvention::X86JniCallingConvention(bool is_static, bool is_synchronized, const char* shorty) : JniCallingConvention(is_static, is_synchronized, shorty, kFramePointerSize) { - callee_save_regs_.push_back(X86ManagedRegister::FromCpuRegister(EBP)); - callee_save_regs_.push_back(X86ManagedRegister::FromCpuRegister(ESI)); - callee_save_regs_.push_back(X86ManagedRegister::FromCpuRegister(EDI)); } uint32_t X86JniCallingConvention::CoreSpillMask() const { - return 1 << EBP | 1 << ESI | 1 << EDI | 1 << kNumberOfCpuRegisters; + return kCoreCalleeSpillMask; +} + +uint32_t X86JniCallingConvention::FpSpillMask() const { + return kFpCalleeSpillMask; } size_t X86JniCallingConvention::FrameSize() { @@ -192,6 +215,10 @@ size_t X86JniCallingConvention::OutArgSize() { return RoundUp(NumberOfOutgoingStackArgs() * kFramePointerSize, kStackAlignment); } +ArrayRef<const ManagedRegister> X86JniCallingConvention::CalleeSaveRegisters() const { + return ArrayRef<const ManagedRegister>(kCalleeSaveRegisters); +} + bool X86JniCallingConvention::IsCurrentParamInRegister() { return false; // Everything is passed by stack. } diff --git a/compiler/jni/quick/x86/calling_convention_x86.h b/compiler/jni/quick/x86/calling_convention_x86.h index cdf0956c9a..9d678b7a63 100644 --- a/compiler/jni/quick/x86/calling_convention_x86.h +++ b/compiler/jni/quick/x86/calling_convention_x86.h @@ -59,14 +59,10 @@ class X86JniCallingConvention FINAL : public JniCallingConvention { // JNI calling convention size_t FrameSize() OVERRIDE; size_t OutArgSize() OVERRIDE; - const std::vector<ManagedRegister>& CalleeSaveRegisters() const OVERRIDE { - return callee_save_regs_; - } + ArrayRef<const ManagedRegister> CalleeSaveRegisters() const OVERRIDE; ManagedRegister ReturnScratchRegister() const OVERRIDE; uint32_t CoreSpillMask() const OVERRIDE; - uint32_t FpSpillMask() const OVERRIDE { - return 0; - } + uint32_t FpSpillMask() const OVERRIDE; bool IsCurrentParamInRegister() OVERRIDE; bool IsCurrentParamOnStack() OVERRIDE; ManagedRegister CurrentParamRegister() OVERRIDE; @@ -81,9 +77,6 @@ class X86JniCallingConvention FINAL : public JniCallingConvention { size_t NumberOfOutgoingStackArgs() OVERRIDE; private: - // TODO: these values aren't unique and can be shared amongst instances - std::vector<ManagedRegister> callee_save_regs_; - DISALLOW_COPY_AND_ASSIGN(X86JniCallingConvention); }; diff --git a/compiler/jni/quick/x86_64/calling_convention_x86_64.cc b/compiler/jni/quick/x86_64/calling_convention_x86_64.cc index b6b11ca51f..cc4d2324ba 100644 --- a/compiler/jni/quick/x86_64/calling_convention_x86_64.cc +++ b/compiler/jni/quick/x86_64/calling_convention_x86_64.cc @@ -24,6 +24,45 @@ namespace art { namespace x86_64 { +static constexpr ManagedRegister kCalleeSaveRegisters[] = { + // Core registers. + X86_64ManagedRegister::FromCpuRegister(RBX), + X86_64ManagedRegister::FromCpuRegister(RBP), + X86_64ManagedRegister::FromCpuRegister(R12), + X86_64ManagedRegister::FromCpuRegister(R13), + X86_64ManagedRegister::FromCpuRegister(R14), + X86_64ManagedRegister::FromCpuRegister(R15), + // Hard float registers. + X86_64ManagedRegister::FromXmmRegister(XMM12), + X86_64ManagedRegister::FromXmmRegister(XMM13), + X86_64ManagedRegister::FromXmmRegister(XMM14), + X86_64ManagedRegister::FromXmmRegister(XMM15), +}; + +static constexpr uint32_t CalculateCoreCalleeSpillMask() { + // The spilled PC gets a special marker. + uint32_t result = 1 << kNumberOfCpuRegisters; + for (auto&& r : kCalleeSaveRegisters) { + if (r.AsX86_64().IsCpuRegister()) { + result |= (1 << r.AsX86_64().AsCpuRegister().AsRegister()); + } + } + return result; +} + +static constexpr uint32_t CalculateFpCalleeSpillMask() { + uint32_t result = 0; + for (auto&& r : kCalleeSaveRegisters) { + if (r.AsX86_64().IsXmmRegister()) { + result |= (1 << r.AsX86_64().AsXmmRegister().AsFloatRegister()); + } + } + return result; +} + +static constexpr uint32_t kCoreCalleeSpillMask = CalculateCoreCalleeSpillMask(); +static constexpr uint32_t kFpCalleeSpillMask = CalculateFpCalleeSpillMask(); + // Calling convention ManagedRegister X86_64ManagedRuntimeCallingConvention::InterproceduralScratchRegister() { @@ -125,25 +164,14 @@ const ManagedRegisterEntrySpills& X86_64ManagedRuntimeCallingConvention::EntrySp X86_64JniCallingConvention::X86_64JniCallingConvention(bool is_static, bool is_synchronized, const char* shorty) : JniCallingConvention(is_static, is_synchronized, shorty, kFramePointerSize) { - callee_save_regs_.push_back(X86_64ManagedRegister::FromCpuRegister(RBX)); - callee_save_regs_.push_back(X86_64ManagedRegister::FromCpuRegister(RBP)); - callee_save_regs_.push_back(X86_64ManagedRegister::FromCpuRegister(R12)); - callee_save_regs_.push_back(X86_64ManagedRegister::FromCpuRegister(R13)); - callee_save_regs_.push_back(X86_64ManagedRegister::FromCpuRegister(R14)); - callee_save_regs_.push_back(X86_64ManagedRegister::FromCpuRegister(R15)); - callee_save_regs_.push_back(X86_64ManagedRegister::FromXmmRegister(XMM12)); - callee_save_regs_.push_back(X86_64ManagedRegister::FromXmmRegister(XMM13)); - callee_save_regs_.push_back(X86_64ManagedRegister::FromXmmRegister(XMM14)); - callee_save_regs_.push_back(X86_64ManagedRegister::FromXmmRegister(XMM15)); } uint32_t X86_64JniCallingConvention::CoreSpillMask() const { - return 1 << RBX | 1 << RBP | 1 << R12 | 1 << R13 | 1 << R14 | 1 << R15 | - 1 << kNumberOfCpuRegisters; + return kCoreCalleeSpillMask; } uint32_t X86_64JniCallingConvention::FpSpillMask() const { - return 1 << XMM12 | 1 << XMM13 | 1 << XMM14 | 1 << XMM15; + return kFpCalleeSpillMask; } size_t X86_64JniCallingConvention::FrameSize() { @@ -160,6 +188,10 @@ size_t X86_64JniCallingConvention::OutArgSize() { return RoundUp(NumberOfOutgoingStackArgs() * kFramePointerSize, kStackAlignment); } +ArrayRef<const ManagedRegister> X86_64JniCallingConvention::CalleeSaveRegisters() const { + return ArrayRef<const ManagedRegister>(kCalleeSaveRegisters); +} + bool X86_64JniCallingConvention::IsCurrentParamInRegister() { return !IsCurrentParamOnStack(); } diff --git a/compiler/jni/quick/x86_64/calling_convention_x86_64.h b/compiler/jni/quick/x86_64/calling_convention_x86_64.h index 6e47c9fae3..e2d3d48bc0 100644 --- a/compiler/jni/quick/x86_64/calling_convention_x86_64.h +++ b/compiler/jni/quick/x86_64/calling_convention_x86_64.h @@ -55,9 +55,7 @@ class X86_64JniCallingConvention FINAL : public JniCallingConvention { // JNI calling convention size_t FrameSize() OVERRIDE; size_t OutArgSize() OVERRIDE; - const std::vector<ManagedRegister>& CalleeSaveRegisters() const OVERRIDE { - return callee_save_regs_; - } + ArrayRef<const ManagedRegister> CalleeSaveRegisters() const OVERRIDE; ManagedRegister ReturnScratchRegister() const OVERRIDE; uint32_t CoreSpillMask() const OVERRIDE; uint32_t FpSpillMask() const OVERRIDE; @@ -75,9 +73,6 @@ class X86_64JniCallingConvention FINAL : public JniCallingConvention { size_t NumberOfOutgoingStackArgs() OVERRIDE; private: - // TODO: these values aren't unique and can be shared amongst instances - std::vector<ManagedRegister> callee_save_regs_; - DISALLOW_COPY_AND_ASSIGN(X86_64JniCallingConvention); }; diff --git a/compiler/linker/arm64/relative_patcher_arm64.cc b/compiler/linker/arm64/relative_patcher_arm64.cc index b4ecbd8c50..72771079be 100644 --- a/compiler/linker/arm64/relative_patcher_arm64.cc +++ b/compiler/linker/arm64/relative_patcher_arm64.cc @@ -210,7 +210,14 @@ void Arm64RelativePatcher::PatchPcRelativeReference(std::vector<uint8_t>* code, } else { if ((insn & 0xfffffc00) == 0x91000000) { // ADD immediate, 64-bit with imm12 == 0 (unset). - DCHECK(patch.GetType() == LinkerPatch::Type::kStringRelative) << patch.GetType(); + if (!kEmitCompilerReadBarrier) { + DCHECK(patch.GetType() == LinkerPatch::Type::kStringRelative) << patch.GetType(); + } else { + // With the read barrier (non-baker) enabled, it could be kDexCacheArray in the + // HLoadString::LoadKind::kDexCachePcRelative case of VisitLoadString(). + DCHECK(patch.GetType() == LinkerPatch::Type::kStringRelative || + patch.GetType() == LinkerPatch::Type::kDexCacheArray) << patch.GetType(); + } shift = 0u; // No shift for ADD. } else { // LDR 32-bit or 64-bit with imm12 == 0 (unset). diff --git a/compiler/linker/relative_patcher_test.h b/compiler/linker/relative_patcher_test.h index c07de79984..ec69107d8f 100644 --- a/compiler/linker/relative_patcher_test.h +++ b/compiler/linker/relative_patcher_test.h @@ -51,6 +51,7 @@ class RelativePatcherTest : public testing::Test { instruction_set, /* instruction_set_features*/ nullptr, /* boot_image */ false, + /* app_image */ false, /* image_classes */ nullptr, /* compiled_classes */ nullptr, /* compiled_methods */ nullptr, diff --git a/compiler/oat_test.cc b/compiler/oat_test.cc index 73b16d5b46..6d1f94491e 100644 --- a/compiler/oat_test.cc +++ b/compiler/oat_test.cc @@ -112,6 +112,7 @@ class OatTest : public CommonCompilerTest { insn_set, insn_features_.get(), /* boot_image */ false, + /* app_image */ false, /* image_classes */ nullptr, /* compiled_classes */ nullptr, /* compiled_methods */ nullptr, @@ -198,7 +199,7 @@ class OatTest : public CommonCompilerTest { for (const std::unique_ptr<const DexFile>& dex_file : opened_dex_files) { dex_files.push_back(dex_file.get()); ScopedObjectAccess soa(Thread::Current()); - class_linker->RegisterDexFile(*dex_file, runtime->GetLinearAlloc()); + class_linker->RegisterDexFile(*dex_file, nullptr); } linker::MultiOatRelativePatcher patcher(compiler_driver_->GetInstructionSet(), instruction_set_features_.get()); @@ -447,23 +448,23 @@ TEST_F(OatTest, OatHeaderSizeCheck) { } TEST_F(OatTest, OatHeaderIsValid) { - InstructionSet insn_set = kX86; - std::string error_msg; - std::unique_ptr<const InstructionSetFeatures> insn_features( - InstructionSetFeatures::FromVariant(insn_set, "default", &error_msg)); - ASSERT_TRUE(insn_features.get() != nullptr) << error_msg; - std::unique_ptr<OatHeader> oat_header(OatHeader::Create(insn_set, - insn_features.get(), - 0u, - nullptr)); - ASSERT_NE(oat_header.get(), nullptr); - ASSERT_TRUE(oat_header->IsValid()); - - char* magic = const_cast<char*>(oat_header->GetMagic()); - strcpy(magic, ""); // bad magic - ASSERT_FALSE(oat_header->IsValid()); - strcpy(magic, "oat\n000"); // bad version - ASSERT_FALSE(oat_header->IsValid()); + InstructionSet insn_set = kX86; + std::string error_msg; + std::unique_ptr<const InstructionSetFeatures> insn_features( + InstructionSetFeatures::FromVariant(insn_set, "default", &error_msg)); + ASSERT_TRUE(insn_features.get() != nullptr) << error_msg; + std::unique_ptr<OatHeader> oat_header(OatHeader::Create(insn_set, + insn_features.get(), + 0u, + nullptr)); + ASSERT_NE(oat_header.get(), nullptr); + ASSERT_TRUE(oat_header->IsValid()); + + char* magic = const_cast<char*>(oat_header->GetMagic()); + strcpy(magic, ""); // bad magic + ASSERT_FALSE(oat_header->IsValid()); + strcpy(magic, "oat\n000"); // bad version + ASSERT_FALSE(oat_header->IsValid()); } TEST_F(OatTest, EmptyTextSection) { @@ -490,10 +491,7 @@ TEST_F(OatTest, EmptyTextSection) { ClassLinker* const class_linker = Runtime::Current()->GetClassLinker(); for (const DexFile* dex_file : dex_files) { ScopedObjectAccess soa(Thread::Current()); - class_linker->RegisterDexFile( - *dex_file, - class_linker->GetOrCreateAllocatorForClassLoader( - soa.Decode<mirror::ClassLoader*>(class_loader))); + class_linker->RegisterDexFile(*dex_file, soa.Decode<mirror::ClassLoader*>(class_loader)); } compiler_driver_->SetDexFilesForOatFile(dex_files); compiler_driver_->CompileAll(class_loader, dex_files, &timings); @@ -765,4 +763,28 @@ TEST_F(OatTest, ZipFileInputCheckVerifier) { TestZipFileInput(true); } +TEST_F(OatTest, UpdateChecksum) { + InstructionSet insn_set = kX86; + std::string error_msg; + std::unique_ptr<const InstructionSetFeatures> insn_features( + InstructionSetFeatures::FromVariant(insn_set, "default", &error_msg)); + ASSERT_TRUE(insn_features.get() != nullptr) << error_msg; + std::unique_ptr<OatHeader> oat_header(OatHeader::Create(insn_set, + insn_features.get(), + 0u, + nullptr)); + // The starting adler32 value is 1. + EXPECT_EQ(1U, oat_header->GetChecksum()); + + oat_header->UpdateChecksum(OatHeader::kOatMagic, sizeof(OatHeader::kOatMagic)); + EXPECT_EQ(64291151U, oat_header->GetChecksum()); + + // Make sure that null data does not reset the checksum. + oat_header->UpdateChecksum(nullptr, 0); + EXPECT_EQ(64291151U, oat_header->GetChecksum()); + + oat_header->UpdateChecksum(OatHeader::kOatMagic, sizeof(OatHeader::kOatMagic)); + EXPECT_EQ(216138397U, oat_header->GetChecksum()); +} + } // namespace art diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc index e804beef0d..42320028e5 100644 --- a/compiler/oat_writer.cc +++ b/compiler/oat_writer.cc @@ -63,6 +63,29 @@ const UnalignedDexFileHeader* AsUnalignedDexFileHeader(const uint8_t* raw_data) return reinterpret_cast<const UnalignedDexFileHeader*>(raw_data); } +class ChecksumUpdatingOutputStream : public OutputStream { + public: + ChecksumUpdatingOutputStream(OutputStream* out, OatHeader* oat_header) + : OutputStream(out->GetLocation()), out_(out), oat_header_(oat_header) { } + + bool WriteFully(const void* buffer, size_t byte_count) OVERRIDE { + oat_header_->UpdateChecksum(buffer, byte_count); + return out_->WriteFully(buffer, byte_count); + } + + off_t Seek(off_t offset, Whence whence) OVERRIDE { + return out_->Seek(offset, whence); + } + + bool Flush() OVERRIDE { + return out_->Flush(); + } + + private: + OutputStream* const out_; + OatHeader* const oat_header_; +}; + } // anonymous namespace // Defines the location of the raw dex file to write. @@ -422,13 +445,21 @@ bool OatWriter::WriteAndOpenDexFiles( for (OatDexFile& oat_dex_file : oat_dex_files_) { oat_dex_file.ReserveClassOffsets(this); } - if (!WriteOatDexFiles(rodata) || + ChecksumUpdatingOutputStream checksum_updating_rodata(rodata, oat_header_.get()); + if (!WriteOatDexFiles(&checksum_updating_rodata) || !ExtendForTypeLookupTables(rodata, file, size_after_type_lookup_tables) || !OpenDexFiles(file, verify, &dex_files_map, &dex_files) || !WriteTypeLookupTables(dex_files_map.get(), dex_files)) { return false; } + // Do a bulk checksum update for Dex[] and TypeLookupTable[]. Doing it piece by + // piece would be difficult because we're not using the OutpuStream directly. + if (!oat_dex_files_.empty()) { + size_t size = size_after_type_lookup_tables - oat_dex_files_[0].dex_file_offset_; + oat_header_->UpdateChecksum(dex_files_map->Begin(), size); + } + *opened_dex_files_map = std::move(dex_files_map); *opened_dex_files = std::move(dex_files); write_state_ = WriteState::kPrepareLayout; @@ -996,7 +1027,7 @@ class OatWriter::WriteCodeMethodVisitor : public OatDexMethodVisitor { << PrettyMethod(it.GetMemberIndex(), *dex_file_); const OatQuickMethodHeader& method_header = oat_class->method_headers_[method_offsets_index_]; - if (!writer_->WriteData(out, &method_header, sizeof(method_header))) { + if (!out->WriteFully(&method_header, sizeof(method_header))) { ReportWriteFailure("method header", it); return false; } @@ -1063,7 +1094,7 @@ class OatWriter::WriteCodeMethodVisitor : public OatDexMethodVisitor { } } - if (!writer_->WriteData(out, quick_code.data(), code_size)) { + if (!out->WriteFully(quick_code.data(), code_size)) { ReportWriteFailure("method code", it); return false; } @@ -1127,17 +1158,23 @@ class OatWriter::WriteCodeMethodVisitor : public OatDexMethodVisitor { return target_offset; } - mirror::Class* GetTargetType(const LinkerPatch& patch) SHARED_REQUIRES(Locks::mutator_lock_) { - mirror::DexCache* dex_cache = (dex_file_ == patch.TargetTypeDexFile()) + mirror::DexCache* GetDexCache(const DexFile* target_dex_file) + SHARED_REQUIRES(Locks::mutator_lock_) { + return (target_dex_file == dex_file_) ? dex_cache_ - : class_linker_->FindDexCache(Thread::Current(), *patch.TargetTypeDexFile()); + : class_linker_->FindDexCache(Thread::Current(), *target_dex_file); + } + + mirror::Class* GetTargetType(const LinkerPatch& patch) SHARED_REQUIRES(Locks::mutator_lock_) { + mirror::DexCache* dex_cache = GetDexCache(patch.TargetTypeDexFile()); mirror::Class* type = dex_cache->GetResolvedType(patch.TargetTypeIndex()); CHECK(type != nullptr); return type; } mirror::String* GetTargetString(const LinkerPatch& patch) SHARED_REQUIRES(Locks::mutator_lock_) { - mirror::String* string = dex_cache_->GetResolvedString(patch.TargetStringIndex()); + mirror::DexCache* dex_cache = GetDexCache(patch.TargetStringDexFile()); + mirror::String* string = dex_cache->GetResolvedString(patch.TargetStringIndex()); DCHECK(string != nullptr); DCHECK(writer_->HasBootImage() || Runtime::Current()->GetHeap()->ObjectIsInBootImageSpace(string)); @@ -1273,7 +1310,7 @@ class OatWriter::WriteMapMethodVisitor : public OatDexMethodVisitor { size_t map_size = map.size() * sizeof(map[0]); if (map_offset == offset_) { // Write deduplicated map (code info for Optimizing or transformation info for dex2dex). - if (UNLIKELY(!writer_->WriteData(out, map.data(), map_size))) { + if (UNLIKELY(!out->WriteFully(map.data(), map_size))) { ReportWriteFailure(it); return false; } @@ -1407,8 +1444,8 @@ size_t OatWriter::InitOatCode(size_t offset) { offset = CompiledCode::AlignCode(offset, instruction_set); \ adjusted_offset = offset + CompiledCode::CodeDelta(instruction_set); \ oat_header_->Set ## fn_name ## Offset(adjusted_offset); \ - field = compiler_driver_->Create ## fn_name(); \ - offset += field->size(); + (field) = compiler_driver_->Create ## fn_name(); \ + offset += (field)->size(); DO_TRAMPOLINE(jni_dlsym_lookup_, JniDlsymLookup); DO_TRAMPOLINE(quick_generic_jni_trampoline_, QuickGenericJniTrampoline); @@ -1451,6 +1488,10 @@ size_t OatWriter::InitOatCodeDexFiles(size_t offset) { bool OatWriter::WriteRodata(OutputStream* out) { CHECK(write_state_ == WriteState::kWriteRoData); + // Wrap out to update checksum with each write. + ChecksumUpdatingOutputStream checksum_updating_out(out, oat_header_.get()); + out = &checksum_updating_out; + if (!WriteClassOffsets(out)) { LOG(ERROR) << "Failed to write class offsets to " << out->GetLocation(); return false; @@ -1493,6 +1534,10 @@ bool OatWriter::WriteRodata(OutputStream* out) { bool OatWriter::WriteCode(OutputStream* out) { CHECK(write_state_ == WriteState::kWriteText); + // Wrap out to update checksum with each write. + ChecksumUpdatingOutputStream checksum_updating_out(out, oat_header_.get()); + out = &checksum_updating_out; + SetMultiOatRelativePatcherAdjustment(); const size_t file_offset = oat_data_offset_; @@ -1520,8 +1565,8 @@ bool OatWriter::WriteCode(OutputStream* out) { if (kIsDebugBuild) { uint32_t size_total = 0; #define DO_STAT(x) \ - VLOG(compiler) << #x "=" << PrettySize(x) << " (" << x << "B)"; \ - size_total += x; + VLOG(compiler) << #x "=" << PrettySize(x) << " (" << (x) << "B)"; \ + size_total += (x); DO_STAT(size_dex_file_alignment_); DO_STAT(size_executable_offset_alignment_); @@ -1677,12 +1722,12 @@ size_t OatWriter::WriteCode(OutputStream* out, const size_t file_offset, size_t uint32_t alignment_padding = aligned_offset - relative_offset; \ out->Seek(alignment_padding, kSeekCurrent); \ size_trampoline_alignment_ += alignment_padding; \ - if (!WriteData(out, field->data(), field->size())) { \ + if (!out->WriteFully((field)->data(), (field)->size())) { \ PLOG(ERROR) << "Failed to write " # field " to " << out->GetLocation(); \ return false; \ } \ - size_ ## field += field->size(); \ - relative_offset += alignment_padding + field->size(); \ + size_ ## field += (field)->size(); \ + relative_offset += alignment_padding + (field)->size(); \ DCHECK_OFFSET(); \ } while (false) @@ -2194,11 +2239,6 @@ bool OatWriter::WriteCodeAlignment(OutputStream* out, uint32_t aligned_code_delt return true; } -bool OatWriter::WriteData(OutputStream* out, const void* data, size_t size) { - oat_header_->UpdateChecksum(data, size); - return out->WriteFully(data, size); -} - void OatWriter::SetMultiOatRelativePatcherAdjustment() { DCHECK(dex_files_ != nullptr); DCHECK(relative_patcher_ != nullptr); @@ -2268,39 +2308,37 @@ bool OatWriter::OatDexFile::Write(OatWriter* oat_writer, OutputStream* out) cons const size_t file_offset = oat_writer->oat_data_offset_; DCHECK_OFFSET_(); - if (!oat_writer->WriteData(out, &dex_file_location_size_, sizeof(dex_file_location_size_))) { + if (!out->WriteFully(&dex_file_location_size_, sizeof(dex_file_location_size_))) { PLOG(ERROR) << "Failed to write dex file location length to " << out->GetLocation(); return false; } oat_writer->size_oat_dex_file_location_size_ += sizeof(dex_file_location_size_); - if (!oat_writer->WriteData(out, dex_file_location_data_, dex_file_location_size_)) { + if (!out->WriteFully(dex_file_location_data_, dex_file_location_size_)) { PLOG(ERROR) << "Failed to write dex file location data to " << out->GetLocation(); return false; } oat_writer->size_oat_dex_file_location_data_ += dex_file_location_size_; - if (!oat_writer->WriteData(out, - &dex_file_location_checksum_, - sizeof(dex_file_location_checksum_))) { + if (!out->WriteFully(&dex_file_location_checksum_, sizeof(dex_file_location_checksum_))) { PLOG(ERROR) << "Failed to write dex file location checksum to " << out->GetLocation(); return false; } oat_writer->size_oat_dex_file_location_checksum_ += sizeof(dex_file_location_checksum_); - if (!oat_writer->WriteData(out, &dex_file_offset_, sizeof(dex_file_offset_))) { + if (!out->WriteFully(&dex_file_offset_, sizeof(dex_file_offset_))) { PLOG(ERROR) << "Failed to write dex file offset to " << out->GetLocation(); return false; } oat_writer->size_oat_dex_file_offset_ += sizeof(dex_file_offset_); - if (!oat_writer->WriteData(out, &class_offsets_offset_, sizeof(class_offsets_offset_))) { + if (!out->WriteFully(&class_offsets_offset_, sizeof(class_offsets_offset_))) { PLOG(ERROR) << "Failed to write class offsets offset to " << out->GetLocation(); return false; } oat_writer->size_oat_dex_file_class_offsets_offset_ += sizeof(class_offsets_offset_); - if (!oat_writer->WriteData(out, &lookup_table_offset_, sizeof(lookup_table_offset_))) { + if (!out->WriteFully(&lookup_table_offset_, sizeof(lookup_table_offset_))) { PLOG(ERROR) << "Failed to write lookup table offset to " << out->GetLocation(); return false; } @@ -2310,7 +2348,7 @@ bool OatWriter::OatDexFile::Write(OatWriter* oat_writer, OutputStream* out) cons } bool OatWriter::OatDexFile::WriteClassOffsets(OatWriter* oat_writer, OutputStream* out) { - if (!oat_writer->WriteData(out, class_offsets_.data(), GetClassOffsetsRawSize())) { + if (!out->WriteFully(class_offsets_.data(), GetClassOffsetsRawSize())) { PLOG(ERROR) << "Failed to write oat class offsets for " << GetLocation() << " to " << out->GetLocation(); return false; @@ -2399,13 +2437,13 @@ bool OatWriter::OatClass::Write(OatWriter* oat_writer, OutputStream* out, const size_t file_offset) const { DCHECK_OFFSET_(); - if (!oat_writer->WriteData(out, &status_, sizeof(status_))) { + if (!out->WriteFully(&status_, sizeof(status_))) { PLOG(ERROR) << "Failed to write class status to " << out->GetLocation(); return false; } oat_writer->size_oat_class_status_ += sizeof(status_); - if (!oat_writer->WriteData(out, &type_, sizeof(type_))) { + if (!out->WriteFully(&type_, sizeof(type_))) { PLOG(ERROR) << "Failed to write oat class type to " << out->GetLocation(); return false; } @@ -2413,20 +2451,20 @@ bool OatWriter::OatClass::Write(OatWriter* oat_writer, if (method_bitmap_size_ != 0) { CHECK_EQ(kOatClassSomeCompiled, type_); - if (!oat_writer->WriteData(out, &method_bitmap_size_, sizeof(method_bitmap_size_))) { + if (!out->WriteFully(&method_bitmap_size_, sizeof(method_bitmap_size_))) { PLOG(ERROR) << "Failed to write method bitmap size to " << out->GetLocation(); return false; } oat_writer->size_oat_class_method_bitmaps_ += sizeof(method_bitmap_size_); - if (!oat_writer->WriteData(out, method_bitmap_->GetRawStorage(), method_bitmap_size_)) { + if (!out->WriteFully(method_bitmap_->GetRawStorage(), method_bitmap_size_)) { PLOG(ERROR) << "Failed to write method bitmap to " << out->GetLocation(); return false; } oat_writer->size_oat_class_method_bitmaps_ += method_bitmap_size_; } - if (!oat_writer->WriteData(out, method_offsets_.data(), GetMethodOffsetsRawSize())) { + if (!out->WriteFully(method_offsets_.data(), GetMethodOffsetsRawSize())) { PLOG(ERROR) << "Failed to write method offsets to " << out->GetLocation(); return false; } diff --git a/compiler/oat_writer.h b/compiler/oat_writer.h index 3862798329..cc81f39f36 100644 --- a/compiler/oat_writer.h +++ b/compiler/oat_writer.h @@ -271,7 +271,6 @@ class OatWriter { bool WriteTypeLookupTables(MemMap* opened_dex_files_map, const std::vector<std::unique_ptr<const DexFile>>& opened_dex_files); bool WriteCodeAlignment(OutputStream* out, uint32_t aligned_code_delta); - bool WriteData(OutputStream* out, const void* data, size_t size); void SetMultiOatRelativePatcherAdjustment(); enum class WriteState { diff --git a/compiler/optimizing/bounds_check_elimination.cc b/compiler/optimizing/bounds_check_elimination.cc index 084360f22b..e9fcfe2bed 100644 --- a/compiler/optimizing/bounds_check_elimination.cc +++ b/compiler/optimizing/bounds_check_elimination.cc @@ -552,7 +552,11 @@ class BCEVisitor : public HGraphVisitor { DCHECK(!IsAddedBlock(block)); first_index_bounds_check_map_.clear(); HGraphVisitor::VisitBasicBlock(block); - AddComparesWithDeoptimization(block); + // We should never deoptimize from an osr method, otherwise we might wrongly optimize + // code dominated by the deoptimization. + if (!GetGraph()->IsCompilingOsr()) { + AddComparesWithDeoptimization(block); + } } void Finish() { @@ -796,6 +800,27 @@ class BCEVisitor : public HGraphVisitor { ValueRange(GetGraph()->GetArena(), ValueBound::Min(), new_upper); ApplyRangeFromComparison(left, block, false_successor, new_range); } + } else if (cond == kCondNE || cond == kCondEQ) { + if (left->IsArrayLength() && lower.IsConstant() && upper.IsConstant()) { + // Special case: + // length == [c,d] yields [c, d] along true + // length != [c,d] yields [c, d] along false + if (!lower.Equals(ValueBound::Min()) || !upper.Equals(ValueBound::Max())) { + ValueRange* new_range = new (GetGraph()->GetArena()) + ValueRange(GetGraph()->GetArena(), lower, upper); + ApplyRangeFromComparison( + left, block, cond == kCondEQ ? true_successor : false_successor, new_range); + } + // In addition: + // length == 0 yields [1, max] along false + // length != 0 yields [1, max] along true + if (lower.GetConstant() == 0 && upper.GetConstant() == 0) { + ValueRange* new_range = new (GetGraph()->GetArena()) + ValueRange(GetGraph()->GetArena(), ValueBound(nullptr, 1), ValueBound::Max()); + ApplyRangeFromComparison( + left, block, cond == kCondEQ ? false_successor : true_successor, new_range); + } + } } } @@ -887,14 +912,15 @@ class BCEVisitor : public HGraphVisitor { static bool HasSameInputAtBackEdges(HPhi* phi) { DCHECK(phi->IsLoopHeaderPhi()); + auto&& inputs = phi->GetInputs(); // Start with input 1. Input 0 is from the incoming block. - HInstruction* input1 = phi->InputAt(1); + HInstruction* input1 = inputs[1]; DCHECK(phi->GetBlock()->GetLoopInformation()->IsBackEdge( *phi->GetBlock()->GetPredecessors()[1])); - for (size_t i = 2, e = phi->InputCount(); i < e; ++i) { + for (size_t i = 2; i < inputs.size(); ++i) { DCHECK(phi->GetBlock()->GetLoopInformation()->IsBackEdge( *phi->GetBlock()->GetPredecessors()[i])); - if (input1 != phi->InputAt(i)) { + if (input1 != inputs[i]) { return false; } } @@ -951,13 +977,7 @@ class BCEVisitor : public HGraphVisitor { void VisitIf(HIf* instruction) OVERRIDE { if (instruction->InputAt(0)->IsCondition()) { HCondition* cond = instruction->InputAt(0)->AsCondition(); - IfCondition cmp = cond->GetCondition(); - if (cmp == kCondGT || cmp == kCondGE || - cmp == kCondLT || cmp == kCondLE) { - HInstruction* left = cond->GetLeft(); - HInstruction* right = cond->GetRight(); - HandleIf(instruction, left, right, cmp); - } + HandleIf(instruction, cond->GetLeft(), cond->GetRight(), cond->GetCondition()); } } @@ -1150,7 +1170,11 @@ class BCEVisitor : public HGraphVisitor { loop->IsDefinedOutOfTheLoop(array_get->InputAt(1))) { SideEffects loop_effects = side_effects_.GetLoopEffects(loop->GetHeader()); if (!array_get->GetSideEffects().MayDependOn(loop_effects)) { - HoistToPreHeaderOrDeoptBlock(loop, array_get); + // We can hoist ArrayGet only if its execution is guaranteed on every iteration. + // In other words only if array_get_bb dominates all back branches. + if (loop->DominatesAllBackEdges(array_get->GetBlock())) { + HoistToPreHeaderOrDeoptBlock(loop, array_get); + } } } } @@ -1206,9 +1230,9 @@ class BCEVisitor : public HGraphVisitor { GetGraph()->GetArena()->Adapter(kArenaAllocBoundsCheckElimination)); ArenaVector<HBoundsCheck*> standby( GetGraph()->GetArena()->Adapter(kArenaAllocBoundsCheckElimination)); - for (HUseIterator<HInstruction*> it2(array_length->GetUses()); !it2.Done(); it2.Advance()) { + for (const HUseListNode<HInstruction*>& use : array_length->GetUses()) { // Another bounds check in same or dominated block? - HInstruction* user = it2.Current()->GetUser(); + HInstruction* user = use.GetUser(); HBasicBlock* other_block = user->GetBlock(); if (user->IsBoundsCheck() && block->Dominates(other_block)) { HBoundsCheck* other_bounds_check = user->AsBoundsCheck(); @@ -1358,6 +1382,11 @@ class BCEVisitor : public HGraphVisitor { if (loop->IsIrreducible()) { return false; } + // We should never deoptimize from an osr method, otherwise we might wrongly optimize + // code dominated by the deoptimization. + if (GetGraph()->IsCompilingOsr()) { + return false; + } // A try boundary preheader is hard to handle. // TODO: remove this restriction. if (loop->GetPreHeader()->GetLastInstruction()->IsTryBoundary()) { @@ -1370,13 +1399,7 @@ class BCEVisitor : public HGraphVisitor { } // Does the current basic block dominate all back edges? If not, // don't apply dynamic bce to something that may not be executed. - for (HBasicBlock* back_edge : loop->GetBackEdges()) { - if (!block->Dominates(back_edge)) { - return false; - } - } - // Success! - return true; + return loop->DominatesAllBackEdges(block); } return false; } @@ -1635,29 +1658,33 @@ class BCEVisitor : public HGraphVisitor { Primitive::Type type = instruction->GetType(); HPhi* phi = nullptr; // Scan all uses of an instruction and replace each later use with a phi node. - for (HUseIterator<HInstruction*> it2(instruction->GetUses()); - !it2.Done(); - it2.Advance()) { - HInstruction* user = it2.Current()->GetUser(); + const HUseList<HInstruction*>& uses = instruction->GetUses(); + for (auto it2 = uses.begin(), end2 = uses.end(); it2 != end2; /* ++it2 below */) { + HInstruction* user = it2->GetUser(); + size_t index = it2->GetIndex(); + // Increment `it2` now because `*it2` may disappear thanks to user->ReplaceInput(). + ++it2; if (user->GetBlock() != true_block) { if (phi == nullptr) { phi = NewPhi(new_preheader, instruction, type); } - user->ReplaceInput(phi, it2.Current()->GetIndex()); + user->ReplaceInput(phi, index); // Removes the use node from the list. } } // Scan all environment uses of an instruction and replace each later use with a phi node. - for (HUseIterator<HEnvironment*> it2(instruction->GetEnvUses()); - !it2.Done(); - it2.Advance()) { - HEnvironment* user = it2.Current()->GetUser(); + const HUseList<HEnvironment*>& env_uses = instruction->GetEnvUses(); + for (auto it2 = env_uses.begin(), end2 = env_uses.end(); it2 != end2; /* ++it2 below */) { + HEnvironment* user = it2->GetUser(); + size_t index = it2->GetIndex(); + // Increment `it2` now because `*it2` may disappear thanks to user->RemoveAsUserOfInput(). + ++it2; if (user->GetHolder()->GetBlock() != true_block) { if (phi == nullptr) { phi = NewPhi(new_preheader, instruction, type); } - user->RemoveAsUserOfInput(it2.Current()->GetIndex()); - user->SetRawEnvAt(it2.Current()->GetIndex(), phi); - phi->AddEnvUseAt(user, it2.Current()->GetIndex()); + user->RemoveAsUserOfInput(index); + user->SetRawEnvAt(index, phi); + phi->AddEnvUseAt(user, index); } } } diff --git a/compiler/optimizing/builder.h b/compiler/optimizing/builder.h index 4f46d5edda..580ef72767 100644 --- a/compiler/optimizing/builder.h +++ b/compiler/optimizing/builder.h @@ -51,7 +51,7 @@ class HGraphBuilder : public ValueObject { compiler_driver_(driver), compilation_stats_(compiler_stats), block_builder_(graph, dex_file, code_item), - ssa_builder_(graph, handles), + ssa_builder_(graph, dex_compilation_unit->GetDexCache(), handles), instruction_builder_(graph, &block_builder_, &ssa_builder_, @@ -78,7 +78,7 @@ class HGraphBuilder : public ValueObject { null_dex_cache_(), compilation_stats_(nullptr), block_builder_(graph, nullptr, code_item), - ssa_builder_(graph, handles), + ssa_builder_(graph, null_dex_cache_, handles), instruction_builder_(graph, &block_builder_, &ssa_builder_, diff --git a/compiler/optimizing/code_generator.cc b/compiler/optimizing/code_generator.cc index a771cc1567..6e851bf1ba 100644 --- a/compiler/optimizing/code_generator.cc +++ b/compiler/optimizing/code_generator.cc @@ -50,6 +50,7 @@ #include "mirror/array-inl.h" #include "mirror/object_array-inl.h" #include "mirror/object_reference.h" +#include "mirror/string.h" #include "parallel_move_resolver.h" #include "ssa_liveness_analysis.h" #include "utils/assembler.h" @@ -110,10 +111,10 @@ static bool CheckTypeConsistency(HInstruction* instruction) { << " " << locations->Out(); } - for (size_t i = 0, e = instruction->InputCount(); i < e; ++i) { - DCHECK(CheckType(instruction->InputAt(i)->GetType(), locations->InAt(i))) - << instruction->InputAt(i)->GetType() - << " " << locations->InAt(i); + auto&& inputs = instruction->GetInputs(); + for (size_t i = 0; i < inputs.size(); ++i) { + DCHECK(CheckType(inputs[i]->GetType(), locations->InAt(i))) + << inputs[i]->GetType() << " " << locations->InAt(i); } HEnvironment* environment = instruction->GetEnvironment(); @@ -139,6 +140,12 @@ size_t CodeGenerator::GetCachePointerOffset(uint32_t index) { return pointer_size * index; } +uint32_t CodeGenerator::GetArrayLengthOffset(HArrayLength* array_length) { + return array_length->IsStringLength() + ? mirror::String::CountOffset().Uint32Value() + : mirror::Array::LengthOffset().Uint32Value(); +} + bool CodeGenerator::GoesToNextBlock(HBasicBlock* current, HBasicBlock* next) const { DCHECK_EQ((*block_order_)[current_block_index_], current); return GetNextBlockToEmit() == FirstNonEmptyBlock(next); @@ -187,7 +194,8 @@ class DisassemblyScope { void CodeGenerator::GenerateSlowPaths() { size_t code_start = 0; - for (SlowPathCode* slow_path : slow_paths_) { + for (const std::unique_ptr<SlowPathCode>& slow_path_unique_ptr : slow_paths_) { + SlowPathCode* slow_path = slow_path_unique_ptr.get(); current_slow_path_ = slow_path; if (disasm_info_ != nullptr) { code_start = GetAssembler()->CodeSize(); @@ -1297,4 +1305,18 @@ void CodeGenerator::CreateSystemArrayCopyLocationSummary(HInvoke* invoke) { locations->AddTemp(Location::RequiresRegister()); } +uint32_t CodeGenerator::GetReferenceSlowFlagOffset() const { + ScopedObjectAccess soa(Thread::Current()); + mirror::Class* klass = mirror::Reference::GetJavaLangRefReference(); + DCHECK(klass->IsInitialized()); + return klass->GetSlowPathFlagOffset().Uint32Value(); +} + +uint32_t CodeGenerator::GetReferenceDisableFlagOffset() const { + ScopedObjectAccess soa(Thread::Current()); + mirror::Class* klass = mirror::Reference::GetJavaLangRefReference(); + DCHECK(klass->IsInitialized()); + return klass->GetDisableIntrinsicFlagOffset().Uint32Value(); +} + } // namespace art diff --git a/compiler/optimizing/code_generator.h b/compiler/optimizing/code_generator.h index 87832a2d9f..82a54d2ed1 100644 --- a/compiler/optimizing/code_generator.h +++ b/compiler/optimizing/code_generator.h @@ -67,7 +67,7 @@ class CodeAllocator { DISALLOW_COPY_AND_ASSIGN(CodeAllocator); }; -class SlowPathCode : public ArenaObject<kArenaAllocSlowPaths> { +class SlowPathCode : public DeletableArenaObject<kArenaAllocSlowPaths> { public: explicit SlowPathCode(HInstruction* instruction) : instruction_(instruction) { for (size_t i = 0; i < kMaximumNumberOfExpectedRegisters; ++i) { @@ -205,7 +205,7 @@ class CodeGenerator : public DeletableArenaObject<kArenaAllocCodeGenerator> { virtual const Assembler& GetAssembler() const = 0; virtual size_t GetWordSize() const = 0; virtual size_t GetFloatingPointSpillSlotSize() const = 0; - virtual uintptr_t GetAddressOf(HBasicBlock* block) const = 0; + virtual uintptr_t GetAddressOf(HBasicBlock* block) = 0; void InitializeCodeGeneration(size_t number_of_spill_slots, size_t maximum_number_of_live_core_registers, size_t maximum_number_of_live_fpu_registers, @@ -298,8 +298,9 @@ class CodeGenerator : public DeletableArenaObject<kArenaAllocCodeGenerator> { // save live registers, which may be needed by the runtime to set catch phis. bool IsImplicitNullCheckAllowed(HNullCheck* null_check) const; + // TODO: Avoid creating the `std::unique_ptr` here. void AddSlowPath(SlowPathCode* slow_path) { - slow_paths_.push_back(slow_path); + slow_paths_.push_back(std::unique_ptr<SlowPathCode>(slow_path)); } void BuildStackMaps(MemoryRegion region, const DexFile::CodeItem& code_item); @@ -339,6 +340,11 @@ class CodeGenerator : public DeletableArenaObject<kArenaAllocCodeGenerator> { // Pointer variant for ArtMethod and ArtField arrays. size_t GetCachePointerOffset(uint32_t index); + // Helper that returns the offset of the array's length field. + // Note: Besides the normal arrays, we also use the HArrayLength for + // accessing the String's `count` field in String intrinsics. + static uint32_t GetArrayLengthOffset(HArrayLength* array_length); + void EmitParallelMoves(Location from1, Location to1, Primitive::Type type1, @@ -463,6 +469,9 @@ class CodeGenerator : public DeletableArenaObject<kArenaAllocCodeGenerator> { virtual void GenerateNop() = 0; + uint32_t GetReferenceSlowFlagOffset() const; + uint32_t GetReferenceDisableFlagOffset() const; + protected: // Method patch info used for recording locations of required linker patches and // target methods. The target method can be used for various purposes, whether for @@ -617,7 +626,7 @@ class CodeGenerator : public DeletableArenaObject<kArenaAllocCodeGenerator> { HGraph* const graph_; const CompilerOptions& compiler_options_; - ArenaVector<SlowPathCode*> slow_paths_; + ArenaVector<std::unique_ptr<SlowPathCode>> slow_paths_; // The current slow-path that we're generating code for. SlowPathCode* current_slow_path_; diff --git a/compiler/optimizing/code_generator_arm.cc b/compiler/optimizing/code_generator_arm.cc index 45d23fe516..6e74d082e0 100644 --- a/compiler/optimizing/code_generator_arm.cc +++ b/compiler/optimizing/code_generator_arm.cc @@ -59,7 +59,8 @@ static constexpr DRegister DTMP = D31; static constexpr uint32_t kPackedSwitchCompareJumpThreshold = 7; -#define __ down_cast<ArmAssembler*>(codegen->GetAssembler())-> +// NOLINT on __ macro to suppress wrong warning/fix from clang-tidy. +#define __ down_cast<ArmAssembler*>(codegen->GetAssembler())-> // NOLINT #define QUICK_ENTRY_POINT(x) QUICK_ENTRYPOINT_OFFSET(kArmWordSize, x).Int32Value() class NullCheckSlowPathARM : public SlowPathCode { @@ -674,7 +675,8 @@ class ReadBarrierForRootSlowPathARM : public SlowPathCode { }; #undef __ -#define __ down_cast<ArmAssembler*>(GetAssembler())-> +// NOLINT on __ macro to suppress wrong warning/fix from clang-tidy. +#define __ down_cast<ArmAssembler*>(GetAssembler())-> // NOLINT inline Condition ARMCondition(IfCondition cond) { switch (cond) { @@ -3695,7 +3697,7 @@ void InstructionCodeGeneratorARM::VisitCompare(HCompare* compare) { void LocationsBuilderARM::VisitPhi(HPhi* instruction) { LocationSummary* locations = new (GetGraph()->GetArena()) LocationSummary(instruction, LocationSummary::kNoCall); - for (size_t i = 0, e = instruction->InputCount(); i < e; ++i) { + for (size_t i = 0, e = locations->GetInputCount(); i < e; ++i) { locations->SetInAt(i, Location::Any()); } locations->SetOut(Location::Any()); @@ -4742,7 +4744,7 @@ void LocationsBuilderARM::VisitArrayLength(HArrayLength* instruction) { void InstructionCodeGeneratorARM::VisitArrayLength(HArrayLength* instruction) { LocationSummary* locations = instruction->GetLocations(); - uint32_t offset = mirror::Array::LengthOffset().Uint32Value(); + uint32_t offset = CodeGenerator::GetArrayLengthOffset(instruction); Register obj = locations->InAt(0).AsRegister<Register>(); Register out = locations->Out().AsRegister<Register>(); __ LoadFromOffset(kLoadWord, out, obj, offset); @@ -5183,10 +5185,10 @@ HLoadString::LoadKind CodeGeneratorARM::GetSupportedLoadStringKind( case HLoadString::LoadKind::kBootImageAddress: break; case HLoadString::LoadKind::kDexCacheAddress: - DCHECK(Runtime::Current()->UseJit()); + DCHECK(Runtime::Current()->UseJitCompilation()); break; case HLoadString::LoadKind::kDexCachePcRelative: - DCHECK(!Runtime::Current()->UseJit()); + DCHECK(!Runtime::Current()->UseJitCompilation()); // We disable pc-relative load when there is an irreducible loop, as the optimization // is incompatible with it. // TODO: Create as many ArmDexCacheArraysBase instructions as needed for methods diff --git a/compiler/optimizing/code_generator_arm.h b/compiler/optimizing/code_generator_arm.h index 144d58d85a..0020f7b4f4 100644 --- a/compiler/optimizing/code_generator_arm.h +++ b/compiler/optimizing/code_generator_arm.h @@ -339,7 +339,7 @@ class CodeGeneratorARM : public CodeGenerator { return assembler_; } - uintptr_t GetAddressOf(HBasicBlock* block) const OVERRIDE { + uintptr_t GetAddressOf(HBasicBlock* block) OVERRIDE { return GetLabelOf(block)->Position(); } diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc index efe4c06d3f..5560ae2c74 100644 --- a/compiler/optimizing/code_generator_arm64.cc +++ b/compiler/optimizing/code_generator_arm64.cc @@ -132,7 +132,8 @@ Location InvokeRuntimeCallingConvention::GetReturnLocation(Primitive::Type retur return ARM64ReturnLocation(return_type); } -#define __ down_cast<CodeGeneratorARM64*>(codegen)->GetVIXLAssembler()-> +// NOLINT on __ macro to suppress wrong warning/fix from clang-tidy. +#define __ down_cast<CodeGeneratorARM64*>(codegen)->GetVIXLAssembler()-> // NOLINT #define QUICK_ENTRY_POINT(x) QUICK_ENTRYPOINT_OFFSET(kArm64WordSize, x).Int32Value() // Calculate memory accessing operand for save/restore live registers. @@ -899,7 +900,7 @@ CodeGeneratorARM64::CodeGeneratorARM64(HGraph* graph, callee_saved_fp_registers.list(), compiler_options, stats), - block_labels_(nullptr), + block_labels_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)), jump_tables_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)), location_builder_(graph, this), instruction_visitor_(graph, this), @@ -928,7 +929,7 @@ CodeGeneratorARM64::CodeGeneratorARM64(HGraph* graph, #define __ GetVIXLAssembler()-> void CodeGeneratorARM64::EmitJumpTables() { - for (auto jump_table : jump_tables_) { + for (auto&& jump_table : jump_tables_) { jump_table->EmitTable(this); } } @@ -2118,9 +2119,9 @@ void LocationsBuilderARM64::VisitArrayLength(HArrayLength* instruction) { } void InstructionCodeGeneratorARM64::VisitArrayLength(HArrayLength* instruction) { + uint32_t offset = CodeGenerator::GetArrayLengthOffset(instruction); BlockPoolsScope block_pools(GetVIXLAssembler()); - __ Ldr(OutputRegister(instruction), - HeapOperand(InputRegisterAt(instruction, 0), mirror::Array::LengthOffset())); + __ Ldr(OutputRegister(instruction), HeapOperand(InputRegisterAt(instruction, 0), offset)); codegen_->MaybeRecordImplicitNullCheck(instruction); } @@ -4010,10 +4011,10 @@ HLoadString::LoadKind CodeGeneratorARM64::GetSupportedLoadStringKind( case HLoadString::LoadKind::kBootImageAddress: break; case HLoadString::LoadKind::kDexCacheAddress: - DCHECK(Runtime::Current()->UseJit()); + DCHECK(Runtime::Current()->UseJitCompilation()); break; case HLoadString::LoadKind::kDexCachePcRelative: - DCHECK(!Runtime::Current()->UseJit()); + DCHECK(!Runtime::Current()->UseJitCompilation()); break; case HLoadString::LoadKind::kDexCacheViaMethod: break; @@ -4399,7 +4400,7 @@ void InstructionCodeGeneratorARM64::VisitCurrentMethod( void LocationsBuilderARM64::VisitPhi(HPhi* instruction) { LocationSummary* locations = new (GetGraph()->GetArena()) LocationSummary(instruction); - for (size_t i = 0, e = instruction->InputCount(); i < e; ++i) { + for (size_t i = 0, e = locations->GetInputCount(); i < e; ++i) { locations->SetInAt(i, Location::Any()); } locations->SetOut(Location::Any()); @@ -4784,8 +4785,7 @@ void InstructionCodeGeneratorARM64::VisitPackedSwitch(HPackedSwitch* switch_inst __ B(codegen_->GetLabelOf(default_block)); } } else { - JumpTableARM64* jump_table = new (GetGraph()->GetArena()) JumpTableARM64(switch_instr); - codegen_->AddJumpTable(jump_table); + JumpTableARM64* jump_table = codegen_->CreateJumpTable(switch_instr); UseScratchRegisterScope temps(codegen_->GetVIXLAssembler()); diff --git a/compiler/optimizing/code_generator_arm64.h b/compiler/optimizing/code_generator_arm64.h index ec46a34615..422963e7d0 100644 --- a/compiler/optimizing/code_generator_arm64.h +++ b/compiler/optimizing/code_generator_arm64.h @@ -83,7 +83,7 @@ class SlowPathCodeARM64 : public SlowPathCode { DISALLOW_COPY_AND_ASSIGN(SlowPathCodeARM64); }; -class JumpTableARM64 : public ArenaObject<kArenaAllocSwitchTable> { +class JumpTableARM64 : public DeletableArenaObject<kArenaAllocSwitchTable> { public: explicit JumpTableARM64(HPackedSwitch* switch_instr) : switch_instr_(switch_instr), table_start_() {} @@ -352,8 +352,9 @@ class CodeGeneratorARM64 : public CodeGenerator { void Bind(HBasicBlock* block) OVERRIDE; - vixl::Label* GetLabelOf(HBasicBlock* block) const { - return CommonGetLabelOf<vixl::Label>(block_labels_, block); + vixl::Label* GetLabelOf(HBasicBlock* block) { + block = FirstNonEmptyBlock(block); + return &(block_labels_[block->GetBlockId()]); } size_t GetWordSize() const OVERRIDE { @@ -365,7 +366,7 @@ class CodeGeneratorARM64 : public CodeGenerator { return kArm64WordSize; } - uintptr_t GetAddressOf(HBasicBlock* block) const OVERRIDE { + uintptr_t GetAddressOf(HBasicBlock* block) OVERRIDE { vixl::Label* block_entry_label = GetLabelOf(block); DCHECK(block_entry_label->IsBound()); return block_entry_label->location(); @@ -413,11 +414,12 @@ class CodeGeneratorARM64 : public CodeGenerator { } void Initialize() OVERRIDE { - block_labels_ = CommonInitializeLabels<vixl::Label>(); + block_labels_.resize(GetGraph()->GetBlocks().size()); } - void AddJumpTable(JumpTableARM64* jump_table) { - jump_tables_.push_back(jump_table); + JumpTableARM64* CreateJumpTable(HPackedSwitch* switch_instr) { + jump_tables_.emplace_back(new (GetGraph()->GetArena()) JumpTableARM64(switch_instr)); + return jump_tables_.back().get(); } void Finalize(CodeAllocator* allocator) OVERRIDE; @@ -616,9 +618,10 @@ class CodeGeneratorARM64 : public CodeGenerator { void EmitJumpTables(); // Labels for each block that will be compiled. - vixl::Label* block_labels_; // Indexed by block id. + // We use a deque so that the `vixl::Label` objects do not move in memory. + ArenaDeque<vixl::Label> block_labels_; // Indexed by block id. vixl::Label frame_entry_label_; - ArenaVector<JumpTableARM64*> jump_tables_; + ArenaVector<std::unique_ptr<JumpTableARM64>> jump_tables_; LocationsBuilderARM64 location_builder_; InstructionCodeGeneratorARM64 instruction_visitor_; diff --git a/compiler/optimizing/code_generator_mips.cc b/compiler/optimizing/code_generator_mips.cc index abc6b1309d..ed0767ed52 100644 --- a/compiler/optimizing/code_generator_mips.cc +++ b/compiler/optimizing/code_generator_mips.cc @@ -141,7 +141,8 @@ Location InvokeRuntimeCallingConvention::GetReturnLocation(Primitive::Type type) return MipsReturnLocation(type); } -#define __ down_cast<CodeGeneratorMIPS*>(codegen)->GetAssembler()-> +// NOLINT on __ macro to suppress wrong warning/fix from clang-tidy. +#define __ down_cast<CodeGeneratorMIPS*>(codegen)->GetAssembler()-> // NOLINT #define QUICK_ENTRY_POINT(x) QUICK_ENTRYPOINT_OFFSET(kMipsWordSize, x).Int32Value() class BoundsCheckSlowPathMIPS : public SlowPathCodeMIPS { @@ -478,7 +479,8 @@ CodeGeneratorMIPS::CodeGeneratorMIPS(HGraph* graph, } #undef __ -#define __ down_cast<MipsAssembler*>(GetAssembler())-> +// NOLINT on __ macro to suppress wrong warning/fix from clang-tidy. +#define __ down_cast<MipsAssembler*>(GetAssembler())-> // NOLINT #define QUICK_ENTRY_POINT(x) QUICK_ENTRYPOINT_OFFSET(kMipsWordSize, x).Int32Value() void CodeGeneratorMIPS::Finalize(CodeAllocator* allocator) { @@ -1771,7 +1773,7 @@ void LocationsBuilderMIPS::VisitArrayLength(HArrayLength* instruction) { void InstructionCodeGeneratorMIPS::VisitArrayLength(HArrayLength* instruction) { LocationSummary* locations = instruction->GetLocations(); - uint32_t offset = mirror::Array::LengthOffset().Uint32Value(); + uint32_t offset = CodeGenerator::GetArrayLengthOffset(instruction); Register obj = locations->InAt(0).AsRegister<Register>(); Register out = locations->Out().AsRegister<Register>(); __ LoadFromOffset(kLoadWord, out, obj, offset); @@ -4405,7 +4407,7 @@ void InstructionCodeGeneratorMIPS::VisitCurrentMethod(HCurrentMethod* instructio void LocationsBuilderMIPS::VisitPhi(HPhi* instruction) { LocationSummary* locations = new (GetGraph()->GetArena()) LocationSummary(instruction); - for (size_t i = 0, e = instruction->InputCount(); i < e; ++i) { + for (size_t i = 0, e = locations->GetInputCount(); i < e; ++i) { locations->SetInAt(i, Location::Any()); } locations->SetOut(Location::Any()); diff --git a/compiler/optimizing/code_generator_mips.h b/compiler/optimizing/code_generator_mips.h index 46fe3016c4..8c0bae628e 100644 --- a/compiler/optimizing/code_generator_mips.h +++ b/compiler/optimizing/code_generator_mips.h @@ -276,7 +276,7 @@ class CodeGeneratorMIPS : public CodeGenerator { size_t GetFloatingPointSpillSlotSize() const OVERRIDE { return kMipsDoublewordSize; } - uintptr_t GetAddressOf(HBasicBlock* block) const OVERRIDE { + uintptr_t GetAddressOf(HBasicBlock* block) OVERRIDE { return assembler_.GetLabelLocation(GetLabelOf(block)); } diff --git a/compiler/optimizing/code_generator_mips64.cc b/compiler/optimizing/code_generator_mips64.cc index 56ac38ef84..8c73e350f6 100644 --- a/compiler/optimizing/code_generator_mips64.cc +++ b/compiler/optimizing/code_generator_mips64.cc @@ -102,7 +102,8 @@ Location InvokeRuntimeCallingConvention::GetReturnLocation(Primitive::Type type) return Mips64ReturnLocation(type); } -#define __ down_cast<CodeGeneratorMIPS64*>(codegen)->GetAssembler()-> +// NOLINT on __ macro to suppress wrong warning/fix from clang-tidy. +#define __ down_cast<CodeGeneratorMIPS64*>(codegen)->GetAssembler()-> // NOLINT #define QUICK_ENTRY_POINT(x) QUICK_ENTRYPOINT_OFFSET(kMips64DoublewordSize, x).Int32Value() class BoundsCheckSlowPathMIPS64 : public SlowPathCodeMIPS64 { @@ -424,7 +425,8 @@ CodeGeneratorMIPS64::CodeGeneratorMIPS64(HGraph* graph, } #undef __ -#define __ down_cast<Mips64Assembler*>(GetAssembler())-> +// NOLINT on __ macro to suppress wrong warning/fix from clang-tidy. +#define __ down_cast<Mips64Assembler*>(GetAssembler())-> // NOLINT #define QUICK_ENTRY_POINT(x) QUICK_ENTRYPOINT_OFFSET(kMips64DoublewordSize, x).Int32Value() void CodeGeneratorMIPS64::Finalize(CodeAllocator* allocator) { @@ -1426,7 +1428,7 @@ void LocationsBuilderMIPS64::VisitArrayLength(HArrayLength* instruction) { void InstructionCodeGeneratorMIPS64::VisitArrayLength(HArrayLength* instruction) { LocationSummary* locations = instruction->GetLocations(); - uint32_t offset = mirror::Array::LengthOffset().Uint32Value(); + uint32_t offset = CodeGenerator::GetArrayLengthOffset(instruction); GpuRegister obj = locations->InAt(0).AsRegister<GpuRegister>(); GpuRegister out = locations->Out().AsRegister<GpuRegister>(); __ LoadFromOffset(kLoadWord, out, obj, offset); @@ -3592,7 +3594,7 @@ void InstructionCodeGeneratorMIPS64::VisitCurrentMethod(HCurrentMethod* instruct void LocationsBuilderMIPS64::VisitPhi(HPhi* instruction) { LocationSummary* locations = new (GetGraph()->GetArena()) LocationSummary(instruction); - for (size_t i = 0, e = instruction->InputCount(); i < e; ++i) { + for (size_t i = 0, e = locations->GetInputCount(); i < e; ++i) { locations->SetInAt(i, Location::Any()); } locations->SetOut(Location::Any()); diff --git a/compiler/optimizing/code_generator_mips64.h b/compiler/optimizing/code_generator_mips64.h index 4e15cdd7b5..9785a2e8a8 100644 --- a/compiler/optimizing/code_generator_mips64.h +++ b/compiler/optimizing/code_generator_mips64.h @@ -271,7 +271,7 @@ class CodeGeneratorMIPS64 : public CodeGenerator { size_t GetFloatingPointSpillSlotSize() const OVERRIDE { return kMips64DoublewordSize; } - uintptr_t GetAddressOf(HBasicBlock* block) const OVERRIDE { + uintptr_t GetAddressOf(HBasicBlock* block) OVERRIDE { return assembler_.GetLabelLocation(GetLabelOf(block)); } diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc index 1a4e62eb25..8c643a05c8 100644 --- a/compiler/optimizing/code_generator_x86.cc +++ b/compiler/optimizing/code_generator_x86.cc @@ -47,7 +47,8 @@ static constexpr int kC2ConditionMask = 0x400; static constexpr int kFakeReturnRegister = Register(8); -#define __ down_cast<X86Assembler*>(codegen->GetAssembler())-> +// NOLINT on __ macro to suppress wrong warning/fix from clang-tidy. +#define __ down_cast<X86Assembler*>(codegen->GetAssembler())-> // NOLINT #define QUICK_ENTRY_POINT(x) QUICK_ENTRYPOINT_OFFSET(kX86WordSize, x).Int32Value() class NullCheckSlowPathX86 : public SlowPathCode { @@ -691,7 +692,8 @@ class ReadBarrierForRootSlowPathX86 : public SlowPathCode { }; #undef __ -#define __ down_cast<X86Assembler*>(GetAssembler())-> +// NOLINT on __ macro to suppress wrong warning/fix from clang-tidy. +#define __ down_cast<X86Assembler*>(GetAssembler())-> /* NOLINT */ inline Condition X86Condition(IfCondition cond) { switch (cond) { @@ -3303,17 +3305,6 @@ void InstructionCodeGeneratorX86::GenerateDivRemWithAnyConstant(HBinaryOperation int shift; CalculateMagicAndShiftForDivRem(imm, false /* is_long */, &magic, &shift); - NearLabel ndiv; - NearLabel end; - // If numerator is 0, the result is 0, no computation needed. - __ testl(eax, eax); - __ j(kNotEqual, &ndiv); - - __ xorl(out, out); - __ jmp(&end); - - __ Bind(&ndiv); - // Save the numerator. __ movl(num, eax); @@ -3348,7 +3339,6 @@ void InstructionCodeGeneratorX86::GenerateDivRemWithAnyConstant(HBinaryOperation } else { __ movl(eax, edx); } - __ Bind(&end); } void InstructionCodeGeneratorX86::GenerateDivRemIntegral(HBinaryOperation* instruction) { @@ -4239,7 +4229,7 @@ void InstructionCodeGeneratorX86::VisitCompare(HCompare* compare) { void LocationsBuilderX86::VisitPhi(HPhi* instruction) { LocationSummary* locations = new (GetGraph()->GetArena()) LocationSummary(instruction, LocationSummary::kNoCall); - for (size_t i = 0, e = instruction->InputCount(); i < e; ++i) { + for (size_t i = 0, e = locations->GetInputCount(); i < e; ++i) { locations->SetInAt(i, Location::Any()); } locations->SetOut(Location::Any()); @@ -4266,8 +4256,10 @@ void CodeGeneratorX86::GenerateMemoryBarrier(MemBarrierKind kind) { // nop break; } - default: - LOG(FATAL) << "Unexpected memory barrier " << kind; + case MemBarrierKind::kNTStoreStore: + // Non-Temporal Store/Store needs an explicit fence. + MemoryFence(/* non-temporal */ true); + break; } } @@ -4318,16 +4310,18 @@ Register CodeGeneratorX86::GetInvokeStaticOrDirectExtraParameter(HInvokeStaticOr // save one load. However, since this is just an intrinsic slow path we prefer this // simple and more robust approach rather that trying to determine if that's the case. SlowPathCode* slow_path = GetCurrentSlowPath(); - DCHECK(slow_path != nullptr); // For intrinsified invokes the call is emitted on the slow path. - if (slow_path->IsCoreRegisterSaved(location.AsRegister<Register>())) { - int stack_offset = slow_path->GetStackOffsetOfCoreRegister(location.AsRegister<Register>()); - __ movl(temp, Address(ESP, stack_offset)); - return temp; + if (slow_path != nullptr) { + if (slow_path->IsCoreRegisterSaved(location.AsRegister<Register>())) { + int stack_offset = slow_path->GetStackOffsetOfCoreRegister(location.AsRegister<Register>()); + __ movl(temp, Address(ESP, stack_offset)); + return temp; + } } return location.AsRegister<Register>(); } -void CodeGeneratorX86::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) { +Location CodeGeneratorX86::GenerateCalleeMethodStaticOrDirectCall(HInvokeStaticOrDirect* invoke, + Location temp) { Location callee_method = temp; // For all kinds except kRecursive, callee will be in temp. switch (invoke->GetMethodLoadKind()) { case HInvokeStaticOrDirect::MethodLoadKind::kStringInit: @@ -4376,6 +4370,11 @@ void CodeGeneratorX86::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, break; } } + return callee_method; +} + +void CodeGeneratorX86::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) { + Location callee_method = GenerateCalleeMethodStaticOrDirectCall(invoke, temp); switch (invoke->GetCodePtrLocation()) { case HInvokeStaticOrDirect::CodePtrLocation::kCallSelf: @@ -5490,7 +5489,7 @@ void LocationsBuilderX86::VisitArrayLength(HArrayLength* instruction) { void InstructionCodeGeneratorX86::VisitArrayLength(HArrayLength* instruction) { LocationSummary* locations = instruction->GetLocations(); - uint32_t offset = mirror::Array::LengthOffset().Uint32Value(); + uint32_t offset = CodeGenerator::GetArrayLengthOffset(instruction); Register obj = locations->InAt(0).AsRegister<Register>(); Register out = locations->Out().AsRegister<Register>(); __ movl(out, Address(obj, offset)); @@ -5975,7 +5974,7 @@ HLoadString::LoadKind CodeGeneratorX86::GetSupportedLoadStringKind( DCHECK(GetCompilerOptions().GetCompilePic()); FALLTHROUGH_INTENDED; case HLoadString::LoadKind::kDexCachePcRelative: - DCHECK(!Runtime::Current()->UseJit()); // Note: boot image is also non-JIT. + DCHECK(!Runtime::Current()->UseJitCompilation()); // Note: boot image is also non-JIT. // We disable pc-relative load when there is an irreducible loop, as the optimization // is incompatible with it. // TODO: Create as many X86ComputeBaseMethodAddress instructions as needed for methods @@ -5987,7 +5986,7 @@ HLoadString::LoadKind CodeGeneratorX86::GetSupportedLoadStringKind( case HLoadString::LoadKind::kBootImageAddress: break; case HLoadString::LoadKind::kDexCacheAddress: - DCHECK(Runtime::Current()->UseJit()); + DCHECK(Runtime::Current()->UseJitCompilation()); break; case HLoadString::LoadKind::kDexCacheViaMethod: break; diff --git a/compiler/optimizing/code_generator_x86.h b/compiler/optimizing/code_generator_x86.h index 69a625306f..98dc8ca280 100644 --- a/compiler/optimizing/code_generator_x86.h +++ b/compiler/optimizing/code_generator_x86.h @@ -361,7 +361,7 @@ class CodeGeneratorX86 : public CodeGenerator { return assembler_; } - uintptr_t GetAddressOf(HBasicBlock* block) const OVERRIDE { + uintptr_t GetAddressOf(HBasicBlock* block) OVERRIDE { return GetLabelOf(block)->Position(); } @@ -398,6 +398,7 @@ class CodeGeneratorX86 : public CodeGenerator { MethodReference target_method) OVERRIDE; // Generate a call to a static or direct method. + Location GenerateCalleeMethodStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp); void GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) OVERRIDE; // Generate a call to a virtual method. void GenerateVirtualCall(HInvokeVirtual* invoke, Location temp) OVERRIDE; @@ -538,7 +539,7 @@ class CodeGeneratorX86 : public CodeGenerator { // touch (but not change) the top of the stack. // The 'non_temporal' parameter should be used to ensure ordering of non-temporal stores. void MemoryFence(bool non_temporal = false) { - if (!non_temporal && isa_features_.PrefersLockedAddSynchronization()) { + if (!non_temporal) { assembler_.lock()->addl(Address(ESP, 0), Immediate(0)); } else { assembler_.mfence(); diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc index 59cc4444f7..72de3e6e35 100644 --- a/compiler/optimizing/code_generator_x86_64.cc +++ b/compiler/optimizing/code_generator_x86_64.cc @@ -51,7 +51,8 @@ static constexpr FloatRegister kFpuCalleeSaves[] = { XMM12, XMM13, XMM14, XMM15 static constexpr int kC2ConditionMask = 0x400; -#define __ down_cast<X86_64Assembler*>(codegen->GetAssembler())-> +// NOLINT on __ macro to suppress wrong warning/fix from clang-tidy. +#define __ down_cast<X86_64Assembler*>(codegen->GetAssembler())-> // NOLINT #define QUICK_ENTRY_POINT(x) QUICK_ENTRYPOINT_OFFSET(kX86_64WordSize, x).Int32Value() class NullCheckSlowPathX86_64 : public SlowPathCode { @@ -710,7 +711,8 @@ class ReadBarrierForRootSlowPathX86_64 : public SlowPathCode { }; #undef __ -#define __ down_cast<X86_64Assembler*>(GetAssembler())-> +// NOLINT on __ macro to suppress wrong warning/fix from clang-tidy. +#define __ down_cast<X86_64Assembler*>(GetAssembler())-> // NOLINT inline Condition X86_64IntegerCondition(IfCondition cond) { switch (cond) { @@ -762,10 +764,9 @@ HInvokeStaticOrDirect::DispatchInfo CodeGeneratorX86_64::GetSupportedInvokeStati } } -void CodeGeneratorX86_64::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, - Location temp) { +Location CodeGeneratorX86_64::GenerateCalleeMethodStaticOrDirectCall(HInvokeStaticOrDirect* invoke, + Location temp) { // All registers are assumed to be correctly set up. - Location callee_method = temp; // For all kinds except kRecursive, callee will be in temp. switch (invoke->GetMethodLoadKind()) { case HInvokeStaticOrDirect::MethodLoadKind::kStringInit: @@ -815,6 +816,13 @@ void CodeGeneratorX86_64::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invo break; } } + return callee_method; +} + +void CodeGeneratorX86_64::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, + Location temp) { + // All registers are assumed to be correctly set up. + Location callee_method = GenerateCalleeMethodStaticOrDirectCall(invoke, temp); switch (invoke->GetCodePtrLocation()) { case HInvokeStaticOrDirect::CodePtrLocation::kCallSelf: @@ -3390,16 +3398,6 @@ void InstructionCodeGeneratorX86_64::GenerateDivRemWithAnyConstant(HBinaryOperat __ movl(numerator, eax); - NearLabel no_div; - NearLabel end; - __ testl(eax, eax); - __ j(kNotEqual, &no_div); - - __ xorl(out, out); - __ jmp(&end); - - __ Bind(&no_div); - __ movl(eax, Immediate(magic)); __ imull(numerator); @@ -3425,7 +3423,6 @@ void InstructionCodeGeneratorX86_64::GenerateDivRemWithAnyConstant(HBinaryOperat } else { __ movl(eax, edx); } - __ Bind(&end); } else { int64_t imm = second.GetConstant()->AsLongConstant()->GetValue(); @@ -4032,7 +4029,7 @@ void InstructionCodeGeneratorX86_64::VisitBooleanNot(HBooleanNot* bool_not) { void LocationsBuilderX86_64::VisitPhi(HPhi* instruction) { LocationSummary* locations = new (GetGraph()->GetArena()) LocationSummary(instruction, LocationSummary::kNoCall); - for (size_t i = 0, e = instruction->InputCount(); i < e; ++i) { + for (size_t i = 0, e = locations->GetInputCount(); i < e; ++i) { locations->SetInAt(i, Location::Any()); } locations->SetOut(Location::Any()); @@ -4059,8 +4056,10 @@ void CodeGeneratorX86_64::GenerateMemoryBarrier(MemBarrierKind kind) { // nop break; } - default: - LOG(FATAL) << "Unexpected memory barier " << kind; + case MemBarrierKind::kNTStoreStore: + // Non-Temporal Store/Store needs an explicit fence. + MemoryFence(/* non-temporal */ true); + break; } } @@ -4965,7 +4964,7 @@ void LocationsBuilderX86_64::VisitArrayLength(HArrayLength* instruction) { void InstructionCodeGeneratorX86_64::VisitArrayLength(HArrayLength* instruction) { LocationSummary* locations = instruction->GetLocations(); - uint32_t offset = mirror::Array::LengthOffset().Uint32Value(); + uint32_t offset = CodeGenerator::GetArrayLengthOffset(instruction); CpuRegister obj = locations->InAt(0).AsRegister<CpuRegister>(); CpuRegister out = locations->Out().AsRegister<CpuRegister>(); __ movl(out, Address(obj, offset)); @@ -5411,10 +5410,10 @@ HLoadString::LoadKind CodeGeneratorX86_64::GetSupportedLoadStringKind( case HLoadString::LoadKind::kBootImageAddress: break; case HLoadString::LoadKind::kDexCacheAddress: - DCHECK(Runtime::Current()->UseJit()); + DCHECK(Runtime::Current()->UseJitCompilation()); break; case HLoadString::LoadKind::kDexCachePcRelative: - DCHECK(!Runtime::Current()->UseJit()); + DCHECK(!Runtime::Current()->UseJitCompilation()); break; case HLoadString::LoadKind::kDexCacheViaMethod: break; diff --git a/compiler/optimizing/code_generator_x86_64.h b/compiler/optimizing/code_generator_x86_64.h index d7ce7c649f..7cf12459b0 100644 --- a/compiler/optimizing/code_generator_x86_64.h +++ b/compiler/optimizing/code_generator_x86_64.h @@ -346,7 +346,7 @@ class CodeGeneratorX86_64 : public CodeGenerator { return &move_resolver_; } - uintptr_t GetAddressOf(HBasicBlock* block) const OVERRIDE { + uintptr_t GetAddressOf(HBasicBlock* block) OVERRIDE { return GetLabelOf(block)->Position(); } @@ -394,6 +394,7 @@ class CodeGeneratorX86_64 : public CodeGenerator { const HInvokeStaticOrDirect::DispatchInfo& desired_dispatch_info, MethodReference target_method) OVERRIDE; + Location GenerateCalleeMethodStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp); void GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) OVERRIDE; void GenerateVirtualCall(HInvokeVirtual* invoke, Location temp) OVERRIDE; @@ -509,10 +510,10 @@ class CodeGeneratorX86_64 : public CodeGenerator { // Ensure that prior stores complete to memory before subsequent loads. // The locked add implementation will avoid serializing device memory, but will - // touch (but not change) the top of the stack. The locked add should not be used for - // ordering non-temporal stores. + // touch (but not change) the top of the stack. + // The 'non_temporal' parameter should be used to ensure ordering of non-temporal stores. void MemoryFence(bool force_mfence = false) { - if (!force_mfence && isa_features_.PrefersLockedAddSynchronization()) { + if (!force_mfence) { assembler_.lock()->addl(Address(CpuRegister(RSP), 0), Immediate(0)); } else { assembler_.mfence(); diff --git a/compiler/optimizing/common_arm64.h b/compiler/optimizing/common_arm64.h index 6412b24362..a849448cf9 100644 --- a/compiler/optimizing/common_arm64.h +++ b/compiler/optimizing/common_arm64.h @@ -199,7 +199,7 @@ static bool CanEncodeConstantAsImmediate(HConstant* constant, HInstruction* inst // For single uses we let VIXL handle the constant generation since it will // use registers that are not managed by the register allocator (wip0, wip1). - if (constant->GetUses().HasOnlyOneUse()) { + if (constant->GetUses().HasExactlyOneElement()) { return true; } diff --git a/compiler/optimizing/dead_code_elimination.cc b/compiler/optimizing/dead_code_elimination.cc index 5f11024996..49cfff46d8 100644 --- a/compiler/optimizing/dead_code_elimination.cc +++ b/compiler/optimizing/dead_code_elimination.cc @@ -23,7 +23,7 @@ namespace art { static void MarkReachableBlocks(HGraph* graph, ArenaBitVector* visited) { - ArenaVector<HBasicBlock*> worklist(graph->GetArena()->Adapter()); + ArenaVector<HBasicBlock*> worklist(graph->GetArena()->Adapter(kArenaAllocDCE)); constexpr size_t kDefaultWorlistSize = 8; worklist.reserve(kDefaultWorlistSize); visited->SetBit(graph->GetEntryBlock()->GetBlockId()); diff --git a/compiler/optimizing/graph_checker.cc b/compiler/optimizing/graph_checker.cc index 9ea4b2dab4..2bd2403dd6 100644 --- a/compiler/optimizing/graph_checker.cc +++ b/compiler/optimizing/graph_checker.cc @@ -258,6 +258,15 @@ void GraphChecker::VisitBoundsCheck(HBoundsCheck* check) { VisitInstruction(check); } +void GraphChecker::VisitDeoptimize(HDeoptimize* deopt) { + if (GetGraph()->IsCompilingOsr()) { + AddError(StringPrintf("A graph compiled OSR cannot have a HDeoptimize instruction")); + } + + // Perform the instruction base checks too. + VisitInstruction(deopt); +} + void GraphChecker::VisitTryBoundary(HTryBoundary* try_boundary) { ArrayRef<HBasicBlock* const> handlers = try_boundary->GetExceptionHandlers(); @@ -326,9 +335,7 @@ void GraphChecker::VisitInstruction(HInstruction* instruction) { } // Ensure the inputs of `instruction` are defined in a block of the graph. - for (HInputIterator input_it(instruction); !input_it.Done(); - input_it.Advance()) { - HInstruction* input = input_it.Current(); + for (HInstruction* input : instruction->GetInputs()) { const HInstructionList& list = input->IsPhi() ? input->GetBlock()->GetPhis() : input->GetBlock()->GetInstructions(); @@ -342,36 +349,35 @@ void GraphChecker::VisitInstruction(HInstruction* instruction) { // Ensure the uses of `instruction` are defined in a block of the graph, // and the entry in the use list is consistent. - for (HUseIterator<HInstruction*> use_it(instruction->GetUses()); - !use_it.Done(); use_it.Advance()) { - HInstruction* use = use_it.Current()->GetUser(); - const HInstructionList& list = use->IsPhi() - ? use->GetBlock()->GetPhis() - : use->GetBlock()->GetInstructions(); - if (!list.Contains(use)) { + for (const HUseListNode<HInstruction*>& use : instruction->GetUses()) { + HInstruction* user = use.GetUser(); + const HInstructionList& list = user->IsPhi() + ? user->GetBlock()->GetPhis() + : user->GetBlock()->GetInstructions(); + if (!list.Contains(user)) { AddError(StringPrintf("User %s:%d of instruction %d is not defined " "in a basic block of the control-flow graph.", - use->DebugName(), - use->GetId(), + user->DebugName(), + user->GetId(), instruction->GetId())); } - size_t use_index = use_it.Current()->GetIndex(); - if ((use_index >= use->InputCount()) || (use->InputAt(use_index) != instruction)) { + size_t use_index = use.GetIndex(); + auto&& user_inputs = user->GetInputs(); + if ((use_index >= user_inputs.size()) || (user_inputs[use_index] != instruction)) { AddError(StringPrintf("User %s:%d of instruction %s:%d has a wrong " "UseListNode index.", - use->DebugName(), - use->GetId(), + user->DebugName(), + user->GetId(), instruction->DebugName(), instruction->GetId())); } } // Ensure the environment uses entries are consistent. - for (HUseIterator<HEnvironment*> use_it(instruction->GetEnvUses()); - !use_it.Done(); use_it.Advance()) { - HEnvironment* use = use_it.Current()->GetUser(); - size_t use_index = use_it.Current()->GetIndex(); - if ((use_index >= use->Size()) || (use->GetInstructionAt(use_index) != instruction)) { + for (const HUseListNode<HEnvironment*>& use : instruction->GetEnvUses()) { + HEnvironment* user = use.GetUser(); + size_t use_index = use.GetIndex(); + if ((use_index >= user->Size()) || (user->GetInstructionAt(use_index) != instruction)) { AddError(StringPrintf("Environment user of %s:%d has a wrong " "UseListNode index.", instruction->DebugName(), @@ -380,16 +386,15 @@ void GraphChecker::VisitInstruction(HInstruction* instruction) { } // Ensure 'instruction' has pointers to its inputs' use entries. - for (size_t i = 0, e = instruction->InputCount(); i < e; ++i) { - HUserRecord<HInstruction*> input_record = instruction->InputRecordAt(i); + auto&& input_records = instruction->GetInputRecords(); + for (size_t i = 0; i < input_records.size(); ++i) { + const HUserRecord<HInstruction*>& input_record = input_records[i]; HInstruction* input = input_record.GetInstruction(); - HUseListNode<HInstruction*>* use_node = input_record.GetUseNode(); - size_t use_index = use_node->GetIndex(); - if ((use_node == nullptr) - || !input->GetUses().Contains(use_node) - || (use_index >= e) - || (use_index != i)) { - AddError(StringPrintf("Instruction %s:%d has an invalid pointer to use entry " + if ((input_record.GetBeforeUseNode() == input->GetUses().end()) || + (input_record.GetUseNode() == input->GetUses().end()) || + !input->GetUses().ContainsNode(*input_record.GetUseNode()) || + (input_record.GetUseNode()->GetIndex() != i)) { + AddError(StringPrintf("Instruction %s:%d has an invalid iterator before use entry " "at input %u (%s:%d).", instruction->DebugName(), instruction->GetId(), @@ -400,18 +405,17 @@ void GraphChecker::VisitInstruction(HInstruction* instruction) { } // Ensure an instruction dominates all its uses. - for (HUseIterator<HInstruction*> use_it(instruction->GetUses()); - !use_it.Done(); use_it.Advance()) { - HInstruction* use = use_it.Current()->GetUser(); - if (!use->IsPhi() && !instruction->StrictlyDominates(use)) { + for (const HUseListNode<HInstruction*>& use : instruction->GetUses()) { + HInstruction* user = use.GetUser(); + if (!user->IsPhi() && !instruction->StrictlyDominates(user)) { AddError(StringPrintf("Instruction %s:%d in block %d does not dominate " "use %s:%d in block %d.", instruction->DebugName(), instruction->GetId(), current_block_->GetBlockId(), - use->DebugName(), - use->GetId(), - use->GetBlock()->GetBlockId())); + user->DebugName(), + user->GetId(), + user->GetBlock()->GetBlockId())); } } @@ -486,8 +490,7 @@ void GraphChecker::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { VisitInstruction(invoke); if (invoke->IsStaticWithExplicitClinitCheck()) { - size_t last_input_index = invoke->InputCount() - 1; - HInstruction* last_input = invoke->InputAt(last_input_index); + HInstruction* last_input = invoke->GetInputs().back(); if (last_input == nullptr) { AddError(StringPrintf("Static invoke %s:%d marked as having an explicit clinit check " "has a null pointer as last input.", @@ -669,16 +672,21 @@ static bool IsSameSizeConstant(HInstruction* insn1, HInstruction* insn2) { static bool IsConstantEquivalent(HInstruction* insn1, HInstruction* insn2, BitVector* visited) { if (insn1->IsPhi() && - insn1->AsPhi()->IsVRegEquivalentOf(insn2) && - insn1->InputCount() == insn2->InputCount()) { + insn1->AsPhi()->IsVRegEquivalentOf(insn2)) { + auto&& insn1_inputs = insn1->GetInputs(); + auto&& insn2_inputs = insn2->GetInputs(); + if (insn1_inputs.size() != insn2_inputs.size()) { + return false; + } + // Testing only one of the two inputs for recursion is sufficient. if (visited->IsBitSet(insn1->GetId())) { return true; } visited->SetBit(insn1->GetId()); - for (size_t i = 0, e = insn1->InputCount(); i < e; ++i) { - if (!IsConstantEquivalent(insn1->InputAt(i), insn2->InputAt(i), visited)) { + for (size_t i = 0; i < insn1_inputs.size(); ++i) { + if (!IsConstantEquivalent(insn1_inputs[i], insn2_inputs[i], visited)) { return false; } } @@ -694,15 +702,16 @@ void GraphChecker::VisitPhi(HPhi* phi) { VisitInstruction(phi); // Ensure the first input of a phi is not itself. - if (phi->InputAt(0) == phi) { + ArrayRef<HUserRecord<HInstruction*>> input_records = phi->GetInputRecords(); + if (input_records[0].GetInstruction() == phi) { AddError(StringPrintf("Loop phi %d in block %d is its own first input.", phi->GetId(), phi->GetBlock()->GetBlockId())); } // Ensure that the inputs have the same primitive kind as the phi. - for (size_t i = 0, e = phi->InputCount(); i < e; ++i) { - HInstruction* input = phi->InputAt(i); + for (size_t i = 0; i < input_records.size(); ++i) { + HInstruction* input = input_records[i].GetInstruction(); if (Primitive::PrimitiveKind(input->GetType()) != Primitive::PrimitiveKind(phi->GetType())) { AddError(StringPrintf( "Input %d at index %zu of phi %d from block %d does not have the " @@ -725,8 +734,7 @@ void GraphChecker::VisitPhi(HPhi* phi) { // because we do not remove the corresponding inputs when we prove that an // instruction cannot throw. Instead, we at least test that all phis have the // same, non-zero number of inputs (b/24054676). - size_t input_count_this = phi->InputCount(); - if (input_count_this == 0u) { + if (input_records.empty()) { AddError(StringPrintf("Phi %d in catch block %d has zero inputs.", phi->GetId(), phi->GetBlock()->GetBlockId())); @@ -734,12 +742,12 @@ void GraphChecker::VisitPhi(HPhi* phi) { HInstruction* next_phi = phi->GetNext(); if (next_phi != nullptr) { size_t input_count_next = next_phi->InputCount(); - if (input_count_this != input_count_next) { + if (input_records.size() != input_count_next) { AddError(StringPrintf("Phi %d in catch block %d has %zu inputs, " "but phi %d has %zu inputs.", phi->GetId(), phi->GetBlock()->GetBlockId(), - input_count_this, + input_records.size(), next_phi->GetId(), input_count_next)); } @@ -749,17 +757,17 @@ void GraphChecker::VisitPhi(HPhi* phi) { // Ensure the number of inputs of a non-catch phi is the same as the number // of its predecessors. const ArenaVector<HBasicBlock*>& predecessors = phi->GetBlock()->GetPredecessors(); - if (phi->InputCount() != predecessors.size()) { + if (input_records.size() != predecessors.size()) { AddError(StringPrintf( "Phi %d in block %d has %zu inputs, " "but block %d has %zu predecessors.", - phi->GetId(), phi->GetBlock()->GetBlockId(), phi->InputCount(), + phi->GetId(), phi->GetBlock()->GetBlockId(), input_records.size(), phi->GetBlock()->GetBlockId(), predecessors.size())); } else { // Ensure phi input at index I either comes from the Ith // predecessor or from a block that dominates this predecessor. - for (size_t i = 0, e = phi->InputCount(); i < e; ++i) { - HInstruction* input = phi->InputAt(i); + for (size_t i = 0; i < input_records.size(); ++i) { + HInstruction* input = input_records[i].GetInstruction(); HBasicBlock* predecessor = predecessors[i]; if (!(input->GetBlock() == predecessor || input->GetBlock()->Dominates(predecessor))) { diff --git a/compiler/optimizing/graph_checker.h b/compiler/optimizing/graph_checker.h index 83b198474c..3060c80073 100644 --- a/compiler/optimizing/graph_checker.h +++ b/compiler/optimizing/graph_checker.h @@ -57,6 +57,7 @@ class GraphChecker : public HGraphDelegateVisitor { void VisitCheckCast(HCheckCast* check) OVERRIDE; void VisitCondition(HCondition* op) OVERRIDE; void VisitConstant(HConstant* instruction) OVERRIDE; + void VisitDeoptimize(HDeoptimize* instruction) OVERRIDE; void VisitIf(HIf* instruction) OVERRIDE; void VisitInstanceOf(HInstanceOf* check) OVERRIDE; void VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) OVERRIDE; diff --git a/compiler/optimizing/graph_visualizer.cc b/compiler/optimizing/graph_visualizer.cc index fe47f7db7d..3084a4ff2b 100644 --- a/compiler/optimizing/graph_visualizer.cc +++ b/compiler/optimizing/graph_visualizer.cc @@ -98,7 +98,9 @@ typedef Disassembler* create_disasm_prototype(InstructionSet instruction_set, DisassemblerOptions* options); class HGraphVisualizerDisassembler { public: - HGraphVisualizerDisassembler(InstructionSet instruction_set, const uint8_t* base_address) + HGraphVisualizerDisassembler(InstructionSet instruction_set, + const uint8_t* base_address, + const uint8_t* end_address) : instruction_set_(instruction_set), disassembler_(nullptr) { libart_disassembler_handle_ = dlopen(kIsDebugBuild ? "libartd-disassembler.so" : "libart-disassembler.so", RTLD_NOW); @@ -119,6 +121,7 @@ class HGraphVisualizerDisassembler { instruction_set, new DisassemblerOptions(/* absolute_addresses */ false, base_address, + end_address, /* can_read_literals */ true))); } @@ -174,7 +177,9 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { disassembler_(disasm_info_ != nullptr ? new HGraphVisualizerDisassembler( codegen_.GetInstructionSet(), - codegen_.GetAssembler().CodeBufferBaseAddress()) + codegen_.GetAssembler().CodeBufferBaseAddress(), + codegen_.GetAssembler().CodeBufferBaseAddress() + + codegen_.GetAssembler().CodeSize()) : nullptr), indent_(0) {} @@ -389,6 +394,11 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { << instance_of->MustDoNullCheck() << std::noboolalpha; } + void VisitArrayLength(HArrayLength* array_length) OVERRIDE { + StartAttributeStream("is_string_length") << std::boolalpha + << array_length->IsStringLength() << std::noboolalpha; + } + void VisitArraySet(HArraySet* array_set) OVERRIDE { StartAttributeStream("value_can_be_null") << std::boolalpha << array_set->GetValueCanBeNull() << std::noboolalpha; @@ -487,12 +497,13 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { void PrintInstruction(HInstruction* instruction) { output_ << instruction->DebugName(); - if (instruction->InputCount() > 0) { - StringList inputs; - for (HInputIterator it(instruction); !it.Done(); it.Advance()) { - inputs.NewEntryStream() << GetTypeId(it.Current()->GetType()) << it.Current()->GetId(); + auto&& inputs = instruction->GetInputs(); + if (!inputs.empty()) { + StringList input_list; + for (const HInstruction* input : inputs) { + input_list.NewEntryStream() << GetTypeId(input->GetType()) << input->GetId(); } - StartAttributeStream() << inputs; + StartAttributeStream() << input_list; } instruction->Accept(this); if (instruction->HasEnvironment()) { @@ -534,36 +545,29 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { StartAttributeStream("liveness") << instruction->GetLifetimePosition(); LocationSummary* locations = instruction->GetLocations(); if (locations != nullptr) { - StringList inputs; - for (size_t i = 0; i < instruction->InputCount(); ++i) { - DumpLocation(inputs.NewEntryStream(), locations->InAt(i)); + StringList input_list; + for (size_t i = 0, e = locations->GetInputCount(); i < e; ++i) { + DumpLocation(input_list.NewEntryStream(), locations->InAt(i)); } std::ostream& attr = StartAttributeStream("locations"); - attr << inputs << "->"; + attr << input_list << "->"; DumpLocation(attr, locations->Out()); } } - if (IsPass(LICM::kLoopInvariantCodeMotionPassName) - || IsPass(HDeadCodeElimination::kFinalDeadCodeEliminationPassName) - || IsPass(HDeadCodeElimination::kInitialDeadCodeEliminationPassName) - || IsPass(BoundsCheckElimination::kBoundsCheckEliminationPassName) - || IsPass(RegisterAllocator::kRegisterAllocatorPassName) - || IsPass(HGraphBuilder::kBuilderPassName)) { - HLoopInformation* info = instruction->GetBlock()->GetLoopInformation(); - if (info == nullptr) { - StartAttributeStream("loop") << "none"; + HLoopInformation* loop_info = instruction->GetBlock()->GetLoopInformation(); + if (loop_info == nullptr) { + StartAttributeStream("loop") << "none"; + } else { + StartAttributeStream("loop") << "B" << loop_info->GetHeader()->GetBlockId(); + HLoopInformation* outer = loop_info->GetPreHeader()->GetLoopInformation(); + if (outer != nullptr) { + StartAttributeStream("outer_loop") << "B" << outer->GetHeader()->GetBlockId(); } else { - StartAttributeStream("loop") << "B" << info->GetHeader()->GetBlockId(); - HLoopInformation* outer = info->GetPreHeader()->GetLoopInformation(); - if (outer != nullptr) { - StartAttributeStream("outer_loop") << "B" << outer->GetHeader()->GetBlockId(); - } else { - StartAttributeStream("outer_loop") << "none"; - } - StartAttributeStream("irreducible") - << std::boolalpha << info->IsIrreducible() << std::noboolalpha; + StartAttributeStream("outer_loop") << "none"; } + StartAttributeStream("irreducible") + << std::boolalpha << loop_info->IsIrreducible() << std::noboolalpha; } if ((IsPass(HGraphBuilder::kBuilderPassName) @@ -608,12 +612,7 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { for (HInstructionIterator it(list); !it.Done(); it.Advance()) { HInstruction* instruction = it.Current(); int bci = 0; - size_t num_uses = 0; - for (HUseIterator<HInstruction*> use_it(instruction->GetUses()); - !use_it.Done(); - use_it.Advance()) { - ++num_uses; - } + size_t num_uses = instruction->GetUses().SizeSlow(); AddIndent(); output_ << bci << " " << num_uses << " " << GetTypeId(instruction->GetType()) << instruction->GetId() << " "; @@ -741,8 +740,8 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { HInstruction* instruction = it.Current(); output_ << instruction->GetId() << " " << GetTypeId(instruction->GetType()) << instruction->GetId() << "[ "; - for (HInputIterator inputs(instruction); !inputs.Done(); inputs.Advance()) { - output_ << inputs.Current()->GetId() << " "; + for (const HInstruction* input : instruction->GetInputs()) { + output_ << input->GetId() << " "; } output_ << "]\n"; } diff --git a/compiler/optimizing/gvn.cc b/compiler/optimizing/gvn.cc index f7eb2adc6c..1e86b75075 100644 --- a/compiler/optimizing/gvn.cc +++ b/compiler/optimizing/gvn.cc @@ -41,7 +41,7 @@ class ValueSet : public ArenaObject<kArenaAllocGvn> { num_buckets_(kMinimumNumberOfBuckets), buckets_(allocator->AllocArray<Node*>(num_buckets_, kArenaAllocGvn)), buckets_owned_(allocator, num_buckets_, false, kArenaAllocGvn), - num_entries_(0) { + num_entries_(0u) { // ArenaAllocator returns zeroed memory, so no need to set buckets to null. DCHECK(IsPowerOfTwo(num_buckets_)); buckets_owned_.SetInitialBits(num_buckets_); @@ -49,29 +49,35 @@ class ValueSet : public ArenaObject<kArenaAllocGvn> { // Copy constructor. Depending on the load factor, it will either make a deep // copy (all buckets owned) or a shallow one (buckets pointing to the parent). - ValueSet(ArenaAllocator* allocator, const ValueSet& to_copy) + ValueSet(ArenaAllocator* allocator, const ValueSet& other) : allocator_(allocator), - num_buckets_(to_copy.IdealBucketCount()), + num_buckets_(other.IdealBucketCount()), buckets_(allocator->AllocArray<Node*>(num_buckets_, kArenaAllocGvn)), buckets_owned_(allocator, num_buckets_, false, kArenaAllocGvn), - num_entries_(to_copy.num_entries_) { + num_entries_(0u) { // ArenaAllocator returns zeroed memory, so entries of buckets_ and // buckets_owned_ are initialized to null and false, respectively. DCHECK(IsPowerOfTwo(num_buckets_)); - if (num_buckets_ == to_copy.num_buckets_) { - // Hash table remains the same size. We copy the bucket pointers and leave - // all buckets_owned_ bits false. - memcpy(buckets_, to_copy.buckets_, num_buckets_ * sizeof(Node*)); + PopulateFromInternal(other, /* is_dirty */ false); + } + + // Erases all values in this set and populates it with values from `other`. + void PopulateFrom(const ValueSet& other) { + if (this == &other) { + return; + } + PopulateFromInternal(other, /* is_dirty */ true); + } + + // Returns true if `this` has enough buckets so that if `other` is copied into + // it, the load factor will not cross the upper threshold. + // If `exact_match` is set, true is returned only if `this` has the ideal + // number of buckets. Larger number of buckets is allowed otherwise. + bool CanHoldCopyOf(const ValueSet& other, bool exact_match) { + if (exact_match) { + return other.IdealBucketCount() == num_buckets_; } else { - // Hash table size changes. We copy and rehash all entries, and set all - // buckets_owned_ bits to true. - for (size_t i = 0; i < to_copy.num_buckets_; ++i) { - for (Node* node = to_copy.buckets_[i]; node != nullptr; node = node->GetNext()) { - size_t new_index = BucketIndex(node->GetHashCode()); - buckets_[new_index] = node->Dup(allocator_, buckets_[new_index]); - } - } - buckets_owned_.SetInitialBits(num_buckets_); + return other.IdealBucketCount() <= num_buckets_; } } @@ -152,6 +158,46 @@ class ValueSet : public ArenaObject<kArenaAllocGvn> { size_t GetNumberOfEntries() const { return num_entries_; } private: + // Copies all entries from `other` to `this`. + // If `is_dirty` is set to true, existing data will be wiped first. It is + // assumed that `buckets_` and `buckets_owned_` are zero-allocated otherwise. + void PopulateFromInternal(const ValueSet& other, bool is_dirty) { + DCHECK_NE(this, &other); + DCHECK_GE(num_buckets_, other.IdealBucketCount()); + + if (num_buckets_ == other.num_buckets_) { + // Hash table remains the same size. We copy the bucket pointers and leave + // all buckets_owned_ bits false. + if (is_dirty) { + buckets_owned_.ClearAllBits(); + } else { + DCHECK_EQ(buckets_owned_.NumSetBits(), 0u); + } + memcpy(buckets_, other.buckets_, num_buckets_ * sizeof(Node*)); + } else { + // Hash table size changes. We copy and rehash all entries, and set all + // buckets_owned_ bits to true. + if (is_dirty) { + memset(buckets_, 0, num_buckets_ * sizeof(Node*)); + } else { + if (kIsDebugBuild) { + for (size_t i = 0; i < num_buckets_; ++i) { + DCHECK(buckets_[i] == nullptr) << i; + } + } + } + for (size_t i = 0; i < other.num_buckets_; ++i) { + for (Node* node = other.buckets_[i]; node != nullptr; node = node->GetNext()) { + size_t new_index = BucketIndex(node->GetHashCode()); + buckets_[new_index] = node->Dup(allocator_, buckets_[new_index]); + } + } + buckets_owned_.SetInitialBits(num_buckets_); + } + + num_entries_ = other.num_entries_; + } + class Node : public ArenaObject<kArenaAllocGvn> { public: Node(HInstruction* instruction, size_t hash_code, Node* next) @@ -310,7 +356,9 @@ class GlobalValueNumberer : public ValueObject { : graph_(graph), allocator_(allocator), side_effects_(side_effects), - sets_(graph->GetBlocks().size(), nullptr, allocator->Adapter(kArenaAllocGvn)) {} + sets_(graph->GetBlocks().size(), nullptr, allocator->Adapter(kArenaAllocGvn)), + visited_blocks_( + allocator, graph->GetBlocks().size(), /* expandable */ false, kArenaAllocGvn) {} void Run(); @@ -323,11 +371,37 @@ class GlobalValueNumberer : public ValueObject { ArenaAllocator* const allocator_; const SideEffectsAnalysis& side_effects_; + ValueSet* FindSetFor(HBasicBlock* block) const { + ValueSet* result = sets_[block->GetBlockId()]; + DCHECK(result != nullptr) << "Could not find set for block B" << block->GetBlockId(); + return result; + } + + void AbandonSetFor(HBasicBlock* block) { + DCHECK(sets_[block->GetBlockId()] != nullptr) + << "Block B" << block->GetBlockId() << " expected to have a set"; + sets_[block->GetBlockId()] = nullptr; + } + + // Returns false if the GlobalValueNumberer has already visited all blocks + // which may reference `block`. + bool WillBeReferencedAgain(HBasicBlock* block) const; + + // Iterates over visited blocks and finds one which has a ValueSet such that: + // (a) it will not be referenced in the future, and + // (b) it can hold a copy of `reference_set` with a reasonable load factor. + HBasicBlock* FindVisitedBlockWithRecyclableSet(HBasicBlock* block, + const ValueSet& reference_set) const; + // ValueSet for blocks. Initially null, but for an individual block they // are allocated and populated by the dominator, and updated by all blocks // in the path from the dominator to the block. ArenaVector<ValueSet*> sets_; + // BitVector which serves as a fast-access map from block id to + // visited/unvisited boolean. + ArenaBitVector visited_blocks_; + DISALLOW_COPY_AND_ASSIGN(GlobalValueNumberer); }; @@ -344,6 +418,7 @@ void GlobalValueNumberer::Run() { void GlobalValueNumberer::VisitBasicBlock(HBasicBlock* block) { ValueSet* set = nullptr; + const ArenaVector<HBasicBlock*>& predecessors = block->GetPredecessors(); if (predecessors.size() == 0 || predecessors[0]->IsEntryBlock()) { // The entry block should only accumulate constant instructions, and @@ -352,28 +427,49 @@ void GlobalValueNumberer::VisitBasicBlock(HBasicBlock* block) { set = new (allocator_) ValueSet(allocator_); } else { HBasicBlock* dominator = block->GetDominator(); - ValueSet* dominator_set = sets_[dominator->GetBlockId()]; + ValueSet* dominator_set = FindSetFor(dominator); + if (dominator->GetSuccessors().size() == 1) { - DCHECK_EQ(dominator->GetSuccessors()[0], block); + // `block` is a direct successor of its dominator. No need to clone the + // dominator's set, `block` can take over its ownership including its buckets. + DCHECK_EQ(dominator->GetSingleSuccessor(), block); + AbandonSetFor(dominator); set = dominator_set; } else { - // We have to copy if the dominator has other successors, or `block` is not a successor - // of the dominator. - set = new (allocator_) ValueSet(allocator_, *dominator_set); + // Try to find a basic block which will never be referenced again and whose + // ValueSet can therefore be recycled. We will need to copy `dominator_set` + // into the recycled set, so we pass `dominator_set` as a reference for size. + HBasicBlock* recyclable = FindVisitedBlockWithRecyclableSet(block, *dominator_set); + if (recyclable == nullptr) { + // No block with a suitable ValueSet found. Allocate a new one and + // copy `dominator_set` into it. + set = new (allocator_) ValueSet(allocator_, *dominator_set); + } else { + // Block with a recyclable ValueSet found. Clone `dominator_set` into it. + set = FindSetFor(recyclable); + AbandonSetFor(recyclable); + set->PopulateFrom(*dominator_set); + } } + if (!set->IsEmpty()) { if (block->IsLoopHeader()) { - if (block->GetLoopInformation()->IsIrreducible()) { + if (block->GetLoopInformation()->ContainsIrreducibleLoop()) { // To satisfy our linear scan algorithm, no instruction should flow in an irreducible - // loop header. + // loop header. We clear the set at entry of irreducible loops and any loop containing + // an irreducible loop, as in both cases, GVN can extend the liveness of an instruction + // across the irreducible loop. + // Note that, if we're not compiling OSR, we could still do GVN and introduce + // phis at irreducible loop headers. We decided it was not worth the complexity. set->Clear(); } else { + DCHECK(!block->GetLoopInformation()->IsIrreducible()); DCHECK_EQ(block->GetDominator(), block->GetLoopInformation()->GetPreHeader()); set->Kill(side_effects_.GetLoopEffects(block)); } } else if (predecessors.size() > 1) { for (HBasicBlock* predecessor : predecessors) { - set->IntersectWith(sets_[predecessor->GetBlockId()]); + set->IntersectWith(FindSetFor(predecessor)); if (set->IsEmpty()) { break; } @@ -413,6 +509,60 @@ void GlobalValueNumberer::VisitBasicBlock(HBasicBlock* block) { } current = next; } + + visited_blocks_.SetBit(block->GetBlockId()); +} + +bool GlobalValueNumberer::WillBeReferencedAgain(HBasicBlock* block) const { + DCHECK(visited_blocks_.IsBitSet(block->GetBlockId())); + + for (auto dominated_block : block->GetDominatedBlocks()) { + if (!visited_blocks_.IsBitSet(dominated_block->GetBlockId())) { + return true; + } + } + + for (auto successor : block->GetSuccessors()) { + if (!visited_blocks_.IsBitSet(successor->GetBlockId())) { + return true; + } + } + + return false; +} + +HBasicBlock* GlobalValueNumberer::FindVisitedBlockWithRecyclableSet( + HBasicBlock* block, const ValueSet& reference_set) const { + HBasicBlock* secondary_match = nullptr; + + for (size_t block_id : visited_blocks_.Indexes()) { + ValueSet* current_set = sets_[block_id]; + if (current_set == nullptr) { + // Set was already recycled. + continue; + } + + HBasicBlock* current_block = block->GetGraph()->GetBlocks()[block_id]; + + // We test if `current_set` has enough buckets to store a copy of + // `reference_set` with a reasonable load factor. If we find a set whose + // number of buckets matches perfectly, we return right away. If we find one + // that is larger, we return it if no perfectly-matching set is found. + // Note that we defer testing WillBeReferencedAgain until all other criteria + // have been satisfied because it might be expensive. + if (current_set->CanHoldCopyOf(reference_set, /* exact_match */ true)) { + if (!WillBeReferencedAgain(current_block)) { + return current_block; + } + } else if (secondary_match == nullptr && + current_set->CanHoldCopyOf(reference_set, /* exact_match */ false)) { + if (!WillBeReferencedAgain(current_block)) { + secondary_match = current_block; + } + } + } + + return secondary_match; } void GVNOptimization::Run() { diff --git a/compiler/optimizing/induction_var_analysis.cc b/compiler/optimizing/induction_var_analysis.cc index c06d19dce0..0a5cf80e9d 100644 --- a/compiler/optimizing/induction_var_analysis.cc +++ b/compiler/optimizing/induction_var_analysis.cc @@ -152,8 +152,8 @@ void HInductionVarAnalysis::VisitNode(HLoopInformation* loop, HInstruction* inst // Visit all descendants. uint32_t low = d1; - for (size_t i = 0, count = instruction->InputCount(); i < count; ++i) { - low = std::min(low, VisitDescendant(loop, instruction->InputAt(i))); + for (HInstruction* input : instruction->GetInputs()) { + low = std::min(low, VisitDescendant(loop, input)); } // Lower or found SCC? @@ -341,11 +341,11 @@ HInductionVarAnalysis::InductionInfo* HInductionVarAnalysis::TransferPhi(HLoopIn HInstruction* phi, size_t input_index) { // Match all phi inputs from input_index onwards exactly. - const size_t count = phi->InputCount(); - DCHECK_LT(input_index, count); - InductionInfo* a = LookupInfo(loop, phi->InputAt(input_index)); - for (size_t i = input_index + 1; i < count; i++) { - InductionInfo* b = LookupInfo(loop, phi->InputAt(i)); + auto&& inputs = phi->GetInputs(); + DCHECK_LT(input_index, inputs.size()); + InductionInfo* a = LookupInfo(loop, inputs[input_index]); + for (size_t i = input_index + 1; i < inputs.size(); i++) { + InductionInfo* b = LookupInfo(loop, inputs[i]); if (!InductionEqual(a, b)) { return nullptr; } @@ -464,12 +464,12 @@ HInductionVarAnalysis::InductionInfo* HInductionVarAnalysis::TransferCnv(Inducti HInductionVarAnalysis::InductionInfo* HInductionVarAnalysis::SolvePhi(HInstruction* phi, size_t input_index) { // Match all phi inputs from input_index onwards exactly. - const size_t count = phi->InputCount(); - DCHECK_LT(input_index, count); - auto ita = cycle_.find(phi->InputAt(input_index)); + auto&& inputs = phi->GetInputs(); + DCHECK_LT(input_index, inputs.size()); + auto ita = cycle_.find(inputs[input_index]); if (ita != cycle_.end()) { - for (size_t i = input_index + 1; i < count; i++) { - auto itb = cycle_.find(phi->InputAt(i)); + for (size_t i = input_index + 1; i < inputs.size(); i++) { + auto itb = cycle_.find(inputs[i]); if (itb == cycle_.end() || !HInductionVarAnalysis::InductionEqual(ita->second, itb->second)) { return nullptr; diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc index 77e0cbc600..59de895182 100644 --- a/compiler/optimizing/inliner.cc +++ b/compiler/optimizing/inliner.cc @@ -308,7 +308,7 @@ bool HInliner::TryInline(HInvoke* invoke_instruction) { // Check if we can use an inline cache. ArtMethod* caller = graph_->GetArtMethod(); - if (Runtime::Current()->UseJit()) { + if (Runtime::Current()->UseJitCompilation()) { // Under JIT, we should always know the caller. DCHECK(caller != nullptr); ScopedProfilingInfoInlineUse spiis(caller, soa.Self()); @@ -322,7 +322,13 @@ bool HInliner::TryInline(HInvoke* invoke_instruction) { return false; } else if (ic.IsMonomorphic()) { MaybeRecordStat(kMonomorphicCall); - return TryInlineMonomorphicCall(invoke_instruction, resolved_method, ic); + if (outermost_graph_->IsCompilingOsr()) { + // If we are compiling OSR, we pretend this call is polymorphic, as we may come from the + // interpreter and it may have seen different receiver types. + return TryInlinePolymorphicCall(invoke_instruction, resolved_method, ic); + } else { + return TryInlineMonomorphicCall(invoke_instruction, resolved_method, ic); + } } else if (ic.IsPolymorphic()) { MaybeRecordStat(kPolymorphicCall); return TryInlinePolymorphicCall(invoke_instruction, resolved_method, ic); @@ -411,7 +417,10 @@ bool HInliner::TryInlineMonomorphicCall(HInvoke* invoke_instruction, // Run type propagation to get the guard typed, and eventually propagate the // type of the receiver. - ReferenceTypePropagation rtp_fixup(graph_, handles_, /* is_first_run */ false); + ReferenceTypePropagation rtp_fixup(graph_, + outer_compilation_unit_.GetDexCache(), + handles_, + /* is_first_run */ false); rtp_fixup.Run(); MaybeRecordStat(kInlinedMonomorphicCall); @@ -507,6 +516,11 @@ bool HInliner::TryInlinePolymorphicCall(HInvoke* invoke_instruction, bool deoptimize = all_targets_inlined && (i != InlineCache::kIndividualCacheSize - 1) && (ic.GetTypeAt(i + 1) == nullptr); + + if (outermost_graph_->IsCompilingOsr()) { + // We do not support HDeoptimize in OSR methods. + deoptimize = false; + } HInstruction* compare = AddTypeGuard( receiver, cursor, bb_cursor, class_index, is_referrer, invoke_instruction, deoptimize); if (deoptimize) { @@ -532,7 +546,10 @@ bool HInliner::TryInlinePolymorphicCall(HInvoke* invoke_instruction, MaybeRecordStat(kInlinedPolymorphicCall); // Run type propagation to get the guards typed. - ReferenceTypePropagation rtp_fixup(graph_, handles_, /* is_first_run */ false); + ReferenceTypePropagation rtp_fixup(graph_, + outer_compilation_unit_.GetDexCache(), + handles_, + /* is_first_run */ false); rtp_fixup.Run(); return true; } @@ -617,7 +634,7 @@ bool HInliner::TryInlinePolymorphicCallToSameTarget(HInvoke* invoke_instruction, ArtMethod* resolved_method, const InlineCache& ic) { // This optimization only works under JIT for now. - DCHECK(Runtime::Current()->UseJit()); + DCHECK(Runtime::Current()->UseJitCompilation()); if (graph_->GetInstructionSet() == kMips64) { // TODO: Support HClassTableGet for mips64. return false; @@ -666,7 +683,8 @@ bool HInliner::TryInlinePolymorphicCallToSameTarget(HInvoke* invoke_instruction, HInstruction* cursor = invoke_instruction->GetPrevious(); HBasicBlock* bb_cursor = invoke_instruction->GetBlock(); - if (!TryInlineAndReplace(invoke_instruction, actual_method, /* do_rtp */ false)) { + HInstruction* return_replacement = nullptr; + if (!TryBuildAndInline(invoke_instruction, actual_method, &return_replacement)) { return false; } @@ -695,9 +713,6 @@ bool HInliner::TryInlinePolymorphicCallToSameTarget(HInvoke* invoke_instruction, } HNotEqual* compare = new (graph_->GetArena()) HNotEqual(class_table_get, constant); - HDeoptimize* deoptimize = new (graph_->GetArena()) HDeoptimize( - compare, invoke_instruction->GetDexPc()); - // TODO: Extend reference type propagation to understand the guard. if (cursor != nullptr) { bb_cursor->InsertInstructionAfter(receiver_class, cursor); } else { @@ -705,11 +720,26 @@ bool HInliner::TryInlinePolymorphicCallToSameTarget(HInvoke* invoke_instruction, } bb_cursor->InsertInstructionAfter(class_table_get, receiver_class); bb_cursor->InsertInstructionAfter(compare, class_table_get); - bb_cursor->InsertInstructionAfter(deoptimize, compare); - deoptimize->CopyEnvironmentFrom(invoke_instruction->GetEnvironment()); + + if (outermost_graph_->IsCompilingOsr()) { + CreateDiamondPatternForPolymorphicInline(compare, return_replacement, invoke_instruction); + } else { + // TODO: Extend reference type propagation to understand the guard. + HDeoptimize* deoptimize = new (graph_->GetArena()) HDeoptimize( + compare, invoke_instruction->GetDexPc()); + bb_cursor->InsertInstructionAfter(deoptimize, compare); + deoptimize->CopyEnvironmentFrom(invoke_instruction->GetEnvironment()); + if (return_replacement != nullptr) { + invoke_instruction->ReplaceWith(return_replacement); + } + invoke_instruction->GetBlock()->RemoveInstruction(invoke_instruction); + } // Run type propagation to get the guard typed. - ReferenceTypePropagation rtp_fixup(graph_, handles_, /* is_first_run */ false); + ReferenceTypePropagation rtp_fixup(graph_, + outer_compilation_unit_.GetDexCache(), + handles_, + /* is_first_run */ false); rtp_fixup.Run(); MaybeRecordStat(kInlinedPolymorphicCall); @@ -735,6 +765,12 @@ bool HInliner::TryBuildAndInline(HInvoke* invoke_instruction, HInstruction** return_replacement) { const DexFile& caller_dex_file = *caller_compilation_unit_.GetDexFile(); + if (method->IsProxyMethod()) { + VLOG(compiler) << "Method " << PrettyMethod(method) + << " is not inlined because of unimplemented inline support for proxy methods."; + return false; + } + // Check whether we're allowed to inline. The outermost compilation unit is the relevant // dex file here (though the transitivity of an inline chain would allow checking the calller). if (!compiler_driver_->MayInline(method->GetDexFile(), @@ -786,9 +822,14 @@ bool HInliner::TryBuildAndInline(HInvoke* invoke_instruction, return false; } + if (!method->IsCompilable()) { + VLOG(compiler) << "Method " << PrettyMethod(method) + << " has soft failures un-handled by the compiler, so it cannot be inlined"; + } + if (!method->GetDeclaringClass()->IsVerified()) { uint16_t class_def_idx = method->GetDeclaringClass()->GetDexClassDefIndex(); - if (Runtime::Current()->UseJit() || + if (Runtime::Current()->UseJitCompilation() || !compiler_driver_->IsMethodVerifiedWithoutFailures( method->GetDexMethodIndex(), class_def_idx, *method->GetDexFile())) { VLOG(compiler) << "Method " << PrettyMethod(method_index, caller_dex_file) @@ -971,7 +1012,8 @@ HInstanceFieldGet* HInliner::CreateInstanceFieldGet(Handle<mirror::DexCache> dex // dex pc for the associated stack map. 0 is bogus but valid. Bug: 26854537. /* dex_pc */ 0); if (iget->GetType() == Primitive::kPrimNot) { - ReferenceTypePropagation rtp(graph_, handles_, /* is_first_run */ false); + // Use the same dex_cache that we used for field lookup as the hint_dex_cache. + ReferenceTypePropagation rtp(graph_, dex_cache, handles_, /* is_first_run */ false); rtp.Visit(iget); } return iget; @@ -1250,6 +1292,8 @@ bool HInliner::TryBuildAndInlineHelper(HInvoke* invoke_instruction, size_t HInliner::RunOptimizations(HGraph* callee_graph, const DexFile::CodeItem* code_item, const DexCompilationUnit& dex_compilation_unit) { + // Note: if the outermost_graph_ is being compiled OSR, we should not run any + // optimization that could lead to a HDeoptimize. The following optimizations do not. HDeadCodeElimination dce(callee_graph, stats_); HConstantFolding fold(callee_graph); HSharpening sharpening(callee_graph, codegen_, dex_compilation_unit, compiler_driver_); @@ -1303,11 +1347,12 @@ void HInliner::FixUpReturnReferenceType(HInvoke* invoke_instruction, DCHECK(return_replacement->IsPhi()); size_t pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize(); mirror::Class* cls = resolved_method->GetReturnType(false /* resolve */, pointer_size); - if (cls != nullptr) { + if (cls != nullptr && !cls->IsErroneous()) { ReferenceTypeInfo::TypeHandle return_handle = handles_->NewHandle(cls); return_replacement->SetReferenceTypeInfo(ReferenceTypeInfo::Create( return_handle, return_handle->CannotBeAssignedFromOtherTypes() /* is_exact */)); } else { + // Return inexact object type on failures. return_replacement->SetReferenceTypeInfo(graph_->GetInexactObjectRti()); } } @@ -1319,13 +1364,19 @@ void HInliner::FixUpReturnReferenceType(HInvoke* invoke_instruction, if (invoke_rti.IsStrictSupertypeOf(return_rti) || (return_rti.IsExact() && !invoke_rti.IsExact()) || !return_replacement->CanBeNull()) { - ReferenceTypePropagation(graph_, handles_, /* is_first_run */ false).Run(); + ReferenceTypePropagation(graph_, + outer_compilation_unit_.GetDexCache(), + handles_, + /* is_first_run */ false).Run(); } } } else if (return_replacement->IsInstanceOf()) { if (do_rtp) { // Inlining InstanceOf into an If may put a tighter bound on reference types. - ReferenceTypePropagation(graph_, handles_, /* is_first_run */ false).Run(); + ReferenceTypePropagation(graph_, + outer_compilation_unit_.GetDexCache(), + handles_, + /* is_first_run */ false).Run(); } } } diff --git a/compiler/optimizing/instruction_builder.cc b/compiler/optimizing/instruction_builder.cc index 06b39680b2..5e691c7f5f 100644 --- a/compiler/optimizing/instruction_builder.cc +++ b/compiler/optimizing/instruction_builder.cc @@ -215,6 +215,17 @@ void HInstructionBuilder::InitializeInstruction(HInstruction* instruction) { } } +HInstruction* HInstructionBuilder::LoadNullCheckedLocal(uint32_t register_index, uint32_t dex_pc) { + HInstruction* ref = LoadLocal(register_index, Primitive::kPrimNot); + if (!ref->CanBeNull()) { + return ref; + } + + HNullCheck* null_check = new (arena_) HNullCheck(ref, dex_pc); + AppendInstruction(null_check); + return null_check; +} + void HInstructionBuilder::SetLoopHeaderPhiInputs() { for (size_t i = loop_headers_.size(); i > 0; --i) { HBasicBlock* block = loop_headers_[i - 1]; @@ -662,6 +673,16 @@ ArtMethod* HInstructionBuilder::ResolveMethod(uint16_t method_idx, InvokeType in Handle<mirror::ClassLoader> class_loader(hs.NewHandle( soa.Decode<mirror::ClassLoader*>(dex_compilation_unit_->GetClassLoader()))); Handle<mirror::Class> compiling_class(hs.NewHandle(GetCompilingClass())); + // We fetch the referenced class eagerly (that is, the class pointed by in the MethodId + // at method_idx), as `CanAccessResolvedMethod` expects it be be in the dex cache. + Handle<mirror::Class> methods_class(hs.NewHandle(class_linker->ResolveReferencedClassOfMethod( + method_idx, dex_compilation_unit_->GetDexCache(), class_loader))); + + if (UNLIKELY(methods_class.Get() == nullptr)) { + // Clean up any exception left by type resolution. + soa.Self()->ClearException(); + return nullptr; + } ArtMethod* resolved_method = class_linker->ResolveMethod<ClassLinker::kForceICCECheck>( *dex_compilation_unit_->GetDexFile(), @@ -700,47 +721,38 @@ ArtMethod* HInstructionBuilder::ResolveMethod(uint16_t method_idx, InvokeType in DCHECK(Runtime::Current()->IsAotCompiler()); return nullptr; } - ArtMethod* current_method = graph_->GetArtMethod(); - DCHECK(current_method != nullptr); - Handle<mirror::Class> methods_class(hs.NewHandle( - dex_compilation_unit_->GetClassLinker()->ResolveReferencedClassOfMethod(Thread::Current(), - method_idx, - current_method))); - if (methods_class.Get() == nullptr) { - // Invoking a super method requires knowing the actual super class. If we did not resolve - // the compiling method's declaring class (which only happens for ahead of time - // compilation), bail out. - DCHECK(Runtime::Current()->IsAotCompiler()); + if (!methods_class->IsAssignableFrom(compiling_class.Get())) { + // We cannot statically determine the target method. The runtime will throw a + // NoSuchMethodError on this one. return nullptr; + } + ArtMethod* actual_method; + if (methods_class->IsInterface()) { + actual_method = methods_class->FindVirtualMethodForInterfaceSuper( + resolved_method, class_linker->GetImagePointerSize()); } else { - ArtMethod* actual_method; - if (methods_class->IsInterface()) { - actual_method = methods_class->FindVirtualMethodForInterfaceSuper( - resolved_method, class_linker->GetImagePointerSize()); - } else { - uint16_t vtable_index = resolved_method->GetMethodIndex(); - actual_method = compiling_class->GetSuperClass()->GetVTableEntry( - vtable_index, class_linker->GetImagePointerSize()); - } - if (actual_method != resolved_method && - !IsSameDexFile(*actual_method->GetDexFile(), *dex_compilation_unit_->GetDexFile())) { - // The back-end code generator relies on this check in order to ensure that it will not - // attempt to read the dex_cache with a dex_method_index that is not from the correct - // dex_file. If we didn't do this check then the dex_method_index will not be updated in the - // builder, which means that the code-generator (and compiler driver during sharpening and - // inliner, maybe) might invoke an incorrect method. - // TODO: The actual method could still be referenced in the current dex file, so we - // could try locating it. - // TODO: Remove the dex_file restriction. - return nullptr; - } - if (!actual_method->IsInvokable()) { - // Fail if the actual method cannot be invoked. Otherwise, the runtime resolution stub - // could resolve the callee to the wrong method. - return nullptr; - } - resolved_method = actual_method; + uint16_t vtable_index = resolved_method->GetMethodIndex(); + actual_method = compiling_class->GetSuperClass()->GetVTableEntry( + vtable_index, class_linker->GetImagePointerSize()); + } + if (actual_method != resolved_method && + !IsSameDexFile(*actual_method->GetDexFile(), *dex_compilation_unit_->GetDexFile())) { + // The back-end code generator relies on this check in order to ensure that it will not + // attempt to read the dex_cache with a dex_method_index that is not from the correct + // dex_file. If we didn't do this check then the dex_method_index will not be updated in the + // builder, which means that the code-generator (and compiler driver during sharpening and + // inliner, maybe) might invoke an incorrect method. + // TODO: The actual method could still be referenced in the current dex file, so we + // could try locating it. + // TODO: Remove the dex_file restriction. + return nullptr; + } + if (!actual_method->IsInvokable()) { + // Fail if the actual method cannot be invoked. Otherwise, the runtime resolution stub + // could resolve the callee to the wrong method. + return nullptr; } + resolved_method = actual_method; } // Check for incompatible class changes. The class linker has a fast path for @@ -889,26 +901,23 @@ bool HInstructionBuilder::BuildInvoke(const Instruction& instruction, } bool HInstructionBuilder::BuildNewInstance(uint16_t type_index, uint32_t dex_pc) { + ScopedObjectAccess soa(Thread::Current()); + StackHandleScope<1> hs(soa.Self()); + Handle<mirror::DexCache> dex_cache = dex_compilation_unit_->GetDexCache(); + Handle<mirror::Class> resolved_class(hs.NewHandle(dex_cache->GetResolvedType(type_index))); + const DexFile& outer_dex_file = *outer_compilation_unit_->GetDexFile(); + Handle<mirror::DexCache> outer_dex_cache = outer_compilation_unit_->GetDexCache(); + bool finalizable; - bool can_throw = NeedsAccessCheck(type_index, &finalizable); + bool needs_access_check = NeedsAccessCheck(type_index, dex_cache, &finalizable); // Only the non-resolved entrypoint handles the finalizable class case. If we // need access checks, then we haven't resolved the method and the class may // again be finalizable. - QuickEntrypointEnum entrypoint = (finalizable || can_throw) + QuickEntrypointEnum entrypoint = (finalizable || needs_access_check) ? kQuickAllocObject : kQuickAllocObjectInitialized; - ScopedObjectAccess soa(Thread::Current()); - StackHandleScope<3> hs(soa.Self()); - Handle<mirror::DexCache> dex_cache(hs.NewHandle( - dex_compilation_unit_->GetClassLinker()->FindDexCache( - soa.Self(), *dex_compilation_unit_->GetDexFile()))); - Handle<mirror::Class> resolved_class(hs.NewHandle(dex_cache->GetResolvedType(type_index))); - const DexFile& outer_dex_file = *outer_compilation_unit_->GetDexFile(); - Handle<mirror::DexCache> outer_dex_cache(hs.NewHandle( - outer_compilation_unit_->GetClassLinker()->FindDexCache(soa.Self(), outer_dex_file))); - if (outer_dex_cache.Get() != dex_cache.Get()) { // We currently do not support inlining allocations across dex files. return false; @@ -920,8 +929,8 @@ bool HInstructionBuilder::BuildNewInstance(uint16_t type_index, uint32_t dex_pc) outer_dex_file, IsOutermostCompilingClass(type_index), dex_pc, - /*needs_access_check*/ can_throw, - compiler_driver_->CanAssumeTypeIsPresentInDexCache(outer_dex_file, type_index)); + needs_access_check, + compiler_driver_->CanAssumeTypeIsPresentInDexCache(outer_dex_cache, type_index)); AppendInstruction(load_class); HInstruction* cls = load_class; @@ -936,7 +945,7 @@ bool HInstructionBuilder::BuildNewInstance(uint16_t type_index, uint32_t dex_pc) dex_pc, type_index, *dex_compilation_unit_->GetDexFile(), - can_throw, + needs_access_check, finalizable, entrypoint)); return true; @@ -979,13 +988,9 @@ HClinitCheck* HInstructionBuilder::ProcessClinitCheckForInvoke( HInvokeStaticOrDirect::ClinitCheckRequirement* clinit_check_requirement) { const DexFile& outer_dex_file = *outer_compilation_unit_->GetDexFile(); Thread* self = Thread::Current(); - StackHandleScope<4> hs(self); - Handle<mirror::DexCache> dex_cache(hs.NewHandle( - dex_compilation_unit_->GetClassLinker()->FindDexCache( - self, *dex_compilation_unit_->GetDexFile()))); - Handle<mirror::DexCache> outer_dex_cache(hs.NewHandle( - outer_compilation_unit_->GetClassLinker()->FindDexCache( - self, outer_dex_file))); + StackHandleScope<2> hs(self); + Handle<mirror::DexCache> dex_cache = dex_compilation_unit_->GetDexCache(); + Handle<mirror::DexCache> outer_dex_cache = outer_compilation_unit_->GetDexCache(); Handle<mirror::Class> outer_class(hs.NewHandle(GetOutermostCompilingClass())); Handle<mirror::Class> resolved_method_class(hs.NewHandle(resolved_method->GetDeclaringClass())); @@ -1016,7 +1021,7 @@ HClinitCheck* HInstructionBuilder::ProcessClinitCheckForInvoke( is_outer_class, dex_pc, /*needs_access_check*/ false, - compiler_driver_->CanAssumeTypeIsPresentInDexCache(outer_dex_file, storage_index)); + compiler_driver_->CanAssumeTypeIsPresentInDexCache(outer_dex_cache, storage_index)); AppendInstruction(load_class); clinit_check = new (arena_) HClinitCheck(load_class, dex_pc); AppendInstruction(clinit_check); @@ -1091,10 +1096,9 @@ bool HInstructionBuilder::HandleInvoke(HInvoke* invoke, size_t start_index = 0; size_t argument_index = 0; if (invoke->GetOriginalInvokeType() != InvokeType::kStatic) { // Instance call. - HInstruction* arg = LoadLocal(is_range ? register_index : args[0], Primitive::kPrimNot); - HNullCheck* null_check = new (arena_) HNullCheck(arg, invoke->GetDexPc()); - AppendInstruction(null_check); - invoke->SetArgumentAt(0, null_check); + HInstruction* arg = LoadNullCheckedLocal(is_range ? register_index : args[0], + invoke->GetDexPc()); + invoke->SetArgumentAt(0, arg); start_index = 1; argument_index = 1; } @@ -1200,9 +1204,12 @@ bool HInstructionBuilder::BuildInstanceFieldAccess(const Instruction& instructio compiler_driver_->ComputeInstanceFieldInfo(field_index, dex_compilation_unit_, is_put, soa); - HInstruction* object = LoadLocal(obj_reg, Primitive::kPrimNot); - HInstruction* null_check = new (arena_) HNullCheck(object, dex_pc); - AppendInstruction(null_check); + // Generate an explicit null check on the reference, unless the field access + // is unresolved. In that case, we rely on the runtime to perform various + // checks first, followed by a null check. + HInstruction* object = (resolved_field == nullptr) + ? LoadLocal(obj_reg, Primitive::kPrimNot) + : LoadNullCheckedLocal(obj_reg, dex_pc); Primitive::Type field_type = (resolved_field == nullptr) ? GetFieldAccessType(*dex_file_, field_index) @@ -1212,14 +1219,14 @@ bool HInstructionBuilder::BuildInstanceFieldAccess(const Instruction& instructio HInstruction* field_set = nullptr; if (resolved_field == nullptr) { MaybeRecordStat(MethodCompilationStat::kUnresolvedField); - field_set = new (arena_) HUnresolvedInstanceFieldSet(null_check, + field_set = new (arena_) HUnresolvedInstanceFieldSet(object, value, field_type, field_index, dex_pc); } else { uint16_t class_def_index = resolved_field->GetDeclaringClass()->GetDexClassDefIndex(); - field_set = new (arena_) HInstanceFieldSet(null_check, + field_set = new (arena_) HInstanceFieldSet(object, value, field_type, resolved_field->GetOffset(), @@ -1235,13 +1242,13 @@ bool HInstructionBuilder::BuildInstanceFieldAccess(const Instruction& instructio HInstruction* field_get = nullptr; if (resolved_field == nullptr) { MaybeRecordStat(MethodCompilationStat::kUnresolvedField); - field_get = new (arena_) HUnresolvedInstanceFieldGet(null_check, + field_get = new (arena_) HUnresolvedInstanceFieldGet(object, field_type, field_index, dex_pc); } else { uint16_t class_def_index = resolved_field->GetDeclaringClass()->GetDexClassDefIndex(); - field_get = new (arena_) HInstanceFieldGet(null_check, + field_get = new (arena_) HInstanceFieldGet(object, field_type, resolved_field->GetOffset(), resolved_field->IsVolatile(), @@ -1261,12 +1268,10 @@ bool HInstructionBuilder::BuildInstanceFieldAccess(const Instruction& instructio static mirror::Class* GetClassFrom(CompilerDriver* driver, const DexCompilationUnit& compilation_unit) { ScopedObjectAccess soa(Thread::Current()); - StackHandleScope<2> hs(soa.Self()); - const DexFile& dex_file = *compilation_unit.GetDexFile(); + StackHandleScope<1> hs(soa.Self()); Handle<mirror::ClassLoader> class_loader(hs.NewHandle( soa.Decode<mirror::ClassLoader*>(compilation_unit.GetClassLoader()))); - Handle<mirror::DexCache> dex_cache(hs.NewHandle( - compilation_unit.GetClassLinker()->FindDexCache(soa.Self(), dex_file))); + Handle<mirror::DexCache> dex_cache = compilation_unit.GetDexCache(); return driver->ResolveCompilingMethodsClass(soa, dex_cache, class_loader, &compilation_unit); } @@ -1281,10 +1286,8 @@ mirror::Class* HInstructionBuilder::GetCompilingClass() const { bool HInstructionBuilder::IsOutermostCompilingClass(uint16_t type_index) const { ScopedObjectAccess soa(Thread::Current()); - StackHandleScope<4> hs(soa.Self()); - Handle<mirror::DexCache> dex_cache(hs.NewHandle( - dex_compilation_unit_->GetClassLinker()->FindDexCache( - soa.Self(), *dex_compilation_unit_->GetDexFile()))); + StackHandleScope<3> hs(soa.Self()); + Handle<mirror::DexCache> dex_cache = dex_compilation_unit_->GetDexCache(); Handle<mirror::ClassLoader> class_loader(hs.NewHandle( soa.Decode<mirror::ClassLoader*>(dex_compilation_unit_->GetClassLoader()))); Handle<mirror::Class> cls(hs.NewHandle(compiler_driver_->ResolveClass( @@ -1324,10 +1327,8 @@ bool HInstructionBuilder::BuildStaticFieldAccess(const Instruction& instruction, uint16_t field_index = instruction.VRegB_21c(); ScopedObjectAccess soa(Thread::Current()); - StackHandleScope<5> hs(soa.Self()); - Handle<mirror::DexCache> dex_cache(hs.NewHandle( - dex_compilation_unit_->GetClassLinker()->FindDexCache( - soa.Self(), *dex_compilation_unit_->GetDexFile()))); + StackHandleScope<3> hs(soa.Self()); + Handle<mirror::DexCache> dex_cache = dex_compilation_unit_->GetDexCache(); Handle<mirror::ClassLoader> class_loader(hs.NewHandle( soa.Decode<mirror::ClassLoader*>(dex_compilation_unit_->GetClassLoader()))); ArtField* resolved_field = compiler_driver_->ResolveField( @@ -1342,8 +1343,7 @@ bool HInstructionBuilder::BuildStaticFieldAccess(const Instruction& instruction, Primitive::Type field_type = resolved_field->GetTypeAsPrimitiveType(); const DexFile& outer_dex_file = *outer_compilation_unit_->GetDexFile(); - Handle<mirror::DexCache> outer_dex_cache(hs.NewHandle( - outer_compilation_unit_->GetClassLinker()->FindDexCache(soa.Self(), outer_dex_file))); + Handle<mirror::DexCache> outer_dex_cache = outer_compilation_unit_->GetDexCache(); Handle<mirror::Class> outer_class(hs.NewHandle(GetOutermostCompilingClass())); // The index at which the field's class is stored in the DexCache's type array. @@ -1371,7 +1371,7 @@ bool HInstructionBuilder::BuildStaticFieldAccess(const Instruction& instruction, } bool is_in_cache = - compiler_driver_->CanAssumeTypeIsPresentInDexCache(outer_dex_file, storage_index); + compiler_driver_->CanAssumeTypeIsPresentInDexCache(outer_dex_cache, storage_index); HLoadClass* constant = new (arena_) HLoadClass(graph_->GetCurrentMethod(), storage_index, outer_dex_file, @@ -1463,10 +1463,7 @@ void HInstructionBuilder::BuildArrayAccess(const Instruction& instruction, uint8_t array_reg = instruction.VRegB_23x(); uint8_t index_reg = instruction.VRegC_23x(); - HInstruction* object = LoadLocal(array_reg, Primitive::kPrimNot); - object = new (arena_) HNullCheck(object, dex_pc); - AppendInstruction(object); - + HInstruction* object = LoadNullCheckedLocal(array_reg, dex_pc); HInstruction* length = new (arena_) HArrayLength(object, dex_pc); AppendInstruction(length); HInstruction* index = LoadLocal(index_reg, Primitive::kPrimInt); @@ -1541,11 +1538,8 @@ void HInstructionBuilder::BuildFillArrayData(HInstruction* object, } void HInstructionBuilder::BuildFillArrayData(const Instruction& instruction, uint32_t dex_pc) { - HInstruction* array = LoadLocal(instruction.VRegA_31t(), Primitive::kPrimNot); - HNullCheck* null_check = new (arena_) HNullCheck(array, dex_pc); - AppendInstruction(null_check); - - HInstruction* length = new (arena_) HArrayLength(null_check, dex_pc); + HInstruction* array = LoadNullCheckedLocal(instruction.VRegA_31t(), dex_pc); + HInstruction* length = new (arena_) HArrayLength(array, dex_pc); AppendInstruction(length); int32_t payload_offset = instruction.VRegB_31t() + dex_pc; @@ -1561,28 +1555,28 @@ void HInstructionBuilder::BuildFillArrayData(const Instruction& instruction, uin switch (payload->element_width) { case 1: - BuildFillArrayData(null_check, + BuildFillArrayData(array, reinterpret_cast<const int8_t*>(data), element_count, Primitive::kPrimByte, dex_pc); break; case 2: - BuildFillArrayData(null_check, + BuildFillArrayData(array, reinterpret_cast<const int16_t*>(data), element_count, Primitive::kPrimShort, dex_pc); break; case 4: - BuildFillArrayData(null_check, + BuildFillArrayData(array, reinterpret_cast<const int32_t*>(data), element_count, Primitive::kPrimInt, dex_pc); break; case 8: - BuildFillWideArrayData(null_check, + BuildFillWideArrayData(array, reinterpret_cast<const int64_t*>(data), element_count, dex_pc); @@ -1634,22 +1628,17 @@ void HInstructionBuilder::BuildTypeCheck(const Instruction& instruction, uint8_t reference, uint16_t type_index, uint32_t dex_pc) { - bool type_known_final, type_known_abstract, use_declaring_class; - bool can_access = compiler_driver_->CanAccessTypeWithoutChecks( - dex_compilation_unit_->GetDexMethodIndex(), - *dex_compilation_unit_->GetDexFile(), - type_index, - &type_known_final, - &type_known_abstract, - &use_declaring_class); - ScopedObjectAccess soa(Thread::Current()); - StackHandleScope<2> hs(soa.Self()); + StackHandleScope<1> hs(soa.Self()); const DexFile& dex_file = *dex_compilation_unit_->GetDexFile(); - Handle<mirror::DexCache> dex_cache(hs.NewHandle( - dex_compilation_unit_->GetClassLinker()->FindDexCache(soa.Self(), dex_file))); + Handle<mirror::DexCache> dex_cache = dex_compilation_unit_->GetDexCache(); Handle<mirror::Class> resolved_class(hs.NewHandle(dex_cache->GetResolvedType(type_index))); + bool can_access = compiler_driver_->CanAccessTypeWithoutChecks( + dex_compilation_unit_->GetDexMethodIndex(), + dex_cache, + type_index); + HInstruction* object = LoadLocal(reference, Primitive::kPrimNot); HLoadClass* cls = new (arena_) HLoadClass( graph_->GetCurrentMethod(), @@ -1658,7 +1647,7 @@ void HInstructionBuilder::BuildTypeCheck(const Instruction& instruction, IsOutermostCompilingClass(type_index), dex_pc, !can_access, - compiler_driver_->CanAssumeTypeIsPresentInDexCache(dex_file, type_index)); + compiler_driver_->CanAssumeTypeIsPresentInDexCache(dex_cache, type_index)); AppendInstruction(cls); TypeCheckKind check_kind = ComputeTypeCheckKind(resolved_class); @@ -1676,9 +1665,17 @@ void HInstructionBuilder::BuildTypeCheck(const Instruction& instruction, } } -bool HInstructionBuilder::NeedsAccessCheck(uint32_t type_index, bool* finalizable) const { +bool HInstructionBuilder::NeedsAccessCheck(uint32_t type_index, + Handle<mirror::DexCache> dex_cache, + bool* finalizable) const { return !compiler_driver_->CanAccessInstantiableTypeWithoutChecks( - dex_compilation_unit_->GetDexMethodIndex(), *dex_file_, type_index, finalizable); + dex_compilation_unit_->GetDexMethodIndex(), dex_cache, type_index, finalizable); +} + +bool HInstructionBuilder::NeedsAccessCheck(uint32_t type_index, bool* finalizable) const { + ScopedObjectAccess soa(Thread::Current()); + Handle<mirror::DexCache> dex_cache = dex_compilation_unit_->GetDexCache(); + return NeedsAccessCheck(type_index, dex_cache, finalizable); } bool HInstructionBuilder::CanDecodeQuickenedInfo() const { @@ -2586,9 +2583,7 @@ bool HInstructionBuilder::ProcessDexInstruction(const Instruction& instruction, ARRAY_XX(_SHORT, Primitive::kPrimShort); case Instruction::ARRAY_LENGTH: { - HInstruction* object = LoadLocal(instruction.VRegB_12x(), Primitive::kPrimNot); - object = new (arena_) HNullCheck(object, dex_pc); - AppendInstruction(object); + HInstruction* object = LoadNullCheckedLocal(instruction.VRegB_12x(), dex_pc); AppendInstruction(new (arena_) HArrayLength(object, dex_pc)); UpdateLocal(instruction.VRegA_12x(), current_block_->GetLastInstruction()); break; @@ -2612,16 +2607,16 @@ bool HInstructionBuilder::ProcessDexInstruction(const Instruction& instruction, case Instruction::CONST_CLASS: { uint16_t type_index = instruction.VRegB_21c(); - bool type_known_final; - bool type_known_abstract; - bool dont_use_is_referrers_class; // `CanAccessTypeWithoutChecks` will tell whether the method being // built is trying to access its own class, so that the generated // code can optimize for this case. However, the optimization does not // work for inlining, so we use `IsOutermostCompilingClass` instead. + ScopedObjectAccess soa(Thread::Current()); + Handle<mirror::DexCache> dex_cache = dex_compilation_unit_->GetDexCache(); bool can_access = compiler_driver_->CanAccessTypeWithoutChecks( - dex_compilation_unit_->GetDexMethodIndex(), *dex_file_, type_index, - &type_known_final, &type_known_abstract, &dont_use_is_referrers_class); + dex_compilation_unit_->GetDexMethodIndex(), dex_cache, type_index); + bool is_in_dex_cache = + compiler_driver_->CanAssumeTypeIsPresentInDexCache(dex_cache, type_index); AppendInstruction(new (arena_) HLoadClass( graph_->GetCurrentMethod(), type_index, @@ -2629,7 +2624,7 @@ bool HInstructionBuilder::ProcessDexInstruction(const Instruction& instruction, IsOutermostCompilingClass(type_index), dex_pc, !can_access, - compiler_driver_->CanAssumeTypeIsPresentInDexCache(*dex_file_, type_index))); + is_in_dex_cache)); UpdateLocal(instruction.VRegA_21c(), current_block_->GetLastInstruction()); break; } diff --git a/compiler/optimizing/instruction_builder.h b/compiler/optimizing/instruction_builder.h index f480b70062..0e3e5a7c34 100644 --- a/compiler/optimizing/instruction_builder.h +++ b/compiler/optimizing/instruction_builder.h @@ -87,6 +87,7 @@ class HInstructionBuilder : public ValueObject { ArenaVector<HInstruction*>* GetLocalsFor(HBasicBlock* block); HInstruction* ValueOfLocalAt(HBasicBlock* block, size_t local); HInstruction* LoadLocal(uint32_t register_index, Primitive::Type type) const; + HInstruction* LoadNullCheckedLocal(uint32_t register_index, uint32_t dex_pc); void UpdateLocal(uint32_t register_index, HInstruction* instruction); void AppendInstruction(HInstruction* instruction); @@ -97,6 +98,10 @@ class HInstructionBuilder : public ValueObject { // Returns whether the current method needs access check for the type. // Output parameter finalizable is set to whether the type is finalizable. + bool NeedsAccessCheck(uint32_t type_index, + Handle<mirror::DexCache> dex_cache, + /*out*/bool* finalizable) const + SHARED_REQUIRES(Locks::mutator_lock_); bool NeedsAccessCheck(uint32_t type_index, /*out*/bool* finalizable) const; template<typename T> diff --git a/compiler/optimizing/instruction_simplifier.cc b/compiler/optimizing/instruction_simplifier.cc index 1f66db7909..eb1d1560db 100644 --- a/compiler/optimizing/instruction_simplifier.cc +++ b/compiler/optimizing/instruction_simplifier.cc @@ -101,6 +101,7 @@ class InstructionSimplifierVisitor : public HGraphDelegateVisitor { void SimplifyCompare(HInvoke* invoke, bool is_signum, Primitive::Type type); void SimplifyIsNaN(HInvoke* invoke); void SimplifyFP2Int(HInvoke* invoke); + void SimplifyStringIsEmptyOrLength(HInvoke* invoke); void SimplifyMemBarrier(HInvoke* invoke, MemBarrierKind barrier_kind); OptimizingCompilerStats* stats_; @@ -249,6 +250,7 @@ void InstructionSimplifierVisitor::VisitShift(HBinaryOperation* instruction) { // src instruction->ReplaceWith(input_other); instruction->GetBlock()->RemoveInstruction(instruction); + RecordSimplification(); } } } @@ -277,6 +279,7 @@ bool InstructionSimplifierVisitor::ReplaceRotateWithRor(HBinaryOperation* op, if (!shl->GetRight()->HasUses()) { shl->GetRight()->GetBlock()->RemoveInstruction(shl->GetRight()); } + RecordSimplification(); return true; } @@ -409,9 +412,9 @@ bool InstructionSimplifierVisitor::CanEnsureNotNullAt(HInstruction* input, HInst return true; } - for (HUseIterator<HInstruction*> it(input->GetUses()); !it.Done(); it.Advance()) { - HInstruction* use = it.Current()->GetUser(); - if (use->IsNullCheck() && use->StrictlyDominates(at)) { + for (const HUseListNode<HInstruction*>& use : input->GetUses()) { + HInstruction* user = use.GetUser(); + if (user->IsNullCheck() && user->StrictlyDominates(at)) { return true; } } @@ -906,6 +909,7 @@ void InstructionSimplifierVisitor::VisitAdd(HAdd* instruction) { if (Primitive::IsIntegralType(instruction->GetType())) { instruction->ReplaceWith(input_other); instruction->GetBlock()->RemoveInstruction(instruction); + RecordSimplification(); return; } } @@ -998,6 +1002,7 @@ void InstructionSimplifierVisitor::VisitAnd(HAnd* instruction) { // src instruction->ReplaceWith(instruction->GetLeft()); instruction->GetBlock()->RemoveInstruction(instruction); + RecordSimplification(); return; } @@ -1070,12 +1075,12 @@ void InstructionSimplifierVisitor::VisitCondition(HCondition* condition) { } // Is the Compare only used for this purpose? - if (!left->GetUses().HasOnlyOneUse()) { + if (!left->GetUses().HasExactlyOneElement()) { // Someone else also wants the result of the compare. return; } - if (!left->GetEnvUses().IsEmpty()) { + if (!left->GetEnvUses().empty()) { // There is a reference to the compare result in an environment. Do we really need it? if (GetGraph()->IsDebuggable()) { return; @@ -1115,6 +1120,7 @@ void InstructionSimplifierVisitor::VisitDiv(HDiv* instruction) { // src instruction->ReplaceWith(input_other); instruction->GetBlock()->RemoveInstruction(instruction); + RecordSimplification(); return; } @@ -1175,6 +1181,7 @@ void InstructionSimplifierVisitor::VisitMul(HMul* instruction) { // src instruction->ReplaceWith(input_other); instruction->GetBlock()->RemoveInstruction(instruction); + RecordSimplification(); return; } @@ -1215,6 +1222,7 @@ void InstructionSimplifierVisitor::VisitMul(HMul* instruction) { // 0 instruction->ReplaceWith(input_cst); instruction->GetBlock()->RemoveInstruction(instruction); + RecordSimplification(); } else if (IsPowerOfTwo(factor)) { // Replace code looking like // MUL dst, src, pow_of_2 @@ -1333,6 +1341,7 @@ void InstructionSimplifierVisitor::VisitOr(HOr* instruction) { // src instruction->ReplaceWith(input_other); instruction->GetBlock()->RemoveInstruction(instruction); + RecordSimplification(); return; } @@ -1346,6 +1355,7 @@ void InstructionSimplifierVisitor::VisitOr(HOr* instruction) { // src instruction->ReplaceWith(instruction->GetLeft()); instruction->GetBlock()->RemoveInstruction(instruction); + RecordSimplification(); return; } @@ -1381,6 +1391,7 @@ void InstructionSimplifierVisitor::VisitSub(HSub* instruction) { // yields `-0.0`. instruction->ReplaceWith(input_other); instruction->GetBlock()->RemoveInstruction(instruction); + RecordSimplification(); return; } @@ -1459,6 +1470,7 @@ void InstructionSimplifierVisitor::VisitXor(HXor* instruction) { // src instruction->ReplaceWith(input_other); instruction->GetBlock()->RemoveInstruction(instruction); + RecordSimplification(); return; } @@ -1538,7 +1550,7 @@ void InstructionSimplifierVisitor::SimplifyRotate(HInvoke* invoke, HRor* ror = new (GetGraph()->GetArena()) HRor(type, value, distance); invoke->GetBlock()->ReplaceAndRemoveInstructionWith(invoke, ror); // Remove ClinitCheck and LoadClass, if possible. - HInstruction* clinit = invoke->InputAt(invoke->InputCount() - 1); + HInstruction* clinit = invoke->GetInputs().back(); if (clinit->IsClinitCheck() && !clinit->HasUses()) { clinit->GetBlock()->RemoveInstruction(clinit); HInstruction* ldclass = clinit->InputAt(0); @@ -1673,6 +1685,27 @@ void InstructionSimplifierVisitor::SimplifyFP2Int(HInvoke* invoke) { invoke->ReplaceWithExceptInReplacementAtIndex(select, 0); // false at index 0 } +void InstructionSimplifierVisitor::SimplifyStringIsEmptyOrLength(HInvoke* invoke) { + HInstruction* str = invoke->InputAt(0); + uint32_t dex_pc = invoke->GetDexPc(); + // We treat String as an array to allow DCE and BCE to seamlessly work on strings, + // so create the HArrayLength. + HArrayLength* length = new (GetGraph()->GetArena()) HArrayLength(str, dex_pc); + length->MarkAsStringLength(); + HInstruction* replacement; + if (invoke->GetIntrinsic() == Intrinsics::kStringIsEmpty) { + // For String.isEmpty(), create the `HEqual` representing the `length == 0`. + invoke->GetBlock()->InsertInstructionBefore(length, invoke); + HIntConstant* zero = GetGraph()->GetIntConstant(0); + HEqual* equal = new (GetGraph()->GetArena()) HEqual(length, zero, dex_pc); + replacement = equal; + } else { + DCHECK_EQ(invoke->GetIntrinsic(), Intrinsics::kStringLength); + replacement = length; + } + invoke->GetBlock()->ReplaceAndRemoveInstructionWith(invoke, replacement); +} + void InstructionSimplifierVisitor::SimplifyMemBarrier(HInvoke* invoke, MemBarrierKind barrier_kind) { uint32_t dex_pc = invoke->GetDexPc(); HMemoryBarrier* mem_barrier = new (GetGraph()->GetArena()) HMemoryBarrier(barrier_kind, dex_pc); @@ -1719,6 +1752,10 @@ void InstructionSimplifierVisitor::VisitInvoke(HInvoke* instruction) { case Intrinsics::kDoubleDoubleToLongBits: SimplifyFP2Int(instruction); break; + case Intrinsics::kStringIsEmpty: + case Intrinsics::kStringLength: + SimplifyStringIsEmptyOrLength(instruction); + break; case Intrinsics::kUnsafeLoadFence: SimplifyMemBarrier(instruction, MemBarrierKind::kLoadAny); break; diff --git a/compiler/optimizing/instruction_simplifier_arm64.cc b/compiler/optimizing/instruction_simplifier_arm64.cc index f00d960877..e4a711ec83 100644 --- a/compiler/optimizing/instruction_simplifier_arm64.cc +++ b/compiler/optimizing/instruction_simplifier_arm64.cc @@ -140,7 +140,7 @@ bool InstructionSimplifierArm64Visitor::TryMergeIntoShifterOperand(HInstruction* shift_amount, use->GetDexPc()); use->GetBlock()->ReplaceAndRemoveInstructionWith(use, alu_with_op); - if (bitfield_op->GetUses().IsEmpty()) { + if (bitfield_op->GetUses().empty()) { bitfield_op->GetBlock()->RemoveInstruction(bitfield_op); } RecordSimplification(); @@ -160,20 +160,22 @@ bool InstructionSimplifierArm64Visitor::TryMergeIntoUsersShifterOperand(HInstruc const HUseList<HInstruction*>& uses = bitfield_op->GetUses(); // Check whether we can merge the instruction in all its users' shifter operand. - for (HUseIterator<HInstruction*> it_use(uses); !it_use.Done(); it_use.Advance()) { - HInstruction* use = it_use.Current()->GetUser(); - if (!HasShifterOperand(use)) { + for (const HUseListNode<HInstruction*>& use : uses) { + HInstruction* user = use.GetUser(); + if (!HasShifterOperand(user)) { return false; } - if (!CanMergeIntoShifterOperand(use, bitfield_op)) { + if (!CanMergeIntoShifterOperand(user, bitfield_op)) { return false; } } // Merge the instruction into its uses. - for (HUseIterator<HInstruction*> it_use(uses); !it_use.Done(); it_use.Advance()) { - HInstruction* use = it_use.Current()->GetUser(); - bool merged = MergeIntoShifterOperand(use, bitfield_op); + for (auto it = uses.begin(), end = uses.end(); it != end; /* ++it below */) { + HInstruction* user = it->GetUser(); + // Increment `it` now because `*it` will disappear thanks to MergeIntoShifterOperand(). + ++it; + bool merged = MergeIntoShifterOperand(user, bitfield_op); DCHECK(merged); } diff --git a/compiler/optimizing/instruction_simplifier_shared.cc b/compiler/optimizing/instruction_simplifier_shared.cc index a11b5bd5c3..dab1ebc16d 100644 --- a/compiler/optimizing/instruction_simplifier_shared.cc +++ b/compiler/optimizing/instruction_simplifier_shared.cc @@ -103,13 +103,10 @@ bool TryCombineMultiplyAccumulate(HMul* mul, InstructionSet isa) { return false; } - HInstruction* use = mul->HasNonEnvironmentUses() - ? mul->GetUses().GetFirst()->GetUser() - : nullptr; - ArenaAllocator* arena = mul->GetBlock()->GetGraph()->GetArena(); if (mul->HasOnlyOneNonEnvironmentUse()) { + HInstruction* use = mul->GetUses().front().GetUser(); if (use->IsAdd() || use->IsSub()) { // Replace code looking like // MUL tmp, x, y diff --git a/compiler/optimizing/intrinsics.cc b/compiler/optimizing/intrinsics.cc index 5d4c4e2950..418d59c6cb 100644 --- a/compiler/optimizing/intrinsics.cc +++ b/compiler/optimizing/intrinsics.cc @@ -388,10 +388,8 @@ static Intrinsics GetIntrinsic(InlineMethod method) { 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; + return ((method.d.data & kIntrinsicFlagIsEmpty) == 0) ? + Intrinsics::kStringLength : Intrinsics::kStringIsEmpty; case kIntrinsicIndexOf: return ((method.d.data & kIntrinsicFlagBase0) == 0) ? Intrinsics::kStringIndexOfAfter : Intrinsics::kStringIndexOf; diff --git a/compiler/optimizing/intrinsics.h b/compiler/optimizing/intrinsics.h index 863dd1c6f6..83a512738b 100644 --- a/compiler/optimizing/intrinsics.h +++ b/compiler/optimizing/intrinsics.h @@ -30,6 +30,10 @@ class DexFile; // Temporary measure until we have caught up with the Java 7 definition of Math.round. b/26327751 static constexpr bool kRoundIsPlusPointFive = false; +// Positive floating-point infinities. +static constexpr uint32_t kPositiveInfinityFloat = 0x7f800000U; +static constexpr uint64_t kPositiveInfinityDouble = UINT64_C(0x7ff0000000000000); + // Recognize intrinsics from HInvoke nodes. class IntrinsicsRecognizer : public HOptimization { public: @@ -161,7 +165,7 @@ public: \ void Set##name() { SetBit(k##name); } \ bool Get##name() const { return IsBitSet(k##name); } \ private: \ -static constexpr size_t k##name = bit + kNumberOfGenericOptimizations +static constexpr size_t k##name = (bit) + kNumberOfGenericOptimizations class StringEqualsOptimizations : public IntrinsicOptimizations { public: @@ -235,6 +239,8 @@ UNREACHABLE_INTRINSIC(Arch, IntegerCompare) \ UNREACHABLE_INTRINSIC(Arch, LongCompare) \ UNREACHABLE_INTRINSIC(Arch, IntegerSignum) \ UNREACHABLE_INTRINSIC(Arch, LongSignum) \ +UNREACHABLE_INTRINSIC(Arch, StringIsEmpty) \ +UNREACHABLE_INTRINSIC(Arch, StringLength) \ UNREACHABLE_INTRINSIC(Arch, UnsafeLoadFence) \ UNREACHABLE_INTRINSIC(Arch, UnsafeStoreFence) \ UNREACHABLE_INTRINSIC(Arch, UnsafeFullFence) diff --git a/compiler/optimizing/intrinsics_arm.cc b/compiler/optimizing/intrinsics_arm.cc index 86b7bc138c..29f7672b0a 100644 --- a/compiler/optimizing/intrinsics_arm.cc +++ b/compiler/optimizing/intrinsics_arm.cc @@ -987,31 +987,126 @@ void IntrinsicCodeGeneratorARM::VisitStringCharAt(HInvoke* invoke) { void IntrinsicLocationsBuilderARM::VisitStringCompareTo(HInvoke* invoke) { // The inputs plus one temp. LocationSummary* locations = new (arena_) LocationSummary(invoke, - LocationSummary::kCall, + invoke->InputAt(1)->CanBeNull() + ? LocationSummary::kCallOnSlowPath + : LocationSummary::kNoCall, kIntrinsified); - InvokeRuntimeCallingConvention calling_convention; - locations->SetInAt(0, Location::RegisterLocation(calling_convention.GetRegisterAt(0))); - locations->SetInAt(1, Location::RegisterLocation(calling_convention.GetRegisterAt(1))); - locations->SetOut(Location::RegisterLocation(R0)); + locations->SetInAt(0, Location::RequiresRegister()); + locations->SetInAt(1, Location::RequiresRegister()); + locations->AddTemp(Location::RequiresRegister()); + locations->AddTemp(Location::RequiresRegister()); + locations->AddTemp(Location::RequiresRegister()); + locations->SetOut(Location::RequiresRegister(), Location::kOutputOverlap); } void IntrinsicCodeGeneratorARM::VisitStringCompareTo(HInvoke* invoke) { ArmAssembler* assembler = GetAssembler(); LocationSummary* locations = invoke->GetLocations(); + Register str = locations->InAt(0).AsRegister<Register>(); + Register arg = locations->InAt(1).AsRegister<Register>(); + Register out = locations->Out().AsRegister<Register>(); + + Register temp0 = locations->GetTemp(0).AsRegister<Register>(); + Register temp1 = locations->GetTemp(1).AsRegister<Register>(); + Register temp2 = locations->GetTemp(2).AsRegister<Register>(); + + Label loop; + Label find_char_diff; + Label end; + + // Get offsets of count and value fields within a string object. + const int32_t count_offset = mirror::String::CountOffset().Int32Value(); + const int32_t value_offset = mirror::String::ValueOffset().Int32Value(); + // Note that the null check must have been done earlier. DCHECK(!invoke->CanDoImplicitNullCheckOn(invoke->InputAt(0))); - Register argument = locations->InAt(1).AsRegister<Register>(); - __ cmp(argument, ShifterOperand(0)); - SlowPathCode* slow_path = new (GetAllocator()) IntrinsicSlowPathARM(invoke); - codegen_->AddSlowPath(slow_path); - __ b(slow_path->GetEntryLabel(), EQ); + // Take slow path and throw if input can be and is null. + SlowPathCode* slow_path = nullptr; + const bool can_slow_path = invoke->InputAt(1)->CanBeNull(); + if (can_slow_path) { + slow_path = new (GetAllocator()) IntrinsicSlowPathARM(invoke); + codegen_->AddSlowPath(slow_path); + __ CompareAndBranchIfZero(arg, slow_path->GetEntryLabel()); + } - __ LoadFromOffset( - kLoadWord, LR, TR, QUICK_ENTRYPOINT_OFFSET(kArmWordSize, pStringCompareTo).Int32Value()); - __ blx(LR); - __ Bind(slow_path->GetExitLabel()); + // Reference equality check, return 0 if same reference. + __ subs(out, str, ShifterOperand(arg)); + __ b(&end, EQ); + // Load lengths of this and argument strings. + __ ldr(temp2, Address(str, count_offset)); + __ ldr(temp1, Address(arg, count_offset)); + // out = length diff. + __ subs(out, temp2, ShifterOperand(temp1)); + // temp0 = min(len(str), len(arg)). + __ it(Condition::LT, kItElse); + __ mov(temp0, ShifterOperand(temp2), Condition::LT); + __ mov(temp0, ShifterOperand(temp1), Condition::GE); + // Shorter string is empty? + __ CompareAndBranchIfZero(temp0, &end); + + // Store offset of string value in preparation for comparison loop. + __ mov(temp1, ShifterOperand(value_offset)); + + // Assertions that must hold in order to compare multiple characters at a time. + CHECK_ALIGNED(value_offset, 8); + static_assert(IsAligned<8>(kObjectAlignment), + "String data must be 8-byte aligned for unrolled CompareTo loop."); + + const size_t char_size = Primitive::ComponentSize(Primitive::kPrimChar); + DCHECK_EQ(char_size, 2u); + + // Unrolled loop comparing 4x16-bit chars per iteration (ok because of string data alignment). + __ Bind(&loop); + __ ldr(IP, Address(str, temp1)); + __ ldr(temp2, Address(arg, temp1)); + __ cmp(IP, ShifterOperand(temp2)); + __ b(&find_char_diff, NE); + __ add(temp1, temp1, ShifterOperand(char_size * 2)); + __ sub(temp0, temp0, ShifterOperand(2)); + + __ ldr(IP, Address(str, temp1)); + __ ldr(temp2, Address(arg, temp1)); + __ cmp(IP, ShifterOperand(temp2)); + __ b(&find_char_diff, NE); + __ add(temp1, temp1, ShifterOperand(char_size * 2)); + __ subs(temp0, temp0, ShifterOperand(2)); + + __ b(&loop, GT); + __ b(&end); + + // Find the single 16-bit character difference. + __ Bind(&find_char_diff); + // Get the bit position of the first character that differs. + __ eor(temp1, temp2, ShifterOperand(IP)); + __ rbit(temp1, temp1); + __ clz(temp1, temp1); + + // temp0 = number of 16-bit characters remaining to compare. + // (it could be < 1 if a difference is found after the first SUB in the comparison loop, and + // after the end of the shorter string data). + + // (temp1 >> 4) = character where difference occurs between the last two words compared, on the + // interval [0,1] (0 for low half-word different, 1 for high half-word different). + + // If temp0 <= (temp1 >> 4), the difference occurs outside the remaining string data, so just + // return length diff (out). + __ cmp(temp0, ShifterOperand(temp1, LSR, 4)); + __ b(&end, LE); + // Extract the characters and calculate the difference. + __ bic(temp1, temp1, ShifterOperand(0xf)); + __ Lsr(temp2, temp2, temp1); + __ Lsr(IP, IP, temp1); + __ movt(temp2, 0); + __ movt(IP, 0); + __ sub(out, IP, ShifterOperand(temp2)); + + __ Bind(&end); + + if (can_slow_path) { + __ Bind(slow_path->GetExitLabel()); + } } void IntrinsicLocationsBuilderARM::VisitStringEquals(HInvoke* invoke) { @@ -1055,17 +1150,22 @@ void IntrinsicCodeGeneratorARM::VisitStringEquals(HInvoke* invoke) { // Note that the null check must have been done earlier. DCHECK(!invoke->CanDoImplicitNullCheckOn(invoke->InputAt(0))); - // Check if input is null, return false if it is. - __ CompareAndBranchIfZero(arg, &return_false); + StringEqualsOptimizations optimizations(invoke); + if (!optimizations.GetArgumentNotNull()) { + // Check if input is null, return false if it is. + __ CompareAndBranchIfZero(arg, &return_false); + } - // Instanceof check for the argument by comparing class fields. - // All string objects must have the same type since String cannot be subclassed. - // Receiver must be a string object, so its class field is equal to all strings' class fields. - // If the argument is a string object, its class field must be equal to receiver's class field. - __ ldr(temp, Address(str, class_offset)); - __ ldr(temp1, Address(arg, class_offset)); - __ cmp(temp, ShifterOperand(temp1)); - __ b(&return_false, NE); + if (!optimizations.GetArgumentIsString()) { + // Instanceof check for the argument by comparing class fields. + // All string objects must have the same type since String cannot be subclassed. + // Receiver must be a string object, so its class field is equal to all strings' class fields. + // If the argument is a string object, its class field must be equal to receiver's class field. + __ ldr(temp, Address(str, class_offset)); + __ ldr(temp1, Address(arg, class_offset)); + __ cmp(temp, ShifterOperand(temp1)); + __ b(&return_false, NE); + } // Load lengths of this and argument strings. __ ldr(temp, Address(str, count_offset)); @@ -1082,7 +1182,7 @@ void IntrinsicCodeGeneratorARM::VisitStringEquals(HInvoke* invoke) { // Assertions that must hold in order to compare strings 2 characters at a time. DCHECK_ALIGNED(value_offset, 4); - static_assert(IsAligned<4>(kObjectAlignment), "String of odd length is not zero padded"); + static_assert(IsAligned<4>(kObjectAlignment), "String data must be aligned for fast compare."); __ LoadImmediate(temp1, value_offset); @@ -1115,16 +1215,16 @@ static void GenerateVisitStringIndexOf(HInvoke* invoke, ArenaAllocator* allocator, bool start_at_zero) { LocationSummary* locations = invoke->GetLocations(); - Register tmp_reg = locations->GetTemp(0).AsRegister<Register>(); // Note that the null check must have been done earlier. DCHECK(!invoke->CanDoImplicitNullCheckOn(invoke->InputAt(0))); // Check for code points > 0xFFFF. Either a slow-path check when we don't know statically, - // or directly dispatch if we have a constant. + // or directly dispatch for a large constant, or omit slow-path for a small constant or a char. SlowPathCode* slow_path = nullptr; - if (invoke->InputAt(1)->IsIntConstant()) { - if (static_cast<uint32_t>(invoke->InputAt(1)->AsIntConstant()->GetValue()) > + HInstruction* code_point = invoke->InputAt(1); + if (code_point->IsIntConstant()) { + if (static_cast<uint32_t>(code_point->AsIntConstant()->GetValue()) > std::numeric_limits<uint16_t>::max()) { // Always needs the slow-path. We could directly dispatch to it, but this case should be // rare, so for simplicity just put the full slow-path down and branch unconditionally. @@ -1134,16 +1234,18 @@ static void GenerateVisitStringIndexOf(HInvoke* invoke, __ Bind(slow_path->GetExitLabel()); return; } - } else { + } else if (code_point->GetType() != Primitive::kPrimChar) { Register char_reg = locations->InAt(1).AsRegister<Register>(); - __ LoadImmediate(tmp_reg, std::numeric_limits<uint16_t>::max()); - __ cmp(char_reg, ShifterOperand(tmp_reg)); + // 0xffff is not modified immediate but 0x10000 is, so use `>= 0x10000` instead of `> 0xffff`. + __ cmp(char_reg, + ShifterOperand(static_cast<uint32_t>(std::numeric_limits<uint16_t>::max()) + 1)); slow_path = new (allocator) IntrinsicSlowPathARM(invoke); codegen->AddSlowPath(slow_path); - __ b(slow_path->GetEntryLabel(), HI); + __ b(slow_path->GetEntryLabel(), HS); } if (start_at_zero) { + Register tmp_reg = locations->GetTemp(0).AsRegister<Register>(); DCHECK_EQ(tmp_reg, R2); // Start-index = 0. __ LoadImmediate(tmp_reg, 0); @@ -1170,7 +1272,7 @@ void IntrinsicLocationsBuilderARM::VisitStringIndexOf(HInvoke* invoke) { locations->SetInAt(1, Location::RegisterLocation(calling_convention.GetRegisterAt(1))); locations->SetOut(Location::RegisterLocation(R0)); - // Need a temp for slow-path codepoint compare, and need to send start-index=0. + // Need to send start-index=0. locations->AddTemp(Location::RegisterLocation(calling_convention.GetRegisterAt(2))); } @@ -1190,9 +1292,6 @@ void IntrinsicLocationsBuilderARM::VisitStringIndexOfAfter(HInvoke* invoke) { locations->SetInAt(1, Location::RegisterLocation(calling_convention.GetRegisterAt(1))); locations->SetInAt(2, Location::RegisterLocation(calling_convention.GetRegisterAt(2))); locations->SetOut(Location::RegisterLocation(R0)); - - // Need a temp for slow-path codepoint compare. - locations->AddTemp(Location::RequiresRegister()); } void IntrinsicCodeGeneratorARM::VisitStringIndexOfAfter(HInvoke* invoke) { @@ -1985,6 +2084,56 @@ void IntrinsicCodeGeneratorARM::VisitStringGetCharsNoCheck(HInvoke* invoke) { __ Bind(&done); } +void IntrinsicLocationsBuilderARM::VisitFloatIsInfinite(HInvoke* invoke) { + CreateFPToIntLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorARM::VisitFloatIsInfinite(HInvoke* invoke) { + ArmAssembler* const assembler = GetAssembler(); + LocationSummary* const locations = invoke->GetLocations(); + const Register out = locations->Out().AsRegister<Register>(); + // Shifting left by 1 bit makes the value encodable as an immediate operand; + // we don't care about the sign bit anyway. + constexpr uint32_t infinity = kPositiveInfinityFloat << 1U; + + __ vmovrs(out, locations->InAt(0).AsFpuRegister<SRegister>()); + // We don't care about the sign bit, so shift left. + __ Lsl(out, out, 1); + __ eor(out, out, ShifterOperand(infinity)); + // If the result is 0, then it has 32 leading zeros, and less than that otherwise. + __ clz(out, out); + // Any number less than 32 logically shifted right by 5 bits results in 0; + // the same operation on 32 yields 1. + __ Lsr(out, out, 5); +} + +void IntrinsicLocationsBuilderARM::VisitDoubleIsInfinite(HInvoke* invoke) { + CreateFPToIntLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorARM::VisitDoubleIsInfinite(HInvoke* invoke) { + ArmAssembler* const assembler = GetAssembler(); + LocationSummary* const locations = invoke->GetLocations(); + const Register out = locations->Out().AsRegister<Register>(); + // The highest 32 bits of double precision positive infinity separated into + // two constants encodable as immediate operands. + constexpr uint32_t infinity_high = 0x7f000000U; + constexpr uint32_t infinity_high2 = 0x00f00000U; + + static_assert((infinity_high | infinity_high2) == static_cast<uint32_t>(kPositiveInfinityDouble >> 32U), + "The constants do not add up to the high 32 bits of double precision positive infinity."); + __ vmovrrd(IP, out, FromLowSToD(locations->InAt(0).AsFpuRegisterPairLow<SRegister>())); + __ eor(out, out, ShifterOperand(infinity_high)); + __ eor(out, out, ShifterOperand(infinity_high2)); + // We don't care about the sign bit, so shift left. + __ orr(out, IP, ShifterOperand(out, LSL, 1)); + // If the result is 0, then it has 32 leading zeros, and less than that otherwise. + __ clz(out, out); + // Any number less than 32 logically shifted right by 5 bits results in 0; + // the same operation on 32 yields 1. + __ Lsr(out, out, 5); +} + UNIMPLEMENTED_INTRINSIC(ARM, IntegerBitCount) UNIMPLEMENTED_INTRINSIC(ARM, LongBitCount) UNIMPLEMENTED_INTRINSIC(ARM, MathMinDoubleDouble) @@ -2001,8 +2150,6 @@ UNIMPLEMENTED_INTRINSIC(ARM, MathRoundFloat) // Could be done by changing rou UNIMPLEMENTED_INTRINSIC(ARM, UnsafeCASLong) // High register pressure. UNIMPLEMENTED_INTRINSIC(ARM, SystemArrayCopyChar) UNIMPLEMENTED_INTRINSIC(ARM, ReferenceGetReferent) -UNIMPLEMENTED_INTRINSIC(ARM, FloatIsInfinite) -UNIMPLEMENTED_INTRINSIC(ARM, DoubleIsInfinite) UNIMPLEMENTED_INTRINSIC(ARM, IntegerHighestOneBit) UNIMPLEMENTED_INTRINSIC(ARM, LongHighestOneBit) UNIMPLEMENTED_INTRINSIC(ARM, IntegerLowestOneBit) diff --git a/compiler/optimizing/intrinsics_arm64.cc b/compiler/optimizing/intrinsics_arm64.cc index 04ae3a6732..d776fb4406 100644 --- a/compiler/optimizing/intrinsics_arm64.cc +++ b/compiler/optimizing/intrinsics_arm64.cc @@ -47,6 +47,7 @@ using helpers::SRegisterFrom; using helpers::WRegisterFrom; using helpers::XRegisterFrom; using helpers::InputRegisterAt; +using helpers::OutputRegister; namespace { @@ -1173,31 +1174,118 @@ void IntrinsicCodeGeneratorARM64::VisitStringCharAt(HInvoke* invoke) { void IntrinsicLocationsBuilderARM64::VisitStringCompareTo(HInvoke* invoke) { LocationSummary* locations = new (arena_) LocationSummary(invoke, - LocationSummary::kCall, + invoke->InputAt(1)->CanBeNull() + ? LocationSummary::kCallOnSlowPath + : LocationSummary::kNoCall, kIntrinsified); - InvokeRuntimeCallingConvention calling_convention; - locations->SetInAt(0, LocationFrom(calling_convention.GetRegisterAt(0))); - locations->SetInAt(1, LocationFrom(calling_convention.GetRegisterAt(1))); - locations->SetOut(calling_convention.GetReturnLocation(Primitive::kPrimInt)); + locations->SetInAt(0, Location::RequiresRegister()); + locations->SetInAt(1, Location::RequiresRegister()); + locations->AddTemp(Location::RequiresRegister()); + locations->AddTemp(Location::RequiresRegister()); + locations->AddTemp(Location::RequiresRegister()); + locations->SetOut(Location::RequiresRegister(), Location::kOutputOverlap); } void IntrinsicCodeGeneratorARM64::VisitStringCompareTo(HInvoke* invoke) { vixl::MacroAssembler* masm = GetVIXLAssembler(); LocationSummary* locations = invoke->GetLocations(); + Register str = XRegisterFrom(locations->InAt(0)); + Register arg = XRegisterFrom(locations->InAt(1)); + Register out = OutputRegister(invoke); + + Register temp0 = WRegisterFrom(locations->GetTemp(0)); + Register temp1 = WRegisterFrom(locations->GetTemp(1)); + Register temp2 = WRegisterFrom(locations->GetTemp(2)); + + vixl::Label loop; + vixl::Label find_char_diff; + vixl::Label end; + + // Get offsets of count and value fields within a string object. + const int32_t count_offset = mirror::String::CountOffset().Int32Value(); + const int32_t value_offset = mirror::String::ValueOffset().Int32Value(); + // Note that the null check must have been done earlier. DCHECK(!invoke->CanDoImplicitNullCheckOn(invoke->InputAt(0))); - Register argument = WRegisterFrom(locations->InAt(1)); - __ Cmp(argument, 0); - SlowPathCodeARM64* slow_path = new (GetAllocator()) IntrinsicSlowPathARM64(invoke); - codegen_->AddSlowPath(slow_path); - __ B(eq, slow_path->GetEntryLabel()); + // Take slow path and throw if input can be and is null. + SlowPathCodeARM64* slow_path = nullptr; + const bool can_slow_path = invoke->InputAt(1)->CanBeNull(); + if (can_slow_path) { + slow_path = new (GetAllocator()) IntrinsicSlowPathARM64(invoke); + codegen_->AddSlowPath(slow_path); + __ Cbz(arg, slow_path->GetEntryLabel()); + } - __ Ldr( - lr, MemOperand(tr, QUICK_ENTRYPOINT_OFFSET(kArm64WordSize, pStringCompareTo).Int32Value())); - __ Blr(lr); - __ Bind(slow_path->GetExitLabel()); + // Reference equality check, return 0 if same reference. + __ Subs(out, str, arg); + __ B(&end, eq); + // Load lengths of this and argument strings. + __ Ldr(temp0, MemOperand(str.X(), count_offset)); + __ Ldr(temp1, MemOperand(arg.X(), count_offset)); + // Return zero if both strings are empty. + __ Orr(out, temp0, temp1); + __ Cbz(out, &end); + // out = length diff. + __ Subs(out, temp0, temp1); + // temp2 = min(len(str), len(arg)). + __ Csel(temp2, temp1, temp0, ge); + // Shorter string is empty? + __ Cbz(temp2, &end); + + // Store offset of string value in preparation for comparison loop. + __ Mov(temp1, value_offset); + + UseScratchRegisterScope scratch_scope(masm); + Register temp4 = scratch_scope.AcquireX(); + + // Assertions that must hold in order to compare strings 4 characters at a time. + DCHECK_ALIGNED(value_offset, 8); + static_assert(IsAligned<8>(kObjectAlignment), "String of odd length is not zero padded"); + + const size_t char_size = Primitive::ComponentSize(Primitive::kPrimChar); + DCHECK_EQ(char_size, 2u); + + // Promote temp0 to an X reg, ready for LDR. + temp0 = temp0.X(); + + // Loop to compare 4x16-bit characters at a time (ok because of string data alignment). + __ Bind(&loop); + __ Ldr(temp4, MemOperand(str.X(), temp1)); + __ Ldr(temp0, MemOperand(arg.X(), temp1)); + __ Cmp(temp4, temp0); + __ B(ne, &find_char_diff); + __ Add(temp1, temp1, char_size * 4); + __ Subs(temp2, temp2, 4); + __ B(gt, &loop); + __ B(&end); + + // Promote temp1 to an X reg, ready for EOR. + temp1 = temp1.X(); + + // Find the single 16-bit character difference. + __ Bind(&find_char_diff); + // Get the bit position of the first character that differs. + __ Eor(temp1, temp0, temp4); + __ Rbit(temp1, temp1); + __ Clz(temp1, temp1); + // If the number of 16-bit chars remaining <= the index where the difference occurs (0-3), then + // the difference occurs outside the remaining string data, so just return length diff (out). + __ Cmp(temp2, Operand(temp1, LSR, 4)); + __ B(le, &end); + // Extract the characters and calculate the difference. + __ Bic(temp1, temp1, 0xf); + __ Lsr(temp0, temp0, temp1); + __ Lsr(temp4, temp4, temp1); + __ And(temp4, temp4, 0xffff); + __ Sub(out, temp4, Operand(temp0, UXTH)); + + __ Bind(&end); + + if (can_slow_path) { + __ Bind(slow_path->GetExitLabel()); + } } void IntrinsicLocationsBuilderARM64::VisitStringEquals(HInvoke* invoke) { @@ -1239,21 +1327,26 @@ void IntrinsicCodeGeneratorARM64::VisitStringEquals(HInvoke* invoke) { // Note that the null check must have been done earlier. DCHECK(!invoke->CanDoImplicitNullCheckOn(invoke->InputAt(0))); - // Check if input is null, return false if it is. - __ Cbz(arg, &return_false); + StringEqualsOptimizations optimizations(invoke); + if (!optimizations.GetArgumentNotNull()) { + // Check if input is null, return false if it is. + __ Cbz(arg, &return_false); + } // Reference equality check, return true if same reference. __ Cmp(str, arg); __ B(&return_true, eq); - // Instanceof check for the argument by comparing class fields. - // All string objects must have the same type since String cannot be subclassed. - // Receiver must be a string object, so its class field is equal to all strings' class fields. - // If the argument is a string object, its class field must be equal to receiver's class field. - __ Ldr(temp, MemOperand(str.X(), class_offset)); - __ Ldr(temp1, MemOperand(arg.X(), class_offset)); - __ Cmp(temp, temp1); - __ B(&return_false, ne); + if (!optimizations.GetArgumentIsString()) { + // Instanceof check for the argument by comparing class fields. + // All string objects must have the same type since String cannot be subclassed. + // Receiver must be a string object, so its class field is equal to all strings' class fields. + // If the argument is a string object, its class field must be equal to receiver's class field. + __ Ldr(temp, MemOperand(str.X(), class_offset)); + __ Ldr(temp1, MemOperand(arg.X(), class_offset)); + __ Cmp(temp, temp1); + __ B(&return_false, ne); + } // Load lengths of this and argument strings. __ Ldr(temp, MemOperand(str.X(), count_offset)); @@ -1302,16 +1395,16 @@ static void GenerateVisitStringIndexOf(HInvoke* invoke, ArenaAllocator* allocator, bool start_at_zero) { LocationSummary* locations = invoke->GetLocations(); - Register tmp_reg = WRegisterFrom(locations->GetTemp(0)); // Note that the null check must have been done earlier. DCHECK(!invoke->CanDoImplicitNullCheckOn(invoke->InputAt(0))); // Check for code points > 0xFFFF. Either a slow-path check when we don't know statically, - // or directly dispatch if we have a constant. + // or directly dispatch for a large constant, or omit slow-path for a small constant or a char. SlowPathCodeARM64* slow_path = nullptr; - if (invoke->InputAt(1)->IsIntConstant()) { - if (static_cast<uint32_t>(invoke->InputAt(1)->AsIntConstant()->GetValue()) > 0xFFFFU) { + HInstruction* code_point = invoke->InputAt(1); + if (code_point->IsIntConstant()) { + if (static_cast<uint32_t>(code_point->AsIntConstant()->GetValue()) > 0xFFFFU) { // Always needs the slow-path. We could directly dispatch to it, but this case should be // rare, so for simplicity just put the full slow-path down and branch unconditionally. slow_path = new (allocator) IntrinsicSlowPathARM64(invoke); @@ -1320,17 +1413,17 @@ static void GenerateVisitStringIndexOf(HInvoke* invoke, __ Bind(slow_path->GetExitLabel()); return; } - } else { + } else if (code_point->GetType() != Primitive::kPrimChar) { Register char_reg = WRegisterFrom(locations->InAt(1)); - __ Mov(tmp_reg, 0xFFFF); - __ Cmp(char_reg, Operand(tmp_reg)); + __ Tst(char_reg, 0xFFFF0000); slow_path = new (allocator) IntrinsicSlowPathARM64(invoke); codegen->AddSlowPath(slow_path); - __ B(hi, slow_path->GetEntryLabel()); + __ B(ne, slow_path->GetEntryLabel()); } if (start_at_zero) { // Start-index = 0. + Register tmp_reg = WRegisterFrom(locations->GetTemp(0)); __ Mov(tmp_reg, 0); } @@ -1354,7 +1447,7 @@ void IntrinsicLocationsBuilderARM64::VisitStringIndexOf(HInvoke* invoke) { locations->SetInAt(1, LocationFrom(calling_convention.GetRegisterAt(1))); locations->SetOut(calling_convention.GetReturnLocation(Primitive::kPrimInt)); - // Need a temp for slow-path codepoint compare, and need to send start_index=0. + // Need to send start_index=0. locations->AddTemp(LocationFrom(calling_convention.GetRegisterAt(2))); } @@ -1374,9 +1467,6 @@ void IntrinsicLocationsBuilderARM64::VisitStringIndexOfAfter(HInvoke* invoke) { locations->SetInAt(1, LocationFrom(calling_convention.GetRegisterAt(1))); locations->SetInAt(2, LocationFrom(calling_convention.GetRegisterAt(2))); locations->SetOut(calling_convention.GetReturnLocation(Primitive::kPrimInt)); - - // Need a temp for slow-path codepoint compare. - locations->AddTemp(Location::RequiresRegister()); } void IntrinsicCodeGeneratorARM64::VisitStringIndexOfAfter(HInvoke* invoke) { @@ -2201,9 +2291,46 @@ void IntrinsicCodeGeneratorARM64::VisitSystemArrayCopy(HInvoke* invoke) { __ Bind(slow_path->GetExitLabel()); } +static void GenIsInfinite(LocationSummary* locations, + bool is64bit, + vixl::MacroAssembler* masm) { + Operand infinity; + Register out; + + if (is64bit) { + infinity = kPositiveInfinityDouble; + out = XRegisterFrom(locations->Out()); + } else { + infinity = kPositiveInfinityFloat; + out = WRegisterFrom(locations->Out()); + } + + const Register zero = vixl::Assembler::AppropriateZeroRegFor(out); + + MoveFPToInt(locations, is64bit, masm); + __ Eor(out, out, infinity); + // We don't care about the sign bit, so shift left. + __ Cmp(zero, Operand(out, LSL, 1)); + __ Cset(out, eq); +} + +void IntrinsicLocationsBuilderARM64::VisitFloatIsInfinite(HInvoke* invoke) { + CreateFPToIntLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorARM64::VisitFloatIsInfinite(HInvoke* invoke) { + GenIsInfinite(invoke->GetLocations(), /* is64bit */ false, GetVIXLAssembler()); +} + +void IntrinsicLocationsBuilderARM64::VisitDoubleIsInfinite(HInvoke* invoke) { + CreateFPToIntLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorARM64::VisitDoubleIsInfinite(HInvoke* invoke) { + GenIsInfinite(invoke->GetLocations(), /* is64bit */ true, GetVIXLAssembler()); +} + UNIMPLEMENTED_INTRINSIC(ARM64, ReferenceGetReferent) -UNIMPLEMENTED_INTRINSIC(ARM64, FloatIsInfinite) -UNIMPLEMENTED_INTRINSIC(ARM64, DoubleIsInfinite) UNIMPLEMENTED_INTRINSIC(ARM64, IntegerHighestOneBit) UNIMPLEMENTED_INTRINSIC(ARM64, LongHighestOneBit) UNIMPLEMENTED_INTRINSIC(ARM64, IntegerLowestOneBit) diff --git a/compiler/optimizing/intrinsics_list.h b/compiler/optimizing/intrinsics_list.h index dd9294d486..db60238fb4 100644 --- a/compiler/optimizing/intrinsics_list.h +++ b/compiler/optimizing/intrinsics_list.h @@ -107,6 +107,8 @@ V(StringGetCharsNoCheck, kDirect, kNeedsEnvironmentOrCache, kReadSideEffects, kCanThrow) \ V(StringIndexOf, kDirect, kNeedsEnvironmentOrCache, kReadSideEffects, kCanThrow) \ V(StringIndexOfAfter, kDirect, kNeedsEnvironmentOrCache, kReadSideEffects, kCanThrow) \ + V(StringIsEmpty, kDirect, kNeedsEnvironmentOrCache, kReadSideEffects, kNoThrow) \ + V(StringLength, kDirect, kNeedsEnvironmentOrCache, kReadSideEffects, kNoThrow) \ V(StringNewStringFromBytes, kStatic, kNeedsEnvironmentOrCache, kAllSideEffects, kCanThrow) \ V(StringNewStringFromChars, kStatic, kNeedsEnvironmentOrCache, kAllSideEffects, kCanThrow) \ V(StringNewStringFromString, kStatic, kNeedsEnvironmentOrCache, kAllSideEffects, kCanThrow) \ diff --git a/compiler/optimizing/intrinsics_mips.cc b/compiler/optimizing/intrinsics_mips.cc index 19c6a225ac..140f56a870 100644 --- a/compiler/optimizing/intrinsics_mips.cc +++ b/compiler/optimizing/intrinsics_mips.cc @@ -2067,11 +2067,12 @@ static void GenerateStringIndexOf(HInvoke* invoke, // Note that the null check must have been done earlier. DCHECK(!invoke->CanDoImplicitNullCheckOn(invoke->InputAt(0))); - // Check for code points > 0xFFFF. Either a slow-path check when we - // don't know statically, or directly dispatch if we have a constant. + // Check for code points > 0xFFFF. Either a slow-path check when we don't know statically, + // or directly dispatch for a large constant, or omit slow-path for a small constant or a char. SlowPathCodeMIPS* slow_path = nullptr; - if (invoke->InputAt(1)->IsIntConstant()) { - if (!IsUint<16>(invoke->InputAt(1)->AsIntConstant()->GetValue())) { + HInstruction* code_point = invoke->InputAt(1); + if (code_point->IsIntConstant()) { + if (!IsUint<16>(code_point->AsIntConstant()->GetValue())) { // Always needs the slow-path. We could directly dispatch to it, // but this case should be rare, so for simplicity just put the // full slow-path down and branch unconditionally. @@ -2081,7 +2082,7 @@ static void GenerateStringIndexOf(HInvoke* invoke, __ Bind(slow_path->GetExitLabel()); return; } - } else { + } else if (code_point->GetType() != Primitive::kPrimChar) { Register char_reg = locations->InAt(1).AsRegister<Register>(); // The "bltu" conditional branch tests to see if the character value // fits in a valid 16-bit (MIPS halfword) value. If it doesn't then @@ -2283,10 +2284,10 @@ static void GenIsInfinite(LocationSummary* locations, // If one, or more, of the exponent bits is zero, then the number can't be infinite. if (type == Primitive::kPrimDouble) { __ MoveFromFpuHigh(TMP, in); - __ LoadConst32(AT, 0x7FF00000); + __ LoadConst32(AT, High32Bits(kPositiveInfinityDouble)); } else { __ Mfc1(TMP, in); - __ LoadConst32(AT, 0x7F800000); + __ LoadConst32(AT, kPositiveInfinityFloat); } __ Xor(TMP, TMP, AT); @@ -2432,13 +2433,128 @@ void IntrinsicCodeGeneratorMIPS::VisitLongLowestOneBit(HInvoke* invoke) { GenLowestOneBit(invoke->GetLocations(), Primitive::kPrimLong, IsR6(), GetAssembler()); } +// int java.lang.Math.round(float) +void IntrinsicLocationsBuilderMIPS::VisitMathRoundFloat(HInvoke* invoke) { + LocationSummary* locations = new (arena_) LocationSummary(invoke, + LocationSummary::kNoCall, + kIntrinsified); + locations->SetInAt(0, Location::RequiresFpuRegister()); + locations->AddTemp(Location::RequiresFpuRegister()); + locations->SetOut(Location::RequiresRegister()); +} + +void IntrinsicCodeGeneratorMIPS::VisitMathRoundFloat(HInvoke* invoke) { + LocationSummary* locations = invoke->GetLocations(); + MipsAssembler* assembler = GetAssembler(); + FRegister in = locations->InAt(0).AsFpuRegister<FRegister>(); + FRegister half = locations->GetTemp(0).AsFpuRegister<FRegister>(); + Register out = locations->Out().AsRegister<Register>(); + + MipsLabel done; + MipsLabel finite; + MipsLabel add; + + // if (in.isNaN) { + // return 0; + // } + // + // out = floor.w.s(in); + // + // /* + // * This "if" statement is only needed for the pre-R6 version of floor.w.s + // * which outputs Integer.MAX_VALUE for negative numbers with magnitudes + // * too large to fit in a 32-bit integer. + // * + // * Starting with MIPSR6, which always sets FCSR.NAN2008=1, negative + // * numbers which are too large to be represented in a 32-bit signed + // * integer will be processed by floor.w.s to output Integer.MIN_VALUE, + // * and will no longer be processed by this "if" statement. + // */ + // if (out == Integer.MAX_VALUE) { + // TMP = (in < 0.0f) ? 1 : 0; + // /* + // * If TMP is 1, then adding it to out will wrap its value from + // * Integer.MAX_VALUE to Integer.MIN_VALUE. + // */ + // return out += TMP; + // } + // + // /* + // * For negative values not handled by the previous "if" statement the + // * test here will correctly set the value of TMP. + // */ + // TMP = ((in - out) >= 0.5f) ? 1 : 0; + // return out += TMP; + + // Test for NaN. + if (IsR6()) { + __ CmpUnS(FTMP, in, in); + } else { + __ CunS(in, in); + } + + // Return zero for NaN. + __ Move(out, ZERO); + if (IsR6()) { + __ Bc1nez(FTMP, &done); + } else { + __ Bc1t(&done); + } + + // out = floor(in); + __ FloorWS(FTMP, in); + __ Mfc1(out, FTMP); + + __ LoadConst32(TMP, 1); + + // TMP = (out = java.lang.Integer.MAX_VALUE) ? 1 : 0; + __ LoadConst32(AT, std::numeric_limits<int32_t>::max()); + __ Bne(AT, out, &finite); + + __ Mtc1(ZERO, FTMP); + if (IsR6()) { + __ CmpLtS(FTMP, in, FTMP); + __ Mfc1(AT, FTMP); + } else { + __ ColtS(in, FTMP); + } + + __ B(&add); + + __ Bind(&finite); + + // TMP = (0.5f <= (in - out)) ? 1 : 0; + __ Cvtsw(FTMP, FTMP); // Convert output of floor.w.s back to "float". + __ LoadConst32(AT, bit_cast<int32_t, float>(0.5f)); + __ SubS(FTMP, in, FTMP); + __ Mtc1(AT, half); + if (IsR6()) { + __ CmpLeS(FTMP, half, FTMP); + __ Mfc1(AT, FTMP); + } else { + __ ColeS(half, FTMP); + } + + __ Bind(&add); + + if (IsR6()) { + __ Selnez(TMP, TMP, AT); + } else { + __ Movf(TMP, ZERO); + } + + // Return out += TMP. + __ Addu(out, out, TMP); + + __ Bind(&done); +} + // Unimplemented intrinsics. UNIMPLEMENTED_INTRINSIC(MIPS, MathCeil) UNIMPLEMENTED_INTRINSIC(MIPS, MathFloor) UNIMPLEMENTED_INTRINSIC(MIPS, MathRint) UNIMPLEMENTED_INTRINSIC(MIPS, MathRoundDouble) -UNIMPLEMENTED_INTRINSIC(MIPS, MathRoundFloat) UNIMPLEMENTED_INTRINSIC(MIPS, UnsafeCASLong) UNIMPLEMENTED_INTRINSIC(MIPS, ReferenceGetReferent) diff --git a/compiler/optimizing/intrinsics_mips64.cc b/compiler/optimizing/intrinsics_mips64.cc index cf973aa841..6c4e64e4b1 100644 --- a/compiler/optimizing/intrinsics_mips64.cc +++ b/compiler/optimizing/intrinsics_mips64.cc @@ -385,6 +385,92 @@ static void CreateFPToFPLocations(ArenaAllocator* arena, HInvoke* invoke) { locations->SetOut(Location::RequiresFpuRegister(), Location::kNoOutputOverlap); } +static void GenBitCount(LocationSummary* locations, + const Primitive::Type type, + Mips64Assembler* assembler) { + GpuRegister out = locations->Out().AsRegister<GpuRegister>(); + GpuRegister in = locations->InAt(0).AsRegister<GpuRegister>(); + + DCHECK(type == Primitive::kPrimInt || type == Primitive::kPrimLong); + + // https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel + // + // A generalization of the best bit counting method to integers of + // bit-widths up to 128 (parameterized by type T) is this: + // + // v = v - ((v >> 1) & (T)~(T)0/3); // temp + // v = (v & (T)~(T)0/15*3) + ((v >> 2) & (T)~(T)0/15*3); // temp + // v = (v + (v >> 4)) & (T)~(T)0/255*15; // temp + // c = (T)(v * ((T)~(T)0/255)) >> (sizeof(T) - 1) * BITS_PER_BYTE; // count + // + // For comparison, for 32-bit quantities, this algorithm can be executed + // using 20 MIPS instructions (the calls to LoadConst32() generate two + // machine instructions each for the values being used in this algorithm). + // A(n unrolled) loop-based algorithm requires 25 instructions. + // + // For a 64-bit operand this can be performed in 24 instructions compared + // to a(n unrolled) loop based algorithm which requires 38 instructions. + // + // There are algorithms which are faster in the cases where very few + // bits are set but the algorithm here attempts to minimize the total + // number of instructions executed even when a large number of bits + // are set. + + if (type == Primitive::kPrimInt) { + __ Srl(TMP, in, 1); + __ LoadConst32(AT, 0x55555555); + __ And(TMP, TMP, AT); + __ Subu(TMP, in, TMP); + __ LoadConst32(AT, 0x33333333); + __ And(out, TMP, AT); + __ Srl(TMP, TMP, 2); + __ And(TMP, TMP, AT); + __ Addu(TMP, out, TMP); + __ Srl(out, TMP, 4); + __ Addu(out, out, TMP); + __ LoadConst32(AT, 0x0F0F0F0F); + __ And(out, out, AT); + __ LoadConst32(TMP, 0x01010101); + __ MulR6(out, out, TMP); + __ Srl(out, out, 24); + } else if (type == Primitive::kPrimLong) { + __ Dsrl(TMP, in, 1); + __ LoadConst64(AT, 0x5555555555555555L); + __ And(TMP, TMP, AT); + __ Dsubu(TMP, in, TMP); + __ LoadConst64(AT, 0x3333333333333333L); + __ And(out, TMP, AT); + __ Dsrl(TMP, TMP, 2); + __ And(TMP, TMP, AT); + __ Daddu(TMP, out, TMP); + __ Dsrl(out, TMP, 4); + __ Daddu(out, out, TMP); + __ LoadConst64(AT, 0x0F0F0F0F0F0F0F0FL); + __ And(out, out, AT); + __ LoadConst64(TMP, 0x0101010101010101L); + __ Dmul(out, out, TMP); + __ Dsrl32(out, out, 24); + } +} + +// int java.lang.Integer.bitCount(int) +void IntrinsicLocationsBuilderMIPS64::VisitIntegerBitCount(HInvoke* invoke) { + CreateIntToIntLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitIntegerBitCount(HInvoke* invoke) { + GenBitCount(invoke->GetLocations(), Primitive::kPrimInt, GetAssembler()); +} + +// int java.lang.Long.bitCount(long) +void IntrinsicLocationsBuilderMIPS64::VisitLongBitCount(HInvoke* invoke) { + CreateIntToIntLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitLongBitCount(HInvoke* invoke) { + GenBitCount(invoke->GetLocations(), Primitive::kPrimLong, GetAssembler()); +} + static void MathAbsFP(LocationSummary* locations, bool is64bit, Mips64Assembler* assembler) { FpuRegister in = locations->InAt(0).AsFpuRegister<FpuRegister>(); FpuRegister out = locations->Out().AsFpuRegister<FpuRegister>(); @@ -1477,11 +1563,12 @@ static void GenerateStringIndexOf(HInvoke* invoke, // Note that the null check must have been done earlier. DCHECK(!invoke->CanDoImplicitNullCheckOn(invoke->InputAt(0))); - // Check for code points > 0xFFFF. Either a slow-path check when we - // don't know statically, or directly dispatch if we have a constant. + // Check for code points > 0xFFFF. Either a slow-path check when we don't know statically, + // or directly dispatch for a large constant, or omit slow-path for a small constant or a char. SlowPathCodeMIPS64* slow_path = nullptr; - if (invoke->InputAt(1)->IsIntConstant()) { - if (!IsUint<16>(invoke->InputAt(1)->AsIntConstant()->GetValue())) { + HInstruction* code_point = invoke->InputAt(1); + if (code_point->IsIntConstant()) { + if (!IsUint<16>(code_point->AsIntConstant()->GetValue())) { // Always needs the slow-path. We could directly dispatch to it, // but this case should be rare, so for simplicity just put the // full slow-path down and branch unconditionally. @@ -1491,7 +1578,7 @@ static void GenerateStringIndexOf(HInvoke* invoke, __ Bind(slow_path->GetExitLabel()); return; } - } else { + } else if (code_point->GetType() != Primitive::kPrimChar) { GpuRegister char_reg = locations->InAt(1).AsRegister<GpuRegister>(); __ LoadConst32(tmp_reg, std::numeric_limits<uint16_t>::max()); slow_path = new (allocator) IntrinsicSlowPathMIPS64(invoke); @@ -1693,9 +1780,6 @@ void IntrinsicCodeGeneratorMIPS64::VisitDoubleIsInfinite(HInvoke* invoke) { GenIsInfinite(invoke->GetLocations(), /* is64bit */ true, GetAssembler()); } -UNIMPLEMENTED_INTRINSIC(MIPS64, IntegerBitCount) -UNIMPLEMENTED_INTRINSIC(MIPS64, LongBitCount) - UNIMPLEMENTED_INTRINSIC(MIPS64, MathRoundDouble) UNIMPLEMENTED_INTRINSIC(MIPS64, MathRoundFloat) diff --git a/compiler/optimizing/intrinsics_x86.cc b/compiler/optimizing/intrinsics_x86.cc index 4aab3e2768..05377f984b 100644 --- a/compiler/optimizing/intrinsics_x86.cc +++ b/compiler/optimizing/intrinsics_x86.cc @@ -1319,11 +1319,11 @@ void IntrinsicCodeGeneratorX86::VisitStringEquals(HInvoke* invoke) { __ j(kEqual, &return_false); } - // Instanceof check for the argument by comparing class fields. - // All string objects must have the same type since String cannot be subclassed. - // Receiver must be a string object, so its class field is equal to all strings' class fields. - // If the argument is a string object, its class field must be equal to receiver's class field. if (!optimizations.GetArgumentIsString()) { + // Instanceof check for the argument by comparing class fields. + // All string objects must have the same type since String cannot be subclassed. + // Receiver must be a string object, so its class field is equal to all strings' class fields. + // If the argument is a string object, its class field must be equal to receiver's class field. __ movl(ecx, Address(str, class_offset)); __ cmpl(ecx, Address(arg, class_offset)); __ j(kNotEqual, &return_false); @@ -1418,10 +1418,11 @@ static void GenerateStringIndexOf(HInvoke* invoke, DCHECK_EQ(out, EDI); // Check for code points > 0xFFFF. Either a slow-path check when we don't know statically, - // or directly dispatch if we have a constant. + // or directly dispatch for a large constant, or omit slow-path for a small constant or a char. SlowPathCode* slow_path = nullptr; - if (invoke->InputAt(1)->IsIntConstant()) { - if (static_cast<uint32_t>(invoke->InputAt(1)->AsIntConstant()->GetValue()) > + HInstruction* code_point = invoke->InputAt(1); + if (code_point->IsIntConstant()) { + if (static_cast<uint32_t>(code_point->AsIntConstant()->GetValue()) > std::numeric_limits<uint16_t>::max()) { // Always needs the slow-path. We could directly dispatch to it, but this case should be // rare, so for simplicity just put the full slow-path down and branch unconditionally. @@ -1431,7 +1432,7 @@ static void GenerateStringIndexOf(HInvoke* invoke, __ Bind(slow_path->GetExitLabel()); return; } - } else { + } else if (code_point->GetType() != Primitive::kPrimChar) { __ cmpl(search_value, Immediate(std::numeric_limits<uint16_t>::max())); slow_path = new (allocator) IntrinsicSlowPathX86(invoke); codegen->AddSlowPath(slow_path); @@ -2630,8 +2631,66 @@ void IntrinsicCodeGeneratorX86::VisitLongNumberOfTrailingZeros(HInvoke* invoke) GenTrailingZeros(GetAssembler(), codegen_, invoke, /* is_long */ true); } +void IntrinsicLocationsBuilderX86::VisitReferenceGetReferent(HInvoke* invoke) { + if (kEmitCompilerReadBarrier) { + // Do not intrinsify this call with the read barrier configuration. + return; + } + LocationSummary* locations = new (arena_) LocationSummary(invoke, + LocationSummary::kCallOnSlowPath, + kIntrinsified); + locations->SetInAt(0, Location::RequiresRegister()); + locations->SetOut(Location::SameAsFirstInput()); + locations->AddTemp(Location::RequiresRegister()); +} + +void IntrinsicCodeGeneratorX86::VisitReferenceGetReferent(HInvoke* invoke) { + DCHECK(!kEmitCompilerReadBarrier); + LocationSummary* locations = invoke->GetLocations(); + X86Assembler* assembler = GetAssembler(); + + Register obj = locations->InAt(0).AsRegister<Register>(); + Register out = locations->Out().AsRegister<Register>(); + + SlowPathCode* slow_path = new (GetAllocator()) IntrinsicSlowPathX86(invoke); + codegen_->AddSlowPath(slow_path); + + // Load ArtMethod first. + HInvokeStaticOrDirect* invoke_direct = invoke->AsInvokeStaticOrDirect(); + DCHECK(invoke_direct != nullptr); + Location temp_loc = codegen_->GenerateCalleeMethodStaticOrDirectCall( + invoke_direct, locations->GetTemp(0)); + DCHECK(temp_loc.Equals(locations->GetTemp(0))); + Register temp = temp_loc.AsRegister<Register>(); + + // Now get declaring class. + __ movl(temp, Address(temp, ArtMethod::DeclaringClassOffset().Int32Value())); + + uint32_t slow_path_flag_offset = codegen_->GetReferenceSlowFlagOffset(); + uint32_t disable_flag_offset = codegen_->GetReferenceDisableFlagOffset(); + DCHECK_NE(slow_path_flag_offset, 0u); + DCHECK_NE(disable_flag_offset, 0u); + DCHECK_NE(slow_path_flag_offset, disable_flag_offset); + + // Check static flags preventing us for using intrinsic. + if (slow_path_flag_offset == disable_flag_offset + 1) { + __ cmpw(Address(temp, disable_flag_offset), Immediate(0)); + __ j(kNotEqual, slow_path->GetEntryLabel()); + } else { + __ cmpb(Address(temp, disable_flag_offset), Immediate(0)); + __ j(kNotEqual, slow_path->GetEntryLabel()); + __ cmpb(Address(temp, slow_path_flag_offset), Immediate(0)); + __ j(kNotEqual, slow_path->GetEntryLabel()); + } + + // Fast path. + __ movl(out, Address(obj, mirror::Reference::ReferentOffset().Int32Value())); + codegen_->MaybeRecordImplicitNullCheck(invoke); + __ MaybeUnpoisonHeapReference(out); + __ Bind(slow_path->GetExitLabel()); +} + UNIMPLEMENTED_INTRINSIC(X86, MathRoundDouble) -UNIMPLEMENTED_INTRINSIC(X86, ReferenceGetReferent) UNIMPLEMENTED_INTRINSIC(X86, SystemArrayCopy) UNIMPLEMENTED_INTRINSIC(X86, FloatIsInfinite) UNIMPLEMENTED_INTRINSIC(X86, DoubleIsInfinite) diff --git a/compiler/optimizing/intrinsics_x86_64.cc b/compiler/optimizing/intrinsics_x86_64.cc index 1d32dc7bc5..67c2f3a866 100644 --- a/compiler/optimizing/intrinsics_x86_64.cc +++ b/compiler/optimizing/intrinsics_x86_64.cc @@ -1416,17 +1416,22 @@ void IntrinsicCodeGeneratorX86_64::VisitStringEquals(HInvoke* invoke) { // Note that the null check must have been done earlier. DCHECK(!invoke->CanDoImplicitNullCheckOn(invoke->InputAt(0))); - // Check if input is null, return false if it is. - __ testl(arg, arg); - __ j(kEqual, &return_false); - - // Instanceof check for the argument by comparing class fields. - // All string objects must have the same type since String cannot be subclassed. - // Receiver must be a string object, so its class field is equal to all strings' class fields. - // If the argument is a string object, its class field must be equal to receiver's class field. - __ movl(rcx, Address(str, class_offset)); - __ cmpl(rcx, Address(arg, class_offset)); - __ j(kNotEqual, &return_false); + StringEqualsOptimizations optimizations(invoke); + if (!optimizations.GetArgumentNotNull()) { + // Check if input is null, return false if it is. + __ testl(arg, arg); + __ j(kEqual, &return_false); + } + + if (!optimizations.GetArgumentIsString()) { + // Instanceof check for the argument by comparing class fields. + // All string objects must have the same type since String cannot be subclassed. + // Receiver must be a string object, so its class field is equal to all strings' class fields. + // If the argument is a string object, its class field must be equal to receiver's class field. + __ movl(rcx, Address(str, class_offset)); + __ cmpl(rcx, Address(arg, class_offset)); + __ j(kNotEqual, &return_false); + } // Reference equality check, return true if same reference. __ cmpl(str, arg); @@ -1517,10 +1522,11 @@ static void GenerateStringIndexOf(HInvoke* invoke, DCHECK_EQ(out.AsRegister(), RDI); // Check for code points > 0xFFFF. Either a slow-path check when we don't know statically, - // or directly dispatch if we have a constant. + // or directly dispatch for a large constant, or omit slow-path for a small constant or a char. SlowPathCode* slow_path = nullptr; - if (invoke->InputAt(1)->IsIntConstant()) { - if (static_cast<uint32_t>(invoke->InputAt(1)->AsIntConstant()->GetValue()) > + HInstruction* code_point = invoke->InputAt(1); + if (code_point->IsIntConstant()) { + if (static_cast<uint32_t>(code_point->AsIntConstant()->GetValue()) > std::numeric_limits<uint16_t>::max()) { // Always needs the slow-path. We could directly dispatch to it, but this case should be // rare, so for simplicity just put the full slow-path down and branch unconditionally. @@ -1530,7 +1536,7 @@ static void GenerateStringIndexOf(HInvoke* invoke, __ Bind(slow_path->GetExitLabel()); return; } - } else { + } else if (code_point->GetType() != Primitive::kPrimChar) { __ cmpl(search_value, Immediate(std::numeric_limits<uint16_t>::max())); slow_path = new (allocator) IntrinsicSlowPathX86_64(invoke); codegen->AddSlowPath(slow_path); @@ -2718,7 +2724,65 @@ void IntrinsicCodeGeneratorX86_64::VisitLongNumberOfTrailingZeros(HInvoke* invok GenTrailingZeros(GetAssembler(), codegen_, invoke, /* is_long */ true); } -UNIMPLEMENTED_INTRINSIC(X86_64, ReferenceGetReferent) +void IntrinsicLocationsBuilderX86_64::VisitReferenceGetReferent(HInvoke* invoke) { + if (kEmitCompilerReadBarrier) { + // Do not intrinsify this call with the read barrier configuration. + return; + } + LocationSummary* locations = new (arena_) LocationSummary(invoke, + LocationSummary::kCallOnSlowPath, + kIntrinsified); + locations->SetInAt(0, Location::RequiresRegister()); + locations->SetOut(Location::SameAsFirstInput()); + locations->AddTemp(Location::RequiresRegister()); +} + +void IntrinsicCodeGeneratorX86_64::VisitReferenceGetReferent(HInvoke* invoke) { + DCHECK(!kEmitCompilerReadBarrier); + LocationSummary* locations = invoke->GetLocations(); + X86_64Assembler* assembler = GetAssembler(); + + CpuRegister obj = locations->InAt(0).AsRegister<CpuRegister>(); + CpuRegister out = locations->Out().AsRegister<CpuRegister>(); + + SlowPathCode* slow_path = new (GetAllocator()) IntrinsicSlowPathX86_64(invoke); + codegen_->AddSlowPath(slow_path); + + // Load ArtMethod first. + HInvokeStaticOrDirect* invoke_direct = invoke->AsInvokeStaticOrDirect(); + DCHECK(invoke_direct != nullptr); + Location temp_loc = codegen_->GenerateCalleeMethodStaticOrDirectCall( + invoke_direct, locations->GetTemp(0)); + DCHECK(temp_loc.Equals(locations->GetTemp(0))); + CpuRegister temp = temp_loc.AsRegister<CpuRegister>(); + + // Now get declaring class. + __ movl(temp, Address(temp, ArtMethod::DeclaringClassOffset().Int32Value())); + + uint32_t slow_path_flag_offset = codegen_->GetReferenceSlowFlagOffset(); + uint32_t disable_flag_offset = codegen_->GetReferenceDisableFlagOffset(); + DCHECK_NE(slow_path_flag_offset, 0u); + DCHECK_NE(disable_flag_offset, 0u); + DCHECK_NE(slow_path_flag_offset, disable_flag_offset); + + // Check static flags preventing us for using intrinsic. + if (slow_path_flag_offset == disable_flag_offset + 1) { + __ cmpw(Address(temp, disable_flag_offset), Immediate(0)); + __ j(kNotEqual, slow_path->GetEntryLabel()); + } else { + __ cmpb(Address(temp, disable_flag_offset), Immediate(0)); + __ j(kNotEqual, slow_path->GetEntryLabel()); + __ cmpb(Address(temp, slow_path_flag_offset), Immediate(0)); + __ j(kNotEqual, slow_path->GetEntryLabel()); + } + + // Fast path. + __ movl(out, Address(obj, mirror::Reference::ReferentOffset().Int32Value())); + codegen_->MaybeRecordImplicitNullCheck(invoke); + __ MaybeUnpoisonHeapReference(out); + __ Bind(slow_path->GetExitLabel()); +} + UNIMPLEMENTED_INTRINSIC(X86_64, FloatIsInfinite) UNIMPLEMENTED_INTRINSIC(X86_64, DoubleIsInfinite) diff --git a/compiler/optimizing/licm.cc b/compiler/optimizing/licm.cc index 7a1e06b951..a0ded74d6d 100644 --- a/compiler/optimizing/licm.cc +++ b/compiler/optimizing/licm.cc @@ -30,8 +30,8 @@ static bool IsPhiOf(HInstruction* instruction, HBasicBlock* block) { static bool InputsAreDefinedBeforeLoop(HInstruction* instruction) { DCHECK(instruction->IsInLoop()); HLoopInformation* info = instruction->GetBlock()->GetLoopInformation(); - for (HInputIterator it(instruction); !it.Done(); it.Advance()) { - HLoopInformation* input_loop = it.Current()->GetBlock()->GetLoopInformation(); + for (const HInstruction* input : instruction->GetInputs()) { + HLoopInformation* input_loop = input->GetBlock()->GetLoopInformation(); // We only need to check whether the input is defined in the loop. If it is not // it is defined before the loop. if (input_loop != nullptr && input_loop->IsIn(*info)) { @@ -79,8 +79,15 @@ static void UpdateLoopPhisIn(HEnvironment* environment, HLoopInformation* info) void LICM::Run() { DCHECK(side_effects_.HasRun()); + // Only used during debug. - ArenaBitVector visited(graph_->GetArena(), graph_->GetBlocks().size(), false, kArenaAllocLICM); + ArenaBitVector* visited = nullptr; + if (kIsDebugBuild) { + visited = new (graph_->GetArena()) ArenaBitVector(graph_->GetArena(), + graph_->GetBlocks().size(), + false, + kArenaAllocLICM); + } // Post order visit to visit inner loops before outer loops. for (HPostOrderIterator it(*graph_); !it.Done(); it.Advance()) { @@ -94,31 +101,24 @@ void LICM::Run() { SideEffects loop_effects = side_effects_.GetLoopEffects(block); HBasicBlock* pre_header = loop_info->GetPreHeader(); - bool contains_irreducible_loop = false; - if (graph_->HasIrreducibleLoops()) { - for (HBlocksInLoopIterator it_loop(*loop_info); !it_loop.Done(); it_loop.Advance()) { - if (it_loop.Current()->GetLoopInformation()->IsIrreducible()) { - contains_irreducible_loop = true; - break; - } - } - } - for (HBlocksInLoopIterator it_loop(*loop_info); !it_loop.Done(); it_loop.Advance()) { HBasicBlock* inner = it_loop.Current(); DCHECK(inner->IsInLoop()); if (inner->GetLoopInformation() != loop_info) { // Thanks to post order visit, inner loops were already visited. - DCHECK(visited.IsBitSet(inner->GetBlockId())); + DCHECK(visited->IsBitSet(inner->GetBlockId())); continue; } - visited.SetBit(inner->GetBlockId()); + if (kIsDebugBuild) { + visited->SetBit(inner->GetBlockId()); + } - if (contains_irreducible_loop) { + if (loop_info->ContainsIrreducibleLoop()) { // We cannot licm in an irreducible loop, or in a natural loop containing an // irreducible loop. continue; } + DCHECK(!loop_info->IsIrreducible()); // We can move an instruction that can throw only if it is the first // throwing instruction in the loop. Note that the first potentially diff --git a/compiler/optimizing/live_ranges_test.cc b/compiler/optimizing/live_ranges_test.cc index bdaef1d0e9..f9a955fb0a 100644 --- a/compiler/optimizing/live_ranges_test.cc +++ b/compiler/optimizing/live_ranges_test.cc @@ -441,7 +441,7 @@ TEST_F(LiveRangesTest, CFG4) { ASSERT_TRUE(range->GetNext() == nullptr); HPhi* phi = liveness.GetInstructionFromSsaIndex(4)->AsPhi(); - ASSERT_TRUE(phi->GetUses().HasOnlyOneUse()); + ASSERT_TRUE(phi->GetUses().HasExactlyOneElement()); interval = phi->GetLiveInterval(); range = interval->GetFirstRange(); ASSERT_EQ(26u, range->GetStart()); diff --git a/compiler/optimizing/load_store_elimination.cc b/compiler/optimizing/load_store_elimination.cc index e1977b1798..8a75a90cfd 100644 --- a/compiler/optimizing/load_store_elimination.cc +++ b/compiler/optimizing/load_store_elimination.cc @@ -43,31 +43,29 @@ class ReferenceInfo : public ArenaObject<kArenaAllocMisc> { // Visit all uses to determine if this reference can spread into the heap, // a method call, etc. - for (HUseIterator<HInstruction*> use_it(reference_->GetUses()); - !use_it.Done(); - use_it.Advance()) { - HInstruction* use = use_it.Current()->GetUser(); - DCHECK(!use->IsNullCheck()) << "NullCheck should have been eliminated"; - if (use->IsBoundType()) { + for (const HUseListNode<HInstruction*>& use : reference_->GetUses()) { + HInstruction* user = use.GetUser(); + DCHECK(!user->IsNullCheck()) << "NullCheck should have been eliminated"; + if (user->IsBoundType()) { // BoundType shouldn't normally be necessary for a NewInstance. // Just be conservative for the uncommon cases. is_singleton_ = false; is_singleton_and_not_returned_ = false; return; } - if (use->IsPhi() || use->IsSelect() || use->IsInvoke() || - (use->IsInstanceFieldSet() && (reference_ == use->InputAt(1))) || - (use->IsUnresolvedInstanceFieldSet() && (reference_ == use->InputAt(1))) || - (use->IsStaticFieldSet() && (reference_ == use->InputAt(1))) || - (use->IsUnresolvedStaticFieldSet() && (reference_ == use->InputAt(0))) || - (use->IsArraySet() && (reference_ == use->InputAt(2)))) { + if (user->IsPhi() || user->IsSelect() || user->IsInvoke() || + (user->IsInstanceFieldSet() && (reference_ == user->InputAt(1))) || + (user->IsUnresolvedInstanceFieldSet() && (reference_ == user->InputAt(1))) || + (user->IsStaticFieldSet() && (reference_ == user->InputAt(1))) || + (user->IsUnresolvedStaticFieldSet() && (reference_ == user->InputAt(0))) || + (user->IsArraySet() && (reference_ == user->InputAt(2)))) { // reference_ is merged to HPhi/HSelect, passed to a callee, or stored to heap. // reference_ isn't the only name that can refer to its value anymore. is_singleton_ = false; is_singleton_and_not_returned_ = false; return; } - if (use->IsReturn()) { + if (user->IsReturn()) { is_singleton_and_not_returned_ = false; } } @@ -480,7 +478,7 @@ class HeapLocationCollector : public HGraphVisitor { // alias analysis and won't be as effective. bool has_volatile_; // If there are volatile field accesses. bool has_monitor_operations_; // If there are monitor operations. - bool may_deoptimize_; + bool may_deoptimize_; // Only true for HDeoptimize with single-frame deoptimization. DISALLOW_COPY_AND_ASSIGN(HeapLocationCollector); }; @@ -551,19 +549,20 @@ class LSEVisitor : public HGraphVisitor { } // At this point, stores in possibly_removed_stores_ can be safely removed. - size = possibly_removed_stores_.size(); - for (size_t i = 0; i < size; i++) { + for (size_t i = 0, e = possibly_removed_stores_.size(); i < e; i++) { HInstruction* store = possibly_removed_stores_[i]; DCHECK(store->IsInstanceFieldSet() || store->IsStaticFieldSet() || store->IsArraySet()); store->GetBlock()->RemoveInstruction(store); } - // TODO: remove unnecessary allocations. - // Eliminate instructions in singleton_new_instances_ that: - // - don't have uses, - // - don't have finalizers, - // - are instantiable and accessible, - // - have no/separate clinit check. + // Eliminate allocations that are not used. + for (size_t i = 0, e = singleton_new_instances_.size(); i < e; i++) { + HInstruction* new_instance = singleton_new_instances_[i]; + if (!new_instance->HasNonEnvironmentUses()) { + new_instance->RemoveEnvironmentUsers(); + new_instance->GetBlock()->RemoveInstruction(new_instance); + } + } } private: @@ -734,19 +733,14 @@ class LSEVisitor : public HGraphVisitor { if (Primitive::PrimitiveKind(heap_value->GetType()) != Primitive::PrimitiveKind(instruction->GetType())) { // The only situation where the same heap location has different type is when - // we do an array get from a null constant. In order to stay properly typed - // we do not merge the array gets. + // we do an array get on an instruction that originates from the null constant + // (the null could be behind a field access, an array access, a null check or + // a bound type). + // In order to stay properly typed on primitive types, we do not eliminate + // the array gets. if (kIsDebugBuild) { DCHECK(heap_value->IsArrayGet()) << heap_value->DebugName(); DCHECK(instruction->IsArrayGet()) << instruction->DebugName(); - HInstruction* array = instruction->AsArrayGet()->GetArray(); - DCHECK(array->IsNullCheck()) << array->DebugName(); - HInstruction* input = HuntForOriginalReference(array->InputAt(0)); - DCHECK(input->IsNullConstant()) << input->DebugName(); - array = heap_value->AsArrayGet()->GetArray(); - DCHECK(array->IsNullCheck()) << array->DebugName(); - input = HuntForOriginalReference(array->InputAt(0)); - DCHECK(input->IsNullConstant()) << input->DebugName(); } return; } @@ -969,8 +963,8 @@ class LSEVisitor : public HGraphVisitor { if (!heap_location_collector_.MayDeoptimize() && ref_info->IsSingletonAndNotReturned() && !new_instance->IsFinalizable() && - !new_instance->CanThrow()) { - // TODO: add new_instance to singleton_new_instances_ and enable allocation elimination. + !new_instance->NeedsAccessCheck()) { + singleton_new_instances_.push_back(new_instance); } ArenaVector<HInstruction*>& heap_values = heap_values_for_[new_instance->GetBlock()->GetBlockId()]; diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc index 1afa36a89c..150d6b029b 100644 --- a/compiler/optimizing/nodes.cc +++ b/compiler/optimizing/nodes.cc @@ -56,9 +56,11 @@ void HGraph::FindBackEdges(ArenaBitVector* visited) { // Nodes that we're currently visiting, indexed by block id. ArenaBitVector visiting(arena_, blocks_.size(), false, kArenaAllocGraphBuilder); // Number of successors visited from a given node, indexed by block id. - ArenaVector<size_t> successors_visited(blocks_.size(), 0u, arena_->Adapter()); + ArenaVector<size_t> successors_visited(blocks_.size(), + 0u, + arena_->Adapter(kArenaAllocGraphBuilder)); // Stack of nodes that we're currently visiting (same as marked in "visiting" above). - ArenaVector<HBasicBlock*> worklist(arena_->Adapter()); + ArenaVector<HBasicBlock*> worklist(arena_->Adapter(kArenaAllocGraphBuilder)); constexpr size_t kDefaultWorklistSize = 8; worklist.reserve(kDefaultWorklistSize); visited->SetBit(entry_block_->GetBlockId()); @@ -99,10 +101,7 @@ static void RemoveEnvironmentUses(HInstruction* instruction) { } static void RemoveAsUser(HInstruction* instruction) { - for (size_t i = 0; i < instruction->InputCount(); i++) { - instruction->RemoveAsUserOfInput(i); - } - + instruction->RemoveAsUserOfAllInputs(); RemoveEnvironmentUses(instruction); } @@ -206,17 +205,35 @@ HInstruction* HBasicBlock::GetFirstInstructionDisregardMoves() const { return instruction; } +static bool UpdateDominatorOfSuccessor(HBasicBlock* block, HBasicBlock* successor) { + DCHECK(ContainsElement(block->GetSuccessors(), successor)); + + HBasicBlock* old_dominator = successor->GetDominator(); + HBasicBlock* new_dominator = + (old_dominator == nullptr) ? block + : CommonDominator::ForPair(old_dominator, block); + + if (old_dominator == new_dominator) { + return false; + } else { + successor->SetDominator(new_dominator); + return true; + } +} + void HGraph::ComputeDominanceInformation() { DCHECK(reverse_post_order_.empty()); reverse_post_order_.reserve(blocks_.size()); reverse_post_order_.push_back(entry_block_); // Number of visits of a given node, indexed by block id. - ArenaVector<size_t> visits(blocks_.size(), 0u, arena_->Adapter()); + ArenaVector<size_t> visits(blocks_.size(), 0u, arena_->Adapter(kArenaAllocGraphBuilder)); // Number of successors visited from a given node, indexed by block id. - ArenaVector<size_t> successors_visited(blocks_.size(), 0u, arena_->Adapter()); + ArenaVector<size_t> successors_visited(blocks_.size(), + 0u, + arena_->Adapter(kArenaAllocGraphBuilder)); // Nodes for which we need to visit successors. - ArenaVector<HBasicBlock*> worklist(arena_->Adapter()); + ArenaVector<HBasicBlock*> worklist(arena_->Adapter(kArenaAllocGraphBuilder)); constexpr size_t kDefaultWorklistSize = 8; worklist.reserve(kDefaultWorklistSize); worklist.push_back(entry_block_); @@ -228,15 +245,7 @@ void HGraph::ComputeDominanceInformation() { worklist.pop_back(); } else { HBasicBlock* successor = current->GetSuccessors()[successors_visited[current_id]++]; - - if (successor->GetDominator() == nullptr) { - successor->SetDominator(current); - } else { - // The CommonDominator can work for multiple blocks as long as the - // domination information doesn't change. However, since we're changing - // that information here, we can use the finder only for pairs of blocks. - successor->SetDominator(CommonDominator::ForPair(successor->GetDominator(), current)); - } + UpdateDominatorOfSuccessor(current, successor); // Once all the forward edges have been visited, we know the immediate // dominator of the block. We can then start visiting its successors. @@ -248,6 +257,44 @@ void HGraph::ComputeDominanceInformation() { } } + // Check if the graph has back edges not dominated by their respective headers. + // If so, we need to update the dominators of those headers and recursively of + // their successors. We do that with a fix-point iteration over all blocks. + // The algorithm is guaranteed to terminate because it loops only if the sum + // of all dominator chains has decreased in the current iteration. + bool must_run_fix_point = false; + for (HBasicBlock* block : blocks_) { + if (block != nullptr && + block->IsLoopHeader() && + block->GetLoopInformation()->HasBackEdgeNotDominatedByHeader()) { + must_run_fix_point = true; + break; + } + } + if (must_run_fix_point) { + bool update_occurred = true; + while (update_occurred) { + update_occurred = false; + for (HReversePostOrderIterator it(*this); !it.Done(); it.Advance()) { + HBasicBlock* block = it.Current(); + for (HBasicBlock* successor : block->GetSuccessors()) { + update_occurred |= UpdateDominatorOfSuccessor(block, successor); + } + } + } + } + + // Make sure that there are no remaining blocks whose dominator information + // needs to be updated. + if (kIsDebugBuild) { + for (HReversePostOrderIterator it(*this); !it.Done(); it.Advance()) { + HBasicBlock* block = it.Current(); + for (HBasicBlock* successor : block->GetSuccessors()) { + DCHECK(!UpdateDominatorOfSuccessor(block, successor)); + } + } + } + // Populate `dominated_blocks_` information after computing all dominators. // The potential presence of irreducible loops requires to do it after. for (HReversePostOrderIterator it(*this); !it.Done(); it.Advance()) { @@ -396,8 +443,10 @@ void HGraph::SimplifyCFG() { } GraphAnalysisResult HGraph::AnalyzeLoops() const { - // Order does not matter. - for (HReversePostOrderIterator it(*this); !it.Done(); it.Advance()) { + // We iterate post order to ensure we visit inner loops before outer loops. + // `PopulateRecursive` needs this guarantee to know whether a natural loop + // contains an irreducible loop. + for (HPostOrderIterator it(*this); !it.Done(); it.Advance()) { HBasicBlock* block = it.Current(); if (block->IsLoopHeader()) { if (block->IsCatchBlock()) { @@ -530,6 +579,14 @@ void HLoopInformation::PopulateRecursive(HBasicBlock* block) { blocks_.SetBit(block->GetBlockId()); block->SetInLoop(this); + if (block->IsLoopHeader()) { + // We're visiting loops in post-order, so inner loops must have been + // populated already. + DCHECK(block->GetLoopInformation()->IsPopulated()); + if (block->GetLoopInformation()->IsIrreducible()) { + contains_irreducible_loop_ = true; + } + } for (HBasicBlock* predecessor : block->GetPredecessors()) { PopulateRecursive(predecessor); } @@ -595,20 +652,16 @@ void HLoopInformation::Populate() { blocks_.SetBit(header_->GetBlockId()); header_->SetInLoop(this); - bool is_irreducible_loop = false; - for (HBasicBlock* back_edge : GetBackEdges()) { - DCHECK(back_edge->GetDominator() != nullptr); - if (!header_->Dominates(back_edge)) { - is_irreducible_loop = true; - break; - } - } + bool is_irreducible_loop = HasBackEdgeNotDominatedByHeader(); if (is_irreducible_loop) { ArenaBitVector visited(graph->GetArena(), graph->GetBlocks().size(), /* expandable */ false, kArenaAllocGraphBuilder); + // Stop marking blocks at the loop header. + visited.SetBit(header_->GetBlockId()); + for (HBasicBlock* back_edge : GetBackEdges()) { PopulateIrreducibleRecursive(back_edge, &visited); } @@ -618,8 +671,26 @@ void HLoopInformation::Populate() { } } - if (is_irreducible_loop || graph->IsCompilingOsr()) { + if (!is_irreducible_loop && graph->IsCompilingOsr()) { + // When compiling in OSR mode, all loops in the compiled method may be entered + // from the interpreter. We treat this OSR entry point just like an extra entry + // to an irreducible loop, so we need to mark the method's loops as irreducible. + // This does not apply to inlined loops which do not act as OSR entry points. + if (suspend_check_ == nullptr) { + // Just building the graph in OSR mode, this loop is not inlined. We never build an + // inner graph in OSR mode as we can do OSR transition only from the outer method. + is_irreducible_loop = true; + } else { + // Look at the suspend check's environment to determine if the loop was inlined. + DCHECK(suspend_check_->HasEnvironment()); + if (!suspend_check_->GetEnvironment()->IsFromInlinedInvoke()) { + is_irreducible_loop = true; + } + } + } + if (is_irreducible_loop) { irreducible_ = true; + contains_irreducible_loop_ = true; graph->SetHasIrreducibleLoops(true); } } @@ -650,6 +721,25 @@ size_t HLoopInformation::GetLifetimeEnd() const { return last_position; } +bool HLoopInformation::HasBackEdgeNotDominatedByHeader() const { + for (HBasicBlock* back_edge : GetBackEdges()) { + DCHECK(back_edge->GetDominator() != nullptr); + if (!header_->Dominates(back_edge)) { + return true; + } + } + return false; +} + +bool HLoopInformation::DominatesAllBackEdges(HBasicBlock* block) { + for (HBasicBlock* back_edge : GetBackEdges()) { + if (!block->Dominates(back_edge)) { + return false; + } + } + return true; +} + bool HBasicBlock::Dominates(HBasicBlock* other) const { // Walk up the dominator tree from `other`, to find out if `this` // is an ancestor. @@ -664,8 +754,9 @@ bool HBasicBlock::Dominates(HBasicBlock* other) const { } static void UpdateInputsUsers(HInstruction* instruction) { - for (size_t i = 0, e = instruction->InputCount(); i < e; ++i) { - instruction->InputAt(i)->AddUseAt(instruction, i); + auto&& inputs = instruction->GetInputs(); + for (size_t i = 0; i < inputs.size(); ++i) { + inputs[i]->AddUseAt(instruction, i); } // Environment should be created later. DCHECK(!instruction->HasEnvironment()); @@ -681,8 +772,8 @@ void HBasicBlock::ReplaceAndRemoveInstructionWith(HInstruction* initial, DCHECK_EQ(replacement->GetType(), Primitive::kPrimVoid); DCHECK_EQ(initial->GetBlock(), this); DCHECK_EQ(initial->GetType(), Primitive::kPrimVoid); - DCHECK(initial->GetUses().IsEmpty()); - DCHECK(initial->GetEnvUses().IsEmpty()); + DCHECK(initial->GetUses().empty()); + DCHECK(initial->GetEnvUses().empty()); replacement->SetBlock(this); replacement->SetId(GetGraph()->GetNextInstructionId()); instructions_.InsertInstructionBefore(replacement, initial); @@ -774,8 +865,8 @@ static void Remove(HInstructionList* instruction_list, instruction->SetBlock(nullptr); instruction_list->RemoveInstruction(instruction); if (ensure_safety) { - DCHECK(instruction->GetUses().IsEmpty()); - DCHECK(instruction->GetEnvUses().IsEmpty()); + DCHECK(instruction->GetUses().empty()); + DCHECK(instruction->GetEnvUses().empty()); RemoveAsUser(instruction); } } @@ -839,8 +930,11 @@ void HEnvironment::CopyFromWithLoopPhiAdjustment(HEnvironment* env, } void HEnvironment::RemoveAsUserOfInput(size_t index) const { - const HUserRecord<HEnvironment*>& user_record = vregs_[index]; - user_record.GetInstruction()->RemoveEnvironmentUser(user_record.GetUseNode()); + const HUserRecord<HEnvironment*>& env_use = vregs_[index]; + HInstruction* user = env_use.GetInstruction(); + auto before_env_use_node = env_use.GetBeforeUseNode(); + user->env_uses_.erase_after(before_env_use_node); + user->FixUpUserRecordsAfterEnvUseRemoval(before_env_use_node); } HInstruction::InstructionKind HInstruction::GetKind() const { @@ -985,30 +1079,36 @@ void HInstruction::RemoveEnvironment() { void HInstruction::ReplaceWith(HInstruction* other) { DCHECK(other != nullptr); - for (HUseIterator<HInstruction*> it(GetUses()); !it.Done(); it.Advance()) { - HUseListNode<HInstruction*>* current = it.Current(); - HInstruction* user = current->GetUser(); - size_t input_index = current->GetIndex(); - user->SetRawInputAt(input_index, other); - other->AddUseAt(user, input_index); - } + // Note: fixup_end remains valid across splice_after(). + auto fixup_end = other->uses_.empty() ? other->uses_.begin() : ++other->uses_.begin(); + other->uses_.splice_after(other->uses_.before_begin(), uses_); + other->FixUpUserRecordsAfterUseInsertion(fixup_end); - for (HUseIterator<HEnvironment*> it(GetEnvUses()); !it.Done(); it.Advance()) { - HUseListNode<HEnvironment*>* current = it.Current(); - HEnvironment* user = current->GetUser(); - size_t input_index = current->GetIndex(); - user->SetRawEnvAt(input_index, other); - other->AddEnvUseAt(user, input_index); - } + // Note: env_fixup_end remains valid across splice_after(). + auto env_fixup_end = + other->env_uses_.empty() ? other->env_uses_.begin() : ++other->env_uses_.begin(); + other->env_uses_.splice_after(other->env_uses_.before_begin(), env_uses_); + other->FixUpUserRecordsAfterEnvUseInsertion(env_fixup_end); - uses_.Clear(); - env_uses_.Clear(); + DCHECK(uses_.empty()); + DCHECK(env_uses_.empty()); } void HInstruction::ReplaceInput(HInstruction* replacement, size_t index) { - RemoveAsUserOfInput(index); - SetRawInputAt(index, replacement); - replacement->AddUseAt(this, index); + HUserRecord<HInstruction*> input_use = InputRecordAt(index); + if (input_use.GetInstruction() == replacement) { + // Nothing to do. + return; + } + HUseList<HInstruction*>::iterator before_use_node = input_use.GetBeforeUseNode(); + // Note: fixup_end remains valid across splice_after(). + auto fixup_end = + replacement->uses_.empty() ? replacement->uses_.begin() : ++replacement->uses_.begin(); + replacement->uses_.splice_after(replacement->uses_.before_begin(), + input_use.GetInstruction()->uses_, + before_use_node); + replacement->FixUpUserRecordsAfterUseInsertion(fixup_end); + input_use.GetInstruction()->FixUpUserRecordsAfterUseRemoval(before_use_node); } size_t HInstruction::EnvironmentSize() const { @@ -1024,9 +1124,10 @@ void HPhi::AddInput(HInstruction* input) { void HPhi::RemoveInputAt(size_t index) { RemoveAsUserOfInput(index); inputs_.erase(inputs_.begin() + index); - for (size_t i = index, e = InputCount(); i < e; ++i) { - DCHECK_EQ(InputRecordAt(i).GetUseNode()->GetIndex(), i + 1u); - InputRecordAt(i).GetUseNode()->SetIndex(i); + // Update indexes in use nodes of inputs that have been pulled forward by the erase(). + for (size_t i = index, e = inputs_.size(); i < e; ++i) { + DCHECK_EQ(inputs_[i].GetUseNode()->GetIndex(), i + 1u); + inputs_[i].GetUseNode()->SetIndex(i); } } @@ -1222,16 +1323,18 @@ bool HCondition::IsBeforeWhenDisregardMoves(HInstruction* instruction) const { return this == instruction->GetPreviousDisregardingMoves(); } -bool HInstruction::Equals(HInstruction* other) const { +bool HInstruction::Equals(const HInstruction* other) const { if (!InstructionTypeEquals(other)) return false; DCHECK_EQ(GetKind(), other->GetKind()); if (!InstructionDataEquals(other)) return false; if (GetType() != other->GetType()) return false; - if (InputCount() != other->InputCount()) return false; - - for (size_t i = 0, e = InputCount(); i < e; ++i) { - if (InputAt(i) != other->InputAt(i)) return false; + auto&& inputs = GetInputs(); + auto&& other_inputs = other->GetInputs(); + if (inputs.size() != other_inputs.size()) return false; + for (size_t i = 0; i != inputs.size(); ++i) { + if (inputs[i] != other_inputs[i]) return false; } + DCHECK_EQ(ComputeHashCode(), other->ComputeHashCode()); return true; } @@ -1280,17 +1383,18 @@ void HInstruction::MoveBeforeFirstUserAndOutOfLoops() { DCHECK_EQ(InputCount(), 0u); // Find the target block. - HUseIterator<HInstruction*> uses_it(GetUses()); - HBasicBlock* target_block = uses_it.Current()->GetUser()->GetBlock(); - uses_it.Advance(); - while (!uses_it.Done() && uses_it.Current()->GetUser()->GetBlock() == target_block) { - uses_it.Advance(); - } - if (!uses_it.Done()) { + auto uses_it = GetUses().begin(); + auto uses_end = GetUses().end(); + HBasicBlock* target_block = uses_it->GetUser()->GetBlock(); + ++uses_it; + while (uses_it != uses_end && uses_it->GetUser()->GetBlock() == target_block) { + ++uses_it; + } + if (uses_it != uses_end) { // This instruction has uses in two or more blocks. Find the common dominator. CommonDominator finder(target_block); - for (; !uses_it.Done(); uses_it.Advance()) { - finder.Update(uses_it.Current()->GetUser()->GetBlock()); + for (; uses_it != uses_end; ++uses_it) { + finder.Update(uses_it->GetUser()->GetBlock()); } target_block = finder.Get(); DCHECK(target_block != nullptr); @@ -1303,10 +1407,10 @@ void HInstruction::MoveBeforeFirstUserAndOutOfLoops() { // Find insertion position. HInstruction* insert_pos = nullptr; - for (HUseIterator<HInstruction*> uses_it2(GetUses()); !uses_it2.Done(); uses_it2.Advance()) { - if (uses_it2.Current()->GetUser()->GetBlock() == target_block && - (insert_pos == nullptr || uses_it2.Current()->GetUser()->StrictlyDominates(insert_pos))) { - insert_pos = uses_it2.Current()->GetUser(); + for (const HUseListNode<HInstruction*>& use : GetUses()) { + if (use.GetUser()->GetBlock() == target_block && + (insert_pos == nullptr || use.GetUser()->StrictlyDominates(insert_pos))) { + insert_pos = use.GetUser(); } } if (insert_pos == nullptr) { @@ -1586,10 +1690,10 @@ void HInstructionList::Add(const HInstructionList& instruction_list) { static void RemoveUsesOfDeadInstruction(HInstruction* insn) { DCHECK(!insn->HasEnvironmentUses()); while (insn->HasNonEnvironmentUses()) { - HUseListNode<HInstruction*>* use = insn->GetUses().GetFirst(); - size_t use_index = use->GetIndex(); - HBasicBlock* user_block = use->GetUser()->GetBlock(); - DCHECK(use->GetUser()->IsPhi() && user_block->IsCatchBlock()); + const HUseListNode<HInstruction*>& use = insn->GetUses().front(); + size_t use_index = use.GetIndex(); + HBasicBlock* user_block = use.GetUser()->GetBlock(); + DCHECK(use.GetUser()->IsPhi() && user_block->IsCatchBlock()); for (HInstructionIterator phi_it(user_block->GetPhis()); !phi_it.Done(); phi_it.Advance()) { phi_it.Current()->AsPhi()->RemoveInputAt(use_index); } @@ -2190,6 +2294,8 @@ ReferenceTypeInfo ReferenceTypeInfo::Create(TypeHandle type_handle, bool is_exac if (kIsDebugBuild) { ScopedObjectAccess soa(Thread::Current()); DCHECK(IsValidHandle(type_handle)); + DCHECK(!type_handle->IsErroneous()); + DCHECK(!type_handle->IsArrayClass() || !type_handle->GetComponentType()->IsErroneous()); if (!is_exact) { DCHECK(!type_handle->CannotBeAssignedFromOtherTypes()) << "Callers of ReferenceTypeInfo::Create should ensure is_exact is properly computed"; @@ -2287,9 +2393,9 @@ void HInvokeStaticOrDirect::InsertInputAt(size_t index, HInstruction* input) { inputs_.insert(inputs_.begin() + index, HUserRecord<HInstruction*>(input)); input->AddUseAt(this, index); // Update indexes in use nodes of inputs that have been pushed further back by the insert(). - for (size_t i = index + 1u, size = inputs_.size(); i != size; ++i) { - DCHECK_EQ(InputRecordAt(i).GetUseNode()->GetIndex(), i - 1u); - InputRecordAt(i).GetUseNode()->SetIndex(i); + for (size_t i = index + 1u, e = inputs_.size(); i < e; ++i) { + DCHECK_EQ(inputs_[i].GetUseNode()->GetIndex(), i - 1u); + inputs_[i].GetUseNode()->SetIndex(i); } } @@ -2297,9 +2403,9 @@ void HInvokeStaticOrDirect::RemoveInputAt(size_t index) { RemoveAsUserOfInput(index); inputs_.erase(inputs_.begin() + index); // Update indexes in use nodes of inputs that have been pulled forward by the erase(). - for (size_t i = index, e = InputCount(); i < e; ++i) { - DCHECK_EQ(InputRecordAt(i).GetUseNode()->GetIndex(), i + 1u); - InputRecordAt(i).GetUseNode()->SetIndex(i); + for (size_t i = index, e = inputs_.size(); i < e; ++i) { + DCHECK_EQ(inputs_[i].GetUseNode()->GetIndex(), i + 1u); + inputs_[i].GetUseNode()->SetIndex(i); } } @@ -2337,8 +2443,8 @@ std::ostream& operator<<(std::ostream& os, HInvokeStaticOrDirect::ClinitCheckReq } } -bool HLoadString::InstructionDataEquals(HInstruction* other) const { - HLoadString* other_load_string = other->AsLoadString(); +bool HLoadString::InstructionDataEquals(const HInstruction* other) const { + const HLoadString* other_load_string = other->AsLoadString(); if (string_index_ != other_load_string->string_index_ || GetPackedFields() != other_load_string->GetPackedFields()) { return false; @@ -2367,6 +2473,7 @@ void HLoadString::SetLoadKindInternal(LoadKind load_kind) { } if (!NeedsEnvironment()) { RemoveEnvironment(); + SetSideEffects(SideEffects::None()); } } @@ -2391,12 +2498,11 @@ std::ostream& operator<<(std::ostream& os, HLoadString::LoadKind rhs) { } void HInstruction::RemoveEnvironmentUsers() { - for (HUseIterator<HEnvironment*> use_it(GetEnvUses()); !use_it.Done(); use_it.Advance()) { - HUseListNode<HEnvironment*>* user_node = use_it.Current(); - HEnvironment* user = user_node->GetUser(); - user->SetRawEnvAt(user_node->GetIndex(), nullptr); + for (const HUseListNode<HEnvironment*>& use : GetEnvUses()) { + HEnvironment* user = use.GetUser(); + user->SetRawEnvAt(use.GetIndex(), nullptr); } - env_uses_.Clear(); + env_uses_.clear(); } // Returns an instruction with the opposite Boolean value from 'cond'. diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index 6f3e536d05..da84afbb39 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -36,6 +36,8 @@ #include "offsets.h" #include "primitive.h" #include "utils/array_ref.h" +#include "utils/intrusive_forward_list.h" +#include "utils/transform_array_ref.h" namespace art { @@ -169,7 +171,7 @@ class ReferenceTypeInfo : ValueObject { return handle.GetReference() != nullptr; } - bool IsValid() const SHARED_REQUIRES(Locks::mutator_lock_) { + bool IsValid() const { return IsValidHandle(type_handle_); } @@ -649,6 +651,7 @@ class HLoopInformation : public ArenaObject<kArenaAllocLoopInfo> { : header_(header), suspend_check_(nullptr), irreducible_(false), + contains_irreducible_loop_(false), back_edges_(graph->GetArena()->Adapter(kArenaAllocLoopInfoBackEdges)), // Make bit vector growable, as the number of blocks may change. blocks_(graph->GetArena(), graph->GetBlocks().size(), true, kArenaAllocLoopInfoBackEdges) { @@ -656,6 +659,7 @@ class HLoopInformation : public ArenaObject<kArenaAllocLoopInfo> { } bool IsIrreducible() const { return irreducible_; } + bool ContainsIrreducibleLoop() const { return contains_irreducible_loop_; } void Dump(std::ostream& os); @@ -724,6 +728,14 @@ class HLoopInformation : public ArenaObject<kArenaAllocLoopInfo> { blocks_.ClearAllBits(); } + bool HasBackEdgeNotDominatedByHeader() const; + + bool IsPopulated() const { + return blocks_.GetHighestBitSet() != -1; + } + + bool DominatesAllBackEdges(HBasicBlock* block); + private: // Internal recursive implementation of `Populate`. void PopulateRecursive(HBasicBlock* block); @@ -732,6 +744,7 @@ class HLoopInformation : public ArenaObject<kArenaAllocLoopInfo> { HBasicBlock* header_; HSuspendCheck* suspend_check_; bool irreducible_; + bool contains_irreducible_loop_; ArenaVector<HBasicBlock*> back_edges_; ArenaBitVector blocks_; @@ -1321,12 +1334,12 @@ class HLoopInformationOutwardIterator : public ValueObject { FOR_EACH_INSTRUCTION(FORWARD_DECLARATION) #undef FORWARD_DECLARATION -#define DECLARE_INSTRUCTION(type) \ - InstructionKind GetKindInternal() const OVERRIDE { return k##type; } \ - const char* DebugName() const OVERRIDE { return #type; } \ - bool InstructionTypeEquals(HInstruction* other) const OVERRIDE { \ - return other->Is##type(); \ - } \ +#define DECLARE_INSTRUCTION(type) \ + InstructionKind GetKindInternal() const OVERRIDE { return k##type; } \ + const char* DebugName() const OVERRIDE { return #type; } \ + bool InstructionTypeEquals(const HInstruction* other) const OVERRIDE { \ + return other->Is##type(); \ + } \ void Accept(HGraphVisitor* visitor) OVERRIDE #define DECLARE_ABSTRACT_INSTRUCTION(type) \ @@ -1334,127 +1347,31 @@ FOR_EACH_INSTRUCTION(FORWARD_DECLARATION) const H##type* As##type() const { return this; } \ H##type* As##type() { return this; } -template <typename T> class HUseList; - template <typename T> class HUseListNode : public ArenaObject<kArenaAllocUseListNode> { public: - HUseListNode* GetPrevious() const { return prev_; } - HUseListNode* GetNext() const { return next_; } T GetUser() const { return user_; } size_t GetIndex() const { return index_; } void SetIndex(size_t index) { index_ = index; } + // Hook for the IntrusiveForwardList<>. + // TODO: Hide this better. + IntrusiveForwardListHook hook; + private: HUseListNode(T user, size_t index) - : user_(user), index_(index), prev_(nullptr), next_(nullptr) {} + : user_(user), index_(index) {} T const user_; size_t index_; - HUseListNode<T>* prev_; - HUseListNode<T>* next_; - friend class HUseList<T>; + friend class HInstruction; DISALLOW_COPY_AND_ASSIGN(HUseListNode); }; template <typename T> -class HUseList : public ValueObject { - public: - HUseList() : first_(nullptr) {} - - void Clear() { - first_ = nullptr; - } - - // Adds a new entry at the beginning of the use list and returns - // the newly created node. - HUseListNode<T>* AddUse(T user, size_t index, ArenaAllocator* arena) { - HUseListNode<T>* new_node = new (arena) HUseListNode<T>(user, index); - if (IsEmpty()) { - first_ = new_node; - } else { - first_->prev_ = new_node; - new_node->next_ = first_; - first_ = new_node; - } - return new_node; - } - - HUseListNode<T>* GetFirst() const { - return first_; - } - - void Remove(HUseListNode<T>* node) { - DCHECK(node != nullptr); - DCHECK(Contains(node)); - - if (node->prev_ != nullptr) { - node->prev_->next_ = node->next_; - } - if (node->next_ != nullptr) { - node->next_->prev_ = node->prev_; - } - if (node == first_) { - first_ = node->next_; - } - } - - bool Contains(const HUseListNode<T>* node) const { - if (node == nullptr) { - return false; - } - for (HUseListNode<T>* current = first_; current != nullptr; current = current->GetNext()) { - if (current == node) { - return true; - } - } - return false; - } - - bool IsEmpty() const { - return first_ == nullptr; - } - - bool HasOnlyOneUse() const { - return first_ != nullptr && first_->next_ == nullptr; - } - - size_t SizeSlow() const { - size_t count = 0; - for (HUseListNode<T>* current = first_; current != nullptr; current = current->GetNext()) { - ++count; - } - return count; - } - - private: - HUseListNode<T>* first_; -}; - -template<typename T> -class HUseIterator : public ValueObject { - public: - explicit HUseIterator(const HUseList<T>& uses) : current_(uses.GetFirst()) {} - - bool Done() const { return current_ == nullptr; } - - void Advance() { - DCHECK(!Done()); - current_ = current_->GetNext(); - } - - HUseListNode<T>* Current() const { - DCHECK(!Done()); - return current_; - } - - private: - HUseListNode<T>* current_; - - friend class HValue; -}; +using HUseList = IntrusiveForwardList<HUseListNode<T>>; // This class is used by HEnvironment and HInstruction classes to record the // instructions they use and pointers to the corresponding HUseListNodes kept @@ -1462,25 +1379,26 @@ class HUseIterator : public ValueObject { template <typename T> class HUserRecord : public ValueObject { public: - HUserRecord() : instruction_(nullptr), use_node_(nullptr) {} - explicit HUserRecord(HInstruction* instruction) : instruction_(instruction), use_node_(nullptr) {} + HUserRecord() : instruction_(nullptr), before_use_node_() {} + explicit HUserRecord(HInstruction* instruction) : instruction_(instruction), before_use_node_() {} - HUserRecord(const HUserRecord<T>& old_record, HUseListNode<T>* use_node) - : instruction_(old_record.instruction_), use_node_(use_node) { + HUserRecord(const HUserRecord<T>& old_record, typename HUseList<T>::iterator before_use_node) + : HUserRecord(old_record.instruction_, before_use_node) {} + HUserRecord(HInstruction* instruction, typename HUseList<T>::iterator before_use_node) + : instruction_(instruction), before_use_node_(before_use_node) { DCHECK(instruction_ != nullptr); - DCHECK(use_node_ != nullptr); - DCHECK(old_record.use_node_ == nullptr); } HInstruction* GetInstruction() const { return instruction_; } - HUseListNode<T>* GetUseNode() const { return use_node_; } + typename HUseList<T>::iterator GetBeforeUseNode() const { return before_use_node_; } + typename HUseList<T>::iterator GetUseNode() const { return ++GetBeforeUseNode(); } private: // Instruction used by the user. HInstruction* instruction_; - // Corresponding entry in the use list kept by 'instruction_'. - HUseListNode<T>* use_node_; + // Iterator before the corresponding entry in the use list kept by 'instruction_'. + typename HUseList<T>::iterator before_use_node_; }; /** @@ -1805,14 +1723,6 @@ class HEnvironment : public ArenaObject<kArenaAllocEnvironment> { } private: - // Record instructions' use entries of this environment for constant-time removal. - // It should only be called by HInstruction when a new environment use is added. - void RecordEnvUse(HUseListNode<HEnvironment*>* env_use) { - DCHECK(env_use->GetUser() == this); - size_t index = env_use->GetIndex(); - vregs_[index] = HUserRecord<HEnvironment*>(vregs_[index], env_use); - } - ArenaVector<HUserRecord<HEnvironment*>> vregs_; ArenaVector<Location> locations_; HEnvironment* parent_; @@ -1872,16 +1782,41 @@ class HInstruction : public ArenaObject<kArenaAllocInstruction> { return IsLoopHeaderPhi() && GetBlock()->GetLoopInformation()->IsIrreducible(); } - virtual size_t InputCount() const = 0; + virtual ArrayRef<HUserRecord<HInstruction*>> GetInputRecords() = 0; + + ArrayRef<const HUserRecord<HInstruction*>> GetInputRecords() const { + // One virtual method is enough, just const_cast<> and then re-add the const. + return ArrayRef<const HUserRecord<HInstruction*>>( + const_cast<HInstruction*>(this)->GetInputRecords()); + } + + auto GetInputs() { + return MakeTransformArrayRef( + GetInputRecords(), + [](HUserRecord<HInstruction*>& record) -> HInstruction* { + return record.GetInstruction(); + }); + } + + auto GetInputs() const { + return MakeTransformArrayRef( + GetInputRecords(), + [](const HUserRecord<HInstruction*>& record) -> const HInstruction* { + return record.GetInstruction(); + }); + } + + size_t InputCount() const { return GetInputRecords().size(); } HInstruction* InputAt(size_t i) const { return InputRecordAt(i).GetInstruction(); } + void SetRawInputAt(size_t index, HInstruction* input) { + SetRawInputRecordAt(index, HUserRecord<HInstruction*>(input)); + } + virtual void Accept(HGraphVisitor* visitor) = 0; virtual const char* DebugName() const = 0; virtual Primitive::Type GetType() const { return Primitive::kPrimVoid; } - void SetRawInputAt(size_t index, HInstruction* input) { - SetRawInputRecordAt(index, HUserRecord<HInstruction*>(input)); - } virtual bool NeedsEnvironment() const { return false; } @@ -1916,36 +1851,52 @@ class HInstruction : public ArenaObject<kArenaAllocInstruction> { ReferenceTypeInfo GetReferenceTypeInfo() const { DCHECK_EQ(GetType(), Primitive::kPrimNot); return ReferenceTypeInfo::CreateUnchecked(reference_type_handle_, - GetPackedFlag<kFlagReferenceTypeIsExact>());; + GetPackedFlag<kFlagReferenceTypeIsExact>()); } void AddUseAt(HInstruction* user, size_t index) { DCHECK(user != nullptr); - HUseListNode<HInstruction*>* use = - uses_.AddUse(user, index, GetBlock()->GetGraph()->GetArena()); - user->SetRawInputRecordAt(index, HUserRecord<HInstruction*>(user->InputRecordAt(index), use)); + // Note: fixup_end remains valid across push_front(). + auto fixup_end = uses_.empty() ? uses_.begin() : ++uses_.begin(); + HUseListNode<HInstruction*>* new_node = + new (GetBlock()->GetGraph()->GetArena()) HUseListNode<HInstruction*>(user, index); + uses_.push_front(*new_node); + FixUpUserRecordsAfterUseInsertion(fixup_end); } void AddEnvUseAt(HEnvironment* user, size_t index) { DCHECK(user != nullptr); - HUseListNode<HEnvironment*>* env_use = - env_uses_.AddUse(user, index, GetBlock()->GetGraph()->GetArena()); - user->RecordEnvUse(env_use); + // Note: env_fixup_end remains valid across push_front(). + auto env_fixup_end = env_uses_.empty() ? env_uses_.begin() : ++env_uses_.begin(); + HUseListNode<HEnvironment*>* new_node = + new (GetBlock()->GetGraph()->GetArena()) HUseListNode<HEnvironment*>(user, index); + env_uses_.push_front(*new_node); + FixUpUserRecordsAfterEnvUseInsertion(env_fixup_end); } void RemoveAsUserOfInput(size_t input) { HUserRecord<HInstruction*> input_use = InputRecordAt(input); - input_use.GetInstruction()->uses_.Remove(input_use.GetUseNode()); + HUseList<HInstruction*>::iterator before_use_node = input_use.GetBeforeUseNode(); + input_use.GetInstruction()->uses_.erase_after(before_use_node); + input_use.GetInstruction()->FixUpUserRecordsAfterUseRemoval(before_use_node); + } + + void RemoveAsUserOfAllInputs() { + for (const HUserRecord<HInstruction*>& input_use : GetInputRecords()) { + HUseList<HInstruction*>::iterator before_use_node = input_use.GetBeforeUseNode(); + input_use.GetInstruction()->uses_.erase_after(before_use_node); + input_use.GetInstruction()->FixUpUserRecordsAfterUseRemoval(before_use_node); + } } const HUseList<HInstruction*>& GetUses() const { return uses_; } const HUseList<HEnvironment*>& GetEnvUses() const { return env_uses_; } - bool HasUses() const { return !uses_.IsEmpty() || !env_uses_.IsEmpty(); } - bool HasEnvironmentUses() const { return !env_uses_.IsEmpty(); } - bool HasNonEnvironmentUses() const { return !uses_.IsEmpty(); } + bool HasUses() const { return !uses_.empty() || !env_uses_.empty(); } + bool HasEnvironmentUses() const { return !env_uses_.empty(); } + bool HasNonEnvironmentUses() const { return !uses_.empty(); } bool HasOnlyOneNonEnvironmentUse() const { - return !HasEnvironmentUses() && GetUses().HasOnlyOneUse(); + return !HasEnvironmentUses() && GetUses().HasExactlyOneElement(); } // Does this instruction strictly dominate `other_instruction`? @@ -2042,21 +1993,21 @@ class HInstruction : public ArenaObject<kArenaAllocInstruction> { virtual bool CanBeMoved() const { return false; } // Returns whether the two instructions are of the same kind. - virtual bool InstructionTypeEquals(HInstruction* other ATTRIBUTE_UNUSED) const { + virtual bool InstructionTypeEquals(const HInstruction* other ATTRIBUTE_UNUSED) const { return false; } // Returns whether any data encoded in the two instructions is equal. // This method does not look at the inputs. Both instructions must be // of the same type, otherwise the method has undefined behavior. - virtual bool InstructionDataEquals(HInstruction* other ATTRIBUTE_UNUSED) const { + virtual bool InstructionDataEquals(const HInstruction* other ATTRIBUTE_UNUSED) const { return false; } // Returns whether two instructions are equal, that is: // 1) They have the same type and contain the same data (InstructionDataEquals). // 2) Their inputs are identical. - bool Equals(HInstruction* other) const; + bool Equals(const HInstruction* other) const; // TODO: Remove this indirection when the [[pure]] attribute proposal (n3744) // is adopted and implemented by our C++ compiler(s). Fow now, we need to hide @@ -2067,8 +2018,8 @@ class HInstruction : public ArenaObject<kArenaAllocInstruction> { virtual size_t ComputeHashCode() const { size_t result = GetKind(); - for (size_t i = 0, e = InputCount(); i < e; ++i) { - result = (result * 31) + InputAt(i)->GetId(); + for (const HInstruction* input : GetInputs()) { + result = (result * 31) + input->GetId(); } return result; } @@ -2118,8 +2069,14 @@ class HInstruction : public ArenaObject<kArenaAllocInstruction> { static constexpr size_t kNumberOfGenericPackedBits = kFlagReferenceTypeIsExact + 1; static constexpr size_t kMaxNumberOfPackedBits = sizeof(uint32_t) * kBitsPerByte; - virtual const HUserRecord<HInstruction*> InputRecordAt(size_t i) const = 0; - virtual void SetRawInputRecordAt(size_t index, const HUserRecord<HInstruction*>& input) = 0; + const HUserRecord<HInstruction*> InputRecordAt(size_t i) const { + return GetInputRecords()[i]; + } + + void SetRawInputRecordAt(size_t index, const HUserRecord<HInstruction*>& input) { + ArrayRef<HUserRecord<HInstruction*>> input_records = GetInputRecords(); + input_records[index] = input; + } uint32_t GetPackedFields() const { return packed_fields_; @@ -2147,7 +2104,45 @@ class HInstruction : public ArenaObject<kArenaAllocInstruction> { } private: - void RemoveEnvironmentUser(HUseListNode<HEnvironment*>* use_node) { env_uses_.Remove(use_node); } + void FixUpUserRecordsAfterUseInsertion(HUseList<HInstruction*>::iterator fixup_end) { + auto before_use_node = uses_.before_begin(); + for (auto use_node = uses_.begin(); use_node != fixup_end; ++use_node) { + HInstruction* user = use_node->GetUser(); + size_t input_index = use_node->GetIndex(); + user->SetRawInputRecordAt(input_index, HUserRecord<HInstruction*>(this, before_use_node)); + before_use_node = use_node; + } + } + + void FixUpUserRecordsAfterUseRemoval(HUseList<HInstruction*>::iterator before_use_node) { + auto next = ++HUseList<HInstruction*>::iterator(before_use_node); + if (next != uses_.end()) { + HInstruction* next_user = next->GetUser(); + size_t next_index = next->GetIndex(); + DCHECK(next_user->InputRecordAt(next_index).GetInstruction() == this); + next_user->SetRawInputRecordAt(next_index, HUserRecord<HInstruction*>(this, before_use_node)); + } + } + + void FixUpUserRecordsAfterEnvUseInsertion(HUseList<HEnvironment*>::iterator env_fixup_end) { + auto before_env_use_node = env_uses_.before_begin(); + for (auto env_use_node = env_uses_.begin(); env_use_node != env_fixup_end; ++env_use_node) { + HEnvironment* user = env_use_node->GetUser(); + size_t input_index = env_use_node->GetIndex(); + user->vregs_[input_index] = HUserRecord<HEnvironment*>(this, before_env_use_node); + before_env_use_node = env_use_node; + } + } + + void FixUpUserRecordsAfterEnvUseRemoval(HUseList<HEnvironment*>::iterator before_env_use_node) { + auto next = ++HUseList<HEnvironment*>::iterator(before_env_use_node); + if (next != env_uses_.end()) { + HEnvironment* next_user = next->GetUser(); + size_t next_index = next->GetIndex(); + DCHECK(next_user->vregs_[next_index].GetInstruction() == this); + next_user->vregs_[next_index] = HUserRecord<HEnvironment*>(this, before_env_use_node); + } + } HInstruction* previous_; HInstruction* next_; @@ -2202,21 +2197,6 @@ class HInstruction : public ArenaObject<kArenaAllocInstruction> { }; std::ostream& operator<<(std::ostream& os, const HInstruction::InstructionKind& rhs); -class HInputIterator : public ValueObject { - public: - explicit HInputIterator(HInstruction* instruction) : instruction_(instruction), index_(0) {} - - bool Done() const { return index_ == instruction_->InputCount(); } - HInstruction* Current() const { return instruction_->InputAt(index_); } - void Advance() { index_++; } - - private: - HInstruction* instruction_; - size_t index_; - - DISALLOW_COPY_AND_ASSIGN(HInputIterator); -}; - class HInstructionIterator : public ValueObject { public: explicit HInstructionIterator(const HInstructionList& instructions) @@ -2266,17 +2246,9 @@ class HTemplateInstruction: public HInstruction { : HInstruction(side_effects, dex_pc), inputs_() {} virtual ~HTemplateInstruction() {} - size_t InputCount() const OVERRIDE { return N; } - - protected: - const HUserRecord<HInstruction*> InputRecordAt(size_t i) const OVERRIDE { - DCHECK_LT(i, N); - return inputs_[i]; - } - - void SetRawInputRecordAt(size_t i, const HUserRecord<HInstruction*>& input) OVERRIDE { - DCHECK_LT(i, N); - inputs_[i] = input; + using HInstruction::GetInputRecords; // Keep the const version visible. + ArrayRef<HUserRecord<HInstruction*>> GetInputRecords() OVERRIDE FINAL { + return ArrayRef<HUserRecord<HInstruction*>>(inputs_); } private: @@ -2294,18 +2266,9 @@ class HTemplateInstruction<0>: public HInstruction { virtual ~HTemplateInstruction() {} - size_t InputCount() const OVERRIDE { return 0; } - - protected: - const HUserRecord<HInstruction*> InputRecordAt(size_t i ATTRIBUTE_UNUSED) const OVERRIDE { - LOG(FATAL) << "Unreachable"; - UNREACHABLE(); - } - - void SetRawInputRecordAt(size_t i ATTRIBUTE_UNUSED, - const HUserRecord<HInstruction*>& input ATTRIBUTE_UNUSED) OVERRIDE { - LOG(FATAL) << "Unreachable"; - UNREACHABLE(); + using HInstruction::GetInputRecords; // Keep the const version visible. + ArrayRef<HUserRecord<HInstruction*>> GetInputRecords() OVERRIDE FINAL { + return ArrayRef<HUserRecord<HInstruction*>>(); } private: @@ -2337,7 +2300,7 @@ class HExpression : public HTemplateInstruction<N> { // Represents dex's RETURN_VOID opcode. A HReturnVoid is a control flow // instruction that branches to the exit block. -class HReturnVoid : public HTemplateInstruction<0> { +class HReturnVoid FINAL : public HTemplateInstruction<0> { public: explicit HReturnVoid(uint32_t dex_pc = kNoDexPc) : HTemplateInstruction(SideEffects::None(), dex_pc) {} @@ -2352,7 +2315,7 @@ class HReturnVoid : public HTemplateInstruction<0> { // Represents dex's RETURN opcodes. A HReturn is a control flow // instruction that branches to the exit block. -class HReturn : public HTemplateInstruction<1> { +class HReturn FINAL : public HTemplateInstruction<1> { public: explicit HReturn(HInstruction* value, uint32_t dex_pc = kNoDexPc) : HTemplateInstruction(SideEffects::None(), dex_pc) { @@ -2367,7 +2330,7 @@ class HReturn : public HTemplateInstruction<1> { DISALLOW_COPY_AND_ASSIGN(HReturn); }; -class HPhi : public HInstruction { +class HPhi FINAL : public HInstruction { public: HPhi(ArenaAllocator* arena, uint32_t reg_number, @@ -2393,7 +2356,10 @@ class HPhi : public HInstruction { bool IsCatchPhi() const { return GetBlock()->IsCatchBlock(); } - size_t InputCount() const OVERRIDE { return inputs_.size(); } + using HInstruction::GetInputRecords; // Keep the const version visible. + ArrayRef<HUserRecord<HInstruction*>> GetInputRecords() OVERRIDE FINAL { + return ArrayRef<HUserRecord<HInstruction*>>(inputs_); + } void AddInput(HInstruction* input); void RemoveInputAt(size_t index); @@ -2443,15 +2409,6 @@ class HPhi : public HInstruction { DECLARE_INSTRUCTION(Phi); - protected: - const HUserRecord<HInstruction*> InputRecordAt(size_t index) const OVERRIDE { - return inputs_[index]; - } - - void SetRawInputRecordAt(size_t index, const HUserRecord<HInstruction*>& input) OVERRIDE { - inputs_[index] = input; - } - private: static constexpr size_t kFieldType = HInstruction::kNumberOfGenericPackedBits; static constexpr size_t kFieldTypeSize = @@ -2462,7 +2419,7 @@ class HPhi : public HInstruction { static_assert(kNumberOfPhiPackedBits <= kMaxNumberOfPackedBits, "Too many packed fields."); using TypeField = BitField<Primitive::Type, kFieldType, kFieldTypeSize>; - ArenaVector<HUserRecord<HInstruction*> > inputs_; + ArenaVector<HUserRecord<HInstruction*>> inputs_; const uint32_t reg_number_; DISALLOW_COPY_AND_ASSIGN(HPhi); @@ -2471,7 +2428,7 @@ class HPhi : public HInstruction { // The exit instruction is the only instruction of the exit block. // Instructions aborting the method (HThrow and HReturn) must branch to the // exit block. -class HExit : public HTemplateInstruction<0> { +class HExit FINAL : public HTemplateInstruction<0> { public: explicit HExit(uint32_t dex_pc = kNoDexPc) : HTemplateInstruction(SideEffects::None(), dex_pc) {} @@ -2484,7 +2441,7 @@ class HExit : public HTemplateInstruction<0> { }; // Jumps from one block to another. -class HGoto : public HTemplateInstruction<0> { +class HGoto FINAL : public HTemplateInstruction<0> { public: explicit HGoto(uint32_t dex_pc = kNoDexPc) : HTemplateInstruction(SideEffects::None(), dex_pc) {} @@ -2524,9 +2481,9 @@ class HConstant : public HExpression<0> { DISALLOW_COPY_AND_ASSIGN(HConstant); }; -class HNullConstant : public HConstant { +class HNullConstant FINAL : public HConstant { public: - bool InstructionDataEquals(HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { + bool InstructionDataEquals(const HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { return true; } @@ -2548,7 +2505,7 @@ class HNullConstant : public HConstant { // Constants of the type int. Those can be from Dex instructions, or // synthesized (for example with the if-eqz instruction). -class HIntConstant : public HConstant { +class HIntConstant FINAL : public HConstant { public: int32_t GetValue() const { return value_; } @@ -2556,7 +2513,7 @@ class HIntConstant : public HConstant { return static_cast<uint64_t>(static_cast<uint32_t>(value_)); } - bool InstructionDataEquals(HInstruction* other) const OVERRIDE { + bool InstructionDataEquals(const HInstruction* other) const OVERRIDE { DCHECK(other->IsIntConstant()) << other->DebugName(); return other->AsIntConstant()->value_ == value_; } @@ -2589,13 +2546,13 @@ class HIntConstant : public HConstant { DISALLOW_COPY_AND_ASSIGN(HIntConstant); }; -class HLongConstant : public HConstant { +class HLongConstant FINAL : public HConstant { public: int64_t GetValue() const { return value_; } uint64_t GetValueAsUint64() const OVERRIDE { return value_; } - bool InstructionDataEquals(HInstruction* other) const OVERRIDE { + bool InstructionDataEquals(const HInstruction* other) const OVERRIDE { DCHECK(other->IsLongConstant()) << other->DebugName(); return other->AsLongConstant()->value_ == value_; } @@ -2619,7 +2576,7 @@ class HLongConstant : public HConstant { DISALLOW_COPY_AND_ASSIGN(HLongConstant); }; -class HFloatConstant : public HConstant { +class HFloatConstant FINAL : public HConstant { public: float GetValue() const { return value_; } @@ -2627,7 +2584,7 @@ class HFloatConstant : public HConstant { return static_cast<uint64_t>(bit_cast<uint32_t, float>(value_)); } - bool InstructionDataEquals(HInstruction* other) const OVERRIDE { + bool InstructionDataEquals(const HInstruction* other) const OVERRIDE { DCHECK(other->IsFloatConstant()) << other->DebugName(); return other->AsFloatConstant()->GetValueAsUint64() == GetValueAsUint64(); } @@ -2672,13 +2629,13 @@ class HFloatConstant : public HConstant { DISALLOW_COPY_AND_ASSIGN(HFloatConstant); }; -class HDoubleConstant : public HConstant { +class HDoubleConstant FINAL : public HConstant { public: double GetValue() const { return value_; } uint64_t GetValueAsUint64() const OVERRIDE { return bit_cast<uint64_t, double>(value_); } - bool InstructionDataEquals(HInstruction* other) const OVERRIDE { + bool InstructionDataEquals(const HInstruction* other) const OVERRIDE { DCHECK(other->IsDoubleConstant()) << other->DebugName(); return other->AsDoubleConstant()->GetValueAsUint64() == GetValueAsUint64(); } @@ -2725,7 +2682,7 @@ class HDoubleConstant : public HConstant { // Conditional branch. A block ending with an HIf instruction must have // two successors. -class HIf : public HTemplateInstruction<1> { +class HIf FINAL : public HTemplateInstruction<1> { public: explicit HIf(HInstruction* input, uint32_t dex_pc = kNoDexPc) : HTemplateInstruction(SideEffects::None(), dex_pc) { @@ -2754,7 +2711,7 @@ class HIf : public HTemplateInstruction<1> { // non-exceptional control flow. // Normal-flow successor is stored at index zero, exception handlers under // higher indices in no particular order. -class HTryBoundary : public HTemplateInstruction<0> { +class HTryBoundary FINAL : public HTemplateInstruction<0> { public: enum class BoundaryKind { kEntry, @@ -2812,7 +2769,7 @@ class HTryBoundary : public HTemplateInstruction<0> { }; // Deoptimize to interpreter, upon checking a condition. -class HDeoptimize : public HTemplateInstruction<1> { +class HDeoptimize FINAL : public HTemplateInstruction<1> { public: // We set CanTriggerGC to prevent any intermediate address to be live // at the point of the `HDeoptimize`. @@ -2822,7 +2779,7 @@ class HDeoptimize : public HTemplateInstruction<1> { } bool CanBeMoved() const OVERRIDE { return true; } - bool InstructionDataEquals(HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { + bool InstructionDataEquals(const HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { return true; } bool NeedsEnvironment() const OVERRIDE { return true; } @@ -2837,7 +2794,7 @@ class HDeoptimize : public HTemplateInstruction<1> { // Represents the ArtMethod that was passed as a first argument to // the method. It is used by instructions that depend on it, like // instructions that work with the dex cache. -class HCurrentMethod : public HExpression<0> { +class HCurrentMethod FINAL : public HExpression<0> { public: explicit HCurrentMethod(Primitive::Type type, uint32_t dex_pc = kNoDexPc) : HExpression(type, SideEffects::None(), dex_pc) {} @@ -2850,7 +2807,7 @@ class HCurrentMethod : public HExpression<0> { // Fetches an ArtMethod from the virtual table or the interface method table // of a class. -class HClassTableGet : public HExpression<1> { +class HClassTableGet FINAL : public HExpression<1> { public: enum class TableKind { kVTable, @@ -2869,7 +2826,7 @@ class HClassTableGet : public HExpression<1> { } bool CanBeMoved() const OVERRIDE { return true; } - bool InstructionDataEquals(HInstruction* other) const OVERRIDE { + bool InstructionDataEquals(const HInstruction* other) const OVERRIDE { return other->AsClassTableGet()->GetIndex() == index_ && other->AsClassTableGet()->GetPackedFields() == GetPackedFields(); } @@ -2897,7 +2854,7 @@ class HClassTableGet : public HExpression<1> { // PackedSwitch (jump table). A block ending with a PackedSwitch instruction will // have one successor for each entry in the switch table, and the final successor // will be the block containing the next Dex opcode. -class HPackedSwitch : public HTemplateInstruction<1> { +class HPackedSwitch FINAL : public HTemplateInstruction<1> { public: HPackedSwitch(int32_t start_value, uint32_t num_entries, @@ -2939,7 +2896,7 @@ class HUnaryOperation : public HExpression<1> { Primitive::Type GetResultType() const { return GetType(); } bool CanBeMoved() const OVERRIDE { return true; } - bool InstructionDataEquals(HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { + bool InstructionDataEquals(const HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { return true; } @@ -3011,7 +2968,7 @@ class HBinaryOperation : public HExpression<2> { } bool CanBeMoved() const OVERRIDE { return true; } - bool InstructionDataEquals(HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { + bool InstructionDataEquals(const HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { return true; } @@ -3084,7 +3041,7 @@ class HCondition : public HBinaryOperation { ComparisonBias GetBias() const { return GetPackedField<ComparisonBiasField>(); } void SetBias(ComparisonBias bias) { SetPackedField<ComparisonBiasField>(bias); } - bool InstructionDataEquals(HInstruction* other) const OVERRIDE { + bool InstructionDataEquals(const HInstruction* other) const OVERRIDE { return GetPackedFields() == other->AsCondition()->GetPackedFields(); } @@ -3142,7 +3099,7 @@ class HCondition : public HBinaryOperation { }; // Instruction to check if two inputs are equal to each other. -class HEqual : public HCondition { +class HEqual FINAL : public HCondition { public: HEqual(HInstruction* first, HInstruction* second, uint32_t dex_pc = kNoDexPc) : HCondition(first, second, dex_pc) {} @@ -3186,7 +3143,7 @@ class HEqual : public HCondition { DISALLOW_COPY_AND_ASSIGN(HEqual); }; -class HNotEqual : public HCondition { +class HNotEqual FINAL : public HCondition { public: HNotEqual(HInstruction* first, HInstruction* second, uint32_t dex_pc = kNoDexPc) : HCondition(first, second, dex_pc) {} @@ -3229,7 +3186,7 @@ class HNotEqual : public HCondition { DISALLOW_COPY_AND_ASSIGN(HNotEqual); }; -class HLessThan : public HCondition { +class HLessThan FINAL : public HCondition { public: HLessThan(HInstruction* first, HInstruction* second, uint32_t dex_pc = kNoDexPc) : HCondition(first, second, dex_pc) {} @@ -3266,7 +3223,7 @@ class HLessThan : public HCondition { DISALLOW_COPY_AND_ASSIGN(HLessThan); }; -class HLessThanOrEqual : public HCondition { +class HLessThanOrEqual FINAL : public HCondition { public: HLessThanOrEqual(HInstruction* first, HInstruction* second, uint32_t dex_pc = kNoDexPc) : HCondition(first, second, dex_pc) {} @@ -3303,7 +3260,7 @@ class HLessThanOrEqual : public HCondition { DISALLOW_COPY_AND_ASSIGN(HLessThanOrEqual); }; -class HGreaterThan : public HCondition { +class HGreaterThan FINAL : public HCondition { public: HGreaterThan(HInstruction* first, HInstruction* second, uint32_t dex_pc = kNoDexPc) : HCondition(first, second, dex_pc) {} @@ -3340,7 +3297,7 @@ class HGreaterThan : public HCondition { DISALLOW_COPY_AND_ASSIGN(HGreaterThan); }; -class HGreaterThanOrEqual : public HCondition { +class HGreaterThanOrEqual FINAL : public HCondition { public: HGreaterThanOrEqual(HInstruction* first, HInstruction* second, uint32_t dex_pc = kNoDexPc) : HCondition(first, second, dex_pc) {} @@ -3377,7 +3334,7 @@ class HGreaterThanOrEqual : public HCondition { DISALLOW_COPY_AND_ASSIGN(HGreaterThanOrEqual); }; -class HBelow : public HCondition { +class HBelow FINAL : public HCondition { public: HBelow(HInstruction* first, HInstruction* second, uint32_t dex_pc = kNoDexPc) : HCondition(first, second, dex_pc) {} @@ -3417,7 +3374,7 @@ class HBelow : public HCondition { DISALLOW_COPY_AND_ASSIGN(HBelow); }; -class HBelowOrEqual : public HCondition { +class HBelowOrEqual FINAL : public HCondition { public: HBelowOrEqual(HInstruction* first, HInstruction* second, uint32_t dex_pc = kNoDexPc) : HCondition(first, second, dex_pc) {} @@ -3457,7 +3414,7 @@ class HBelowOrEqual : public HCondition { DISALLOW_COPY_AND_ASSIGN(HBelowOrEqual); }; -class HAbove : public HCondition { +class HAbove FINAL : public HCondition { public: HAbove(HInstruction* first, HInstruction* second, uint32_t dex_pc = kNoDexPc) : HCondition(first, second, dex_pc) {} @@ -3497,7 +3454,7 @@ class HAbove : public HCondition { DISALLOW_COPY_AND_ASSIGN(HAbove); }; -class HAboveOrEqual : public HCondition { +class HAboveOrEqual FINAL : public HCondition { public: HAboveOrEqual(HInstruction* first, HInstruction* second, uint32_t dex_pc = kNoDexPc) : HCondition(first, second, dex_pc) {} @@ -3539,7 +3496,7 @@ class HAboveOrEqual : public HCondition { // Instruction to check how two inputs compare to each other. // Result is 0 if input0 == input1, 1 if input0 > input1, or -1 if input0 < input1. -class HCompare : public HBinaryOperation { +class HCompare FINAL : public HBinaryOperation { public: // Note that `comparison_type` is the type of comparison performed // between the comparison's inputs, not the type of the instantiated @@ -3588,7 +3545,7 @@ class HCompare : public HBinaryOperation { return MakeConstantComparison(ComputeFP(x->GetValue(), y->GetValue()), GetDexPc()); } - bool InstructionDataEquals(HInstruction* other) const OVERRIDE { + bool InstructionDataEquals(const HInstruction* other) const OVERRIDE { return GetPackedFields() == other->AsCompare()->GetPackedFields(); } @@ -3628,21 +3585,21 @@ class HCompare : public HBinaryOperation { DISALLOW_COPY_AND_ASSIGN(HCompare); }; -class HNewInstance : public HExpression<2> { +class HNewInstance FINAL : public HExpression<2> { public: HNewInstance(HInstruction* cls, HCurrentMethod* current_method, uint32_t dex_pc, uint16_t type_index, const DexFile& dex_file, - bool can_throw, + bool needs_access_check, bool finalizable, QuickEntrypointEnum entrypoint) : HExpression(Primitive::kPrimNot, SideEffects::CanTriggerGC(), dex_pc), type_index_(type_index), dex_file_(dex_file), entrypoint_(entrypoint) { - SetPackedFlag<kFlagCanThrow>(can_throw); + SetPackedFlag<kFlagNeedsAccessCheck>(needs_access_check); SetPackedFlag<kFlagFinalizable>(finalizable); SetRawInputAt(0, cls); SetRawInputAt(1, current_method); @@ -3654,10 +3611,11 @@ class HNewInstance : public HExpression<2> { // Calls runtime so needs an environment. bool NeedsEnvironment() const OVERRIDE { return true; } - // It may throw when called on type that's not instantiable/accessible. - // It can throw OOME. - // TODO: distinguish between the two cases so we can for example allow allocation elimination. - bool CanThrow() const OVERRIDE { return GetPackedFlag<kFlagCanThrow>() || true; } + // Can throw errors when out-of-memory or if it's not instantiable/accessible. + bool CanThrow() const OVERRIDE { return true; } + + // Needs to call into runtime to make sure it's instantiable/accessible. + bool NeedsAccessCheck() const { return GetPackedFlag<kFlagNeedsAccessCheck>(); } bool IsFinalizable() const { return GetPackedFlag<kFlagFinalizable>(); } @@ -3674,8 +3632,8 @@ class HNewInstance : public HExpression<2> { DECLARE_INSTRUCTION(NewInstance); private: - static constexpr size_t kFlagCanThrow = kNumberOfExpressionPackedBits; - static constexpr size_t kFlagFinalizable = kFlagCanThrow + 1; + static constexpr size_t kFlagNeedsAccessCheck = kNumberOfExpressionPackedBits; + static constexpr size_t kFlagFinalizable = kFlagNeedsAccessCheck + 1; static constexpr size_t kNumberOfNewInstancePackedBits = kFlagFinalizable + 1; static_assert(kNumberOfNewInstancePackedBits <= kMaxNumberOfPackedBits, "Too many packed fields."); @@ -3717,10 +3675,13 @@ enum IntrinsicExceptions { class HInvoke : public HInstruction { public: - size_t InputCount() const OVERRIDE { return inputs_.size(); } - bool NeedsEnvironment() const OVERRIDE; + using HInstruction::GetInputRecords; // Keep the const version visible. + ArrayRef<HUserRecord<HInstruction*>> GetInputRecords() OVERRIDE { + return ArrayRef<HUserRecord<HInstruction*>>(inputs_); + } + void SetArgumentAt(size_t index, HInstruction* argument) { SetRawInputAt(index, argument); } @@ -3757,7 +3718,7 @@ class HInvoke : public HInstruction { bool CanBeMoved() const OVERRIDE { return IsIntrinsic(); } - bool InstructionDataEquals(HInstruction* other) const OVERRIDE { + bool InstructionDataEquals(const HInstruction* other) const OVERRIDE { return intrinsic_ != Intrinsics::kNone && intrinsic_ == other->AsInvoke()->intrinsic_; } @@ -3808,14 +3769,6 @@ class HInvoke : public HInstruction { SetPackedFlag<kFlagCanThrow>(true); } - const HUserRecord<HInstruction*> InputRecordAt(size_t index) const OVERRIDE { - return inputs_[index]; - } - - void SetRawInputRecordAt(size_t index, const HUserRecord<HInstruction*>& input) OVERRIDE { - inputs_[index] = input; - } - void SetCanThrow(bool can_throw) { SetPackedFlag<kFlagCanThrow>(can_throw); } uint32_t number_of_arguments_; @@ -3830,7 +3783,7 @@ class HInvoke : public HInstruction { DISALLOW_COPY_AND_ASSIGN(HInvoke); }; -class HInvokeUnresolved : public HInvoke { +class HInvokeUnresolved FINAL : public HInvoke { public: HInvokeUnresolved(ArenaAllocator* arena, uint32_t number_of_arguments, @@ -3853,7 +3806,7 @@ class HInvokeUnresolved : public HInvoke { DISALLOW_COPY_AND_ASSIGN(HInvokeUnresolved); }; -class HInvokeStaticOrDirect : public HInvoke { +class HInvokeStaticOrDirect FINAL : public HInvoke { public: // Requirements of this method call regarding the class // initialization (clinit) check of its declaring class. @@ -3982,6 +3935,25 @@ class HInvokeStaticOrDirect : public HInvoke { InsertInputAt(GetSpecialInputIndex(), input); } + using HInstruction::GetInputRecords; // Keep the const version visible. + ArrayRef<HUserRecord<HInstruction*>> GetInputRecords() OVERRIDE { + ArrayRef<HUserRecord<HInstruction*>> input_records = HInvoke::GetInputRecords(); + if (kIsDebugBuild && IsStaticWithExplicitClinitCheck()) { + DCHECK(!input_records.empty()); + DCHECK_GT(input_records.size(), GetNumberOfArguments()); + HInstruction* last_input = input_records.back().GetInstruction(); + // Note: `last_input` may be null during arguments setup. + if (last_input != nullptr) { + // `last_input` is the last input of a static invoke marked as having + // an explicit clinit check. It must either be: + // - an art::HClinitCheck instruction, set by art::HGraphBuilder; or + // - an art::HLoadClass instruction, set by art::PrepareForRegisterAllocation. + DCHECK(last_input->IsClinitCheck() || last_input->IsLoadClass()) << last_input->DebugName(); + } + } + return input_records; + } + bool CanDoImplicitNullCheckOn(HInstruction* obj ATTRIBUTE_UNUSED) const OVERRIDE { // We access the method via the dex cache so we can't do an implicit null check. // TODO: for intrinsics we can generate implicit null checks. @@ -4066,8 +4038,8 @@ class HInvokeStaticOrDirect : public HInvoke { // instruction; only relevant for static calls with explicit clinit check. void RemoveExplicitClinitCheck(ClinitCheckRequirement new_requirement) { DCHECK(IsStaticWithExplicitClinitCheck()); - size_t last_input_index = InputCount() - 1; - HInstruction* last_input = InputAt(last_input_index); + size_t last_input_index = inputs_.size() - 1u; + HInstruction* last_input = inputs_.back().GetInstruction(); DCHECK(last_input != nullptr); DCHECK(last_input->IsLoadClass() || last_input->IsClinitCheck()) << last_input->DebugName(); RemoveAsUserOfInput(last_input_index); @@ -4096,20 +4068,6 @@ class HInvokeStaticOrDirect : public HInvoke { DECLARE_INSTRUCTION(InvokeStaticOrDirect); protected: - const HUserRecord<HInstruction*> InputRecordAt(size_t i) const OVERRIDE { - const HUserRecord<HInstruction*> input_record = HInvoke::InputRecordAt(i); - if (kIsDebugBuild && IsStaticWithExplicitClinitCheck() && (i == InputCount() - 1)) { - HInstruction* input = input_record.GetInstruction(); - // `input` is the last input of a static invoke marked as having - // an explicit clinit check. It must either be: - // - an art::HClinitCheck instruction, set by art::HGraphBuilder; or - // - an art::HLoadClass instruction, set by art::PrepareForRegisterAllocation. - DCHECK(input != nullptr); - DCHECK(input->IsClinitCheck() || input->IsLoadClass()) << input->DebugName(); - } - return input_record; - } - void InsertInputAt(size_t index, HInstruction* input); void RemoveInputAt(size_t index); @@ -4142,7 +4100,7 @@ class HInvokeStaticOrDirect : public HInvoke { std::ostream& operator<<(std::ostream& os, HInvokeStaticOrDirect::MethodLoadKind rhs); std::ostream& operator<<(std::ostream& os, HInvokeStaticOrDirect::ClinitCheckRequirement rhs); -class HInvokeVirtual : public HInvoke { +class HInvokeVirtual FINAL : public HInvoke { public: HInvokeVirtual(ArenaAllocator* arena, uint32_t number_of_arguments, @@ -4168,7 +4126,7 @@ class HInvokeVirtual : public HInvoke { DISALLOW_COPY_AND_ASSIGN(HInvokeVirtual); }; -class HInvokeInterface : public HInvoke { +class HInvokeInterface FINAL : public HInvoke { public: HInvokeInterface(ArenaAllocator* arena, uint32_t number_of_arguments, @@ -4195,7 +4153,7 @@ class HInvokeInterface : public HInvoke { DISALLOW_COPY_AND_ASSIGN(HInvokeInterface); }; -class HNeg : public HUnaryOperation { +class HNeg FINAL : public HUnaryOperation { public: HNeg(Primitive::Type result_type, HInstruction* input, uint32_t dex_pc = kNoDexPc) : HUnaryOperation(result_type, input, dex_pc) { @@ -4223,7 +4181,7 @@ class HNeg : public HUnaryOperation { DISALLOW_COPY_AND_ASSIGN(HNeg); }; -class HNewArray : public HExpression<2> { +class HNewArray FINAL : public HExpression<2> { public: HNewArray(HInstruction* length, HCurrentMethod* current_method, @@ -4262,7 +4220,7 @@ class HNewArray : public HExpression<2> { DISALLOW_COPY_AND_ASSIGN(HNewArray); }; -class HAdd : public HBinaryOperation { +class HAdd FINAL : public HBinaryOperation { public: HAdd(Primitive::Type result_type, HInstruction* left, @@ -4297,7 +4255,7 @@ class HAdd : public HBinaryOperation { DISALLOW_COPY_AND_ASSIGN(HAdd); }; -class HSub : public HBinaryOperation { +class HSub FINAL : public HBinaryOperation { public: HSub(Primitive::Type result_type, HInstruction* left, @@ -4330,7 +4288,7 @@ class HSub : public HBinaryOperation { DISALLOW_COPY_AND_ASSIGN(HSub); }; -class HMul : public HBinaryOperation { +class HMul FINAL : public HBinaryOperation { public: HMul(Primitive::Type result_type, HInstruction* left, @@ -4365,7 +4323,7 @@ class HMul : public HBinaryOperation { DISALLOW_COPY_AND_ASSIGN(HMul); }; -class HDiv : public HBinaryOperation { +class HDiv FINAL : public HBinaryOperation { public: HDiv(Primitive::Type result_type, HInstruction* left, @@ -4417,7 +4375,7 @@ class HDiv : public HBinaryOperation { DISALLOW_COPY_AND_ASSIGN(HDiv); }; -class HRem : public HBinaryOperation { +class HRem FINAL : public HBinaryOperation { public: HRem(Primitive::Type result_type, HInstruction* left, @@ -4468,7 +4426,7 @@ class HRem : public HBinaryOperation { DISALLOW_COPY_AND_ASSIGN(HRem); }; -class HDivZeroCheck : public HExpression<1> { +class HDivZeroCheck FINAL : public HExpression<1> { public: // `HDivZeroCheck` can trigger GC, as it may call the `ArithmeticException` // constructor. @@ -4481,7 +4439,7 @@ class HDivZeroCheck : public HExpression<1> { bool CanBeMoved() const OVERRIDE { return true; } - bool InstructionDataEquals(HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { + bool InstructionDataEquals(const HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { return true; } @@ -4494,7 +4452,7 @@ class HDivZeroCheck : public HExpression<1> { DISALLOW_COPY_AND_ASSIGN(HDivZeroCheck); }; -class HShl : public HBinaryOperation { +class HShl FINAL : public HBinaryOperation { public: HShl(Primitive::Type result_type, HInstruction* value, @@ -4540,7 +4498,7 @@ class HShl : public HBinaryOperation { DISALLOW_COPY_AND_ASSIGN(HShl); }; -class HShr : public HBinaryOperation { +class HShr FINAL : public HBinaryOperation { public: HShr(Primitive::Type result_type, HInstruction* value, @@ -4586,7 +4544,7 @@ class HShr : public HBinaryOperation { DISALLOW_COPY_AND_ASSIGN(HShr); }; -class HUShr : public HBinaryOperation { +class HUShr FINAL : public HBinaryOperation { public: HUShr(Primitive::Type result_type, HInstruction* value, @@ -4634,7 +4592,7 @@ class HUShr : public HBinaryOperation { DISALLOW_COPY_AND_ASSIGN(HUShr); }; -class HAnd : public HBinaryOperation { +class HAnd FINAL : public HBinaryOperation { public: HAnd(Primitive::Type result_type, HInstruction* left, @@ -4671,7 +4629,7 @@ class HAnd : public HBinaryOperation { DISALLOW_COPY_AND_ASSIGN(HAnd); }; -class HOr : public HBinaryOperation { +class HOr FINAL : public HBinaryOperation { public: HOr(Primitive::Type result_type, HInstruction* left, @@ -4708,7 +4666,7 @@ class HOr : public HBinaryOperation { DISALLOW_COPY_AND_ASSIGN(HOr); }; -class HXor : public HBinaryOperation { +class HXor FINAL : public HBinaryOperation { public: HXor(Primitive::Type result_type, HInstruction* left, @@ -4745,7 +4703,7 @@ class HXor : public HBinaryOperation { DISALLOW_COPY_AND_ASSIGN(HXor); }; -class HRor : public HBinaryOperation { +class HRor FINAL : public HBinaryOperation { public: HRor(Primitive::Type result_type, HInstruction* value, HInstruction* distance) : HBinaryOperation(result_type, value, distance) { @@ -4798,7 +4756,7 @@ class HRor : public HBinaryOperation { // The value of a parameter in this method. Its location depends on // the calling convention. -class HParameterValue : public HExpression<0> { +class HParameterValue FINAL : public HExpression<0> { public: HParameterValue(const DexFile& dex_file, uint16_t type_index, @@ -4840,13 +4798,13 @@ class HParameterValue : public HExpression<0> { DISALLOW_COPY_AND_ASSIGN(HParameterValue); }; -class HNot : public HUnaryOperation { +class HNot FINAL : public HUnaryOperation { public: HNot(Primitive::Type result_type, HInstruction* input, uint32_t dex_pc = kNoDexPc) : HUnaryOperation(result_type, input, dex_pc) {} bool CanBeMoved() const OVERRIDE { return true; } - bool InstructionDataEquals(HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { + bool InstructionDataEquals(const HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { return true; } @@ -4873,13 +4831,13 @@ class HNot : public HUnaryOperation { DISALLOW_COPY_AND_ASSIGN(HNot); }; -class HBooleanNot : public HUnaryOperation { +class HBooleanNot FINAL : public HUnaryOperation { public: explicit HBooleanNot(HInstruction* input, uint32_t dex_pc = kNoDexPc) : HUnaryOperation(Primitive::Type::kPrimBoolean, input, dex_pc) {} bool CanBeMoved() const OVERRIDE { return true; } - bool InstructionDataEquals(HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { + bool InstructionDataEquals(const HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { return true; } @@ -4910,7 +4868,7 @@ class HBooleanNot : public HUnaryOperation { DISALLOW_COPY_AND_ASSIGN(HBooleanNot); }; -class HTypeConversion : public HExpression<1> { +class HTypeConversion FINAL : public HExpression<1> { public: // Instantiate a type conversion of `input` to `result_type`. HTypeConversion(Primitive::Type result_type, HInstruction* input, uint32_t dex_pc) @@ -4927,7 +4885,9 @@ class HTypeConversion : public HExpression<1> { Primitive::Type GetResultType() const { return GetType(); } bool CanBeMoved() const OVERRIDE { return true; } - bool InstructionDataEquals(HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { return true; } + bool InstructionDataEquals(const HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { + return true; + } // Try to statically evaluate the conversion and return a HConstant // containing the result. If the input cannot be converted, return nullptr. @@ -4953,7 +4913,7 @@ class HTypeConversion : public HExpression<1> { static constexpr uint32_t kNoRegNumber = -1; -class HNullCheck : public HExpression<1> { +class HNullCheck FINAL : public HExpression<1> { public: // `HNullCheck` can trigger GC, as it may call the `NullPointerException` // constructor. @@ -4963,7 +4923,7 @@ class HNullCheck : public HExpression<1> { } bool CanBeMoved() const OVERRIDE { return true; } - bool InstructionDataEquals(HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { + bool InstructionDataEquals(const HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { return true; } @@ -5015,7 +4975,7 @@ class FieldInfo : public ValueObject { const Handle<mirror::DexCache> dex_cache_; }; -class HInstanceFieldGet : public HExpression<1> { +class HInstanceFieldGet FINAL : public HExpression<1> { public: HInstanceFieldGet(HInstruction* value, Primitive::Type field_type, @@ -5041,8 +5001,8 @@ class HInstanceFieldGet : public HExpression<1> { bool CanBeMoved() const OVERRIDE { return !IsVolatile(); } - bool InstructionDataEquals(HInstruction* other) const OVERRIDE { - HInstanceFieldGet* other_get = other->AsInstanceFieldGet(); + bool InstructionDataEquals(const HInstruction* other) const OVERRIDE { + const HInstanceFieldGet* other_get = other->AsInstanceFieldGet(); return GetFieldOffset().SizeValue() == other_get->GetFieldOffset().SizeValue(); } @@ -5067,7 +5027,7 @@ class HInstanceFieldGet : public HExpression<1> { DISALLOW_COPY_AND_ASSIGN(HInstanceFieldGet); }; -class HInstanceFieldSet : public HTemplateInstruction<2> { +class HInstanceFieldSet FINAL : public HTemplateInstruction<2> { public: HInstanceFieldSet(HInstruction* object, HInstruction* value, @@ -5118,22 +5078,16 @@ class HInstanceFieldSet : public HTemplateInstruction<2> { DISALLOW_COPY_AND_ASSIGN(HInstanceFieldSet); }; -class HArrayGet : public HExpression<2> { +class HArrayGet FINAL : public HExpression<2> { public: - HArrayGet(HInstruction* array, - HInstruction* index, - Primitive::Type type, - uint32_t dex_pc, - SideEffects additional_side_effects = SideEffects::None()) - : HExpression(type, - SideEffects::ArrayReadOfType(type).Union(additional_side_effects), - dex_pc) { + HArrayGet(HInstruction* array, HInstruction* index, Primitive::Type type, uint32_t dex_pc) + : HExpression(type, SideEffects::ArrayReadOfType(type), dex_pc) { SetRawInputAt(0, array); SetRawInputAt(1, index); } bool CanBeMoved() const OVERRIDE { return true; } - bool InstructionDataEquals(HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { + bool InstructionDataEquals(const HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { return true; } bool CanDoImplicitNullCheckOn(HInstruction* obj ATTRIBUTE_UNUSED) const OVERRIDE { @@ -5170,17 +5124,14 @@ class HArrayGet : public HExpression<2> { DISALLOW_COPY_AND_ASSIGN(HArrayGet); }; -class HArraySet : public HTemplateInstruction<3> { +class HArraySet FINAL : public HTemplateInstruction<3> { public: HArraySet(HInstruction* array, HInstruction* index, HInstruction* value, Primitive::Type expected_component_type, - uint32_t dex_pc, - SideEffects additional_side_effects = SideEffects::None()) - : HTemplateInstruction( - SideEffectsForArchRuntimeCalls(value->GetType()).Union(additional_side_effects), - dex_pc) { + uint32_t dex_pc) + : HTemplateInstruction(SideEffects::None(), dex_pc) { SetPackedField<ExpectedComponentTypeField>(expected_component_type); SetPackedFlag<kFlagNeedsTypeCheck>(value->GetType() == Primitive::kPrimNot); SetPackedFlag<kFlagValueCanBeNull>(true); @@ -5188,8 +5139,8 @@ class HArraySet : public HTemplateInstruction<3> { SetRawInputAt(0, array); SetRawInputAt(1, index); SetRawInputAt(2, value); - // We can now call component type logic to set correct type-based side effects. - AddSideEffects(SideEffects::ArrayWriteOfType(GetComponentType())); + // Make a best guess now, may be refined during SSA building. + ComputeSideEffects(); } bool NeedsEnvironment() const OVERRIDE { @@ -5242,6 +5193,12 @@ class HArraySet : public HTemplateInstruction<3> { return GetPackedField<ExpectedComponentTypeField>(); } + void ComputeSideEffects() { + Primitive::Type type = GetComponentType(); + SetSideEffects(SideEffects::ArrayWriteOfType(type).Union( + SideEffectsForArchRuntimeCalls(type))); + } + static SideEffects SideEffectsForArchRuntimeCalls(Primitive::Type value_type) { return (value_type == Primitive::kPrimNot) ? SideEffects::CanTriggerGC() : SideEffects::None(); } @@ -5267,7 +5224,7 @@ class HArraySet : public HTemplateInstruction<3> { DISALLOW_COPY_AND_ASSIGN(HArraySet); }; -class HArrayLength : public HExpression<1> { +class HArrayLength FINAL : public HExpression<1> { public: HArrayLength(HInstruction* array, uint32_t dex_pc) : HExpression(Primitive::kPrimInt, SideEffects::None(), dex_pc) { @@ -5277,20 +5234,33 @@ class HArrayLength : public HExpression<1> { } bool CanBeMoved() const OVERRIDE { return true; } - bool InstructionDataEquals(HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { + bool InstructionDataEquals(const HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { return true; } bool CanDoImplicitNullCheckOn(HInstruction* obj) const OVERRIDE { return obj == InputAt(0); } + void MarkAsStringLength() { SetPackedFlag<kFlagIsStringLength>(); } + bool IsStringLength() const { return GetPackedFlag<kFlagIsStringLength>(); } + DECLARE_INSTRUCTION(ArrayLength); private: + // We treat a String as an array, creating the HArrayLength from String.length() + // or String.isEmpty() intrinsic in the instruction simplifier. We can always + // determine whether a particular HArrayLength is actually a String.length() by + // looking at the type of the input but that requires holding the mutator lock, so + // we prefer to use a flag, so that code generators don't need to do the locking. + static constexpr size_t kFlagIsStringLength = kNumberOfExpressionPackedBits; + static constexpr size_t kNumberOfArrayLengthPackedBits = kFlagIsStringLength + 1; + static_assert(kNumberOfArrayLengthPackedBits <= HInstruction::kMaxNumberOfPackedBits, + "Too many packed fields."); + DISALLOW_COPY_AND_ASSIGN(HArrayLength); }; -class HBoundsCheck : public HExpression<2> { +class HBoundsCheck FINAL : public HExpression<2> { public: // `HBoundsCheck` can trigger GC, as it may call the `IndexOutOfBoundsException` // constructor. @@ -5302,7 +5272,7 @@ class HBoundsCheck : public HExpression<2> { } bool CanBeMoved() const OVERRIDE { return true; } - bool InstructionDataEquals(HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { + bool InstructionDataEquals(const HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { return true; } @@ -5318,7 +5288,7 @@ class HBoundsCheck : public HExpression<2> { DISALLOW_COPY_AND_ASSIGN(HBoundsCheck); }; -class HSuspendCheck : public HTemplateInstruction<0> { +class HSuspendCheck FINAL : public HTemplateInstruction<0> { public: explicit HSuspendCheck(uint32_t dex_pc = kNoDexPc) : HTemplateInstruction(SideEffects::CanTriggerGC(), dex_pc), slow_path_(nullptr) {} @@ -5360,7 +5330,7 @@ class HNativeDebugInfo : public HTemplateInstruction<0> { /** * Instruction to load a Class object. */ -class HLoadClass : public HExpression<1> { +class HLoadClass FINAL : public HExpression<1> { public: HLoadClass(HCurrentMethod* current_method, uint16_t type_index, @@ -5386,7 +5356,7 @@ class HLoadClass : public HExpression<1> { bool CanBeMoved() const OVERRIDE { return true; } - bool InstructionDataEquals(HInstruction* other) const OVERRIDE { + bool InstructionDataEquals(const HInstruction* other) const OVERRIDE { // Note that we don't need to test for generate_clinit_check_. // Whether or not we need to generate the clinit check is processed in // prepare_for_register_allocator based on existing HInvokes and HClinitChecks. @@ -5464,7 +5434,7 @@ class HLoadClass : public HExpression<1> { DISALLOW_COPY_AND_ASSIGN(HLoadClass); }; -class HLoadString : public HExpression<1> { +class HLoadString FINAL : public HInstruction { public: // Determines how to load the String. enum class LoadKind { @@ -5503,12 +5473,12 @@ class HLoadString : public HExpression<1> { uint32_t string_index, const DexFile& dex_file, uint32_t dex_pc) - : HExpression(Primitive::kPrimNot, SideEffectsForArchRuntimeCalls(), dex_pc), + : HInstruction(SideEffectsForArchRuntimeCalls(), dex_pc), + special_input_(HUserRecord<HInstruction*>(current_method)), string_index_(string_index) { SetPackedFlag<kFlagIsInDexCache>(false); SetPackedField<LoadKindField>(LoadKind::kDexCacheViaMethod); load_data_.ref.dex_file = &dex_file; - SetRawInputAt(0, current_method); } void SetLoadKindWithAddress(LoadKind load_kind, uint64_t address) { @@ -5555,7 +5525,7 @@ class HLoadString : public HExpression<1> { bool CanBeMoved() const OVERRIDE { return true; } - bool InstructionDataEquals(HInstruction* other) const OVERRIDE; + bool InstructionDataEquals(const HInstruction* other) const OVERRIDE; size_t ComputeHashCode() const OVERRIDE { return string_index_; } @@ -5588,18 +5558,25 @@ class HLoadString : public HExpression<1> { SetPackedFlag<kFlagIsInDexCache>(true); DCHECK(!NeedsEnvironment()); RemoveEnvironment(); + SetSideEffects(SideEffects::None()); } - size_t InputCount() const OVERRIDE { - return (InputAt(0) != nullptr) ? 1u : 0u; + void AddSpecialInput(HInstruction* special_input); + + using HInstruction::GetInputRecords; // Keep the const version visible. + ArrayRef<HUserRecord<HInstruction*>> GetInputRecords() OVERRIDE FINAL { + return ArrayRef<HUserRecord<HInstruction*>>( + &special_input_, (special_input_.GetInstruction() != nullptr) ? 1u : 0u); } - void AddSpecialInput(HInstruction* special_input); + Primitive::Type GetType() const OVERRIDE { + return Primitive::kPrimNot; + } DECLARE_INSTRUCTION(LoadString); private: - static constexpr size_t kFlagIsInDexCache = kNumberOfExpressionPackedBits; + static constexpr size_t kFlagIsInDexCache = kNumberOfGenericPackedBits; static constexpr size_t kFieldLoadKind = kFlagIsInDexCache + 1; static constexpr size_t kFieldLoadKindSize = MinimumBitsToStore(static_cast<size_t>(LoadKind::kLast)); @@ -5623,6 +5600,8 @@ class HLoadString : public HExpression<1> { void SetLoadKindInternal(LoadKind load_kind); + HUserRecord<HInstruction*> special_input_; + // String index serves also as the hash code and it's also needed for slow-paths, // so it must not be overwritten with other load data. uint32_t string_index_; @@ -5657,15 +5636,17 @@ inline void HLoadString::AddSpecialInput(HInstruction* special_input) { // The special input is used for PC-relative loads on some architectures. DCHECK(GetLoadKind() == LoadKind::kBootImageLinkTimePcRelative || GetLoadKind() == LoadKind::kDexCachePcRelative) << GetLoadKind(); - DCHECK(InputAt(0) == nullptr); - SetRawInputAt(0u, special_input); + // HLoadString::GetInputRecords() returns an empty array at this point, + // so use the GetInputRecords() from the base class to set the input record. + DCHECK(special_input_.GetInstruction() == nullptr); + special_input_ = HUserRecord<HInstruction*>(special_input); special_input->AddUseAt(this, 0); } /** * Performs an initialization check on its Class object input. */ -class HClinitCheck : public HExpression<1> { +class HClinitCheck FINAL : public HExpression<1> { public: HClinitCheck(HLoadClass* constant, uint32_t dex_pc) : HExpression( @@ -5676,7 +5657,7 @@ class HClinitCheck : public HExpression<1> { } bool CanBeMoved() const OVERRIDE { return true; } - bool InstructionDataEquals(HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { + bool InstructionDataEquals(const HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { return true; } @@ -5695,7 +5676,7 @@ class HClinitCheck : public HExpression<1> { DISALLOW_COPY_AND_ASSIGN(HClinitCheck); }; -class HStaticFieldGet : public HExpression<1> { +class HStaticFieldGet FINAL : public HExpression<1> { public: HStaticFieldGet(HInstruction* cls, Primitive::Type field_type, @@ -5722,8 +5703,8 @@ class HStaticFieldGet : public HExpression<1> { bool CanBeMoved() const OVERRIDE { return !IsVolatile(); } - bool InstructionDataEquals(HInstruction* other) const OVERRIDE { - HStaticFieldGet* other_get = other->AsStaticFieldGet(); + bool InstructionDataEquals(const HInstruction* other) const OVERRIDE { + const HStaticFieldGet* other_get = other->AsStaticFieldGet(); return GetFieldOffset().SizeValue() == other_get->GetFieldOffset().SizeValue(); } @@ -5744,7 +5725,7 @@ class HStaticFieldGet : public HExpression<1> { DISALLOW_COPY_AND_ASSIGN(HStaticFieldGet); }; -class HStaticFieldSet : public HTemplateInstruction<2> { +class HStaticFieldSet FINAL : public HTemplateInstruction<2> { public: HStaticFieldSet(HInstruction* cls, HInstruction* value, @@ -5792,7 +5773,7 @@ class HStaticFieldSet : public HTemplateInstruction<2> { DISALLOW_COPY_AND_ASSIGN(HStaticFieldSet); }; -class HUnresolvedInstanceFieldGet : public HExpression<1> { +class HUnresolvedInstanceFieldGet FINAL : public HExpression<1> { public: HUnresolvedInstanceFieldGet(HInstruction* obj, Primitive::Type field_type, @@ -5817,7 +5798,7 @@ class HUnresolvedInstanceFieldGet : public HExpression<1> { DISALLOW_COPY_AND_ASSIGN(HUnresolvedInstanceFieldGet); }; -class HUnresolvedInstanceFieldSet : public HTemplateInstruction<2> { +class HUnresolvedInstanceFieldSet FINAL : public HTemplateInstruction<2> { public: HUnresolvedInstanceFieldSet(HInstruction* obj, HInstruction* value, @@ -5855,7 +5836,7 @@ class HUnresolvedInstanceFieldSet : public HTemplateInstruction<2> { DISALLOW_COPY_AND_ASSIGN(HUnresolvedInstanceFieldSet); }; -class HUnresolvedStaticFieldGet : public HExpression<0> { +class HUnresolvedStaticFieldGet FINAL : public HExpression<0> { public: HUnresolvedStaticFieldGet(Primitive::Type field_type, uint32_t field_index, @@ -5878,7 +5859,7 @@ class HUnresolvedStaticFieldGet : public HExpression<0> { DISALLOW_COPY_AND_ASSIGN(HUnresolvedStaticFieldGet); }; -class HUnresolvedStaticFieldSet : public HTemplateInstruction<1> { +class HUnresolvedStaticFieldSet FINAL : public HTemplateInstruction<1> { public: HUnresolvedStaticFieldSet(HInstruction* value, Primitive::Type field_type, @@ -5915,7 +5896,7 @@ class HUnresolvedStaticFieldSet : public HTemplateInstruction<1> { }; // Implement the move-exception DEX instruction. -class HLoadException : public HExpression<0> { +class HLoadException FINAL : public HExpression<0> { public: explicit HLoadException(uint32_t dex_pc = kNoDexPc) : HExpression(Primitive::kPrimNot, SideEffects::None(), dex_pc) {} @@ -5930,7 +5911,7 @@ class HLoadException : public HExpression<0> { // Implicit part of move-exception which clears thread-local exception storage. // Must not be removed because the runtime expects the TLS to get cleared. -class HClearException : public HTemplateInstruction<0> { +class HClearException FINAL : public HTemplateInstruction<0> { public: explicit HClearException(uint32_t dex_pc = kNoDexPc) : HTemplateInstruction(SideEffects::AllWrites(), dex_pc) {} @@ -5941,7 +5922,7 @@ class HClearException : public HTemplateInstruction<0> { DISALLOW_COPY_AND_ASSIGN(HClearException); }; -class HThrow : public HTemplateInstruction<1> { +class HThrow FINAL : public HTemplateInstruction<1> { public: HThrow(HInstruction* exception, uint32_t dex_pc) : HTemplateInstruction(SideEffects::CanTriggerGC(), dex_pc) { @@ -5978,7 +5959,7 @@ enum class TypeCheckKind { std::ostream& operator<<(std::ostream& os, TypeCheckKind rhs); -class HInstanceOf : public HExpression<2> { +class HInstanceOf FINAL : public HExpression<2> { public: HInstanceOf(HInstruction* object, HLoadClass* constant, @@ -5995,7 +5976,7 @@ class HInstanceOf : public HExpression<2> { bool CanBeMoved() const OVERRIDE { return true; } - bool InstructionDataEquals(HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { + bool InstructionDataEquals(const HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { return true; } @@ -6032,7 +6013,7 @@ class HInstanceOf : public HExpression<2> { DISALLOW_COPY_AND_ASSIGN(HInstanceOf); }; -class HBoundType : public HExpression<1> { +class HBoundType FINAL : public HExpression<1> { public: HBoundType(HInstruction* input, uint32_t dex_pc = kNoDexPc) : HExpression(Primitive::kPrimNot, SideEffects::None(), dex_pc), @@ -6076,7 +6057,7 @@ class HBoundType : public HExpression<1> { DISALLOW_COPY_AND_ASSIGN(HBoundType); }; -class HCheckCast : public HTemplateInstruction<2> { +class HCheckCast FINAL : public HTemplateInstruction<2> { public: HCheckCast(HInstruction* object, HLoadClass* constant, @@ -6091,7 +6072,7 @@ class HCheckCast : public HTemplateInstruction<2> { bool CanBeMoved() const OVERRIDE { return true; } - bool InstructionDataEquals(HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { + bool InstructionDataEquals(const HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { return true; } @@ -6121,7 +6102,7 @@ class HCheckCast : public HTemplateInstruction<2> { DISALLOW_COPY_AND_ASSIGN(HCheckCast); }; -class HMemoryBarrier : public HTemplateInstruction<0> { +class HMemoryBarrier FINAL : public HTemplateInstruction<0> { public: explicit HMemoryBarrier(MemBarrierKind barrier_kind, uint32_t dex_pc = kNoDexPc) : HTemplateInstruction( @@ -6146,7 +6127,7 @@ class HMemoryBarrier : public HTemplateInstruction<0> { DISALLOW_COPY_AND_ASSIGN(HMemoryBarrier); }; -class HMonitorOperation : public HTemplateInstruction<1> { +class HMonitorOperation FINAL : public HTemplateInstruction<1> { public: enum class OperationKind { kEnter, @@ -6191,7 +6172,7 @@ class HMonitorOperation : public HTemplateInstruction<1> { DISALLOW_COPY_AND_ASSIGN(HMonitorOperation); }; -class HSelect : public HExpression<3> { +class HSelect FINAL : public HExpression<3> { public: HSelect(HInstruction* condition, HInstruction* true_value, @@ -6214,7 +6195,9 @@ class HSelect : public HExpression<3> { HInstruction* GetCondition() const { return InputAt(2); } bool CanBeMoved() const OVERRIDE { return true; } - bool InstructionDataEquals(HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { return true; } + bool InstructionDataEquals(const HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { + return true; + } bool CanBeNull() const OVERRIDE { return GetTrueValue()->CanBeNull() || GetFalseValue()->CanBeNull(); @@ -6304,7 +6287,7 @@ std::ostream& operator<<(std::ostream& os, const MoveOperands& rhs); static constexpr size_t kDefaultNumberOfMoves = 4; -class HParallelMove : public HTemplateInstruction<0> { +class HParallelMove FINAL : public HTemplateInstruction<0> { public: explicit HParallelMove(ArenaAllocator* arena, uint32_t dex_pc = kNoDexPc) : HTemplateInstruction(SideEffects::None(), dex_pc), diff --git a/compiler/optimizing/nodes_arm.h b/compiler/optimizing/nodes_arm.h index 6a1dbb9e70..371e8ef6bb 100644 --- a/compiler/optimizing/nodes_arm.h +++ b/compiler/optimizing/nodes_arm.h @@ -19,7 +19,7 @@ namespace art { -class HArmDexCacheArraysBase : public HExpression<0> { +class HArmDexCacheArraysBase FINAL : public HExpression<0> { public: explicit HArmDexCacheArraysBase(const DexFile& dex_file) : HExpression(Primitive::kPrimInt, SideEffects::None(), kNoDexPc), diff --git a/compiler/optimizing/nodes_arm64.h b/compiler/optimizing/nodes_arm64.h index 173852a55d..06b073c3e2 100644 --- a/compiler/optimizing/nodes_arm64.h +++ b/compiler/optimizing/nodes_arm64.h @@ -21,7 +21,7 @@ namespace art { -class HArm64DataProcWithShifterOp : public HExpression<2> { +class HArm64DataProcWithShifterOp FINAL : public HExpression<2> { public: enum OpKind { kLSL, // Logical shift left. @@ -56,8 +56,8 @@ class HArm64DataProcWithShifterOp : public HExpression<2> { } bool CanBeMoved() const OVERRIDE { return true; } - bool InstructionDataEquals(HInstruction* other_instr) const OVERRIDE { - HArm64DataProcWithShifterOp* other = other_instr->AsArm64DataProcWithShifterOp(); + bool InstructionDataEquals(const HInstruction* other_instr) const OVERRIDE { + const HArm64DataProcWithShifterOp* other = other_instr->AsArm64DataProcWithShifterOp(); return instr_kind_ == other->instr_kind_ && op_kind_ == other->op_kind_ && shift_amount_ == other->shift_amount_; @@ -97,7 +97,7 @@ std::ostream& operator<<(std::ostream& os, const HArm64DataProcWithShifterOp::Op // This instruction computes an intermediate address pointing in the 'middle' of an object. The // result pointer cannot be handled by GC, so extra care is taken to make sure that this value is // never used across anything that can trigger GC. -class HArm64IntermediateAddress : public HExpression<2> { +class HArm64IntermediateAddress FINAL : public HExpression<2> { public: HArm64IntermediateAddress(HInstruction* base_address, HInstruction* offset, uint32_t dex_pc) : HExpression(Primitive::kPrimNot, SideEffects::DependsOnGC(), dex_pc) { @@ -106,7 +106,9 @@ class HArm64IntermediateAddress : public HExpression<2> { } bool CanBeMoved() const OVERRIDE { return true; } - bool InstructionDataEquals(HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { return true; } + bool InstructionDataEquals(const HInstruction* other ATTRIBUTE_UNUSED) const OVERRIDE { + return true; + } bool IsActualObject() const OVERRIDE { return false; } HInstruction* GetBaseAddress() const { return InputAt(0); } diff --git a/compiler/optimizing/nodes_shared.h b/compiler/optimizing/nodes_shared.h index c10c718ff4..f2d5cf3253 100644 --- a/compiler/optimizing/nodes_shared.h +++ b/compiler/optimizing/nodes_shared.h @@ -19,7 +19,7 @@ namespace art { -class HMultiplyAccumulate : public HExpression<3> { +class HMultiplyAccumulate FINAL : public HExpression<3> { public: HMultiplyAccumulate(Primitive::Type type, InstructionKind op, @@ -38,7 +38,7 @@ class HMultiplyAccumulate : public HExpression<3> { static constexpr int kInputMulRightIndex = 2; bool CanBeMoved() const OVERRIDE { return true; } - bool InstructionDataEquals(HInstruction* other) const OVERRIDE { + bool InstructionDataEquals(const HInstruction* other) const OVERRIDE { return op_kind_ == other->AsMultiplyAccumulate()->op_kind_; } @@ -53,7 +53,7 @@ class HMultiplyAccumulate : public HExpression<3> { DISALLOW_COPY_AND_ASSIGN(HMultiplyAccumulate); }; -class HBitwiseNegatedRight : public HBinaryOperation { +class HBitwiseNegatedRight FINAL : public HBinaryOperation { public: HBitwiseNegatedRight(Primitive::Type result_type, InstructionKind op, diff --git a/compiler/optimizing/nodes_test.cc b/compiler/optimizing/nodes_test.cc index 764f5fec5b..d4e2a58103 100644 --- a/compiler/optimizing/nodes_test.cc +++ b/compiler/optimizing/nodes_test.cc @@ -91,7 +91,7 @@ TEST(Node, InsertInstruction) { entry->InsertInstructionBefore(to_insert, parameter2); ASSERT_TRUE(parameter1->HasUses()); - ASSERT_TRUE(parameter1->GetUses().HasOnlyOneUse()); + ASSERT_TRUE(parameter1->GetUses().HasExactlyOneElement()); } /** @@ -115,7 +115,7 @@ TEST(Node, AddInstruction) { entry->AddInstruction(to_add); ASSERT_TRUE(parameter->HasUses()); - ASSERT_TRUE(parameter->GetUses().HasOnlyOneUse()); + ASSERT_TRUE(parameter->GetUses().HasExactlyOneElement()); } TEST(Node, ParentEnvironment) { @@ -134,7 +134,7 @@ TEST(Node, ParentEnvironment) { entry->AddInstruction(new (&allocator) HExit()); ASSERT_TRUE(parameter1->HasUses()); - ASSERT_TRUE(parameter1->GetUses().HasOnlyOneUse()); + ASSERT_TRUE(parameter1->GetUses().HasExactlyOneElement()); HEnvironment* environment = new (&allocator) HEnvironment( &allocator, 1, graph->GetDexFile(), graph->GetMethodIdx(), 0, kStatic, with_environment); @@ -145,7 +145,7 @@ TEST(Node, ParentEnvironment) { with_environment->SetRawEnvironment(environment); ASSERT_TRUE(parameter1->HasEnvironmentUses()); - ASSERT_TRUE(parameter1->GetEnvUses().HasOnlyOneUse()); + ASSERT_TRUE(parameter1->GetEnvUses().HasExactlyOneElement()); HEnvironment* parent1 = new (&allocator) HEnvironment( &allocator, 1, graph->GetDexFile(), graph->GetMethodIdx(), 0, kStatic, nullptr); diff --git a/compiler/optimizing/nodes_x86.h b/compiler/optimizing/nodes_x86.h index 0b3a84d3d3..c3696b5936 100644 --- a/compiler/optimizing/nodes_x86.h +++ b/compiler/optimizing/nodes_x86.h @@ -20,7 +20,7 @@ namespace art { // Compute the address of the method for X86 Constant area support. -class HX86ComputeBaseMethodAddress : public HExpression<0> { +class HX86ComputeBaseMethodAddress FINAL : public HExpression<0> { public: // Treat the value as an int32_t, but it is really a 32 bit native pointer. HX86ComputeBaseMethodAddress() @@ -33,7 +33,7 @@ class HX86ComputeBaseMethodAddress : public HExpression<0> { }; // Load a constant value from the constant table. -class HX86LoadFromConstantTable : public HExpression<2> { +class HX86LoadFromConstantTable FINAL : public HExpression<2> { public: HX86LoadFromConstantTable(HX86ComputeBaseMethodAddress* method_base, HConstant* constant) @@ -57,7 +57,7 @@ class HX86LoadFromConstantTable : public HExpression<2> { }; // Version of HNeg with access to the constant table for FP types. -class HX86FPNeg : public HExpression<2> { +class HX86FPNeg FINAL : public HExpression<2> { public: HX86FPNeg(Primitive::Type result_type, HInstruction* input, @@ -76,7 +76,7 @@ class HX86FPNeg : public HExpression<2> { }; // X86 version of HPackedSwitch that holds a pointer to the base method address. -class HX86PackedSwitch : public HTemplateInstruction<2> { +class HX86PackedSwitch FINAL : public HTemplateInstruction<2> { public: HX86PackedSwitch(int32_t start_value, int32_t num_entries, diff --git a/compiler/optimizing/optimizing_cfi_test.cc b/compiler/optimizing/optimizing_cfi_test.cc index 400686d236..a6d234d739 100644 --- a/compiler/optimizing/optimizing_cfi_test.cc +++ b/compiler/optimizing/optimizing_cfi_test.cc @@ -32,7 +32,7 @@ namespace art { // Run the tests only on host. -#ifndef __ANDROID__ +#ifndef ART_TARGET_ANDROID class OptimizingCFITest : public CFITest { public: @@ -241,6 +241,6 @@ TEST_F(OptimizingCFITest, kMips64Adjust) { Check(kMips64, "kMips64_adjust", expected_asm, expected_cfi); } -#endif // __ANDROID__ +#endif // ART_TARGET_ANDROID } // namespace art diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc index 3670ce2253..37197af460 100644 --- a/compiler/optimizing/optimizing_compiler.cc +++ b/compiler/optimizing/optimizing_compiler.cc @@ -769,15 +769,6 @@ CodeGenerator* OptimizingCompiler::TryCompile(ArenaAllocator* arena, return codegen.release(); } -static bool CanHandleVerificationFailure(const VerifiedMethod* verified_method) { - // For access errors the compiler will use the unresolved helpers (e.g. HInvokeUnresolved). - uint32_t unresolved_mask = verifier::VerifyError::VERIFY_ERROR_NO_CLASS - | verifier::VerifyError::VERIFY_ERROR_ACCESS_CLASS - | verifier::VerifyError::VERIFY_ERROR_ACCESS_FIELD - | verifier::VerifyError::VERIFY_ERROR_ACCESS_METHOD; - return (verified_method->GetEncounteredVerificationFailures() & (~unresolved_mask)) == 0; -} - CompiledMethod* OptimizingCompiler::Compile(const DexFile::CodeItem* code_item, uint32_t access_flags, InvokeType invoke_type, @@ -792,7 +783,8 @@ CompiledMethod* OptimizingCompiler::Compile(const DexFile::CodeItem* code_item, const VerifiedMethod* verified_method = compiler_driver->GetVerifiedMethod(&dex_file, method_idx); DCHECK(!verified_method->HasRuntimeThrow()); if (compiler_driver->IsMethodVerifiedWithoutFailures(method_idx, class_def_idx, dex_file) - || CanHandleVerificationFailure(verified_method)) { + || verifier::MethodVerifier::CanCompilerHandleVerificationFailure( + verified_method->GetEncounteredVerificationFailures())) { ArenaAllocator arena(Runtime::Current()->GetArenaPool()); CodeVectorAllocator code_allocator(&arena); std::unique_ptr<CodeGenerator> codegen( @@ -865,6 +857,7 @@ bool OptimizingCompiler::JitCompile(Thread* self, Handle<mirror::ClassLoader> class_loader(hs.NewHandle( method->GetDeclaringClass()->GetClassLoader())); Handle<mirror::DexCache> dex_cache(hs.NewHandle(method->GetDexCache())); + DCHECK(method->IsCompilable()); jobject jclass_loader = class_loader.ToJObject(); const DexFile* dex_file = method->GetDexFile(); diff --git a/compiler/optimizing/pc_relative_fixups_x86.cc b/compiler/optimizing/pc_relative_fixups_x86.cc index dafbd3d7d1..cb2fc0a19a 100644 --- a/compiler/optimizing/pc_relative_fixups_x86.cc +++ b/compiler/optimizing/pc_relative_fixups_x86.cc @@ -202,8 +202,9 @@ class PCRelativeHandlerVisitor : public HGraphVisitor { } // Ensure that we can load FP arguments from the constant area. - for (size_t i = 0, e = invoke->InputCount(); i < e; i++) { - HConstant* input = invoke->InputAt(i)->AsConstant(); + auto&& inputs = invoke->GetInputs(); + for (size_t i = 0; i < inputs.size(); i++) { + HConstant* input = inputs[i]->AsConstant(); if (input != nullptr && Primitive::IsFloatingPointType(input->GetType())) { ReplaceInput(invoke, input, i, true); } diff --git a/compiler/optimizing/prepare_for_register_allocation.cc b/compiler/optimizing/prepare_for_register_allocation.cc index fc72727196..c941c0c086 100644 --- a/compiler/optimizing/prepare_for_register_allocation.cc +++ b/compiler/optimizing/prepare_for_register_allocation.cc @@ -63,8 +63,8 @@ void PrepareForRegisterAllocation::VisitArraySet(HArraySet* instruction) { void PrepareForRegisterAllocation::VisitClinitCheck(HClinitCheck* check) { // Try to find a static invoke or a new-instance from which this check originated. HInstruction* implicit_clinit = nullptr; - for (HUseIterator<HInstruction*> it(check->GetUses()); !it.Done(); it.Advance()) { - HInstruction* user = it.Current()->GetUser(); + for (const HUseListNode<HInstruction*>& use : check->GetUses()) { + HInstruction* user = use.GetUser(); if ((user->IsInvokeStaticOrDirect() || user->IsNewInstance()) && CanMoveClinitCheck(check, user)) { implicit_clinit = user; @@ -85,11 +85,12 @@ void PrepareForRegisterAllocation::VisitClinitCheck(HClinitCheck* check) { // If we found a static invoke or new-instance for merging, remove the check // from dominated static invokes. if (implicit_clinit != nullptr) { - for (HUseIterator<HInstruction*> it(check->GetUses()); !it.Done(); ) { - HInstruction* user = it.Current()->GetUser(); + const HUseList<HInstruction*>& uses = check->GetUses(); + for (auto it = uses.begin(), end = uses.end(); it != end; /* ++it below */) { + HInstruction* user = it->GetUser(); // All other uses must be dominated. DCHECK(implicit_clinit->StrictlyDominates(user) || (implicit_clinit == user)); - it.Advance(); // Advance before we remove the node, reference to the next node is preserved. + ++it; // Advance before we remove the node, reference to the next node is preserved. if (user->IsInvokeStaticOrDirect()) { user->AsInvokeStaticOrDirect()->RemoveExplicitClinitCheck( HInvokeStaticOrDirect::ClinitCheckRequirement::kNone); @@ -159,7 +160,7 @@ bool PrepareForRegisterAllocation::CanEmitConditionAt(HCondition* condition, void PrepareForRegisterAllocation::VisitCondition(HCondition* condition) { if (condition->HasOnlyOneNonEnvironmentUse()) { - HInstruction* user = condition->GetUses().GetFirst()->GetUser(); + HInstruction* user = condition->GetUses().front().GetUser(); if (CanEmitConditionAt(condition, user)) { condition->MarkEmittedAtUseSite(); } @@ -168,8 +169,7 @@ void PrepareForRegisterAllocation::VisitCondition(HCondition* condition) { void PrepareForRegisterAllocation::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { if (invoke->IsStaticWithExplicitClinitCheck()) { - size_t last_input_index = invoke->InputCount() - 1; - HLoadClass* last_input = invoke->InputAt(last_input_index)->AsLoadClass(); + HLoadClass* last_input = invoke->GetInputs().back()->AsLoadClass(); DCHECK(last_input != nullptr) << "Last input is not HLoadClass. It is " << last_input->DebugName(); diff --git a/compiler/optimizing/pretty_printer.h b/compiler/optimizing/pretty_printer.h index 429e6e3d3f..f9bef6809f 100644 --- a/compiler/optimizing/pretty_printer.h +++ b/compiler/optimizing/pretty_printer.h @@ -39,29 +39,30 @@ class HPrettyPrinter : public HGraphVisitor { } void PrintPostInstruction(HInstruction* instruction) { - if (instruction->InputCount() != 0) { + auto&& inputs = instruction->GetInputs(); + if (!inputs.empty()) { PrintString("("); bool first = true; - for (HInputIterator it(instruction); !it.Done(); it.Advance()) { + for (const HInstruction* input : inputs) { if (first) { first = false; } else { PrintString(", "); } - PrintInt(it.Current()->GetId()); + PrintInt(input->GetId()); } PrintString(")"); } if (instruction->HasUses()) { PrintString(" ["); bool first = true; - for (HUseIterator<HInstruction*> it(instruction->GetUses()); !it.Done(); it.Advance()) { + for (const HUseListNode<HInstruction*>& use : instruction->GetUses()) { if (first) { first = false; } else { PrintString(", "); } - PrintInt(it.Current()->GetUser()->GetId()); + PrintInt(use.GetUser()->GetId()); } PrintString("]"); } diff --git a/compiler/optimizing/reference_type_propagation.cc b/compiler/optimizing/reference_type_propagation.cc index 95f10e0720..2a281dd46d 100644 --- a/compiler/optimizing/reference_type_propagation.cc +++ b/compiler/optimizing/reference_type_propagation.cc @@ -23,6 +23,17 @@ namespace art { +static inline mirror::DexCache* FindDexCacheWithHint(Thread* self, + const DexFile& dex_file, + Handle<mirror::DexCache> hint_dex_cache) + SHARED_REQUIRES(Locks::mutator_lock_) { + if (LIKELY(hint_dex_cache->GetDexFile() == &dex_file)) { + return hint_dex_cache.Get(); + } else { + return Runtime::Current()->GetClassLinker()->FindDexCache(self, dex_file); + } +} + static inline ReferenceTypeInfo::TypeHandle GetRootHandle(StackHandleScopeCollection* handles, ClassLinker::ClassRoot class_root, ReferenceTypeInfo::TypeHandle* cache) { @@ -35,6 +46,13 @@ static inline ReferenceTypeInfo::TypeHandle GetRootHandle(StackHandleScopeCollec return *cache; } +// Returns true if klass is admissible to the propagation: non-null and non-erroneous. +// For an array type, we also check if the component type is admissible. +static bool IsAdmissible(mirror::Class* klass) SHARED_REQUIRES(Locks::mutator_lock_) { + return klass != nullptr && !klass->IsErroneous() && + (!klass->IsArrayClass() || IsAdmissible(klass->GetComponentType())); +} + ReferenceTypeInfo::TypeHandle ReferenceTypePropagation::HandleCache::GetObjectClassHandle() { return GetRootHandle(handles_, ClassLinker::kJavaLangObject, &object_class_handle_); } @@ -54,10 +72,12 @@ ReferenceTypeInfo::TypeHandle ReferenceTypePropagation::HandleCache::GetThrowabl class ReferenceTypePropagation::RTPVisitor : public HGraphDelegateVisitor { public: RTPVisitor(HGraph* graph, + Handle<mirror::DexCache> hint_dex_cache, HandleCache* handle_cache, ArenaVector<HInstruction*>* worklist, bool is_first_run) : HGraphDelegateVisitor(graph), + hint_dex_cache_(hint_dex_cache), handle_cache_(handle_cache), worklist_(worklist), is_first_run_(is_first_run) {} @@ -70,7 +90,8 @@ class ReferenceTypePropagation::RTPVisitor : public HGraphDelegateVisitor { void VisitNewArray(HNewArray* instr) OVERRIDE; void VisitParameterValue(HParameterValue* instr) OVERRIDE; void UpdateFieldAccessTypeInfo(HInstruction* instr, const FieldInfo& info); - void SetClassAsTypeInfo(HInstruction* instr, mirror::Class* klass, bool is_exact); + void SetClassAsTypeInfo(HInstruction* instr, mirror::Class* klass, bool is_exact) + SHARED_REQUIRES(Locks::mutator_lock_); void VisitInstanceFieldGet(HInstanceFieldGet* instr) OVERRIDE; void VisitStaticFieldGet(HStaticFieldGet* instr) OVERRIDE; void VisitUnresolvedInstanceFieldGet(HUnresolvedInstanceFieldGet* instr) OVERRIDE; @@ -86,16 +107,19 @@ class ReferenceTypePropagation::RTPVisitor : public HGraphDelegateVisitor { bool is_exact); private: + Handle<mirror::DexCache> hint_dex_cache_; HandleCache* handle_cache_; ArenaVector<HInstruction*>* worklist_; const bool is_first_run_; }; ReferenceTypePropagation::ReferenceTypePropagation(HGraph* graph, + Handle<mirror::DexCache> hint_dex_cache, StackHandleScopeCollection* handles, bool is_first_run, const char* name) : HOptimization(graph, name), + hint_dex_cache_(hint_dex_cache), handle_cache_(handles), worklist_(graph->GetArena()->Adapter(kArenaAllocReferenceTypePropagation)), is_first_run_(is_first_run) { @@ -130,7 +154,7 @@ void ReferenceTypePropagation::ValidateTypes() { } void ReferenceTypePropagation::Visit(HInstruction* instruction) { - RTPVisitor visitor(graph_, &handle_cache_, &worklist_, is_first_run_); + RTPVisitor visitor(graph_, hint_dex_cache_, &handle_cache_, &worklist_, is_first_run_); instruction->Accept(&visitor); } @@ -149,7 +173,7 @@ void ReferenceTypePropagation::Run() { } void ReferenceTypePropagation::VisitBasicBlock(HBasicBlock* block) { - RTPVisitor visitor(graph_, &handle_cache_, &worklist_, is_first_run_); + RTPVisitor visitor(graph_, hint_dex_cache_, &handle_cache_, &worklist_, is_first_run_); // Handle Phis first as there might be instructions in the same block who depend on them. for (HInstructionIterator it(block->GetPhis()); !it.Done(); it.Advance()) { VisitPhi(it.Current()->AsPhi()); @@ -187,8 +211,8 @@ static bool ShouldCreateBoundType(HInstruction* position, if (existing_bound_type->GetUpperBound().IsSupertypeOf(upper_bound)) { if (kIsDebugBuild) { // Check that the existing HBoundType dominates all the uses. - for (HUseIterator<HInstruction*> it(obj->GetUses()); !it.Done(); it.Advance()) { - HInstruction* user = it.Current()->GetUser(); + for (const HUseListNode<HInstruction*>& use : obj->GetUses()) { + HInstruction* user = use.GetUser(); if (dominator_instr != nullptr) { DCHECK(!dominator_instr->StrictlyDominates(user) || user == existing_bound_type @@ -242,8 +266,12 @@ void ReferenceTypePropagation::BoundTypeForIfNotNull(HBasicBlock* block) { ? ifInstruction->IfTrueSuccessor() : ifInstruction->IfFalseSuccessor(); - for (HUseIterator<HInstruction*> it(obj->GetUses()); !it.Done(); it.Advance()) { - HInstruction* user = it.Current()->GetUser(); + const HUseList<HInstruction*>& uses = obj->GetUses(); + for (auto it = uses.begin(), end = uses.end(); it != end; /* ++it below */) { + HInstruction* user = it->GetUser(); + size_t index = it->GetIndex(); + // Increment `it` now because `*it` may disappear thanks to user->ReplaceInput(). + ++it; if (notNullBlock->Dominates(user->GetBlock())) { if (bound_type == nullptr) { ScopedObjectAccess soa(Thread::Current()); @@ -264,7 +292,7 @@ void ReferenceTypePropagation::BoundTypeForIfNotNull(HBasicBlock* block) { break; } } - user->ReplaceInput(bound_type, it.Current()->GetIndex()); + user->ReplaceInput(bound_type, index); } } } @@ -358,7 +386,6 @@ void ReferenceTypePropagation::BoundTypeForIfInstanceOf(HBasicBlock* block) { HLoadClass* load_class = instanceOf->InputAt(1)->AsLoadClass(); ReferenceTypeInfo class_rti = load_class->GetLoadedClassRTI(); { - ScopedObjectAccess soa(Thread::Current()); if (!class_rti.IsValid()) { // He have loaded an unresolved class. Don't bother bounding the type. return; @@ -379,8 +406,12 @@ void ReferenceTypePropagation::BoundTypeForIfInstanceOf(HBasicBlock* block) { return; } DCHECK(!obj->IsLoadClass()) << "We should not replace HLoadClass instructions"; - for (HUseIterator<HInstruction*> it(obj->GetUses()); !it.Done(); it.Advance()) { - HInstruction* user = it.Current()->GetUser(); + const HUseList<HInstruction*>& uses = obj->GetUses(); + for (auto it = uses.begin(), end = uses.end(); it != end; /* ++it below */) { + HInstruction* user = it->GetUser(); + size_t index = it->GetIndex(); + // Increment `it` now because `*it` may disappear thanks to user->ReplaceInput(). + ++it; if (instanceOfTrueBlock->Dominates(user->GetBlock())) { if (bound_type == nullptr) { ScopedObjectAccess soa(Thread::Current()); @@ -396,7 +427,7 @@ void ReferenceTypePropagation::BoundTypeForIfInstanceOf(HBasicBlock* block) { break; } } - user->ReplaceInput(bound_type, it.Current()->GetIndex()); + user->ReplaceInput(bound_type, index); } } } @@ -409,10 +440,10 @@ void ReferenceTypePropagation::RTPVisitor::SetClassAsTypeInfo(HInstruction* inst if (kIsDebugBuild) { HInvoke* invoke = instr->AsInvoke(); ClassLinker* cl = Runtime::Current()->GetClassLinker(); - ScopedObjectAccess soa(Thread::Current()); - StackHandleScope<2> hs(soa.Self()); + Thread* self = Thread::Current(); + StackHandleScope<2> hs(self); Handle<mirror::DexCache> dex_cache( - hs.NewHandle(cl->FindDexCache(soa.Self(), invoke->GetDexFile(), false))); + hs.NewHandle(FindDexCacheWithHint(self, invoke->GetDexFile(), hint_dex_cache_))); // Use a null loader. We should probably use the compiling method's class loader, // but then we would need to pass it to RTPVisitor just for this debug check. Since // the method is from the String class, the null loader is good enough. @@ -429,8 +460,7 @@ void ReferenceTypePropagation::RTPVisitor::SetClassAsTypeInfo(HInstruction* inst } instr->SetReferenceTypeInfo( ReferenceTypeInfo::Create(handle_cache_->GetStringClassHandle(), /* is_exact */ true)); - } else if (klass != nullptr) { - ScopedObjectAccess soa(Thread::Current()); + } else if (IsAdmissible(klass)) { ReferenceTypeInfo::TypeHandle handle = handle_cache_->NewHandle(klass); is_exact = is_exact || handle->CannotBeAssignedFromOtherTypes(); instr->SetReferenceTypeInfo(ReferenceTypeInfo::Create(handle, is_exact)); @@ -446,8 +476,7 @@ void ReferenceTypePropagation::RTPVisitor::UpdateReferenceTypeInfo(HInstruction* DCHECK_EQ(instr->GetType(), Primitive::kPrimNot); ScopedObjectAccess soa(Thread::Current()); - mirror::DexCache* dex_cache = Runtime::Current()->GetClassLinker()->FindDexCache( - soa.Self(), dex_file, false); + mirror::DexCache* dex_cache = FindDexCacheWithHint(soa.Self(), dex_file, hint_dex_cache_); // Get type from dex cache assuming it was populated by the verifier. SetClassAsTypeInfo(instr, dex_cache->GetResolvedType(type_idx), is_exact); } @@ -460,24 +489,24 @@ void ReferenceTypePropagation::RTPVisitor::VisitNewArray(HNewArray* instr) { UpdateReferenceTypeInfo(instr, instr->GetTypeIndex(), instr->GetDexFile(), /* is_exact */ true); } -static mirror::Class* GetClassFromDexCache(Thread* self, const DexFile& dex_file, uint16_t type_idx) +static mirror::Class* GetClassFromDexCache(Thread* self, + const DexFile& dex_file, + uint16_t type_idx, + Handle<mirror::DexCache> hint_dex_cache) SHARED_REQUIRES(Locks::mutator_lock_) { - mirror::DexCache* dex_cache = - Runtime::Current()->GetClassLinker()->FindDexCache(self, dex_file, /* allow_failure */ true); - if (dex_cache == nullptr) { - // Dex cache could not be found. This should only happen during gtests. - return nullptr; - } + mirror::DexCache* dex_cache = FindDexCacheWithHint(self, dex_file, hint_dex_cache); // Get type from dex cache assuming it was populated by the verifier. return dex_cache->GetResolvedType(type_idx); } void ReferenceTypePropagation::RTPVisitor::VisitParameterValue(HParameterValue* instr) { - ScopedObjectAccess soa(Thread::Current()); // We check if the existing type is valid: the inliner may have set it. if (instr->GetType() == Primitive::kPrimNot && !instr->GetReferenceTypeInfo().IsValid()) { - mirror::Class* resolved_class = - GetClassFromDexCache(soa.Self(), instr->GetDexFile(), instr->GetTypeIndex()); + ScopedObjectAccess soa(Thread::Current()); + mirror::Class* resolved_class = GetClassFromDexCache(soa.Self(), + instr->GetDexFile(), + instr->GetTypeIndex(), + hint_dex_cache_); SetClassAsTypeInfo(instr, resolved_class, /* is_exact */ false); } } @@ -532,9 +561,11 @@ void ReferenceTypePropagation::RTPVisitor::VisitUnresolvedStaticFieldGet( void ReferenceTypePropagation::RTPVisitor::VisitLoadClass(HLoadClass* instr) { ScopedObjectAccess soa(Thread::Current()); // Get type from dex cache assuming it was populated by the verifier. - mirror::Class* resolved_class = - GetClassFromDexCache(soa.Self(), instr->GetDexFile(), instr->GetTypeIndex()); - if (resolved_class != nullptr) { + mirror::Class* resolved_class = GetClassFromDexCache(soa.Self(), + instr->GetDexFile(), + instr->GetTypeIndex(), + hint_dex_cache_); + if (IsAdmissible(resolved_class)) { instr->SetLoadedClassRTI(ReferenceTypeInfo::Create( handle_cache_->NewHandle(resolved_class), /* is_exact */ true)); } @@ -567,7 +598,6 @@ void ReferenceTypePropagation::RTPVisitor::VisitLoadException(HLoadException* in } void ReferenceTypePropagation::RTPVisitor::VisitNullCheck(HNullCheck* instr) { - ScopedObjectAccess soa(Thread::Current()); ReferenceTypeInfo parent_rti = instr->InputAt(0)->GetReferenceTypeInfo(); if (parent_rti.IsValid()) { instr->SetReferenceTypeInfo(parent_rti); @@ -575,10 +605,9 @@ void ReferenceTypePropagation::RTPVisitor::VisitNullCheck(HNullCheck* instr) { } void ReferenceTypePropagation::RTPVisitor::VisitBoundType(HBoundType* instr) { - ScopedObjectAccess soa(Thread::Current()); - ReferenceTypeInfo class_rti = instr->GetUpperBound(); if (class_rti.IsValid()) { + ScopedObjectAccess soa(Thread::Current()); // Narrow the type as much as possible. HInstruction* obj = instr->InputAt(0); ReferenceTypeInfo obj_rti = obj->GetReferenceTypeInfo(); @@ -609,8 +638,6 @@ void ReferenceTypePropagation::RTPVisitor::VisitBoundType(HBoundType* instr) { } void ReferenceTypePropagation::RTPVisitor::VisitCheckCast(HCheckCast* check_cast) { - ScopedObjectAccess soa(Thread::Current()); - HLoadClass* load_class = check_cast->InputAt(1)->AsLoadClass(); ReferenceTypeInfo class_rti = load_class->GetLoadedClassRTI(); HBoundType* bound_type = check_cast->GetNext()->AsBoundType(); @@ -639,13 +666,6 @@ void ReferenceTypePropagation::VisitPhi(HPhi* phi) { } if (phi->GetBlock()->IsLoopHeader()) { - if (!is_first_run_ && graph_->IsCompilingOsr()) { - // Don't update the type of a loop phi when compiling OSR: we may have done - // speculative optimizations dominating that phi, that do not hold at the - // point the interpreter jumps to that loop header. - return; - } - ScopedObjectAccess soa(Thread::Current()); // Set the initial type for the phi. Use the non back edge input for reaching // a fixed point faster. HInstruction* first_input = phi->InputAt(0); @@ -718,7 +738,7 @@ void ReferenceTypePropagation::UpdateArrayGet(HArrayGet* instr, HandleCache* han } Handle<mirror::Class> handle = parent_rti.GetTypeHandle(); - if (handle->IsObjectArrayClass()) { + if (handle->IsObjectArrayClass() && IsAdmissible(handle->GetComponentType())) { ReferenceTypeInfo::TypeHandle component_handle = handle_cache->NewHandle(handle->GetComponentType()); bool is_exact = component_handle->CannotBeAssignedFromOtherTypes(); @@ -760,7 +780,8 @@ void ReferenceTypePropagation::RTPVisitor::VisitInvoke(HInvoke* instr) { ScopedObjectAccess soa(Thread::Current()); ClassLinker* cl = Runtime::Current()->GetClassLinker(); - mirror::DexCache* dex_cache = cl->FindDexCache(soa.Self(), instr->GetDexFile()); + mirror::DexCache* dex_cache = + FindDexCacheWithHint(soa.Self(), instr->GetDexFile(), hint_dex_cache_); size_t pointer_size = cl->GetImagePointerSize(); ArtMethod* method = dex_cache->GetResolvedMethod(instr->GetDexMethodIndex(), pointer_size); mirror::Class* klass = (method == nullptr) ? nullptr : method->GetReturnType(false, pointer_size); @@ -788,7 +809,11 @@ void ReferenceTypePropagation::UpdateBoundType(HBoundType* instr) { // Make sure that we don't go over the bounded type. ReferenceTypeInfo upper_bound_rti = instr->GetUpperBound(); if (!upper_bound_rti.IsSupertypeOf(new_rti)) { - new_rti = upper_bound_rti; + // Note that the input might be exact, in which case we know the branch leading + // to the bound type is dead. We play it safe by not marking the bound type as + // exact. + bool is_exact = upper_bound_rti.GetTypeHandle()->CannotBeAssignedFromOtherTypes(); + new_rti = ReferenceTypeInfo::Create(upper_bound_rti.GetTypeHandle(), is_exact); } instr->SetReferenceTypeInfo(new_rti); } @@ -798,13 +823,13 @@ void ReferenceTypePropagation::UpdateBoundType(HBoundType* instr) { void ReferenceTypePropagation::UpdatePhi(HPhi* instr) { DCHECK(instr->IsLive()); - size_t input_count = instr->InputCount(); + auto&& inputs = instr->GetInputs(); size_t first_input_index_not_null = 0; - while (first_input_index_not_null < input_count && - instr->InputAt(first_input_index_not_null)->IsNullConstant()) { + while (first_input_index_not_null < inputs.size() && + inputs[first_input_index_not_null]->IsNullConstant()) { first_input_index_not_null++; } - if (first_input_index_not_null == input_count) { + if (first_input_index_not_null == inputs.size()) { // All inputs are NullConstants, set the type to object. // This may happen in the presence of inlining. instr->SetReferenceTypeInfo(instr->GetBlock()->GetGraph()->GetInexactObjectRti()); @@ -819,11 +844,11 @@ void ReferenceTypePropagation::UpdatePhi(HPhi* instr) { return; } - for (size_t i = first_input_index_not_null + 1; i < input_count; i++) { - if (instr->InputAt(i)->IsNullConstant()) { + for (size_t i = first_input_index_not_null + 1; i < inputs.size(); i++) { + if (inputs[i]->IsNullConstant()) { continue; } - new_rti = MergeTypes(new_rti, instr->InputAt(i)->GetReferenceTypeInfo()); + new_rti = MergeTypes(new_rti, inputs[i]->GetReferenceTypeInfo()); if (new_rti.IsValid() && new_rti.IsObjectClass()) { if (!new_rti.IsExact()) { break; @@ -854,8 +879,8 @@ bool ReferenceTypePropagation::UpdateNullability(HInstruction* instr) { if (instr->IsPhi()) { HPhi* phi = instr->AsPhi(); bool new_can_be_null = false; - for (size_t i = 0; i < phi->InputCount(); i++) { - if (phi->InputAt(i)->CanBeNull()) { + for (HInstruction* input : phi->GetInputs()) { + if (input->CanBeNull()) { new_can_be_null = true; break; } @@ -887,8 +912,8 @@ void ReferenceTypePropagation::AddToWorklist(HInstruction* instruction) { } void ReferenceTypePropagation::AddDependentInstructionsToWorklist(HInstruction* instruction) { - for (HUseIterator<HInstruction*> it(instruction->GetUses()); !it.Done(); it.Advance()) { - HInstruction* user = it.Current()->GetUser(); + for (const HUseListNode<HInstruction*>& use : instruction->GetUses()) { + HInstruction* user = use.GetUser(); if ((user->IsPhi() && user->AsPhi()->IsLive()) || user->IsBoundType() || user->IsNullCheck() diff --git a/compiler/optimizing/reference_type_propagation.h b/compiler/optimizing/reference_type_propagation.h index 028a6fc514..2106be6b53 100644 --- a/compiler/optimizing/reference_type_propagation.h +++ b/compiler/optimizing/reference_type_propagation.h @@ -32,6 +32,7 @@ namespace art { class ReferenceTypePropagation : public HOptimization { public: ReferenceTypePropagation(HGraph* graph, + Handle<mirror::DexCache> hint_dex_cache, StackHandleScopeCollection* handles, bool is_first_run, const char* name = kReferenceTypePropagationPassName); @@ -90,6 +91,10 @@ class ReferenceTypePropagation : public HOptimization { void ValidateTypes(); + // Note: hint_dex_cache_ is usually, but not necessarily, the dex cache associated with + // graph_->GetDexFile(). Since we may look up also in other dex files, it's used only + // as a hint, to reduce the number of calls to the costly ClassLinker::FindDexCache(). + Handle<mirror::DexCache> hint_dex_cache_; HandleCache handle_cache_; ArenaVector<HInstruction*> worklist_; @@ -99,6 +104,8 @@ class ReferenceTypePropagation : public HOptimization { static constexpr size_t kDefaultWorklistSize = 8; + friend class ReferenceTypePropagationTest; + DISALLOW_COPY_AND_ASSIGN(ReferenceTypePropagation); }; diff --git a/compiler/optimizing/reference_type_propagation_test.cc b/compiler/optimizing/reference_type_propagation_test.cc new file mode 100644 index 0000000000..7649b5093c --- /dev/null +++ b/compiler/optimizing/reference_type_propagation_test.cc @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2016 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 "base/arena_allocator.h" +#include "builder.h" +#include "nodes.h" +#include "object_lock.h" +#include "optimizing_unit_test.h" +#include "reference_type_propagation.h" + +namespace art { + +/** + * Fixture class for unit testing the ReferenceTypePropagation phase. Used to verify the + * functionality of methods and situations that are hard to set up with checker tests. + */ +class ReferenceTypePropagationTest : public CommonCompilerTest { + public: + ReferenceTypePropagationTest() : pool_(), allocator_(&pool_) { + graph_ = CreateGraph(&allocator_); + } + + ~ReferenceTypePropagationTest() { } + + void SetupPropagation(StackHandleScopeCollection* handles) { + graph_->InitializeInexactObjectRTI(handles); + propagation_ = new (&allocator_) ReferenceTypePropagation(graph_, + Handle<mirror::DexCache>(), + handles, + true, + "test_prop"); + } + + // Relay method to merge type in reference type propagation. + ReferenceTypeInfo MergeTypes(const ReferenceTypeInfo& a, + const ReferenceTypeInfo& b) SHARED_REQUIRES(Locks::mutator_lock_) { + return propagation_->MergeTypes(a, b); + } + + // Helper method to construct an invalid type. + ReferenceTypeInfo InvalidType() { + return ReferenceTypeInfo::CreateInvalid(); + } + + // Helper method to construct the Object type. + ReferenceTypeInfo ObjectType(bool is_exact = true) SHARED_REQUIRES(Locks::mutator_lock_) { + return ReferenceTypeInfo::Create(propagation_->handle_cache_.GetObjectClassHandle(), is_exact); + } + + // Helper method to construct the String type. + ReferenceTypeInfo StringType(bool is_exact = true) SHARED_REQUIRES(Locks::mutator_lock_) { + return ReferenceTypeInfo::Create(propagation_->handle_cache_.GetStringClassHandle(), is_exact); + } + + // General building fields. + ArenaPool pool_; + ArenaAllocator allocator_; + HGraph* graph_; + + ReferenceTypePropagation* propagation_; +}; + +// +// The actual ReferenceTypePropgation unit tests. +// + +TEST_F(ReferenceTypePropagationTest, ProperSetup) { + ScopedObjectAccess soa(Thread::Current()); + StackHandleScopeCollection handles(soa.Self()); + SetupPropagation(&handles); + + EXPECT_TRUE(propagation_ != nullptr); + EXPECT_TRUE(graph_->GetInexactObjectRti().IsEqual(ObjectType(false))); +} + +TEST_F(ReferenceTypePropagationTest, MergeInvalidTypes) { + ScopedObjectAccess soa(Thread::Current()); + StackHandleScopeCollection handles(soa.Self()); + SetupPropagation(&handles); + + // Two invalid types. + ReferenceTypeInfo t1(MergeTypes(InvalidType(), InvalidType())); + EXPECT_FALSE(t1.IsValid()); + EXPECT_FALSE(t1.IsExact()); + EXPECT_TRUE(t1.IsEqual(InvalidType())); + + // Valid type on right. + ReferenceTypeInfo t2(MergeTypes(InvalidType(), ObjectType())); + EXPECT_TRUE(t2.IsValid()); + EXPECT_TRUE(t2.IsExact()); + EXPECT_TRUE(t2.IsEqual(ObjectType())); + ReferenceTypeInfo t3(MergeTypes(InvalidType(), StringType())); + EXPECT_TRUE(t3.IsValid()); + EXPECT_TRUE(t3.IsExact()); + EXPECT_TRUE(t3.IsEqual(StringType())); + + // Valid type on left. + ReferenceTypeInfo t4(MergeTypes(ObjectType(), InvalidType())); + EXPECT_TRUE(t4.IsValid()); + EXPECT_TRUE(t4.IsExact()); + EXPECT_TRUE(t4.IsEqual(ObjectType())); + ReferenceTypeInfo t5(MergeTypes(StringType(), InvalidType())); + EXPECT_TRUE(t5.IsValid()); + EXPECT_TRUE(t5.IsExact()); + EXPECT_TRUE(t5.IsEqual(StringType())); +} + +TEST_F(ReferenceTypePropagationTest, MergeValidTypes) { + ScopedObjectAccess soa(Thread::Current()); + StackHandleScopeCollection handles(soa.Self()); + SetupPropagation(&handles); + + // Same types. + ReferenceTypeInfo t1(MergeTypes(ObjectType(), ObjectType())); + EXPECT_TRUE(t1.IsValid()); + EXPECT_TRUE(t1.IsExact()); + EXPECT_TRUE(t1.IsEqual(ObjectType())); + ReferenceTypeInfo t2(MergeTypes(StringType(), StringType())); + EXPECT_TRUE(t2.IsValid()); + EXPECT_TRUE(t2.IsExact()); + EXPECT_TRUE(t2.IsEqual(StringType())); + + // Left is super class of right. + ReferenceTypeInfo t3(MergeTypes(ObjectType(), StringType())); + EXPECT_TRUE(t3.IsValid()); + EXPECT_FALSE(t3.IsExact()); + EXPECT_TRUE(t3.IsEqual(ObjectType(false))); + + // Right is super class of left. + ReferenceTypeInfo t4(MergeTypes(StringType(), ObjectType())); + EXPECT_TRUE(t4.IsValid()); + EXPECT_FALSE(t4.IsExact()); + EXPECT_TRUE(t4.IsEqual(ObjectType(false))); + + // Same types, but one or both are inexact. + ReferenceTypeInfo t5(MergeTypes(ObjectType(false), ObjectType())); + EXPECT_TRUE(t5.IsValid()); + EXPECT_FALSE(t5.IsExact()); + EXPECT_TRUE(t5.IsEqual(ObjectType(false))); + ReferenceTypeInfo t6(MergeTypes(ObjectType(), ObjectType(false))); + EXPECT_TRUE(t6.IsValid()); + EXPECT_FALSE(t6.IsExact()); + EXPECT_TRUE(t6.IsEqual(ObjectType(false))); + ReferenceTypeInfo t7(MergeTypes(ObjectType(false), ObjectType(false))); + EXPECT_TRUE(t7.IsValid()); + EXPECT_FALSE(t7.IsExact()); + EXPECT_TRUE(t7.IsEqual(ObjectType(false))); +} + +} // namespace art + diff --git a/compiler/optimizing/register_allocator.cc b/compiler/optimizing/register_allocator.cc index b1f9cbcdfa..4a6b835e80 100644 --- a/compiler/optimizing/register_allocator.cc +++ b/compiler/optimizing/register_allocator.cc @@ -305,7 +305,7 @@ void RegisterAllocator::ProcessInstruction(HInstruction* instruction) { BlockRegisters(position, position + 1, /* caller_save_only */ true); } - for (size_t i = 0; i < instruction->InputCount(); ++i) { + for (size_t i = 0; i < locations->GetInputCount(); ++i) { Location input = locations->InAt(i); if (input.IsRegister() || input.IsFpuRegister()) { BlockRegister(input, position, position + 1); @@ -753,10 +753,11 @@ bool RegisterAllocator::TryAllocateFreeReg(LiveInterval* current) { if (defined_by != nullptr && !current->IsSplit()) { LocationSummary* locations = defined_by->GetLocations(); if (!locations->OutputCanOverlapWithInputs() && locations->Out().IsUnallocated()) { - for (size_t i = 0, e = defined_by->InputCount(); i < e; ++i) { + auto&& inputs = defined_by->GetInputs(); + for (size_t i = 0; i < inputs.size(); ++i) { // Take the last interval of the input. It is the location of that interval // that will be used at `defined_by`. - LiveInterval* interval = defined_by->InputAt(i)->GetLiveInterval()->GetLastSibling(); + LiveInterval* interval = inputs[i]->GetLiveInterval()->GetLastSibling(); // Note that interval may have not been processed yet. // TODO: Handle non-split intervals last in the work list. if (locations->InAt(i).IsValid() @@ -1773,7 +1774,9 @@ void RegisterAllocator::ConnectSplitSiblings(LiveInterval* interval, // therefore will not have a location for that instruction for `to`. // Because the instruction is a constant or the ArtMethod, we don't need to // do anything: it will be materialized in the irreducible loop. - DCHECK(IsMaterializableEntryBlockInstructionOfGraphWithIrreducibleLoop(defined_by)); + DCHECK(IsMaterializableEntryBlockInstructionOfGraphWithIrreducibleLoop(defined_by)) + << defined_by->DebugName() << ":" << defined_by->GetId() + << " " << from->GetBlockId() << " -> " << to->GetBlockId(); return; } diff --git a/compiler/optimizing/sharpening.cc b/compiler/optimizing/sharpening.cc index 7a1bb316e4..08bd35f14a 100644 --- a/compiler/optimizing/sharpening.cc +++ b/compiler/optimizing/sharpening.cc @@ -99,7 +99,7 @@ void HSharpening::ProcessInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { if (direct_method != 0u) { // Should we use a direct pointer to the method? // Note: For JIT, kDirectAddressWithFixup doesn't make sense at all and while // kDirectAddress would be fine for image methods, we don't support it at the moment. - DCHECK(!Runtime::Current()->UseJit()); + DCHECK(!Runtime::Current()->UseJitCompilation()); if (direct_method != static_cast<uintptr_t>(-1)) { // Is the method pointer known now? method_load_kind = HInvokeStaticOrDirect::MethodLoadKind::kDirectAddress; method_load_data = direct_method; @@ -109,7 +109,7 @@ void HSharpening::ProcessInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { } else { // Use dex cache. DCHECK_EQ(target_method.dex_file, &graph_->GetDexFile()); if (use_pc_relative_instructions) { // Can we use PC-relative access to the dex cache arrays? - DCHECK(!Runtime::Current()->UseJit()); + DCHECK(!Runtime::Current()->UseJitCompilation()); method_load_kind = HInvokeStaticOrDirect::MethodLoadKind::kDexCachePcRelative; DexCacheArraysLayout layout(GetInstructionSetPointerSize(codegen_->GetInstructionSet()), &graph_->GetDexFile()); @@ -121,7 +121,7 @@ void HSharpening::ProcessInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { if (direct_code != 0u) { // Should we use a direct pointer to the code? // Note: For JIT, kCallPCRelative and kCallDirectWithFixup don't make sense at all and // while kCallDirect would be fine for image methods, we don't support it at the moment. - DCHECK(!Runtime::Current()->UseJit()); + DCHECK(!Runtime::Current()->UseJitCompilation()); if (direct_code != static_cast<uintptr_t>(-1)) { // Is the code pointer known now? code_ptr_location = HInvokeStaticOrDirect::CodePtrLocation::kCallDirect; direct_code_ptr = direct_code; @@ -174,7 +174,7 @@ void HSharpening::ProcessLoadString(HLoadString* load_string) { if (compiler_driver_->IsBootImage()) { // Compiling boot image. Resolve the string and allocate it if needed. - DCHECK(!runtime->UseJit()); + DCHECK(!runtime->UseJitCompilation()); mirror::String* string = class_linker->ResolveString(dex_file, string_index, dex_cache); CHECK(string != nullptr); if (!compiler_driver_->GetSupportBootImageFixup()) { @@ -187,7 +187,7 @@ void HSharpening::ProcessLoadString(HLoadString* load_string) { ? HLoadString::LoadKind::kBootImageLinkTimePcRelative : HLoadString::LoadKind::kBootImageLinkTimeAddress; } - } else if (runtime->UseJit()) { + } else if (runtime->UseJitCompilation()) { // TODO: Make sure we don't set the "compile PIC" flag for JIT as that's bogus. // DCHECK(!codegen_->GetCompilerOptions().GetCompilePic()); mirror::String* string = dex_cache->GetResolvedString(string_index); diff --git a/compiler/optimizing/ssa_builder.cc b/compiler/optimizing/ssa_builder.cc index eeadbeb0d1..ed50c69b5d 100644 --- a/compiler/optimizing/ssa_builder.cc +++ b/compiler/optimizing/ssa_builder.cc @@ -108,8 +108,8 @@ static void AddDependentInstructionsToWorklist(HInstruction* instruction, // marked dead/conflicting too, so we add them to the worklist. Otherwise we // add users whose type does not match and needs to be updated. bool add_all_live_phis = instruction->IsPhi() && instruction->AsPhi()->IsDead(); - for (HUseIterator<HInstruction*> it(instruction->GetUses()); !it.Done(); it.Advance()) { - HInstruction* user = it.Current()->GetUser(); + for (const HUseListNode<HInstruction*>& use : instruction->GetUses()) { + HInstruction* user = use.GetUser(); if (user->IsPhi() && user->AsPhi()->IsLive()) { if (add_all_live_phis || user->GetType() != instruction->GetType()) { worklist->push_back(user->AsPhi()); @@ -123,8 +123,7 @@ static void AddDependentInstructionsToWorklist(HInstruction* instruction, static bool TypePhiFromInputs(HPhi* phi) { Primitive::Type common_type = phi->GetType(); - for (HInputIterator it(phi); !it.Done(); it.Advance()) { - HInstruction* input = it.Current(); + for (HInstruction* input : phi->GetInputs()) { if (input->IsPhi() && input->AsPhi()->IsDead()) { // Phis are constructed live so if an input is a dead phi, it must have // been made dead due to type conflict. Mark this phi conflicting too. @@ -169,8 +168,7 @@ bool SsaBuilder::TypeInputsOfPhi(HPhi* phi, ArenaVector<HPhi*>* worklist) { // or `common_type` is integral and we do not need to retype ambiguous inputs // because they are always constructed with the integral type candidate. if (kIsDebugBuild) { - for (size_t i = 0, e = phi->InputCount(); i < e; ++i) { - HInstruction* input = phi->InputAt(i); + for (HInstruction* input : phi->GetInputs()) { if (common_type == Primitive::kPrimVoid) { DCHECK(input->IsPhi() && input->GetType() == Primitive::kPrimVoid); } else { @@ -183,8 +181,9 @@ bool SsaBuilder::TypeInputsOfPhi(HPhi* phi, ArenaVector<HPhi*>* worklist) { return true; } else { DCHECK(common_type == Primitive::kPrimNot || Primitive::IsFloatingPointType(common_type)); - for (size_t i = 0, e = phi->InputCount(); i < e; ++i) { - HInstruction* input = phi->InputAt(i); + auto&& inputs = phi->GetInputs(); + for (size_t i = 0; i < inputs.size(); ++i) { + HInstruction* input = inputs[i]; if (input->GetType() != common_type) { // Input type does not match phi's type. Try to retype the input or // generate a suitably typed equivalent. @@ -233,7 +232,7 @@ bool SsaBuilder::UpdatePrimitiveType(HPhi* phi, ArenaVector<HPhi*>* worklist) { } void SsaBuilder::RunPrimitiveTypePropagation() { - ArenaVector<HPhi*> worklist(graph_->GetArena()->Adapter()); + ArenaVector<HPhi*> worklist(graph_->GetArena()->Adapter(kArenaAllocGraphBuilder)); for (HReversePostOrderIterator it(*graph_); !it.Done(); it.Advance()) { HBasicBlock* block = it.Current(); @@ -319,7 +318,7 @@ bool SsaBuilder::FixAmbiguousArrayOps() { // uses (because they are untyped) and environment uses (if --debuggable). // After resolving all ambiguous ArrayGets, we will re-run primitive type // propagation on the Phis which need to be updated. - ArenaVector<HPhi*> worklist(graph_->GetArena()->Adapter()); + ArenaVector<HPhi*> worklist(graph_->GetArena()->Adapter(kArenaAllocGraphBuilder)); { ScopedObjectAccess soa(Thread::Current()); @@ -391,6 +390,9 @@ bool SsaBuilder::FixAmbiguousArrayOps() { worklist.push_back(equivalent->AsPhi()); } } + // Refine the side effects of this floating point aset. Note that we do this even if + // no replacement occurs, since the right-hand-side may have been corrected already. + aset->ComputeSideEffects(); } else { // Array elements are integral and the value assigned to it initially // was integral too. Nothing to do. @@ -409,27 +411,24 @@ bool SsaBuilder::FixAmbiguousArrayOps() { } static bool HasAliasInEnvironments(HInstruction* instruction) { - for (HUseIterator<HEnvironment*> use_it(instruction->GetEnvUses()); - !use_it.Done(); - use_it.Advance()) { - HEnvironment* use = use_it.Current()->GetUser(); - HUseListNode<HEnvironment*>* next = use_it.Current()->GetNext(); - if (next != nullptr && next->GetUser() == use) { + HEnvironment* last_user = nullptr; + for (const HUseListNode<HEnvironment*>& use : instruction->GetEnvUses()) { + DCHECK(use.GetUser() != nullptr); + // Note: The first comparison (== null) always fails. + if (use.GetUser() == last_user) { return true; } + last_user = use.GetUser(); } if (kIsDebugBuild) { // Do a quadratic search to ensure same environment uses are next // to each other. - for (HUseIterator<HEnvironment*> use_it(instruction->GetEnvUses()); - !use_it.Done(); - use_it.Advance()) { - HUseListNode<HEnvironment*>* current = use_it.Current(); - HUseListNode<HEnvironment*>* next = current->GetNext(); - while (next != nullptr) { + const HUseList<HEnvironment*>& env_uses = instruction->GetEnvUses(); + for (auto current = env_uses.begin(), end = env_uses.end(); current != end; ++current) { + auto next = current; + for (++next; next != end; ++next) { DCHECK(next->GetUser() != current->GetUser()); - next = next->GetNext(); } } } @@ -506,7 +505,7 @@ GraphAnalysisResult SsaBuilder::BuildSsa() { // 4) Compute type of reference type instructions. The pass assumes that // NullConstant has been fixed up. - ReferenceTypePropagation(graph_, handles_, /* is_first_run */ true).Run(); + ReferenceTypePropagation(graph_, dex_cache_, handles_, /* is_first_run */ true).Run(); // 5) HInstructionBuilder duplicated ArrayGet instructions with ambiguous type // (int/float or long/double) and marked ArraySets with ambiguous input type. @@ -618,11 +617,14 @@ HPhi* SsaBuilder::GetFloatDoubleOrReferenceEquivalentOfPhi(HPhi* phi, Primitive: || (next->AsPhi()->GetRegNumber() != phi->GetRegNumber()) || (next->GetType() != type)) { ArenaAllocator* allocator = graph_->GetArena(); - HPhi* new_phi = new (allocator) HPhi(allocator, phi->GetRegNumber(), phi->InputCount(), type); - for (size_t i = 0, e = phi->InputCount(); i < e; ++i) { - // Copy the inputs. Note that the graph may not be correctly typed - // by doing this copy, but the type propagation phase will fix it. - new_phi->SetRawInputAt(i, phi->InputAt(i)); + auto&& inputs = phi->GetInputs(); + HPhi* new_phi = + new (allocator) HPhi(allocator, phi->GetRegNumber(), inputs.size(), type); + // Copy the inputs. Note that the graph may not be correctly typed + // by doing this copy, but the type propagation phase will fix it. + ArrayRef<HUserRecord<HInstruction*>> new_input_records = new_phi->GetInputRecords(); + for (size_t i = 0; i < inputs.size(); ++i) { + new_input_records[i] = HUserRecord<HInstruction*>(inputs[i]); } phi->GetBlock()->InsertPhiAfter(new_phi, phi); DCHECK(new_phi->IsLive()); diff --git a/compiler/optimizing/ssa_builder.h b/compiler/optimizing/ssa_builder.h index c37c28c801..d7360adef8 100644 --- a/compiler/optimizing/ssa_builder.h +++ b/compiler/optimizing/ssa_builder.h @@ -47,8 +47,11 @@ namespace art { */ class SsaBuilder : public ValueObject { public: - SsaBuilder(HGraph* graph, StackHandleScopeCollection* handles) + SsaBuilder(HGraph* graph, + Handle<mirror::DexCache> dex_cache, + StackHandleScopeCollection* handles) : graph_(graph), + dex_cache_(dex_cache), handles_(handles), agets_fixed_(false), ambiguous_agets_(graph->GetArena()->Adapter(kArenaAllocGraphBuilder)), @@ -112,6 +115,7 @@ class SsaBuilder : public ValueObject { void RemoveRedundantUninitializedStrings(); HGraph* graph_; + Handle<mirror::DexCache> dex_cache_; StackHandleScopeCollection* const handles_; // True if types of ambiguous ArrayGets have been resolved. diff --git a/compiler/optimizing/ssa_liveness_analysis.cc b/compiler/optimizing/ssa_liveness_analysis.cc index 83e9dacb1a..212d93532c 100644 --- a/compiler/optimizing/ssa_liveness_analysis.cc +++ b/compiler/optimizing/ssa_liveness_analysis.cc @@ -177,8 +177,9 @@ void SsaLivenessAnalysis::ComputeLiveness() { static void RecursivelyProcessInputs(HInstruction* current, HInstruction* actual_user, BitVector* live_in) { - for (size_t i = 0, e = current->InputCount(); i < e; ++i) { - HInstruction* input = current->InputAt(i); + auto&& inputs = current->GetInputs(); + for (size_t i = 0; i < inputs.size(); ++i) { + HInstruction* input = inputs[i]; bool has_in_location = current->GetLocations()->InAt(i).IsValid(); bool has_out_location = input->GetLocations()->Out().IsValid(); @@ -283,11 +284,9 @@ void SsaLivenessAnalysis::ComputeLiveRanges() { if (current->IsEmittedAtUseSite()) { if (kIsDebugBuild) { DCHECK(!current->GetLocations()->Out().IsValid()); - for (HUseIterator<HInstruction*> use_it(current->GetUses()); - !use_it.Done(); - use_it.Advance()) { - HInstruction* user = use_it.Current()->GetUser(); - size_t index = use_it.Current()->GetIndex(); + for (const HUseListNode<HInstruction*>& use : current->GetUses()) { + HInstruction* user = use.GetUser(); + size_t index = use.GetIndex(); DCHECK(!user->GetLocations()->InAt(index).IsValid()); } DCHECK(!current->HasEnvironmentUses()); @@ -311,17 +310,8 @@ void SsaLivenessAnalysis::ComputeLiveRanges() { } if (block->IsLoopHeader()) { - if (kIsDebugBuild && block->GetLoopInformation()->IsIrreducible()) { - // To satisfy our liveness algorithm, we need to ensure loop headers of - // irreducible loops do not have any live-in instructions, except constants - // and the current method, which can be trivially re-materialized. - for (uint32_t idx : live_in->Indexes()) { - HInstruction* instruction = GetInstructionFromSsaIndex(idx); - DCHECK(instruction->GetBlock()->IsEntryBlock()) << instruction->DebugName(); - DCHECK(!instruction->IsParameterValue()) << instruction->DebugName(); - DCHECK(instruction->IsCurrentMethod() || instruction->IsConstant()) - << instruction->DebugName(); - } + if (kIsDebugBuild) { + CheckNoLiveInIrreducibleLoop(*block); } size_t last_position = block->GetLoopInformation()->GetLifetimeEnd(); // For all live_in instructions at the loop header, we need to create a range @@ -346,6 +336,9 @@ void SsaLivenessAnalysis::ComputeLiveInAndLiveOutSets() { // change in this loop), and the live_out set. If the live_out // set does not change, there is no need to update the live_in set. if (UpdateLiveOut(block) && UpdateLiveIn(block)) { + if (kIsDebugBuild) { + CheckNoLiveInIrreducibleLoop(block); + } changed = true; } } @@ -438,12 +431,12 @@ int LiveInterval::FindFirstRegisterHint(size_t* free_until, // If the instruction dies at the phi assignment, we can try having the // same register. if (end == user->GetBlock()->GetPredecessors()[input_index]->GetLifetimeEnd()) { - for (size_t i = 0, e = user->InputCount(); i < e; ++i) { + auto&& inputs = user->GetInputs(); + for (size_t i = 0; i < inputs.size(); ++i) { if (i == input_index) { continue; } - HInstruction* input = user->InputAt(i); - Location location = input->GetLiveInterval()->GetLocationAt( + Location location = inputs[i]->GetLiveInterval()->GetLocationAt( user->GetBlock()->GetPredecessors()[i]->GetLifetimeEnd() - 1); if (location.IsRegisterKind()) { int reg = RegisterOrLowRegister(location); @@ -479,10 +472,10 @@ int LiveInterval::FindHintAtDefinition() const { if (defined_by_->IsPhi()) { // Try to use the same register as one of the inputs. const ArenaVector<HBasicBlock*>& predecessors = defined_by_->GetBlock()->GetPredecessors(); - for (size_t i = 0, e = defined_by_->InputCount(); i < e; ++i) { - HInstruction* input = defined_by_->InputAt(i); + auto&& inputs = defined_by_->GetInputs(); + for (size_t i = 0; i < inputs.size(); ++i) { size_t end = predecessors[i]->GetLifetimeEnd(); - LiveInterval* input_interval = input->GetLiveInterval()->GetSiblingAt(end - 1); + LiveInterval* input_interval = inputs[i]->GetLiveInterval()->GetSiblingAt(end - 1); if (input_interval->GetEnd() == end) { // If the input dies at the end of the predecessor, we know its register can // be reused. diff --git a/compiler/optimizing/ssa_liveness_analysis.h b/compiler/optimizing/ssa_liveness_analysis.h index 97f2aeeb1e..dc98864d9b 100644 --- a/compiler/optimizing/ssa_liveness_analysis.h +++ b/compiler/optimizing/ssa_liveness_analysis.h @@ -797,8 +797,8 @@ class LiveInterval : public ArenaObject<kArenaAllocSsaLiveness> { bool IsUsingInputRegister() const { CHECK(kIsDebugBuild) << "Function should be used only for DCHECKs"; if (defined_by_ != nullptr && !IsSplit()) { - for (HInputIterator it(defined_by_); !it.Done(); it.Advance()) { - LiveInterval* interval = it.Current()->GetLiveInterval(); + for (const HInstruction* input : defined_by_->GetInputs()) { + LiveInterval* interval = input->GetLiveInterval(); // Find the interval that covers `defined_by`_. Calls to this function // are made outside the linear scan, hence we need to use CoversSlow. @@ -828,8 +828,8 @@ class LiveInterval : public ArenaObject<kArenaAllocSsaLiveness> { if (locations->OutputCanOverlapWithInputs()) { return false; } - for (HInputIterator it(defined_by_); !it.Done(); it.Advance()) { - LiveInterval* interval = it.Current()->GetLiveInterval(); + for (const HInstruction* input : defined_by_->GetInputs()) { + LiveInterval* interval = input->GetLiveInterval(); // Find the interval that covers `defined_by`_. Calls to this function // are made outside the linear scan, hence we need to use CoversSlow. @@ -969,8 +969,49 @@ class LiveInterval : public ArenaObject<kArenaAllocSsaLiveness> { return false; } + bool IsLinearOrderWellFormed(const HGraph& graph) { + for (HBasicBlock* header : graph.GetBlocks()) { + if (header == nullptr || !header->IsLoopHeader()) { + continue; + } + + HLoopInformation* loop = header->GetLoopInformation(); + size_t num_blocks = loop->GetBlocks().NumSetBits(); + size_t found_blocks = 0u; + + for (HLinearOrderIterator it(graph); !it.Done(); it.Advance()) { + HBasicBlock* current = it.Current(); + if (loop->Contains(*current)) { + found_blocks++; + if (found_blocks == 1u && current != header) { + // First block is not the header. + return false; + } else if (found_blocks == num_blocks && !loop->IsBackEdge(*current)) { + // Last block is not a back edge. + return false; + } + } else if (found_blocks != 0u && found_blocks != num_blocks) { + // Blocks are not adjacent. + return false; + } + } + DCHECK_EQ(found_blocks, num_blocks); + } + + return true; + } + void AddBackEdgeUses(const HBasicBlock& block_at_use) { DCHECK(block_at_use.IsInLoop()); + if (block_at_use.GetGraph()->HasIrreducibleLoops()) { + // Linear order may not be well formed when irreducible loops are present, + // i.e. loop blocks may not be adjacent and a back edge may not be last, + // which violates assumptions made in this method. + return; + } + + DCHECK(IsLinearOrderWellFormed(*block_at_use.GetGraph())); + // Add synthesized uses at the back edge of loops to help the register allocator. // Note that this method is called in decreasing liveness order, to faciliate adding // uses at the head of the `first_use_` linked list. Because below @@ -999,8 +1040,8 @@ class LiveInterval : public ArenaObject<kArenaAllocSsaLiveness> { break; } - DCHECK(last_in_new_list == nullptr - || back_edge_use_position > last_in_new_list->GetPosition()); + DCHECK(last_in_new_list == nullptr || + back_edge_use_position > last_in_new_list->GetPosition()); UsePosition* new_use = new (allocator_) UsePosition( /* user */ nullptr, @@ -1219,6 +1260,23 @@ class SsaLivenessAnalysis : public ValueObject { return instruction->GetType() == Primitive::kPrimNot; } + void CheckNoLiveInIrreducibleLoop(const HBasicBlock& block) const { + if (!block.IsLoopHeader() || !block.GetLoopInformation()->IsIrreducible()) { + return; + } + BitVector* live_in = GetLiveInSet(block); + // To satisfy our liveness algorithm, we need to ensure loop headers of + // irreducible loops do not have any live-in instructions, except constants + // and the current method, which can be trivially re-materialized. + for (uint32_t idx : live_in->Indexes()) { + HInstruction* instruction = GetInstructionFromSsaIndex(idx); + DCHECK(instruction->GetBlock()->IsEntryBlock()) << instruction->DebugName(); + DCHECK(!instruction->IsParameterValue()); + DCHECK(instruction->IsCurrentMethod() || instruction->IsConstant()) + << instruction->DebugName(); + } + } + HGraph* const graph_; CodeGenerator* const codegen_; ArenaVector<BlockInfo*> block_infos_; diff --git a/compiler/optimizing/ssa_phi_elimination.cc b/compiler/optimizing/ssa_phi_elimination.cc index 6816b6a028..b1ec99ab8e 100644 --- a/compiler/optimizing/ssa_phi_elimination.cc +++ b/compiler/optimizing/ssa_phi_elimination.cc @@ -17,6 +17,7 @@ #include "ssa_phi_elimination.h" #include "base/arena_containers.h" +#include "base/arena_bit_vector.h" #include "base/bit_vector-inl.h" namespace art { @@ -30,7 +31,7 @@ void SsaDeadPhiElimination::MarkDeadPhis() { // Phis are constructed live and should not be revived if previously marked // dead. This algorithm temporarily breaks that invariant but we DCHECK that // only phis which were initially live are revived. - ArenaSet<HPhi*> initially_live(graph_->GetArena()->Adapter()); + ArenaSet<HPhi*> initially_live(graph_->GetArena()->Adapter(kArenaAllocSsaPhiElimination)); // Add to the worklist phis referenced by non-phi instructions. for (HReversePostOrderIterator it(*graph_); !it.Done(); it.Advance()) { @@ -43,8 +44,8 @@ void SsaDeadPhiElimination::MarkDeadPhis() { bool keep_alive = (graph_->IsDebuggable() && phi->HasEnvironmentUses()); if (!keep_alive) { - for (HUseIterator<HInstruction*> use_it(phi->GetUses()); !use_it.Done(); use_it.Advance()) { - if (!use_it.Current()->GetUser()->IsPhi()) { + for (const HUseListNode<HInstruction*>& use : phi->GetUses()) { + if (!use.GetUser()->IsPhi()) { keep_alive = true; break; } @@ -66,8 +67,8 @@ void SsaDeadPhiElimination::MarkDeadPhis() { while (!worklist_.empty()) { HPhi* phi = worklist_.back(); worklist_.pop_back(); - for (HInputIterator it(phi); !it.Done(); it.Advance()) { - HPhi* input = it.Current()->AsPhi(); + for (HInstruction* raw_input : phi->GetInputs()) { + HPhi* input = raw_input->AsPhi(); if (input != nullptr && input->IsDead()) { // Input is a dead phi. Revive it and add to the worklist. We make sure // that the phi was not dead initially (see definition of `initially_live`). @@ -94,23 +95,18 @@ void SsaDeadPhiElimination::EliminateDeadPhis() { if (phi->IsDead()) { // Make sure the phi is only used by other dead phis. if (kIsDebugBuild) { - for (HUseIterator<HInstruction*> use_it(phi->GetUses()); !use_it.Done(); - use_it.Advance()) { - HInstruction* user = use_it.Current()->GetUser(); + for (const HUseListNode<HInstruction*>& use : phi->GetUses()) { + HInstruction* user = use.GetUser(); DCHECK(user->IsLoopHeaderPhi()); DCHECK(user->AsPhi()->IsDead()); } } // Remove the phi from use lists of its inputs. - for (size_t i = 0, e = phi->InputCount(); i < e; ++i) { - phi->RemoveAsUserOfInput(i); - } + phi->RemoveAsUserOfAllInputs(); // Remove the phi from environments that use it. - for (HUseIterator<HEnvironment*> use_it(phi->GetEnvUses()); !use_it.Done(); - use_it.Advance()) { - HUseListNode<HEnvironment*>* user_node = use_it.Current(); - HEnvironment* user = user_node->GetUser(); - user->SetRawEnvAt(user_node->GetIndex(), nullptr); + for (const HUseListNode<HEnvironment*>& use : phi->GetEnvUses()) { + HEnvironment* user = use.GetUser(); + user->SetRawEnvAt(use.GetIndex(), nullptr); } // Delete it from the instruction list. block->RemovePhi(phi, /*ensure_safety=*/ false); @@ -130,8 +126,11 @@ void SsaRedundantPhiElimination::Run() { } } - ArenaSet<uint32_t> visited_phis_in_cycle(graph_->GetArena()->Adapter()); - ArenaVector<HPhi*> cycle_worklist(graph_->GetArena()->Adapter()); + ArenaBitVector visited_phis_in_cycle(graph_->GetArena(), + graph_->GetCurrentInstructionId(), + /* expandable */ false, + kArenaAllocSsaPhiElimination); + ArenaVector<HPhi*> cycle_worklist(graph_->GetArena()->Adapter(kArenaAllocSsaPhiElimination)); while (!worklist_.empty()) { HPhi* phi = worklist_.back(); @@ -142,23 +141,23 @@ void SsaRedundantPhiElimination::Run() { continue; } - if (phi->InputCount() == 0) { - DCHECK(phi->IsDead()); + // If the phi is dead, we know we won't revive it and it will be removed, + // so don't process it. + if (phi->IsDead()) { continue; } HInstruction* candidate = nullptr; - visited_phis_in_cycle.clear(); + visited_phis_in_cycle.ClearAllBits(); cycle_worklist.clear(); cycle_worklist.push_back(phi); - visited_phis_in_cycle.insert(phi->GetId()); + visited_phis_in_cycle.SetBit(phi->GetId()); bool catch_phi_in_cycle = phi->IsCatchPhi(); bool irreducible_loop_phi_in_cycle = phi->IsIrreducibleLoopHeaderPhi(); // First do a simple loop over inputs and check if they are all the same. - for (size_t j = 0; j < phi->InputCount(); ++j) { - HInstruction* input = phi->InputAt(j); + for (HInstruction* input : phi->GetInputs()) { if (input == phi) { continue; } else if (candidate == nullptr) { @@ -179,14 +178,13 @@ void SsaRedundantPhiElimination::Run() { DCHECK(!current->IsLoopHeaderPhi() || current->GetBlock()->IsLoopPreHeaderFirstPredecessor()); - for (size_t j = 0; j < current->InputCount(); ++j) { - HInstruction* input = current->InputAt(j); + for (HInstruction* input : current->GetInputs()) { if (input == current) { continue; } else if (input->IsPhi()) { - if (!ContainsElement(visited_phis_in_cycle, input->GetId())) { + if (!visited_phis_in_cycle.IsBitSet(input->GetId())) { cycle_worklist.push_back(input->AsPhi()); - visited_phis_in_cycle.insert(input->GetId()); + visited_phis_in_cycle.SetBit(input->GetId()); catch_phi_in_cycle |= input->AsPhi()->IsCatchPhi(); irreducible_loop_phi_in_cycle |= input->IsIrreducibleLoopHeaderPhi(); } else { @@ -233,10 +231,9 @@ void SsaRedundantPhiElimination::Run() { // Because we're updating the users of this phi, we may have new candidates // for elimination. Add phis that use this phi to the worklist. - for (HUseIterator<HInstruction*> it(current->GetUses()); !it.Done(); it.Advance()) { - HUseListNode<HInstruction*>* use = it.Current(); - HInstruction* user = use->GetUser(); - if (user->IsPhi() && !ContainsElement(visited_phis_in_cycle, user->GetId())) { + for (const HUseListNode<HInstruction*>& use : current->GetUses()) { + HInstruction* user = use.GetUser(); + if (user->IsPhi() && !visited_phis_in_cycle.IsBitSet(user->GetId())) { worklist_.push_back(user->AsPhi()); } } diff --git a/compiler/optimizing/ssa_test.cc b/compiler/optimizing/ssa_test.cc index 218bd53bc2..429763423c 100644 --- a/compiler/optimizing/ssa_test.cc +++ b/compiler/optimizing/ssa_test.cc @@ -346,7 +346,7 @@ TEST_F(SsaTest, Loop5) { "BasicBlock 7, pred: 6\n" " 12: Exit\n" "BasicBlock 8, pred: 2, 3, succ: 4\n" - " 13: Phi(2, 1) [8, 8, 11]\n" + " 13: Phi(2, 1) [11, 8, 8]\n" " 14: Goto\n"; const uint16_t data[] = ONE_REGISTER_CODE_ITEM( diff --git a/compiler/utils/arm/assembler_arm.cc b/compiler/utils/arm/assembler_arm.cc index e5f91dc8ca..a7f4547514 100644 --- a/compiler/utils/arm/assembler_arm.cc +++ b/compiler/utils/arm/assembler_arm.cc @@ -386,8 +386,9 @@ static dwarf::Reg DWARFReg(SRegister reg) { constexpr size_t kFramePointerSize = kArmPointerSize; -void ArmAssembler::BuildFrame(size_t frame_size, ManagedRegister method_reg, - const std::vector<ManagedRegister>& callee_save_regs, +void ArmAssembler::BuildFrame(size_t frame_size, + ManagedRegister method_reg, + ArrayRef<const ManagedRegister> callee_save_regs, const ManagedRegisterEntrySpills& entry_spills) { CHECK_EQ(buffer_.Size(), 0U); // Nothing emitted yet CHECK_ALIGNED(frame_size, kStackAlignment); @@ -442,7 +443,7 @@ void ArmAssembler::BuildFrame(size_t frame_size, ManagedRegister method_reg, } void ArmAssembler::RemoveFrame(size_t frame_size, - const std::vector<ManagedRegister>& callee_save_regs) { + ArrayRef<const ManagedRegister> callee_save_regs) { CHECK_ALIGNED(frame_size, kStackAlignment); cfi_.RememberState(); diff --git a/compiler/utils/arm/assembler_arm.h b/compiler/utils/arm/assembler_arm.h index ffbe786bf4..274d0de166 100644 --- a/compiler/utils/arm/assembler_arm.h +++ b/compiler/utils/arm/assembler_arm.h @@ -907,12 +907,13 @@ class ArmAssembler : public Assembler { // // Emit code that will create an activation on the stack - void BuildFrame(size_t frame_size, ManagedRegister method_reg, - const std::vector<ManagedRegister>& callee_save_regs, + void BuildFrame(size_t frame_size, + ManagedRegister method_reg, + ArrayRef<const ManagedRegister> callee_save_regs, const ManagedRegisterEntrySpills& entry_spills) OVERRIDE; // Emit code that will remove an activation from the stack - void RemoveFrame(size_t frame_size, const std::vector<ManagedRegister>& callee_save_regs) + void RemoveFrame(size_t frame_size, ArrayRef<const ManagedRegister> callee_save_regs) OVERRIDE; void IncreaseFrameSize(size_t adjust) OVERRIDE; diff --git a/compiler/utils/arm/assembler_thumb2.cc b/compiler/utils/arm/assembler_thumb2.cc index 26f7d0dfcb..546dd653af 100644 --- a/compiler/utils/arm/assembler_thumb2.cc +++ b/compiler/utils/arm/assembler_thumb2.cc @@ -14,6 +14,8 @@ * limitations under the License. */ +#include <type_traits> + #include "assembler_thumb2.h" #include "base/bit_utils.h" @@ -25,33 +27,49 @@ namespace art { namespace arm { -void Thumb2Assembler::Fixup::PrepareDependents(Thumb2Assembler* assembler) { - // For each Fixup, it's easy to find the Fixups that it depends on as they are either - // the following or the preceding Fixups until we find the target. However, for fixup - // adjustment we need the reverse lookup, i.e. what Fixups depend on a given Fixup. - // This function creates a compact representation of this relationship, where we have - // all the dependents in a single array and Fixups reference their ranges by start - // index and count. (Instead of having a per-fixup vector.) - - // Count the number of dependents of each Fixup. - const FixupId end_id = assembler->fixups_.size(); +template <typename Function> +void Thumb2Assembler::Fixup::ForExpandableDependencies(Thumb2Assembler* assembler, Function fn) { + static_assert( + std::is_same<typename std::result_of<Function(FixupId, FixupId)>::type, void>::value, + "Incorrect signature for argument `fn`: expected (FixupId, FixupId) -> void"); Fixup* fixups = assembler->fixups_.data(); - for (FixupId fixup_id = 0u; fixup_id != end_id; ++fixup_id) { + for (FixupId fixup_id = 0u, end_id = assembler->fixups_.size(); fixup_id != end_id; ++fixup_id) { uint32_t target = fixups[fixup_id].target_; if (target > fixups[fixup_id].location_) { for (FixupId id = fixup_id + 1u; id != end_id && fixups[id].location_ < target; ++id) { - fixups[id].dependents_count_ += 1u; + if (fixups[id].CanExpand()) { + fn(id, fixup_id); + } } } else { for (FixupId id = fixup_id; id != 0u && fixups[id - 1u].location_ >= target; --id) { - fixups[id - 1u].dependents_count_ += 1u; + if (fixups[id - 1u].CanExpand()) { + fn(id - 1u, fixup_id); + } } } } +} + +void Thumb2Assembler::Fixup::PrepareDependents(Thumb2Assembler* assembler) { + // For each Fixup, it's easy to find the Fixups that it depends on as they are either + // the following or the preceding Fixups until we find the target. However, for fixup + // adjustment we need the reverse lookup, i.e. what Fixups depend on a given Fixup. + // This function creates a compact representation of this relationship, where we have + // all the dependents in a single array and Fixups reference their ranges by start + // index and count. (Instead of having a per-fixup vector.) + + // Count the number of dependents of each Fixup. + Fixup* fixups = assembler->fixups_.data(); + ForExpandableDependencies( + assembler, + [fixups](FixupId dependency, FixupId dependent ATTRIBUTE_UNUSED) { + fixups[dependency].dependents_count_ += 1u; + }); // Assign index ranges in fixup_dependents_ to individual fixups. Record the end of the // range in dependents_start_, we shall later decrement it as we fill in fixup_dependents_. uint32_t number_of_dependents = 0u; - for (FixupId fixup_id = 0u; fixup_id != end_id; ++fixup_id) { + for (FixupId fixup_id = 0u, end_id = assembler->fixups_.size(); fixup_id != end_id; ++fixup_id) { number_of_dependents += fixups[fixup_id].dependents_count_; fixups[fixup_id].dependents_start_ = number_of_dependents; } @@ -61,20 +79,12 @@ void Thumb2Assembler::Fixup::PrepareDependents(Thumb2Assembler* assembler) { // Create and fill in the fixup_dependents_. assembler->fixup_dependents_.resize(number_of_dependents); FixupId* dependents = assembler->fixup_dependents_.data(); - for (FixupId fixup_id = 0u; fixup_id != end_id; ++fixup_id) { - uint32_t target = fixups[fixup_id].target_; - if (target > fixups[fixup_id].location_) { - for (FixupId id = fixup_id + 1u; id != end_id && fixups[id].location_ < target; ++id) { - fixups[id].dependents_start_ -= 1u; - dependents[fixups[id].dependents_start_] = fixup_id; - } - } else { - for (FixupId id = fixup_id; id != 0u && fixups[id - 1u].location_ >= target; --id) { - fixups[id - 1u].dependents_start_ -= 1u; - dependents[fixups[id - 1u].dependents_start_] = fixup_id; - } - } - } + ForExpandableDependencies( + assembler, + [fixups, dependents](FixupId dependency, FixupId dependent) { + fixups[dependency].dependents_start_ -= 1u; + dependents[fixups[dependency].dependents_start_] = dependent; + }); } void Thumb2Assembler::BindLabel(Label* label, uint32_t bound_pc) { @@ -115,6 +125,7 @@ void Thumb2Assembler::AdjustFixupIfNeeded(Fixup* fixup, uint32_t* current_code_s std::deque<FixupId>* fixups_to_recalculate) { uint32_t adjustment = fixup->AdjustSizeIfNeeded(*current_code_size); if (adjustment != 0u) { + DCHECK(fixup->CanExpand()); *current_code_size += adjustment; for (FixupId dependent_id : fixup->Dependents(*this)) { Fixup* dependent = GetFixup(dependent_id); @@ -256,7 +267,10 @@ void Thumb2Assembler::EmitJumpTables() { for (JumpTable& table : jump_tables_) { // Bulk ensure capacity, as this may be large. size_t orig_size = buffer_.Size(); - buffer_.ExtendCapacity(orig_size + table.GetSize()); + size_t required_capacity = orig_size + table.GetSize(); + if (required_capacity > buffer_.Capacity()) { + buffer_.ExtendCapacity(required_capacity); + } #ifndef NDEBUG buffer_.has_ensured_capacity_ = true; #endif @@ -2543,9 +2557,19 @@ void Thumb2Assembler::EmitBranch(Condition cond, Label* label, bool link, bool x } } else { branch_type = Fixup::kUnconditional; // B. + // The T2 encoding offset is `SignExtend(imm11:'0', 32)` and there is a PC adjustment of 4. + static constexpr size_t kMaxT2BackwardDistance = (1u << 11) - 4u; + if (!use32bit && label->IsBound() && pc - label->Position() > kMaxT2BackwardDistance) { + use32bit = true; + } } } else { branch_type = Fixup::kConditional; // B<cond>. + // The T1 encoding offset is `SignExtend(imm8:'0', 32)` and there is a PC adjustment of 4. + static constexpr size_t kMaxT1BackwardDistance = (1u << 8) - 4u; + if (!use32bit && label->IsBound() && pc - label->Position() > kMaxT1BackwardDistance) { + use32bit = true; + } } Fixup::Size size = use32bit ? Fixup::kBranch32Bit : Fixup::kBranch16Bit; diff --git a/compiler/utils/arm/assembler_thumb2.h b/compiler/utils/arm/assembler_thumb2.h index 111a6b09d7..ce310a4da8 100644 --- a/compiler/utils/arm/assembler_thumb2.h +++ b/compiler/utils/arm/assembler_thumb2.h @@ -538,6 +538,20 @@ class Thumb2Assembler FINAL : public ArmAssembler { return GetType() >= kLoadLiteralNarrow; } + // Returns whether the Fixup can expand from the original size. + bool CanExpand() const { + switch (GetOriginalSize()) { + case kBranch32Bit: + case kCbxz48Bit: + case kLiteralFar: + case kLiteralAddrFar: + case kLongOrFPLiteralFar: + return false; + default: + return true; + } + } + Size GetOriginalSize() const { return original_size_; } @@ -611,6 +625,7 @@ class Thumb2Assembler FINAL : public ArmAssembler { dependents_count_(0u), dependents_start_(0u) { } + static size_t SizeInBytes(Size size); // The size of padding added before the literal pool. @@ -623,6 +638,9 @@ class Thumb2Assembler FINAL : public ArmAssembler { int32_t LoadWideOrFpEncoding(Register rbase, int32_t offset) const; + template <typename Function> + static void ForExpandableDependencies(Thumb2Assembler* assembler, Function fn); + static constexpr uint32_t kUnresolved = 0xffffffff; // Value for target_ for unresolved. const Register rn_; // Rn for cbnz/cbz, Rt for literal loads. diff --git a/compiler/utils/arm/assembler_thumb2_test.cc b/compiler/utils/arm/assembler_thumb2_test.cc index 650b08900b..b5cafcbf66 100644 --- a/compiler/utils/arm/assembler_thumb2_test.cc +++ b/compiler/utils/arm/assembler_thumb2_test.cc @@ -372,6 +372,31 @@ TEST_F(AssemblerThumb2Test, StoreWordPairToNonThumbOffset) { DriverStr(expected, "StoreWordPairToNonThumbOffset"); } +TEST_F(AssemblerThumb2Test, DistantBackBranch) { + Label start, end; + __ Bind(&start); + constexpr size_t kLdrR0R0Count1 = 256; + for (size_t i = 0; i != kLdrR0R0Count1; ++i) { + __ ldr(arm::R0, arm::Address(arm::R0)); + } + __ b(&end, arm::EQ); + __ b(&start, arm::LT); + constexpr size_t kLdrR0R0Count2 = 256; + for (size_t i = 0; i != kLdrR0R0Count2; ++i) { + __ ldr(arm::R0, arm::Address(arm::R0)); + } + __ Bind(&end); + + std::string expected = + "0:\n" + + RepeatInsn(kLdrR0R0Count1, "ldr r0, [r0]\n") + + "beq 1f\n" + "blt 0b\n" + + RepeatInsn(kLdrR0R0Count2, "ldr r0, [r0]\n") + + "1:\n"; + DriverStr(expected, "DistantBackBranch"); +} + TEST_F(AssemblerThumb2Test, TwoCbzMaxOffset) { Label label0, label1, label2; __ cbz(arm::R0, &label1); diff --git a/compiler/utils/arm/managed_register_arm.h b/compiler/utils/arm/managed_register_arm.h index 5b84058f7f..276db4420c 100644 --- a/compiler/utils/arm/managed_register_arm.h +++ b/compiler/utils/arm/managed_register_arm.h @@ -85,34 +85,34 @@ const int kNumberOfAllocIds = // There is a one-to-one mapping between ManagedRegister and register id. class ArmManagedRegister : public ManagedRegister { public: - Register AsCoreRegister() const { + constexpr Register AsCoreRegister() const { CHECK(IsCoreRegister()); return static_cast<Register>(id_); } - SRegister AsSRegister() const { + constexpr SRegister AsSRegister() const { CHECK(IsSRegister()); return static_cast<SRegister>(id_ - kNumberOfCoreRegIds); } - DRegister AsDRegister() const { + constexpr DRegister AsDRegister() const { CHECK(IsDRegister()); return static_cast<DRegister>(id_ - kNumberOfCoreRegIds - kNumberOfSRegIds); } - SRegister AsOverlappingDRegisterLow() const { + constexpr SRegister AsOverlappingDRegisterLow() const { CHECK(IsOverlappingDRegister()); DRegister d_reg = AsDRegister(); return static_cast<SRegister>(d_reg * 2); } - SRegister AsOverlappingDRegisterHigh() const { + constexpr SRegister AsOverlappingDRegisterHigh() const { CHECK(IsOverlappingDRegister()); DRegister d_reg = AsDRegister(); return static_cast<SRegister>(d_reg * 2 + 1); } - RegisterPair AsRegisterPair() const { + constexpr RegisterPair AsRegisterPair() const { CHECK(IsRegisterPair()); Register reg_low = AsRegisterPairLow(); if (reg_low == R1) { @@ -122,50 +122,50 @@ class ArmManagedRegister : public ManagedRegister { } } - Register AsRegisterPairLow() const { + constexpr Register AsRegisterPairLow() const { CHECK(IsRegisterPair()); // Appropriate mapping of register ids allows to use AllocIdLow(). return FromRegId(AllocIdLow()).AsCoreRegister(); } - Register AsRegisterPairHigh() const { + constexpr Register AsRegisterPairHigh() const { CHECK(IsRegisterPair()); // Appropriate mapping of register ids allows to use AllocIdHigh(). return FromRegId(AllocIdHigh()).AsCoreRegister(); } - bool IsCoreRegister() const { + constexpr bool IsCoreRegister() const { CHECK(IsValidManagedRegister()); return (0 <= id_) && (id_ < kNumberOfCoreRegIds); } - bool IsSRegister() const { + constexpr bool IsSRegister() const { CHECK(IsValidManagedRegister()); const int test = id_ - kNumberOfCoreRegIds; return (0 <= test) && (test < kNumberOfSRegIds); } - bool IsDRegister() const { + constexpr bool IsDRegister() const { CHECK(IsValidManagedRegister()); const int test = id_ - (kNumberOfCoreRegIds + kNumberOfSRegIds); return (0 <= test) && (test < kNumberOfDRegIds); } // Returns true if this DRegister overlaps SRegisters. - bool IsOverlappingDRegister() const { + constexpr bool IsOverlappingDRegister() const { CHECK(IsValidManagedRegister()); const int test = id_ - (kNumberOfCoreRegIds + kNumberOfSRegIds); return (0 <= test) && (test < kNumberOfOverlappingDRegIds); } - bool IsRegisterPair() const { + constexpr bool IsRegisterPair() const { CHECK(IsValidManagedRegister()); const int test = id_ - (kNumberOfCoreRegIds + kNumberOfSRegIds + kNumberOfDRegIds); return (0 <= test) && (test < kNumberOfPairRegIds); } - bool IsSameType(ArmManagedRegister test) const { + constexpr bool IsSameType(ArmManagedRegister test) const { CHECK(IsValidManagedRegister() && test.IsValidManagedRegister()); return (IsCoreRegister() && test.IsCoreRegister()) || @@ -182,29 +182,29 @@ class ArmManagedRegister : public ManagedRegister { void Print(std::ostream& os) const; - static ArmManagedRegister FromCoreRegister(Register r) { + static constexpr ArmManagedRegister FromCoreRegister(Register r) { CHECK_NE(r, kNoRegister); return FromRegId(r); } - static ArmManagedRegister FromSRegister(SRegister r) { + static constexpr ArmManagedRegister FromSRegister(SRegister r) { CHECK_NE(r, kNoSRegister); return FromRegId(r + kNumberOfCoreRegIds); } - static ArmManagedRegister FromDRegister(DRegister r) { + static constexpr ArmManagedRegister FromDRegister(DRegister r) { CHECK_NE(r, kNoDRegister); return FromRegId(r + (kNumberOfCoreRegIds + kNumberOfSRegIds)); } - static ArmManagedRegister FromRegisterPair(RegisterPair r) { + static constexpr ArmManagedRegister FromRegisterPair(RegisterPair r) { CHECK_NE(r, kNoRegisterPair); return FromRegId(r + (kNumberOfCoreRegIds + kNumberOfSRegIds + kNumberOfDRegIds)); } // Return a RegisterPair consisting of Register r_low and r_low + 1. - static ArmManagedRegister FromCoreRegisterPair(Register r_low) { + static constexpr ArmManagedRegister FromCoreRegisterPair(Register r_low) { if (r_low != R1) { // not the dalvik special case CHECK_NE(r_low, kNoRegister); CHECK_EQ(0, (r_low % 2)); @@ -217,7 +217,7 @@ class ArmManagedRegister : public ManagedRegister { } // Return a DRegister overlapping SRegister r_low and r_low + 1. - static ArmManagedRegister FromSRegisterPair(SRegister r_low) { + static constexpr ArmManagedRegister FromSRegisterPair(SRegister r_low) { CHECK_NE(r_low, kNoSRegister); CHECK_EQ(0, (r_low % 2)); const int r = r_low / 2; @@ -226,7 +226,7 @@ class ArmManagedRegister : public ManagedRegister { } private: - bool IsValidManagedRegister() const { + constexpr bool IsValidManagedRegister() const { return (0 <= id_) && (id_ < kNumberOfRegIds); } @@ -251,9 +251,9 @@ class ArmManagedRegister : public ManagedRegister { friend class ManagedRegister; - explicit ArmManagedRegister(int reg_id) : ManagedRegister(reg_id) {} + explicit constexpr ArmManagedRegister(int reg_id) : ManagedRegister(reg_id) {} - static ArmManagedRegister FromRegId(int reg_id) { + static constexpr ArmManagedRegister FromRegId(int reg_id) { ArmManagedRegister reg(reg_id); CHECK(reg.IsValidManagedRegister()); return reg; @@ -264,7 +264,7 @@ std::ostream& operator<<(std::ostream& os, const ArmManagedRegister& reg); } // namespace arm -inline arm::ArmManagedRegister ManagedRegister::AsArm() const { +constexpr inline arm::ArmManagedRegister ManagedRegister::AsArm() const { arm::ArmManagedRegister reg(id_); CHECK(reg.IsNoRegister() || reg.IsValidManagedRegister()); return reg; diff --git a/compiler/utils/arm64/assembler_arm64.cc b/compiler/utils/arm64/assembler_arm64.cc index eb851f9534..1842f00ff6 100644 --- a/compiler/utils/arm64/assembler_arm64.cc +++ b/compiler/utils/arm64/assembler_arm64.cc @@ -32,8 +32,8 @@ namespace arm64 { #endif void Arm64Assembler::FinalizeCode() { - for (Arm64Exception* exception : exception_blocks_) { - EmitExceptionPoll(exception); + for (const std::unique_ptr<Arm64Exception>& exception : exception_blocks_) { + EmitExceptionPoll(exception.get()); } ___ FinalizeCode(); } @@ -611,10 +611,9 @@ void Arm64Assembler::LoadReferenceFromHandleScope(ManagedRegister m_out_reg, void Arm64Assembler::ExceptionPoll(ManagedRegister m_scratch, size_t stack_adjust) { CHECK_ALIGNED(stack_adjust, kStackAlignment); Arm64ManagedRegister scratch = m_scratch.AsArm64(); - Arm64Exception *current_exception = new Arm64Exception(scratch, stack_adjust); - exception_blocks_.push_back(current_exception); + exception_blocks_.emplace_back(new Arm64Exception(scratch, stack_adjust)); LoadFromOffset(scratch.AsXRegister(), TR, Thread::ExceptionOffset<8>().Int32Value()); - ___ Cbnz(reg_x(scratch.AsXRegister()), current_exception->Entry()); + ___ Cbnz(reg_x(scratch.AsXRegister()), exception_blocks_.back()->Entry()); } void Arm64Assembler::EmitExceptionPoll(Arm64Exception *exception) { @@ -684,8 +683,9 @@ void Arm64Assembler::UnspillRegisters(vixl::CPURegList registers, int offset) { DCHECK(registers.IsEmpty()); } -void Arm64Assembler::BuildFrame(size_t frame_size, ManagedRegister method_reg, - const std::vector<ManagedRegister>& callee_save_regs, +void Arm64Assembler::BuildFrame(size_t frame_size, + ManagedRegister method_reg, + ArrayRef<const ManagedRegister> callee_save_regs, const ManagedRegisterEntrySpills& entry_spills) { // Setup VIXL CPURegList for callee-saves. CPURegList core_reg_list(CPURegister::kRegister, kXRegSize, 0); @@ -742,7 +742,7 @@ void Arm64Assembler::BuildFrame(size_t frame_size, ManagedRegister method_reg, } void Arm64Assembler::RemoveFrame(size_t frame_size, - const std::vector<ManagedRegister>& callee_save_regs) { + ArrayRef<const ManagedRegister> callee_save_regs) { // Setup VIXL CPURegList for callee-saves. CPURegList core_reg_list(CPURegister::kRegister, kXRegSize, 0); CPURegList fp_reg_list(CPURegister::kFPRegister, kDRegSize, 0); diff --git a/compiler/utils/arm64/assembler_arm64.h b/compiler/utils/arm64/assembler_arm64.h index 03ae996952..91171a8686 100644 --- a/compiler/utils/arm64/assembler_arm64.h +++ b/compiler/utils/arm64/assembler_arm64.h @@ -62,7 +62,25 @@ enum StoreOperandType { kStoreDWord }; -class Arm64Exception; +class Arm64Exception { + private: + Arm64Exception(Arm64ManagedRegister scratch, size_t stack_adjust) + : scratch_(scratch), stack_adjust_(stack_adjust) { + } + + vixl::Label* Entry() { return &exception_entry_; } + + // Register used for passing Thread::Current()->exception_ . + const Arm64ManagedRegister scratch_; + + // Stack adjust for ExceptionPool. + const size_t stack_adjust_; + + vixl::Label exception_entry_; + + friend class Arm64Assembler; + DISALLOW_COPY_AND_ASSIGN(Arm64Exception); +}; class Arm64Assembler FINAL : public Assembler { public: @@ -91,12 +109,13 @@ class Arm64Assembler FINAL : public Assembler { void UnspillRegisters(vixl::CPURegList registers, int offset); // Emit code that will create an activation on the stack. - void BuildFrame(size_t frame_size, ManagedRegister method_reg, - const std::vector<ManagedRegister>& callee_save_regs, + void BuildFrame(size_t frame_size, + ManagedRegister method_reg, + ArrayRef<const ManagedRegister> callee_save_regs, const ManagedRegisterEntrySpills& entry_spills) OVERRIDE; // Emit code that will remove an activation from the stack. - void RemoveFrame(size_t frame_size, const std::vector<ManagedRegister>& callee_save_regs) + void RemoveFrame(size_t frame_size, ArrayRef<const ManagedRegister> callee_save_regs) OVERRIDE; void IncreaseFrameSize(size_t adjust) OVERRIDE; @@ -253,7 +272,7 @@ class Arm64Assembler FINAL : public Assembler { void AddConstant(XRegister rd, XRegister rn, int32_t value, vixl::Condition cond = vixl::al); // List of exception blocks to generate at the end of the code cache. - ArenaVector<Arm64Exception*> exception_blocks_; + ArenaVector<std::unique_ptr<Arm64Exception>> exception_blocks_; public: // Vixl assembler. @@ -263,26 +282,6 @@ class Arm64Assembler FINAL : public Assembler { friend class Arm64ManagedRegister_VixlRegisters_Test; }; -class Arm64Exception { - private: - Arm64Exception(Arm64ManagedRegister scratch, size_t stack_adjust) - : scratch_(scratch), stack_adjust_(stack_adjust) { - } - - vixl::Label* Entry() { return &exception_entry_; } - - // Register used for passing Thread::Current()->exception_ . - const Arm64ManagedRegister scratch_; - - // Stack adjust for ExceptionPool. - const size_t stack_adjust_; - - vixl::Label exception_entry_; - - friend class Arm64Assembler; - DISALLOW_COPY_AND_ASSIGN(Arm64Exception); -}; - } // namespace arm64 } // namespace art diff --git a/compiler/utils/arm64/managed_register_arm64.h b/compiler/utils/arm64/managed_register_arm64.h index 46be1c528c..f7d74d2af4 100644 --- a/compiler/utils/arm64/managed_register_arm64.h +++ b/compiler/utils/arm64/managed_register_arm64.h @@ -56,80 +56,80 @@ const int kNumberOfRegIds = kNumberOfXRegIds + kNumberOfWRegIds + class Arm64ManagedRegister : public ManagedRegister { public: - XRegister AsXRegister() const { + constexpr XRegister AsXRegister() const { CHECK(IsXRegister()); return static_cast<XRegister>(id_); } - WRegister AsWRegister() const { + constexpr WRegister AsWRegister() const { CHECK(IsWRegister()); return static_cast<WRegister>(id_ - kNumberOfXRegIds); } - DRegister AsDRegister() const { + constexpr DRegister AsDRegister() const { CHECK(IsDRegister()); return static_cast<DRegister>(id_ - kNumberOfXRegIds - kNumberOfWRegIds); } - SRegister AsSRegister() const { + constexpr SRegister AsSRegister() const { CHECK(IsSRegister()); return static_cast<SRegister>(id_ - kNumberOfXRegIds - kNumberOfWRegIds - kNumberOfDRegIds); } - WRegister AsOverlappingWRegister() const { + constexpr WRegister AsOverlappingWRegister() const { CHECK(IsValidManagedRegister()); if (IsZeroRegister()) return WZR; return static_cast<WRegister>(AsXRegister()); } - XRegister AsOverlappingXRegister() const { + constexpr XRegister AsOverlappingXRegister() const { CHECK(IsValidManagedRegister()); return static_cast<XRegister>(AsWRegister()); } - SRegister AsOverlappingSRegister() const { + constexpr SRegister AsOverlappingSRegister() const { CHECK(IsValidManagedRegister()); return static_cast<SRegister>(AsDRegister()); } - DRegister AsOverlappingDRegister() const { + constexpr DRegister AsOverlappingDRegister() const { CHECK(IsValidManagedRegister()); return static_cast<DRegister>(AsSRegister()); } - bool IsXRegister() const { + constexpr bool IsXRegister() const { CHECK(IsValidManagedRegister()); return (0 <= id_) && (id_ < kNumberOfXRegIds); } - bool IsWRegister() const { + constexpr bool IsWRegister() const { CHECK(IsValidManagedRegister()); const int test = id_ - kNumberOfXRegIds; return (0 <= test) && (test < kNumberOfWRegIds); } - bool IsDRegister() const { + constexpr bool IsDRegister() const { CHECK(IsValidManagedRegister()); const int test = id_ - (kNumberOfXRegIds + kNumberOfWRegIds); return (0 <= test) && (test < kNumberOfDRegIds); } - bool IsSRegister() const { + constexpr bool IsSRegister() const { CHECK(IsValidManagedRegister()); const int test = id_ - (kNumberOfXRegIds + kNumberOfWRegIds + kNumberOfDRegIds); return (0 <= test) && (test < kNumberOfSRegIds); } - bool IsGPRegister() const { + constexpr bool IsGPRegister() const { return IsXRegister() || IsWRegister(); } - bool IsFPRegister() const { + constexpr bool IsFPRegister() const { return IsDRegister() || IsSRegister(); } - bool IsSameType(Arm64ManagedRegister test) const { + constexpr bool IsSameType(Arm64ManagedRegister test) const { CHECK(IsValidManagedRegister() && test.IsValidManagedRegister()); return (IsXRegister() && test.IsXRegister()) || @@ -145,53 +145,53 @@ class Arm64ManagedRegister : public ManagedRegister { void Print(std::ostream& os) const; - static Arm64ManagedRegister FromXRegister(XRegister r) { + static constexpr Arm64ManagedRegister FromXRegister(XRegister r) { CHECK_NE(r, kNoRegister); return FromRegId(r); } - static Arm64ManagedRegister FromWRegister(WRegister r) { + static constexpr Arm64ManagedRegister FromWRegister(WRegister r) { CHECK_NE(r, kNoWRegister); return FromRegId(r + kNumberOfXRegIds); } - static Arm64ManagedRegister FromDRegister(DRegister r) { + static constexpr Arm64ManagedRegister FromDRegister(DRegister r) { CHECK_NE(r, kNoDRegister); return FromRegId(r + (kNumberOfXRegIds + kNumberOfWRegIds)); } - static Arm64ManagedRegister FromSRegister(SRegister r) { + static constexpr Arm64ManagedRegister FromSRegister(SRegister r) { CHECK_NE(r, kNoSRegister); return FromRegId(r + (kNumberOfXRegIds + kNumberOfWRegIds + kNumberOfDRegIds)); } // Returns the X register overlapping W register r. - static Arm64ManagedRegister FromWRegisterX(WRegister r) { + static constexpr Arm64ManagedRegister FromWRegisterX(WRegister r) { CHECK_NE(r, kNoWRegister); return FromRegId(r); } // Return the D register overlapping S register r. - static Arm64ManagedRegister FromSRegisterD(SRegister r) { + static constexpr Arm64ManagedRegister FromSRegisterD(SRegister r) { CHECK_NE(r, kNoSRegister); return FromRegId(r + (kNumberOfXRegIds + kNumberOfWRegIds)); } private: - bool IsValidManagedRegister() const { + constexpr bool IsValidManagedRegister() const { return (0 <= id_) && (id_ < kNumberOfRegIds); } - bool IsStackPointer() const { + constexpr bool IsStackPointer() const { return IsXRegister() && (id_ == SP); } - bool IsZeroRegister() const { + constexpr bool IsZeroRegister() const { return IsXRegister() && (id_ == XZR); } - int RegId() const { + constexpr int RegId() const { CHECK(!IsNoRegister()); return id_; } @@ -202,9 +202,9 @@ class Arm64ManagedRegister : public ManagedRegister { friend class ManagedRegister; - explicit Arm64ManagedRegister(int reg_id) : ManagedRegister(reg_id) {} + explicit constexpr Arm64ManagedRegister(int reg_id) : ManagedRegister(reg_id) {} - static Arm64ManagedRegister FromRegId(int reg_id) { + static constexpr Arm64ManagedRegister FromRegId(int reg_id) { Arm64ManagedRegister reg(reg_id); CHECK(reg.IsValidManagedRegister()); return reg; @@ -215,7 +215,7 @@ std::ostream& operator<<(std::ostream& os, const Arm64ManagedRegister& reg); } // namespace arm64 -inline arm64::Arm64ManagedRegister ManagedRegister::AsArm64() const { +constexpr inline arm64::Arm64ManagedRegister ManagedRegister::AsArm64() const { arm64::Arm64ManagedRegister reg(id_); CHECK(reg.IsNoRegister() || reg.IsValidManagedRegister()); return reg; diff --git a/compiler/utils/array_ref.h b/compiler/utils/array_ref.h index 5c33639a6a..8dc9ab4a5e 100644 --- a/compiler/utils/array_ref.h +++ b/compiler/utils/array_ref.h @@ -39,9 +39,6 @@ namespace art { */ template <typename T> class ArrayRef { - private: - struct tag { }; - public: typedef T value_type; typedef T& reference; @@ -63,14 +60,14 @@ class ArrayRef { template <size_t size> explicit constexpr ArrayRef(T (&array)[size]) - : array_(array), size_(size) { + : array_(array), size_(size) { } - template <typename U, size_t size> - explicit constexpr ArrayRef(U (&array)[size], - typename std::enable_if<std::is_same<T, const U>::value, tag>::type - t ATTRIBUTE_UNUSED = tag()) - : array_(array), size_(size) { + template <typename U, + size_t size, + typename = typename std::enable_if<std::is_same<T, const U>::value>::type> + explicit constexpr ArrayRef(U (&array)[size]) + : array_(array), size_(size) { } constexpr ArrayRef(T* array_in, size_t size_in) @@ -165,13 +162,21 @@ class ArrayRef { value_type* data() { return array_; } const value_type* data() const { return array_; } - ArrayRef SubArray(size_type pos) const { - return SubArray(pos, size_ - pos); + ArrayRef SubArray(size_type pos) { + return SubArray(pos, size() - pos); + } + ArrayRef<const T> SubArray(size_type pos) const { + return SubArray(pos, size() - pos); + } + ArrayRef SubArray(size_type pos, size_type length) { + DCHECK_LE(pos, size()); + DCHECK_LE(length, size() - pos); + return ArrayRef(data() + pos, length); } - ArrayRef SubArray(size_type pos, size_type length) const { + ArrayRef<const T> SubArray(size_type pos, size_type length) const { DCHECK_LE(pos, size()); DCHECK_LE(length, size() - pos); - return ArrayRef(array_ + pos, length); + return ArrayRef<const T>(data() + pos, length); } private: diff --git a/compiler/utils/assembler.cc b/compiler/utils/assembler.cc index c2aa574f76..e6c3a18d04 100644 --- a/compiler/utils/assembler.cc +++ b/compiler/utils/assembler.cc @@ -47,7 +47,7 @@ namespace art { AssemblerBuffer::AssemblerBuffer(ArenaAllocator* arena) : arena_(arena) { static const size_t kInitialBufferCapacity = 4 * KB; - contents_ = arena_->AllocArray<uint8_t>(kInitialBufferCapacity); + contents_ = arena_->AllocArray<uint8_t>(kInitialBufferCapacity, kArenaAllocAssembler); cursor_ = contents_; limit_ = ComputeLimit(contents_, kInitialBufferCapacity); fixup_ = nullptr; @@ -94,6 +94,7 @@ void AssemblerBuffer::FinalizeInstructions(const MemoryRegion& instructions) { void AssemblerBuffer::ExtendCapacity(size_t min_capacity) { size_t old_size = Size(); size_t old_capacity = Capacity(); + DCHECK_GT(min_capacity, old_capacity); size_t new_capacity = std::min(old_capacity * 2, old_capacity + 1 * MB); new_capacity = std::max(new_capacity, min_capacity); diff --git a/compiler/utils/assembler.h b/compiler/utils/assembler.h index 4ea85a2c18..80aa630424 100644 --- a/compiler/utils/assembler.h +++ b/compiler/utils/assembler.h @@ -32,6 +32,7 @@ #include "memory_region.h" #include "mips/constants_mips.h" #include "offsets.h" +#include "utils/array_ref.h" #include "x86/constants_x86.h" #include "x86_64/constants_x86_64.h" @@ -178,8 +179,8 @@ class AssemblerBuffer { class EnsureCapacity { public: explicit EnsureCapacity(AssemblerBuffer* buffer) { - if (buffer->cursor() >= buffer->limit()) { - buffer->ExtendCapacity(); + if (buffer->cursor() > buffer->limit()) { + buffer->ExtendCapacity(buffer->Size() + kMinimumGap); } // In debug mode, we save the assembler buffer along with the gap // size before we start emitting to the buffer. This allows us to @@ -219,7 +220,9 @@ class AssemblerBuffer { class EnsureCapacity { public: explicit EnsureCapacity(AssemblerBuffer* buffer) { - if (buffer->cursor() >= buffer->limit()) buffer->ExtendCapacity(); + if (buffer->cursor() > buffer->limit()) { + buffer->ExtendCapacity(buffer->Size() + kMinimumGap); + } } }; @@ -233,7 +236,14 @@ class AssemblerBuffer { // Returns the position in the instruction stream. int GetPosition() { return cursor_ - contents_; } - void ExtendCapacity(size_t min_capacity = 0u); + size_t Capacity() const { + CHECK_GE(limit_, contents_); + return (limit_ - contents_) + kMinimumGap; + } + + // Unconditionally increase the capacity. + // The provided `min_capacity` must be higher than current `Capacity()`. + void ExtendCapacity(size_t min_capacity); private: // The limit is set to kMinimumGap bytes before the end of the data area. @@ -255,10 +265,6 @@ class AssemblerBuffer { uint8_t* cursor() const { return cursor_; } uint8_t* limit() const { return limit_; } - size_t Capacity() const { - CHECK_GE(limit_, contents_); - return (limit_ - contents_) + kMinimumGap; - } // Process the fixup chain starting at the given fixup. The offset is // non-zero for fixups in the body if the preamble is non-empty. @@ -306,8 +312,10 @@ class DebugFrameOpCodeWriterForAssembler FINAL // Override the last delayed PC. The new PC can be out of order. void OverrideDelayedPC(size_t pc) { DCHECK(delay_emitting_advance_pc_); - DCHECK(!delayed_advance_pcs_.empty()); - delayed_advance_pcs_.back().pc = pc; + if (enabled_) { + DCHECK(!delayed_advance_pcs_.empty()); + delayed_advance_pcs_.back().pc = pc; + } } // Return the number of delayed advance PC entries. @@ -368,13 +376,14 @@ class Assembler : public DeletableArenaObject<kArenaAllocAssembler> { virtual void Comment(const char* format ATTRIBUTE_UNUSED, ...) {} // Emit code that will create an activation on the stack - virtual void BuildFrame(size_t frame_size, ManagedRegister method_reg, - const std::vector<ManagedRegister>& callee_save_regs, + virtual void BuildFrame(size_t frame_size, + ManagedRegister method_reg, + ArrayRef<const ManagedRegister> callee_save_regs, const ManagedRegisterEntrySpills& entry_spills) = 0; // Emit code that will remove an activation from the stack virtual void RemoveFrame(size_t frame_size, - const std::vector<ManagedRegister>& callee_save_regs) = 0; + ArrayRef<const ManagedRegister> callee_save_regs) = 0; virtual void IncreaseFrameSize(size_t adjust) = 0; virtual void DecreaseFrameSize(size_t adjust) = 0; diff --git a/compiler/utils/assembler_thumb_test.cc b/compiler/utils/assembler_thumb_test.cc index c67cb5a563..9c9271db33 100644 --- a/compiler/utils/assembler_thumb_test.cc +++ b/compiler/utils/assembler_thumb_test.cc @@ -32,7 +32,7 @@ namespace arm { // Include results file (generated manually) #include "assembler_thumb_test_expected.cc.inc" -#ifndef __ANDROID__ +#ifndef ART_TARGET_ANDROID // This controls whether the results are printed to the // screen or compared against the expected output. // To generate new expected output, set this to true and @@ -72,7 +72,7 @@ void InitResults() { } std::string GetToolsDir() { -#ifndef __ANDROID__ +#ifndef ART_TARGET_ANDROID // This will only work on the host. There is no as, objcopy or objdump on the device. static std::string toolsdir; @@ -89,7 +89,7 @@ std::string GetToolsDir() { } void DumpAndCheck(std::vector<uint8_t>& code, const char* testname, const char* const* results) { -#ifndef __ANDROID__ +#ifndef ART_TARGET_ANDROID static std::string toolsdir = GetToolsDir(); ScratchFile file; @@ -169,7 +169,7 @@ void DumpAndCheck(std::vector<uint8_t>& code, const char* testname, const char* snprintf(buf, sizeof(buf), "%s.oo", filename); unlink(buf); -#endif +#endif // ART_TARGET_ANDROID } #define __ assembler-> diff --git a/compiler/utils/intrusive_forward_list.h b/compiler/utils/intrusive_forward_list.h new file mode 100644 index 0000000000..ec2c08722c --- /dev/null +++ b/compiler/utils/intrusive_forward_list.h @@ -0,0 +1,452 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef ART_COMPILER_UTILS_INTRUSIVE_FORWARD_LIST_H_ +#define ART_COMPILER_UTILS_INTRUSIVE_FORWARD_LIST_H_ + +#include <stdint.h> +#include <functional> +#include <iterator> +#include <memory> +#include <type_traits> + +#include "base/logging.h" +#include "base/macros.h" + +namespace art { + +struct IntrusiveForwardListHook { + IntrusiveForwardListHook() : next_hook(nullptr) { } + explicit IntrusiveForwardListHook(const IntrusiveForwardListHook* hook) : next_hook(hook) { } + + // Allow copyable values but do not copy the hook, it is not part of the value. + IntrusiveForwardListHook(const IntrusiveForwardListHook& other ATTRIBUTE_UNUSED) + : next_hook(nullptr) { } + IntrusiveForwardListHook& operator=(const IntrusiveForwardListHook& src ATTRIBUTE_UNUSED) { + return *this; + } + + mutable const IntrusiveForwardListHook* next_hook; +}; + +template <typename T, IntrusiveForwardListHook T::* NextPtr = &T::hook> +class IntrusiveForwardListMemberHook; + +template <typename T, typename HookTraits = IntrusiveForwardListMemberHook<T>> +class IntrusiveForwardList; + +template <typename T, typename HookTraits> +class IntrusiveForwardListIterator : public std::iterator<std::forward_iterator_tag, T> { + public: + // Construct/copy/destroy (except the private constructor used by IntrusiveForwardList<>). + IntrusiveForwardListIterator() : hook_(nullptr) { } + IntrusiveForwardListIterator(const IntrusiveForwardListIterator& src) = default; + IntrusiveForwardListIterator& operator=(const IntrusiveForwardListIterator& src) = default; + + // Conversion from iterator to const_iterator. + template <typename OtherT, + typename = typename std::enable_if<std::is_same<T, const OtherT>::value>::type> + IntrusiveForwardListIterator(const IntrusiveForwardListIterator<OtherT, HookTraits>& src) + : hook_(src.hook_) { } + + // Iteration. + IntrusiveForwardListIterator& operator++() { + DCHECK(hook_ != nullptr); + hook_ = hook_->next_hook; + return *this; + } + IntrusiveForwardListIterator operator++(int) { + IntrusiveForwardListIterator tmp(*this); + ++*this; + return tmp; + } + + // Dereference + T& operator*() const { + DCHECK(hook_ != nullptr); + return *HookTraits::GetValue(hook_); + } + T* operator->() const { + return &**this; + } + + private: + explicit IntrusiveForwardListIterator(const IntrusiveForwardListHook* hook) : hook_(hook) { } + + const IntrusiveForwardListHook* hook_; + + template <typename OtherT, typename OtherTraits> + friend class IntrusiveForwardListIterator; + + template <typename OtherT, typename OtherTraits> + friend class IntrusiveForwardList; + + template <typename OtherT1, typename OtherT2, typename OtherTraits> + friend typename std::enable_if<std::is_same<const OtherT1, const OtherT2>::value, bool>::type + operator==(const IntrusiveForwardListIterator<OtherT1, OtherTraits>& lhs, + const IntrusiveForwardListIterator<OtherT2, OtherTraits>& rhs); +}; + +template <typename T, typename OtherT, typename HookTraits> +typename std::enable_if<std::is_same<const T, const OtherT>::value, bool>::type operator==( + const IntrusiveForwardListIterator<T, HookTraits>& lhs, + const IntrusiveForwardListIterator<OtherT, HookTraits>& rhs) { + return lhs.hook_ == rhs.hook_; +} + +template <typename T, typename OtherT, typename HookTraits> +typename std::enable_if<std::is_same<const T, const OtherT>::value, bool>::type operator!=( + const IntrusiveForwardListIterator<T, HookTraits>& lhs, + const IntrusiveForwardListIterator<OtherT, HookTraits>& rhs) { + return !(lhs == rhs); +} + +// Intrusive version of std::forward_list<>. See also slist<> in Boost.Intrusive. +// +// This class template provides the same interface as std::forward_list<> as long +// as the functions are meaningful for an intrusive container; this excludes emplace +// functions and functions taking an std::initializer_list<> as the container does +// not construct elements. +template <typename T, typename HookTraits> +class IntrusiveForwardList { + public: + typedef HookTraits hook_traits; + typedef T value_type; + typedef T& reference; + typedef const T& const_reference; + typedef T* pointer; + typedef const T* const_pointer; + typedef IntrusiveForwardListIterator< T, hook_traits> iterator; + typedef IntrusiveForwardListIterator<const T, hook_traits> const_iterator; + + // Construct/copy/destroy. + IntrusiveForwardList() = default; + template <typename InputIterator> + IntrusiveForwardList(InputIterator first, InputIterator last) : IntrusiveForwardList() { + insert_after(before_begin(), first, last); + } + IntrusiveForwardList(IntrusiveForwardList&& src) : first_(src.first_.next_hook) { + src.first_.next_hook = nullptr; + } + IntrusiveForwardList& operator=(const IntrusiveForwardList& src) = delete; + IntrusiveForwardList& operator=(IntrusiveForwardList&& src) { + IntrusiveForwardList tmp(std::move(src)); + tmp.swap(*this); + return *this; + } + ~IntrusiveForwardList() = default; + + // Iterators. + iterator before_begin() { return iterator(&first_); } + const_iterator before_begin() const { return const_iterator(&first_); } + iterator begin() { return iterator(first_.next_hook); } + const_iterator begin() const { return const_iterator(first_.next_hook); } + iterator end() { return iterator(nullptr); } + const_iterator end() const { return const_iterator(nullptr); } + const_iterator cbefore_begin() const { return const_iterator(&first_); } + const_iterator cbegin() const { return const_iterator(first_.next_hook); } + const_iterator cend() const { return const_iterator(nullptr); } + + // Capacity. + bool empty() const { return begin() == end(); } + size_t max_size() { return static_cast<size_t>(-1); } + + // Element access. + reference front() { return *begin(); } + const_reference front() const { return *begin(); } + + // Modifiers. + template <typename InputIterator> + void assign(InputIterator first, InputIterator last) { + IntrusiveForwardList tmp(first, last); + tmp.swap(*this); + } + void push_front(value_type& value) { + insert_after(before_begin(), value); + } + void pop_front() { + DCHECK(!empty()); + erase_after(before_begin()); + } + iterator insert_after(const_iterator position, value_type& value) { + const IntrusiveForwardListHook* new_hook = hook_traits::GetHook(&value); + new_hook->next_hook = position.hook_->next_hook; + position.hook_->next_hook = new_hook; + return iterator(new_hook); + } + template <typename InputIterator> + iterator insert_after(const_iterator position, InputIterator first, InputIterator last) { + while (first != last) { + position = insert_after(position, *first++); + } + return iterator(position.hook_); + } + iterator erase_after(const_iterator position) { + const_iterator last = position; + std::advance(last, 2); + return erase_after(position, last); + } + iterator erase_after(const_iterator position, const_iterator last) { + DCHECK(position != last); + position.hook_->next_hook = last.hook_; + return iterator(last.hook_); + } + void swap(IntrusiveForwardList& other) { + std::swap(first_.next_hook, other.first_.next_hook); + } + void clear() { + first_.next_hook = nullptr; + } + + // Operations. + void splice_after(const_iterator position, IntrusiveForwardList& src) { + DCHECK(position != end()); + splice_after(position, src, src.before_begin(), src.end()); + } + void splice_after(const_iterator position, IntrusiveForwardList&& src) { + splice_after(position, src); // Use l-value overload. + } + // Splice the element after `i`. + void splice_after(const_iterator position, IntrusiveForwardList& src, const_iterator i) { + // The standard specifies that this version does nothing if `position == i` + // or `position == ++i`. We must handle the latter here because the overload + // `splice_after(position, src, first, last)` does not allow `position` inside + // the range `(first, last)`. + if (++const_iterator(i) == position) { + return; + } + const_iterator last = i; + std::advance(last, 2); + splice_after(position, src, i, last); + } + // Splice the element after `i`. + void splice_after(const_iterator position, IntrusiveForwardList&& src, const_iterator i) { + splice_after(position, src, i); // Use l-value overload. + } + // Splice elements between `first` and `last`, i.e. open range `(first, last)`. + void splice_after(const_iterator position, + IntrusiveForwardList& src, + const_iterator first, + const_iterator last) { + DCHECK(position != end()); + DCHECK(first != last); + if (++const_iterator(first) == last) { + // Nothing to do. + return; + } + // If position is just before end() and last is src.end(), we can finish this quickly. + if (++const_iterator(position) == end() && last == src.end()) { + position.hook_->next_hook = first.hook_->next_hook; + first.hook_->next_hook = nullptr; + return; + } + // Otherwise we need to find the position before last to fix up the hook. + const_iterator before_last = first; + while (++const_iterator(before_last) != last) { + ++before_last; + } + // Detach (first, last). + const IntrusiveForwardListHook* first_taken = first.hook_->next_hook; + first.hook_->next_hook = last.hook_; + // Attach the sequence to the new position. + before_last.hook_->next_hook = position.hook_->next_hook; + position.hook_->next_hook = first_taken; + } + // Splice elements between `first` and `last`, i.e. open range `(first, last)`. + void splice_after(const_iterator position, + IntrusiveForwardList&& src, + const_iterator first, + const_iterator last) { + splice_after(position, src, first, last); // Use l-value overload. + } + void remove(const value_type& value) { + remove_if([value](const value_type& v) { return value == v; }); + } + template <typename Predicate> + void remove_if(Predicate pred) { + iterator prev = before_begin(); + for (iterator current = begin(); current != end(); ++current) { + if (pred(*current)) { + erase_after(prev); + current = prev; + } else { + prev = current; + } + } + } + void unique() { + unique(std::equal_to<value_type>()); + } + template <typename BinaryPredicate> + void unique(BinaryPredicate pred) { + if (!empty()) { + iterator prev = begin(); + iterator current = prev; + ++current; + for (; current != end(); ++current) { + if (pred(*prev, *current)) { + erase_after(prev); + current = prev; + } else { + prev = current; + } + } + } + } + void merge(IntrusiveForwardList& other) { + merge(other, std::less<value_type>()); + } + void merge(IntrusiveForwardList&& other) { + merge(other); // Use l-value overload. + } + template <typename Compare> + void merge(IntrusiveForwardList& other, Compare cmp) { + iterator prev = before_begin(); + iterator current = begin(); + iterator other_prev = other.before_begin(); + iterator other_current = other.begin(); + while (current != end() && other_current != other.end()) { + if (cmp(*other_current, *current)) { + ++other_current; + splice_after(prev, other, other_prev); + ++prev; + } else { + prev = current; + ++current; + } + DCHECK(++const_iterator(prev) == current); + DCHECK(++const_iterator(other_prev) == other_current); + } + splice_after(prev, other); + } + template <typename Compare> + void merge(IntrusiveForwardList&& other, Compare cmp) { + merge(other, cmp); // Use l-value overload. + } + void sort() { + sort(std::less<value_type>()); + } + template <typename Compare> + void sort(Compare cmp) { + size_t n = std::distance(begin(), end()); + if (n >= 2u) { + const_iterator middle = before_begin(); + std::advance(middle, n / 2u); + IntrusiveForwardList second_half; + second_half.splice_after(second_half.before_begin(), *this, middle, end()); + sort(cmp); + second_half.sort(cmp); + merge(second_half, cmp); + } + } + void reverse() { + IntrusiveForwardList reversed; + while (!empty()) { + value_type& value = front(); + erase_after(before_begin()); + reversed.insert_after(reversed.before_begin(), value); + } + reversed.swap(*this); + } + + // Extensions. + bool HasExactlyOneElement() const { + return !empty() && ++begin() == end(); + } + size_t SizeSlow() const { + return std::distance(begin(), end()); + } + bool ContainsNode(const_reference node) const { + for (auto&& n : *this) { + if (std::addressof(n) == std::addressof(node)) { + return true; + } + } + return false; + } + + private: + static IntrusiveForwardListHook* ModifiableHook(const IntrusiveForwardListHook* hook) { + return const_cast<IntrusiveForwardListHook*>(hook); + } + + IntrusiveForwardListHook first_; +}; + +template <typename T, typename HookTraits> +void swap(IntrusiveForwardList<T, HookTraits>& lhs, IntrusiveForwardList<T, HookTraits>& rhs) { + lhs.swap(rhs); +} + +template <typename T, typename HookTraits> +bool operator==(const IntrusiveForwardList<T, HookTraits>& lhs, + const IntrusiveForwardList<T, HookTraits>& rhs) { + auto lit = lhs.begin(); + auto rit = rhs.begin(); + for (; lit != lhs.end() && rit != rhs.end(); ++lit, ++rit) { + if (*lit != *rit) { + return false; + } + } + return lit == lhs.end() && rit == rhs.end(); +} + +template <typename T, typename HookTraits> +bool operator!=(const IntrusiveForwardList<T, HookTraits>& lhs, + const IntrusiveForwardList<T, HookTraits>& rhs) { + return !(lhs == rhs); +} + +template <typename T, typename HookTraits> +bool operator<(const IntrusiveForwardList<T, HookTraits>& lhs, + const IntrusiveForwardList<T, HookTraits>& rhs) { + return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); +} + +template <typename T, typename HookTraits> +bool operator>(const IntrusiveForwardList<T, HookTraits>& lhs, + const IntrusiveForwardList<T, HookTraits>& rhs) { + return rhs < lhs; +} + +template <typename T, typename HookTraits> +bool operator<=(const IntrusiveForwardList<T, HookTraits>& lhs, + const IntrusiveForwardList<T, HookTraits>& rhs) { + return !(rhs < lhs); +} + +template <typename T, typename HookTraits> +bool operator>=(const IntrusiveForwardList<T, HookTraits>& lhs, + const IntrusiveForwardList<T, HookTraits>& rhs) { + return !(lhs < rhs); +} + +template <typename T, IntrusiveForwardListHook T::* NextPtr> +class IntrusiveForwardListMemberHook { + public: + static const IntrusiveForwardListHook* GetHook(const T* value) { + return &(value->*NextPtr); + } + + static T* GetValue(const IntrusiveForwardListHook* hook) { + return reinterpret_cast<T*>( + reinterpret_cast<uintptr_t>(hook) - OFFSETOF_MEMBERPTR(T, NextPtr)); + } +}; + +} // namespace art + +#endif // ART_COMPILER_UTILS_INTRUSIVE_FORWARD_LIST_H_ diff --git a/compiler/utils/intrusive_forward_list_test.cc b/compiler/utils/intrusive_forward_list_test.cc new file mode 100644 index 0000000000..517142e1b5 --- /dev/null +++ b/compiler/utils/intrusive_forward_list_test.cc @@ -0,0 +1,505 @@ +/* + * Copyright (C) 2016 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 <algorithm> +#include <forward_list> +#include <vector> + +#include "gtest/gtest.h" +#include "intrusive_forward_list.h" + +namespace art { + +struct IFLTestValue { + // Deliberately not explicit. + IFLTestValue(int v) : hook(), value(v) { } // NOLINT(runtime/explicit) + + IntrusiveForwardListHook hook; + int value; +}; + +bool operator==(const IFLTestValue& lhs, const IFLTestValue& rhs) { + return lhs.value == rhs.value; +} + +bool operator<(const IFLTestValue& lhs, const IFLTestValue& rhs) { + return lhs.value < rhs.value; +} + +#define ASSERT_LISTS_EQUAL(expected, value) \ + do { \ + ASSERT_EQ(expected.empty(), value.empty()); \ + ASSERT_EQ(std::distance(expected.begin(), expected.end()), \ + std::distance(value.begin(), value.end())); \ + ASSERT_TRUE(std::equal(expected.begin(), expected.end(), value.begin())); \ + } while (false) + +TEST(IntrusiveForwardList, IteratorToConstIterator) { + IntrusiveForwardList<IFLTestValue> ifl; + IntrusiveForwardList<IFLTestValue>::iterator begin = ifl.begin(); + IntrusiveForwardList<IFLTestValue>::const_iterator cbegin = ifl.cbegin(); + IntrusiveForwardList<IFLTestValue>::const_iterator converted_begin = begin; + ASSERT_TRUE(converted_begin == cbegin); +} + +TEST(IntrusiveForwardList, IteratorOperators) { + IntrusiveForwardList<IFLTestValue> ifl; + ASSERT_TRUE(ifl.begin() == ifl.cbegin()); + ASSERT_FALSE(ifl.begin() != ifl.cbegin()); + ASSERT_TRUE(ifl.end() == ifl.cend()); + ASSERT_FALSE(ifl.end() != ifl.cend()); + + ASSERT_TRUE(ifl.begin() == ifl.end()); // Empty. + ASSERT_FALSE(ifl.begin() != ifl.end()); // Empty. + + IFLTestValue value(1); + ifl.insert_after(ifl.cbefore_begin(), value); + + ASSERT_FALSE(ifl.begin() == ifl.end()); // Not empty. + ASSERT_TRUE(ifl.begin() != ifl.end()); // Not empty. +} + +TEST(IntrusiveForwardList, ConstructRange) { + std::forward_list<int> ref({ 1, 2, 7 }); + std::vector<IFLTestValue> storage(ref.begin(), ref.end()); + IntrusiveForwardList<IFLTestValue> ifl(storage.begin(), storage.end()); + ASSERT_LISTS_EQUAL(ref, ifl); +} + +TEST(IntrusiveForwardList, Assign) { + std::forward_list<int> ref1({ 2, 8, 5 }); + std::vector<IFLTestValue> storage1(ref1.begin(), ref1.end()); + IntrusiveForwardList<IFLTestValue> ifl; + ifl.assign(storage1.begin(), storage1.end()); + ASSERT_LISTS_EQUAL(ref1, ifl); + std::forward_list<int> ref2({ 7, 1, 3 }); + std::vector<IFLTestValue> storage2(ref2.begin(), ref2.end()); + ifl.assign(storage2.begin(), storage2.end()); + ASSERT_LISTS_EQUAL(ref2, ifl); +} + +TEST(IntrusiveForwardList, PushPop) { + IFLTestValue value3(3); + IFLTestValue value7(7); + std::forward_list<int> ref; + IntrusiveForwardList<IFLTestValue> ifl; + ASSERT_LISTS_EQUAL(ref, ifl); + ref.push_front(3); + ifl.push_front(value3); + ASSERT_LISTS_EQUAL(ref, ifl); + ASSERT_EQ(3, ifl.front()); + ref.push_front(7); + ifl.push_front(value7); + ASSERT_LISTS_EQUAL(ref, ifl); + ASSERT_EQ(7, ifl.front()); + ref.pop_front(); + ifl.pop_front(); + ASSERT_LISTS_EQUAL(ref, ifl); + ASSERT_EQ(3, ifl.front()); + ref.pop_front(); + ifl.pop_front(); + ASSERT_LISTS_EQUAL(ref, ifl); +} + +TEST(IntrusiveForwardList, InsertAfter1) { + IFLTestValue value4(4); + IFLTestValue value8(8); + IFLTestValue value5(5); + IFLTestValue value3(3); + std::forward_list<int> ref; + IntrusiveForwardList<IFLTestValue> ifl; + + auto ref_it = ref.insert_after(ref.before_begin(), 4); + auto ifl_it = ifl.insert_after(ifl.before_begin(), value4); + ASSERT_LISTS_EQUAL(ref, ifl); + ASSERT_EQ(*ref_it, *ifl_it); + CHECK(ref_it == ref.begin()); + ASSERT_TRUE(ifl_it == ifl.begin()); + + ref_it = ref.insert_after(ref.begin(), 8); + ifl_it = ifl.insert_after(ifl.begin(), value8); + ASSERT_LISTS_EQUAL(ref, ifl); + ASSERT_EQ(*ref_it, *ifl_it); + CHECK(ref_it != ref.end()); + ASSERT_TRUE(ifl_it != ifl.end()); + CHECK(++ref_it == ref.end()); + ASSERT_TRUE(++ifl_it == ifl.end()); + + ref_it = ref.insert_after(ref.begin(), 5); + ifl_it = ifl.insert_after(ifl.begin(), value5); + ASSERT_LISTS_EQUAL(ref, ifl); + ASSERT_EQ(*ref_it, *ifl_it); + + ref_it = ref.insert_after(ref_it, 3); + ifl_it = ifl.insert_after(ifl_it, value3); + ASSERT_LISTS_EQUAL(ref, ifl); + ASSERT_EQ(*ref_it, *ifl_it); +} + +TEST(IntrusiveForwardList, InsertAfter2) { + std::forward_list<int> ref; + IntrusiveForwardList<IFLTestValue> ifl; + + auto ref_it = ref.insert_after(ref.before_begin(), { 2, 8, 5 }); + std::vector<IFLTestValue> storage1({ { 2 }, { 8 }, { 5 } }); + auto ifl_it = ifl.insert_after(ifl.before_begin(), storage1.begin(), storage1.end()); + ASSERT_LISTS_EQUAL(ref, ifl); + ASSERT_EQ(*ref_it, *ifl_it); + + std::vector<IFLTestValue> storage2({ { 7 }, { 2 } }); + ref_it = ref.insert_after(ref.begin(), { 7, 2 }); + ifl_it = ifl.insert_after(ifl.begin(), storage2.begin(), storage2.end()); + ASSERT_LISTS_EQUAL(ref, ifl); + ASSERT_EQ(*ref_it, *ifl_it); + + std::vector<IFLTestValue> storage3({ { 1 }, { 3 }, { 4 }, { 9 } }); + ref_it = ref.begin(); + ifl_it = ifl.begin(); + std::advance(ref_it, std::distance(ref.begin(), ref.end()) - 1); + std::advance(ifl_it, std::distance(ifl.begin(), ifl.end()) - 1); + ref_it = ref.insert_after(ref_it, { 1, 3, 4, 9 }); + ifl_it = ifl.insert_after(ifl_it, storage3.begin(), storage3.end()); + ASSERT_LISTS_EQUAL(ref, ifl); +} + +TEST(IntrusiveForwardList, EraseAfter1) { + std::forward_list<int> ref({ 1, 2, 7, 4, 5 }); + std::vector<IFLTestValue> storage(ref.begin(), ref.end()); + IntrusiveForwardList<IFLTestValue> ifl(storage.begin(), storage.end()); + ASSERT_LISTS_EQUAL(ref, ifl); + CHECK_EQ(std::distance(ref.begin(), ref.end()), 5); + + auto ref_it = ref.begin(); + auto ifl_it = ifl.begin(); + std::advance(ref_it, 2); + std::advance(ifl_it, 2); + ref_it = ref.erase_after(ref_it); + ifl_it = ifl.erase_after(ifl_it); + ASSERT_LISTS_EQUAL(ref, ifl); + CHECK_EQ(std::distance(ref.begin(), ref.end()), 4); + CHECK(ref_it != ref.end()); + ASSERT_TRUE(ifl_it != ifl.end()); + CHECK(++ref_it == ref.end()); + ASSERT_TRUE(++ifl_it == ifl.end()); + + ref_it = ref.begin(); + ifl_it = ifl.begin(); + std::advance(ref_it, 2); + std::advance(ifl_it, 2); + ref_it = ref.erase_after(ref_it); + ifl_it = ifl.erase_after(ifl_it); + ASSERT_LISTS_EQUAL(ref, ifl); + CHECK_EQ(std::distance(ref.begin(), ref.end()), 3); + CHECK(ref_it == ref.end()); + ASSERT_TRUE(ifl_it == ifl.end()); + + ref_it = ref.erase_after(ref.begin()); + ifl_it = ifl.erase_after(ifl.begin()); + ASSERT_LISTS_EQUAL(ref, ifl); + CHECK_EQ(std::distance(ref.begin(), ref.end()), 2); + CHECK(ref_it != ref.end()); + ASSERT_TRUE(ifl_it != ifl.end()); + CHECK(++ref_it == ref.end()); + ASSERT_TRUE(++ifl_it == ifl.end()); + + ref_it = ref.erase_after(ref.before_begin()); + ifl_it = ifl.erase_after(ifl.before_begin()); + ASSERT_LISTS_EQUAL(ref, ifl); + CHECK_EQ(std::distance(ref.begin(), ref.end()), 1); + CHECK(ref_it == ref.begin()); + ASSERT_TRUE(ifl_it == ifl.begin()); + + ref_it = ref.erase_after(ref.before_begin()); + ifl_it = ifl.erase_after(ifl.before_begin()); + ASSERT_LISTS_EQUAL(ref, ifl); + CHECK_EQ(std::distance(ref.begin(), ref.end()), 0); + CHECK(ref_it == ref.begin()); + ASSERT_TRUE(ifl_it == ifl.begin()); +} + +TEST(IntrusiveForwardList, EraseAfter2) { + std::forward_list<int> ref({ 1, 2, 7, 4, 5, 3, 2, 8, 9 }); + std::vector<IFLTestValue> storage(ref.begin(), ref.end()); + IntrusiveForwardList<IFLTestValue> ifl(storage.begin(), storage.end()); + ASSERT_LISTS_EQUAL(ref, ifl); + CHECK_EQ(std::distance(ref.begin(), ref.end()), 9); + + auto ref_it = ref.begin(); + auto ifl_it = ifl.begin(); + std::advance(ref_it, 3); + std::advance(ifl_it, 3); + ref_it = ref.erase_after(ref.begin(), ref_it); + ifl_it = ifl.erase_after(ifl.begin(), ifl_it); + ASSERT_LISTS_EQUAL(ref, ifl); + ASSERT_EQ(std::distance(ref.begin(), ref_it), std::distance(ifl.begin(), ifl_it)); + CHECK_EQ(std::distance(ref.begin(), ref.end()), 7); + + ref_it = ref.erase_after(ref_it, ref.end()); + ifl_it = ifl.erase_after(ifl_it, ifl.end()); + ASSERT_LISTS_EQUAL(ref, ifl); + CHECK(ref_it == ref.end()); + ASSERT_TRUE(ifl_it == ifl.end()); + CHECK_EQ(std::distance(ref.begin(), ref.end()), 2); + + ref_it = ref.erase_after(ref.before_begin(), ref.end()); + ifl_it = ifl.erase_after(ifl.before_begin(), ifl.end()); + ASSERT_LISTS_EQUAL(ref, ifl); + CHECK(ref_it == ref.end()); + ASSERT_TRUE(ifl_it == ifl.end()); + CHECK_EQ(std::distance(ref.begin(), ref.end()), 0); +} + +TEST(IntrusiveForwardList, SwapClear) { + std::forward_list<int> ref1({ 1, 2, 7 }); + std::vector<IFLTestValue> storage1(ref1.begin(), ref1.end()); + IntrusiveForwardList<IFLTestValue> ifl1(storage1.begin(), storage1.end()); + std::forward_list<int> ref2({ 3, 8, 6 }); + std::vector<IFLTestValue> storage2(ref2.begin(), ref2.end()); + IntrusiveForwardList<IFLTestValue> ifl2(storage2.begin(), storage2.end()); + ASSERT_LISTS_EQUAL(ref1, ifl1); + ASSERT_LISTS_EQUAL(ref2, ifl2); + ref1.swap(ref2); + ifl1.swap(ifl2); + ASSERT_LISTS_EQUAL(ref1, ifl1); + ASSERT_LISTS_EQUAL(ref2, ifl2); + ref1.clear(); + ifl1.clear(); + ASSERT_LISTS_EQUAL(ref1, ifl1); + ASSERT_LISTS_EQUAL(ref2, ifl2); + swap(ref1, ref2); + swap(ifl1, ifl2); + ASSERT_LISTS_EQUAL(ref1, ifl1); + ASSERT_LISTS_EQUAL(ref2, ifl2); + ref1.clear(); + ifl1.clear(); + ASSERT_LISTS_EQUAL(ref1, ifl1); + ASSERT_LISTS_EQUAL(ref2, ifl2); +} + +TEST(IntrusiveForwardList, SpliceAfter) { + std::forward_list<int> ref1({ 3, 1, 2, 7, 4, 5, 4, 8, 7 }); + std::forward_list<int> ref2; + std::vector<IFLTestValue> storage(ref1.begin(), ref1.end()); + IntrusiveForwardList<IFLTestValue> ifl1(storage.begin(), storage.end()); + IntrusiveForwardList<IFLTestValue> ifl2; + ASSERT_LISTS_EQUAL(ref1, ifl1); + ASSERT_LISTS_EQUAL(ref2, ifl2); + + // Move everything to ref2/ifl2. + ref2.splice_after(ref2.before_begin(), ref1); + ifl2.splice_after(ifl2.before_begin(), ifl1); + ASSERT_LISTS_EQUAL(ref1, ifl1); + ASSERT_LISTS_EQUAL(ref2, ifl2); + + // Move first element (3) to ref1/ifl1. + ref1.splice_after(ref1.before_begin(), ref2, ref2.before_begin()); + ifl1.splice_after(ifl1.before_begin(), ifl2, ifl2.before_begin()); + ASSERT_LISTS_EQUAL(ref1, ifl1); + ASSERT_LISTS_EQUAL(ref2, ifl2); + + // Move second element (2) to ref1/ifl1 after the first element (3). + ref1.splice_after(ref1.begin(), ref2, ref2.begin()); + ifl1.splice_after(ifl1.begin(), ifl2, ifl2.begin()); + ASSERT_LISTS_EQUAL(ref1, ifl1); + ASSERT_LISTS_EQUAL(ref2, ifl2); + + // Move everything from ref2/ifl2 between the 2 elements now in ref1/ifl1. + ref1.splice_after(ref1.begin(), ref2); + ifl1.splice_after(ifl1.begin(), ifl2); + ASSERT_LISTS_EQUAL(ref1, ifl1); + ASSERT_LISTS_EQUAL(ref2, ifl2); + + std::forward_list<int> check({ 3, 1, 7, 4, 5, 4, 8, 7, 2 }); + ASSERT_LISTS_EQUAL(check, ifl1); + ASSERT_TRUE(ifl2.empty()); + + // Empty splice_after(). + ref2.splice_after( + ref2.before_begin(), ref1, ref1.before_begin(), ref1.begin()); + ifl2.splice_after(ifl2.before_begin(), ifl1, ifl1.before_begin(), ifl1.begin()); + ASSERT_LISTS_EQUAL(ref1, ifl1); + ASSERT_LISTS_EQUAL(ref2, ifl2); + + // Move { 1, 7 } to ref2/ifl2. + auto ref_it = ref1.begin(); + auto ifl_it = ifl1.begin(); + std::advance(ref_it, 3); + std::advance(ifl_it, 3); + ref2.splice_after(ref2.before_begin(), ref1, ref1.begin(), ref_it); + ifl2.splice_after(ifl2.before_begin(), ifl1, ifl1.begin(), ifl_it); + ASSERT_LISTS_EQUAL(ref1, ifl1); + ASSERT_LISTS_EQUAL(ref2, ifl2); + + // Move { 8, 7, 2 } to the beginning of ref1/ifl1. + ref_it = ref1.begin(); + ifl_it = ifl1.begin(); + std::advance(ref_it, 3); + std::advance(ifl_it, 3); + ref1.splice_after(ref1.before_begin(), ref1, ref_it, ref1.end()); + ifl1.splice_after(ifl1.before_begin(), ifl1, ifl_it, ifl1.end()); + ASSERT_LISTS_EQUAL(ref1, ifl1); + + check.assign({ 8, 7, 2, 3, 4, 5, 4 }); + ASSERT_LISTS_EQUAL(check, ifl1); + check.assign({ 1, 7 }); + ASSERT_LISTS_EQUAL(check, ifl2); + + // Move all but the first element to ref2/ifl2. + ref_it = ref2.begin(); + ifl_it = ifl2.begin(); + std::advance(ref_it, 1); + std::advance(ifl_it, 1); + ref2.splice_after(ref_it, ref1, ref1.begin(), ref1.end()); + ifl2.splice_after(ifl_it, ifl1, ifl1.begin(), ifl1.end()); + ASSERT_LISTS_EQUAL(ref1, ifl1); + ASSERT_LISTS_EQUAL(ref2, ifl2); + + check.assign({8}); + ASSERT_LISTS_EQUAL(check, ifl1); + + // Move the first element of ref1/ifl1 to the beginning of ref1/ifl1 (do nothing). + ref1.splice_after(ref1.before_begin(), ref1, ref1.before_begin()); + ifl1.splice_after(ifl1.before_begin(), ifl1, ifl1.before_begin()); + ASSERT_LISTS_EQUAL(ref1, ifl1); + ASSERT_LISTS_EQUAL(check, ifl1); + + // Move the first element of ref2/ifl2 after itself (do nothing). + ref1.splice_after(ref1.begin(), ref1, ref1.before_begin()); + ifl1.splice_after(ifl1.begin(), ifl1, ifl1.before_begin()); + ASSERT_LISTS_EQUAL(ref1, ifl1); + ASSERT_LISTS_EQUAL(check, ifl1); + + check.assign({ 1, 7, 7, 2, 3, 4, 5, 4 }); + ASSERT_LISTS_EQUAL(check, ifl2); + + // Move the first element of ref2/ifl2 to the beginning of ref2/ifl2 (do nothing). + ref2.splice_after(ref2.before_begin(), ref2, ref2.before_begin()); + ifl2.splice_after(ifl2.before_begin(), ifl2, ifl2.before_begin()); + ASSERT_LISTS_EQUAL(ref2, ifl2); + ASSERT_LISTS_EQUAL(check, ifl2); + + // Move the first element of ref2/ifl2 after itself (do nothing). + ref2.splice_after(ref2.begin(), ref2, ref2.before_begin()); + ifl2.splice_after(ifl2.begin(), ifl2, ifl2.before_begin()); + ASSERT_LISTS_EQUAL(ref2, ifl2); + ASSERT_LISTS_EQUAL(check, ifl2); +} + +TEST(IntrusiveForwardList, Remove) { + std::forward_list<int> ref({ 3, 1, 2, 7, 4, 5, 4, 8, 7 }); + std::vector<IFLTestValue> storage(ref.begin(), ref.end()); + IntrusiveForwardList<IFLTestValue> ifl(storage.begin(), storage.end()); + ASSERT_LISTS_EQUAL(ref, ifl); + ref.remove(1); + ifl.remove(1); + ASSERT_LISTS_EQUAL(ref, ifl); + ref.remove(4); + ifl.remove(4); + ASSERT_LISTS_EQUAL(ref, ifl); + auto odd = [](IFLTestValue value) { return (value.value & 1) != 0; }; // NOLINT(readability/braces) + ref.remove_if(odd); + ifl.remove_if(odd); + ASSERT_LISTS_EQUAL(ref, ifl); + auto all = [](IFLTestValue value ATTRIBUTE_UNUSED) { return true; }; // NOLINT(readability/braces) + ref.remove_if(all); + ifl.remove_if(all); + ASSERT_LISTS_EQUAL(ref, ifl); +} + +TEST(IntrusiveForwardList, Unique) { + std::forward_list<int> ref({ 3, 1, 1, 2, 3, 3, 7, 7, 4, 4, 5, 7 }); + std::vector<IFLTestValue> storage(ref.begin(), ref.end()); + IntrusiveForwardList<IFLTestValue> ifl(storage.begin(), storage.end()); + ASSERT_LISTS_EQUAL(ref, ifl); + ref.unique(); + ifl.unique(); + ASSERT_LISTS_EQUAL(ref, ifl); + std::forward_list<int> check({ 3, 1, 2, 3, 7, 4, 5, 7 }); + ASSERT_LISTS_EQUAL(check, ifl); + + auto bin_pred = [](IFLTestValue lhs, IFLTestValue rhs) { + return (lhs.value & ~1) == (rhs.value & ~1); + }; + ref.unique(bin_pred); + ifl.unique(bin_pred); + ASSERT_LISTS_EQUAL(ref, ifl); + check.assign({ 3, 1, 2, 7, 4, 7 }); + ASSERT_LISTS_EQUAL(check, ifl); +} + +TEST(IntrusiveForwardList, Merge) { + std::forward_list<int> ref1({ 1, 4, 8, 8, 12 }); + std::vector<IFLTestValue> storage1(ref1.begin(), ref1.end()); + IntrusiveForwardList<IFLTestValue> ifl1(storage1.begin(), storage1.end()); + std::forward_list<int> ref2({ 3, 5, 6, 7, 9 }); + std::vector<IFLTestValue> storage2(ref2.begin(), ref2.end()); + IntrusiveForwardList<IFLTestValue> ifl2(storage2.begin(), storage2.end()); + ASSERT_LISTS_EQUAL(ref1, ifl1); + ASSERT_LISTS_EQUAL(ref2, ifl2); + CHECK(std::is_sorted(ref1.begin(), ref1.end())); + CHECK(std::is_sorted(ref2.begin(), ref2.end())); + ref1.merge(ref2); + ifl1.merge(ifl2); + ASSERT_LISTS_EQUAL(ref1, ifl1); + ASSERT_LISTS_EQUAL(ref2, ifl2); + CHECK(ref2.empty()); + std::forward_list<int> check({ 1, 3, 4, 5, 6, 7, 8, 8, 9, 12 }); + ASSERT_LISTS_EQUAL(check, ifl1); +} + +TEST(IntrusiveForwardList, Sort1) { + std::forward_list<int> ref({ 2, 9, 8, 3, 7, 4, 1, 5, 3, 0 }); + std::vector<IFLTestValue> storage(ref.begin(), ref.end()); + IntrusiveForwardList<IFLTestValue> ifl(storage.begin(), storage.end()); + ASSERT_LISTS_EQUAL(ref, ifl); + CHECK(!std::is_sorted(ref.begin(), ref.end())); + ref.sort(); + ifl.sort(); + ASSERT_LISTS_EQUAL(ref, ifl); + std::forward_list<int> check({ 0, 1, 2, 3, 3, 4, 5, 7, 8, 9 }); + ASSERT_LISTS_EQUAL(check, ifl); +} + +TEST(IntrusiveForwardList, Sort2) { + std::forward_list<int> ref({ 2, 9, 8, 3, 7, 4, 1, 5, 3, 0 }); + std::vector<IFLTestValue> storage(ref.begin(), ref.end()); + IntrusiveForwardList<IFLTestValue> ifl(storage.begin(), storage.end()); + ASSERT_LISTS_EQUAL(ref, ifl); + auto cmp = [](IFLTestValue lhs, IFLTestValue rhs) { + return (lhs.value & ~1) < (rhs.value & ~1); + }; + CHECK(!std::is_sorted(ref.begin(), ref.end(), cmp)); + ref.sort(cmp); + ifl.sort(cmp); + ASSERT_LISTS_EQUAL(ref, ifl); + std::forward_list<int> check({ 1, 0, 2, 3, 3, 4, 5, 7, 9, 8 }); + ASSERT_LISTS_EQUAL(check, ifl); +} + +TEST(IntrusiveForwardList, Reverse) { + std::forward_list<int> ref({ 8, 3, 5, 4, 1, 3 }); + std::vector<IFLTestValue> storage(ref.begin(), ref.end()); + IntrusiveForwardList<IFLTestValue> ifl(storage.begin(), storage.end()); + ASSERT_LISTS_EQUAL(ref, ifl); + CHECK(!std::is_sorted(ref.begin(), ref.end())); + ref.reverse(); + ifl.reverse(); + ASSERT_LISTS_EQUAL(ref, ifl); + std::forward_list<int> check({ 3, 1, 4, 5, 3, 8 }); + ASSERT_LISTS_EQUAL(check, ifl); +} + +} // namespace art diff --git a/compiler/utils/managed_register.h b/compiler/utils/managed_register.h index 893daff719..46adb3f2d2 100644 --- a/compiler/utils/managed_register.h +++ b/compiler/utils/managed_register.h @@ -47,40 +47,40 @@ class ManagedRegister { // ManagedRegister is a value class. There exists no method to change the // internal state. We therefore allow a copy constructor and an // assignment-operator. - ManagedRegister(const ManagedRegister& other) : id_(other.id_) { } + constexpr ManagedRegister(const ManagedRegister& other) : id_(other.id_) { } ManagedRegister& operator=(const ManagedRegister& other) { id_ = other.id_; return *this; } - arm::ArmManagedRegister AsArm() const; - arm64::Arm64ManagedRegister AsArm64() const; - mips::MipsManagedRegister AsMips() const; - mips64::Mips64ManagedRegister AsMips64() const; - x86::X86ManagedRegister AsX86() const; - x86_64::X86_64ManagedRegister AsX86_64() const; + constexpr arm::ArmManagedRegister AsArm() const; + constexpr arm64::Arm64ManagedRegister AsArm64() const; + constexpr mips::MipsManagedRegister AsMips() const; + constexpr mips64::Mips64ManagedRegister AsMips64() const; + constexpr x86::X86ManagedRegister AsX86() const; + constexpr x86_64::X86_64ManagedRegister AsX86_64() const; // It is valid to invoke Equals on and with a NoRegister. - bool Equals(const ManagedRegister& other) const { + constexpr bool Equals(const ManagedRegister& other) const { return id_ == other.id_; } - bool IsNoRegister() const { + constexpr bool IsNoRegister() const { return id_ == kNoRegister; } - static ManagedRegister NoRegister() { + static constexpr ManagedRegister NoRegister() { return ManagedRegister(); } - int RegId() const { return id_; } - explicit ManagedRegister(int reg_id) : id_(reg_id) { } + constexpr int RegId() const { return id_; } + explicit constexpr ManagedRegister(int reg_id) : id_(reg_id) { } protected: static const int kNoRegister = -1; - ManagedRegister() : id_(kNoRegister) { } + constexpr ManagedRegister() : id_(kNoRegister) { } int id_; }; diff --git a/compiler/utils/mips/assembler_mips.cc b/compiler/utils/mips/assembler_mips.cc index a1798c0f70..9368301d07 100644 --- a/compiler/utils/mips/assembler_mips.cc +++ b/compiler/utils/mips/assembler_mips.cc @@ -2438,8 +2438,9 @@ static dwarf::Reg DWARFReg(Register reg) { constexpr size_t kFramePointerSize = 4; -void MipsAssembler::BuildFrame(size_t frame_size, ManagedRegister method_reg, - const std::vector<ManagedRegister>& callee_save_regs, +void MipsAssembler::BuildFrame(size_t frame_size, + ManagedRegister method_reg, + ArrayRef<const ManagedRegister> callee_save_regs, const ManagedRegisterEntrySpills& entry_spills) { CHECK_ALIGNED(frame_size, kStackAlignment); DCHECK(!overwriting_); @@ -2453,7 +2454,7 @@ void MipsAssembler::BuildFrame(size_t frame_size, ManagedRegister method_reg, cfi_.RelOffset(DWARFReg(RA), stack_offset); for (int i = callee_save_regs.size() - 1; i >= 0; --i) { stack_offset -= kFramePointerSize; - Register reg = callee_save_regs.at(i).AsMips().AsCoreRegister(); + Register reg = callee_save_regs[i].AsMips().AsCoreRegister(); StoreToOffset(kStoreWord, reg, SP, stack_offset); cfi_.RelOffset(DWARFReg(reg), stack_offset); } @@ -2482,7 +2483,7 @@ void MipsAssembler::BuildFrame(size_t frame_size, ManagedRegister method_reg, } void MipsAssembler::RemoveFrame(size_t frame_size, - const std::vector<ManagedRegister>& callee_save_regs) { + ArrayRef<const ManagedRegister> callee_save_regs) { CHECK_ALIGNED(frame_size, kStackAlignment); DCHECK(!overwriting_); cfi_.RememberState(); @@ -2490,7 +2491,7 @@ void MipsAssembler::RemoveFrame(size_t frame_size, // Pop callee saves and return address. int stack_offset = frame_size - (callee_save_regs.size() * kFramePointerSize) - kFramePointerSize; for (size_t i = 0; i < callee_save_regs.size(); ++i) { - Register reg = callee_save_regs.at(i).AsMips().AsCoreRegister(); + Register reg = callee_save_regs[i].AsMips().AsCoreRegister(); LoadFromOffset(kLoadWord, reg, SP, stack_offset); cfi_.Restore(DWARFReg(reg)); stack_offset += kFramePointerSize; diff --git a/compiler/utils/mips/assembler_mips.h b/compiler/utils/mips/assembler_mips.h index ecb67bd053..d5e62853f4 100644 --- a/compiler/utils/mips/assembler_mips.h +++ b/compiler/utils/mips/assembler_mips.h @@ -414,11 +414,11 @@ class MipsAssembler FINAL : public Assembler { // Emit code that will create an activation on the stack. void BuildFrame(size_t frame_size, ManagedRegister method_reg, - const std::vector<ManagedRegister>& callee_save_regs, + ArrayRef<const ManagedRegister> callee_save_regs, const ManagedRegisterEntrySpills& entry_spills) OVERRIDE; // Emit code that will remove an activation from the stack. - void RemoveFrame(size_t frame_size, const std::vector<ManagedRegister>& callee_save_regs) + void RemoveFrame(size_t frame_size, ArrayRef<const ManagedRegister> callee_save_regs) OVERRIDE; void IncreaseFrameSize(size_t adjust) OVERRIDE; diff --git a/compiler/utils/mips/assembler_mips_test.cc b/compiler/utils/mips/assembler_mips_test.cc index cec43badf8..56e58849c9 100644 --- a/compiler/utils/mips/assembler_mips_test.cc +++ b/compiler/utils/mips/assembler_mips_test.cc @@ -561,6 +561,14 @@ TEST_F(AssemblerMIPSTest, NegD) { DriverStr(RepeatFF(&mips::MipsAssembler::NegD, "neg.d ${reg1}, ${reg2}"), "NegD"); } +TEST_F(AssemblerMIPSTest, FloorWS) { + DriverStr(RepeatFF(&mips::MipsAssembler::FloorWS, "floor.w.s ${reg1}, ${reg2}"), "floor.w.s"); +} + +TEST_F(AssemblerMIPSTest, FloorWD) { + DriverStr(RepeatFF(&mips::MipsAssembler::FloorWD, "floor.w.d ${reg1}, ${reg2}"), "floor.w.d"); +} + TEST_F(AssemblerMIPSTest, CunS) { DriverStr(RepeatIbFF(&mips::MipsAssembler::CunS, 3, "c.un.s $fcc{imm}, ${reg1}, ${reg2}"), "CunS"); diff --git a/compiler/utils/mips/managed_register_mips.h b/compiler/utils/mips/managed_register_mips.h index 5e7ed11f51..66204e70e3 100644 --- a/compiler/utils/mips/managed_register_mips.h +++ b/compiler/utils/mips/managed_register_mips.h @@ -87,70 +87,70 @@ const int kNumberOfAllocIds = // There is a one-to-one mapping between ManagedRegister and register id. class MipsManagedRegister : public ManagedRegister { public: - Register AsCoreRegister() const { + constexpr Register AsCoreRegister() const { CHECK(IsCoreRegister()); return static_cast<Register>(id_); } - FRegister AsFRegister() const { + constexpr FRegister AsFRegister() const { CHECK(IsFRegister()); return static_cast<FRegister>(id_ - kNumberOfCoreRegIds); } - DRegister AsDRegister() const { + constexpr DRegister AsDRegister() const { CHECK(IsDRegister()); return static_cast<DRegister>(id_ - kNumberOfCoreRegIds - kNumberOfFRegIds); } - FRegister AsOverlappingDRegisterLow() const { + constexpr FRegister AsOverlappingDRegisterLow() const { CHECK(IsOverlappingDRegister()); DRegister d_reg = AsDRegister(); return static_cast<FRegister>(d_reg * 2); } - FRegister AsOverlappingDRegisterHigh() const { + constexpr FRegister AsOverlappingDRegisterHigh() const { CHECK(IsOverlappingDRegister()); DRegister d_reg = AsDRegister(); return static_cast<FRegister>(d_reg * 2 + 1); } - Register AsRegisterPairLow() const { + constexpr Register AsRegisterPairLow() const { CHECK(IsRegisterPair()); // Appropriate mapping of register ids allows to use AllocIdLow(). return FromRegId(AllocIdLow()).AsCoreRegister(); } - Register AsRegisterPairHigh() const { + constexpr Register AsRegisterPairHigh() const { CHECK(IsRegisterPair()); // Appropriate mapping of register ids allows to use AllocIdHigh(). return FromRegId(AllocIdHigh()).AsCoreRegister(); } - bool IsCoreRegister() const { + constexpr bool IsCoreRegister() const { CHECK(IsValidManagedRegister()); return (0 <= id_) && (id_ < kNumberOfCoreRegIds); } - bool IsFRegister() const { + constexpr bool IsFRegister() const { CHECK(IsValidManagedRegister()); const int test = id_ - kNumberOfCoreRegIds; return (0 <= test) && (test < kNumberOfFRegIds); } - bool IsDRegister() const { + constexpr bool IsDRegister() const { CHECK(IsValidManagedRegister()); const int test = id_ - (kNumberOfCoreRegIds + kNumberOfFRegIds); return (0 <= test) && (test < kNumberOfDRegIds); } // Returns true if this DRegister overlaps FRegisters. - bool IsOverlappingDRegister() const { + constexpr bool IsOverlappingDRegister() const { CHECK(IsValidManagedRegister()); const int test = id_ - (kNumberOfCoreRegIds + kNumberOfFRegIds); return (0 <= test) && (test < kNumberOfOverlappingDRegIds); } - bool IsRegisterPair() const { + constexpr bool IsRegisterPair() const { CHECK(IsValidManagedRegister()); const int test = id_ - (kNumberOfCoreRegIds + kNumberOfFRegIds + kNumberOfDRegIds); @@ -164,32 +164,32 @@ class MipsManagedRegister : public ManagedRegister { // then false is returned. bool Overlaps(const MipsManagedRegister& other) const; - static MipsManagedRegister FromCoreRegister(Register r) { + static constexpr MipsManagedRegister FromCoreRegister(Register r) { CHECK_NE(r, kNoRegister); return FromRegId(r); } - static MipsManagedRegister FromFRegister(FRegister r) { + static constexpr MipsManagedRegister FromFRegister(FRegister r) { CHECK_NE(r, kNoFRegister); return FromRegId(r + kNumberOfCoreRegIds); } - static MipsManagedRegister FromDRegister(DRegister r) { + static constexpr MipsManagedRegister FromDRegister(DRegister r) { CHECK_NE(r, kNoDRegister); return FromRegId(r + kNumberOfCoreRegIds + kNumberOfFRegIds); } - static MipsManagedRegister FromRegisterPair(RegisterPair r) { + static constexpr MipsManagedRegister FromRegisterPair(RegisterPair r) { CHECK_NE(r, kNoRegisterPair); return FromRegId(r + (kNumberOfCoreRegIds + kNumberOfFRegIds + kNumberOfDRegIds)); } private: - bool IsValidManagedRegister() const { + constexpr bool IsValidManagedRegister() const { return (0 <= id_) && (id_ < kNumberOfRegIds); } - int RegId() const { + constexpr int RegId() const { CHECK(!IsNoRegister()); return id_; } @@ -205,9 +205,9 @@ class MipsManagedRegister : public ManagedRegister { friend class ManagedRegister; - explicit MipsManagedRegister(int reg_id) : ManagedRegister(reg_id) {} + explicit constexpr MipsManagedRegister(int reg_id) : ManagedRegister(reg_id) {} - static MipsManagedRegister FromRegId(int reg_id) { + static constexpr MipsManagedRegister FromRegId(int reg_id) { MipsManagedRegister reg(reg_id); CHECK(reg.IsValidManagedRegister()); return reg; @@ -218,7 +218,7 @@ std::ostream& operator<<(std::ostream& os, const MipsManagedRegister& reg); } // namespace mips -inline mips::MipsManagedRegister ManagedRegister::AsMips() const { +constexpr inline mips::MipsManagedRegister ManagedRegister::AsMips() const { mips::MipsManagedRegister reg(id_); CHECK(reg.IsNoRegister() || reg.IsValidManagedRegister()); return reg; diff --git a/compiler/utils/mips64/assembler_mips64.cc b/compiler/utils/mips64/assembler_mips64.cc index ab480cafd5..447ede5166 100644 --- a/compiler/utils/mips64/assembler_mips64.cc +++ b/compiler/utils/mips64/assembler_mips64.cc @@ -1977,8 +1977,9 @@ static dwarf::Reg DWARFReg(GpuRegister reg) { constexpr size_t kFramePointerSize = 8; -void Mips64Assembler::BuildFrame(size_t frame_size, ManagedRegister method_reg, - const std::vector<ManagedRegister>& callee_save_regs, +void Mips64Assembler::BuildFrame(size_t frame_size, + ManagedRegister method_reg, + ArrayRef<const ManagedRegister> callee_save_regs, const ManagedRegisterEntrySpills& entry_spills) { CHECK_ALIGNED(frame_size, kStackAlignment); DCHECK(!overwriting_); @@ -1992,7 +1993,7 @@ void Mips64Assembler::BuildFrame(size_t frame_size, ManagedRegister method_reg, cfi_.RelOffset(DWARFReg(RA), stack_offset); for (int i = callee_save_regs.size() - 1; i >= 0; --i) { stack_offset -= kFramePointerSize; - GpuRegister reg = callee_save_regs.at(i).AsMips64().AsGpuRegister(); + GpuRegister reg = callee_save_regs[i].AsMips64().AsGpuRegister(); StoreToOffset(kStoreDoubleword, reg, SP, stack_offset); cfi_.RelOffset(DWARFReg(reg), stack_offset); } @@ -2003,7 +2004,7 @@ void Mips64Assembler::BuildFrame(size_t frame_size, ManagedRegister method_reg, // Write out entry spills. int32_t offset = frame_size + kFramePointerSize; for (size_t i = 0; i < entry_spills.size(); ++i) { - Mips64ManagedRegister reg = entry_spills.at(i).AsMips64(); + Mips64ManagedRegister reg = entry_spills[i].AsMips64(); ManagedRegisterSpill spill = entry_spills.at(i); int32_t size = spill.getSize(); if (reg.IsNoRegister()) { @@ -2022,7 +2023,7 @@ void Mips64Assembler::BuildFrame(size_t frame_size, ManagedRegister method_reg, } void Mips64Assembler::RemoveFrame(size_t frame_size, - const std::vector<ManagedRegister>& callee_save_regs) { + ArrayRef<const ManagedRegister> callee_save_regs) { CHECK_ALIGNED(frame_size, kStackAlignment); DCHECK(!overwriting_); cfi_.RememberState(); @@ -2030,7 +2031,7 @@ void Mips64Assembler::RemoveFrame(size_t frame_size, // Pop callee saves and return address int stack_offset = frame_size - (callee_save_regs.size() * kFramePointerSize) - kFramePointerSize; for (size_t i = 0; i < callee_save_regs.size(); ++i) { - GpuRegister reg = callee_save_regs.at(i).AsMips64().AsGpuRegister(); + GpuRegister reg = callee_save_regs[i].AsMips64().AsGpuRegister(); LoadFromOffset(kLoadDoubleword, reg, SP, stack_offset); cfi_.Restore(DWARFReg(reg)); stack_offset += kFramePointerSize; diff --git a/compiler/utils/mips64/assembler_mips64.h b/compiler/utils/mips64/assembler_mips64.h index 8acc38ac82..0cd07089d0 100644 --- a/compiler/utils/mips64/assembler_mips64.h +++ b/compiler/utils/mips64/assembler_mips64.h @@ -365,13 +365,13 @@ class Mips64Assembler FINAL : public Assembler { // // Emit code that will create an activation on the stack. - void BuildFrame(size_t frame_size, ManagedRegister method_reg, - const std::vector<ManagedRegister>& callee_save_regs, + void BuildFrame(size_t frame_size, + ManagedRegister method_reg, + ArrayRef<const ManagedRegister> callee_save_regs, const ManagedRegisterEntrySpills& entry_spills) OVERRIDE; // Emit code that will remove an activation from the stack. - void RemoveFrame(size_t frame_size, - const std::vector<ManagedRegister>& callee_save_regs) OVERRIDE; + void RemoveFrame(size_t frame_size, ArrayRef<const ManagedRegister> callee_save_regs) OVERRIDE; void IncreaseFrameSize(size_t adjust) OVERRIDE; void DecreaseFrameSize(size_t adjust) OVERRIDE; diff --git a/compiler/utils/mips64/managed_register_mips64.h b/compiler/utils/mips64/managed_register_mips64.h index 1d36128a09..c9f95569cf 100644 --- a/compiler/utils/mips64/managed_register_mips64.h +++ b/compiler/utils/mips64/managed_register_mips64.h @@ -39,22 +39,22 @@ const int kNumberOfAllocIds = kNumberOfGpuAllocIds + kNumberOfFpuAllocIds; // There is a one-to-one mapping between ManagedRegister and register id. class Mips64ManagedRegister : public ManagedRegister { public: - GpuRegister AsGpuRegister() const { + constexpr GpuRegister AsGpuRegister() const { CHECK(IsGpuRegister()); return static_cast<GpuRegister>(id_); } - FpuRegister AsFpuRegister() const { + constexpr FpuRegister AsFpuRegister() const { CHECK(IsFpuRegister()); return static_cast<FpuRegister>(id_ - kNumberOfGpuRegIds); } - bool IsGpuRegister() const { + constexpr bool IsGpuRegister() const { CHECK(IsValidManagedRegister()); return (0 <= id_) && (id_ < kNumberOfGpuRegIds); } - bool IsFpuRegister() const { + constexpr bool IsFpuRegister() const { CHECK(IsValidManagedRegister()); const int test = id_ - kNumberOfGpuRegIds; return (0 <= test) && (test < kNumberOfFpuRegIds); @@ -67,22 +67,22 @@ class Mips64ManagedRegister : public ManagedRegister { // then false is returned. bool Overlaps(const Mips64ManagedRegister& other) const; - static Mips64ManagedRegister FromGpuRegister(GpuRegister r) { + static constexpr Mips64ManagedRegister FromGpuRegister(GpuRegister r) { CHECK_NE(r, kNoGpuRegister); return FromRegId(r); } - static Mips64ManagedRegister FromFpuRegister(FpuRegister r) { + static constexpr Mips64ManagedRegister FromFpuRegister(FpuRegister r) { CHECK_NE(r, kNoFpuRegister); return FromRegId(r + kNumberOfGpuRegIds); } private: - bool IsValidManagedRegister() const { + constexpr bool IsValidManagedRegister() const { return (0 <= id_) && (id_ < kNumberOfRegIds); } - int RegId() const { + constexpr int RegId() const { CHECK(!IsNoRegister()); return id_; } @@ -98,9 +98,9 @@ class Mips64ManagedRegister : public ManagedRegister { friend class ManagedRegister; - explicit Mips64ManagedRegister(int reg_id) : ManagedRegister(reg_id) {} + explicit constexpr Mips64ManagedRegister(int reg_id) : ManagedRegister(reg_id) {} - static Mips64ManagedRegister FromRegId(int reg_id) { + static constexpr Mips64ManagedRegister FromRegId(int reg_id) { Mips64ManagedRegister reg(reg_id); CHECK(reg.IsValidManagedRegister()); return reg; @@ -111,7 +111,7 @@ std::ostream& operator<<(std::ostream& os, const Mips64ManagedRegister& reg); } // namespace mips64 -inline mips64::Mips64ManagedRegister ManagedRegister::AsMips64() const { +constexpr inline mips64::Mips64ManagedRegister ManagedRegister::AsMips64() const { mips64::Mips64ManagedRegister reg(id_); CHECK(reg.IsNoRegister() || reg.IsValidManagedRegister()); return reg; diff --git a/compiler/utils/transform_array_ref.h b/compiler/utils/transform_array_ref.h new file mode 100644 index 0000000000..6297b88e69 --- /dev/null +++ b/compiler/utils/transform_array_ref.h @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef ART_COMPILER_UTILS_TRANSFORM_ARRAY_REF_H_ +#define ART_COMPILER_UTILS_TRANSFORM_ARRAY_REF_H_ + +#include <type_traits> + +#include "utils/array_ref.h" +#include "utils/transform_iterator.h" + +namespace art { + +/** + * @brief An ArrayRef<> wrapper that uses a transformation function for element access. + */ +template <typename BaseType, typename Function> +class TransformArrayRef { + private: + using Iter = TransformIterator<typename ArrayRef<BaseType>::iterator, Function>; + + // The Function may take a non-const reference, so const_iterator may not exist. + using FallbackConstIter = std::iterator<std::random_access_iterator_tag, void, void, void, void>; + using PreferredConstIter = + TransformIterator<typename ArrayRef<BaseType>::const_iterator, Function>; + template <typename F, typename = typename std::result_of<F(const BaseType&)>::type> + static PreferredConstIter ConstIterHelper(int&); + template <typename F> + static FallbackConstIter ConstIterHelper(const int&); + + using ConstIter = decltype(ConstIterHelper<Function>(*reinterpret_cast<int*>(0))); + + public: + using value_type = typename Iter::value_type; + using reference = typename Iter::reference; + using const_reference = typename ConstIter::reference; + using pointer = typename Iter::pointer; + using const_pointer = typename ConstIter::pointer; + using iterator = Iter; + using const_iterator = typename std::conditional< + std::is_same<ConstIter, FallbackConstIter>::value, + void, + ConstIter>::type; + using reverse_iterator = std::reverse_iterator<Iter>; + using const_reverse_iterator = typename std::conditional< + std::is_same<ConstIter, FallbackConstIter>::value, + void, + std::reverse_iterator<ConstIter>>::type; + using difference_type = typename ArrayRef<BaseType>::difference_type; + using size_type = typename ArrayRef<BaseType>::size_type; + + // Constructors. + + TransformArrayRef(const TransformArrayRef& other) = default; + + template <typename OtherBT> + TransformArrayRef(const ArrayRef<OtherBT>& base, Function fn) + : data_(base, fn) { } + + // Assignment operators. + + TransformArrayRef& operator=(const TransformArrayRef& other) = default; + + template <typename OtherBT, + typename = typename std::enable_if<std::is_same<BaseType, const OtherBT>::value>::type> + TransformArrayRef& operator=(const TransformArrayRef<OtherBT, Function>& other) { + return *this = TransformArrayRef(other.base(), other.GetFunction()); + } + + // Destructor. + ~TransformArrayRef() = default; + + // Iterators. + iterator begin() { return MakeIterator(base().begin()); } + const_iterator begin() const { return MakeIterator(base().cbegin()); } + const_iterator cbegin() const { return MakeIterator(base().cbegin()); } + iterator end() { return MakeIterator(base().end()); } + const_iterator end() const { MakeIterator(base().cend()); } + const_iterator cend() const { return MakeIterator(base().cend()); } + reverse_iterator rbegin() { return reverse_iterator(end()); } + const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } + const_reverse_iterator crbegin() const { return const_reverse_iterator(cend()); } + reverse_iterator rend() { return reverse_iterator(begin()); } + const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } + const_reverse_iterator crend() const { return const_reverse_iterator(cbegin()); } + + // Size. + size_type size() const { return base().size(); } + bool empty() const { return base().empty(); } + + // Element access. NOTE: Not providing data(). + + reference operator[](size_type n) { return GetFunction()(base()[n]); } + const_reference operator[](size_type n) const { return GetFunction()(base()[n]); } + + reference front() { return GetFunction()(base().front()); } + const_reference front() const { return GetFunction()(base().front()); } + + reference back() { return GetFunction()(base().back()); } + const_reference back() const { return GetFunction()(base().back()); } + + TransformArrayRef SubArray(size_type pos) { + return TransformArrayRef(base().subarray(pos), GetFunction()); + } + TransformArrayRef SubArray(size_type pos) const { + return TransformArrayRef(base().subarray(pos), GetFunction()); + } + TransformArrayRef SubArray(size_type pos, size_type length) const { + return TransformArrayRef(base().subarray(pos, length), GetFunction()); + } + + // Retrieve the base ArrayRef<>. + ArrayRef<BaseType> base() { + return data_.base_; + } + ArrayRef<const BaseType> base() const { + return ArrayRef<const BaseType>(data_.base_); + } + + private: + // Allow EBO for state-less Function. + struct Data : Function { + public: + Data(ArrayRef<BaseType> base, Function fn) : Function(fn), base_(base) { } + + ArrayRef<BaseType> base_; + }; + + const Function& GetFunction() const { + return static_cast<const Function&>(data_); + } + + template <typename BaseIterator> + auto MakeIterator(BaseIterator base) const { + return MakeTransformIterator(base, GetFunction()); + } + + Data data_; +}; + +template <typename BaseType, typename Function> +bool operator==(const TransformArrayRef<BaseType, Function>& lhs, + const TransformArrayRef<BaseType, Function>& rhs) { + return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin()); +} + +template <typename BaseType, typename Function> +bool operator!=(const TransformArrayRef<BaseType, Function>& lhs, + const TransformArrayRef<BaseType, Function>& rhs) { + return !(lhs == rhs); +} + +template <typename ValueType, typename Function> +TransformArrayRef<ValueType, Function> MakeTransformArrayRef( + ArrayRef<ValueType> container, Function f) { + return TransformArrayRef<ValueType, Function>(container, f); +} + +template <typename Container, typename Function> +TransformArrayRef<typename Container::value_type, Function> MakeTransformArrayRef( + Container& container, Function f) { + return TransformArrayRef<typename Container::value_type, Function>( + ArrayRef<typename Container::value_type>(container.data(), container.size()), f); +} + +template <typename Container, typename Function> +TransformArrayRef<const typename Container::value_type, Function> MakeTransformArrayRef( + const Container& container, Function f) { + return TransformArrayRef<const typename Container::value_type, Function>( + ArrayRef<const typename Container::value_type>(container.data(), container.size()), f); +} + +} // namespace art + +#endif // ART_COMPILER_UTILS_TRANSFORM_ARRAY_REF_H_ diff --git a/compiler/utils/transform_array_ref_test.cc b/compiler/utils/transform_array_ref_test.cc new file mode 100644 index 0000000000..2593fade2f --- /dev/null +++ b/compiler/utils/transform_array_ref_test.cc @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2016 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 <algorithm> +#include <vector> + +#include "gtest/gtest.h" + +#include "utils/transform_array_ref.h" + +namespace art { + +namespace { // anonymous namespace + +struct ValueHolder { + // Deliberately not explicit. + ValueHolder(int v) : value(v) { } // NOLINT + int value; +}; + +ATTRIBUTE_UNUSED bool operator==(const ValueHolder& lhs, const ValueHolder& rhs) { + return lhs.value == rhs.value; +} + +} // anonymous namespace + +TEST(TransformArrayRef, ConstRefAdd1) { + auto add1 = [](const ValueHolder& h) { return h.value + 1; }; // NOLINT [readability/braces] + std::vector<ValueHolder> input({ 7, 6, 4, 0 }); + std::vector<int> output; + + auto taref = MakeTransformArrayRef(input, add1); + using TarefIter = decltype(taref)::iterator; + using ConstTarefIter = decltype(taref)::const_iterator; + static_assert(std::is_same<int, decltype(taref)::value_type>::value, "value_type"); + static_assert(std::is_same<TarefIter, decltype(taref)::pointer>::value, "pointer"); + static_assert(std::is_same<int, decltype(taref)::reference>::value, "reference"); + static_assert(std::is_same<ConstTarefIter, decltype(taref)::const_pointer>::value, + "const_pointer"); + static_assert(std::is_same<int, decltype(taref)::const_reference>::value, "const_reference"); + + std::copy(taref.begin(), taref.end(), std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 8, 7, 5, 1 }), output); + output.clear(); + + std::copy(taref.cbegin(), taref.cend(), std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 8, 7, 5, 1 }), output); + output.clear(); + + std::copy(taref.rbegin(), taref.rend(), std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 1, 5, 7, 8 }), output); + output.clear(); + + std::copy(taref.crbegin(), taref.crend(), std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 1, 5, 7, 8 }), output); + output.clear(); + + ASSERT_EQ(input.size(), taref.size()); + ASSERT_EQ(input.empty(), taref.empty()); + ASSERT_EQ(input.front().value + 1, taref.front()); + ASSERT_EQ(input.back().value + 1, taref.back()); + + for (size_t i = 0; i != input.size(); ++i) { + ASSERT_EQ(input[i].value + 1, taref[i]); + } +} + +TEST(TransformArrayRef, NonConstRefSub1) { + auto sub1 = [](ValueHolder& h) { return h.value - 1; }; // NOLINT [readability/braces] + std::vector<ValueHolder> input({ 4, 4, 5, 7, 10 }); + std::vector<int> output; + + auto taref = MakeTransformArrayRef(input, sub1); + using TarefIter = decltype(taref)::iterator; + static_assert(std::is_same<void, decltype(taref)::const_iterator>::value, "const_iterator"); + static_assert(std::is_same<int, decltype(taref)::value_type>::value, "value_type"); + static_assert(std::is_same<TarefIter, decltype(taref)::pointer>::value, "pointer"); + static_assert(std::is_same<int, decltype(taref)::reference>::value, "reference"); + static_assert(std::is_same<void, decltype(taref)::const_pointer>::value, "const_pointer"); + static_assert(std::is_same<void, decltype(taref)::const_reference>::value, "const_reference"); + + std::copy(taref.begin(), taref.end(), std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 3, 3, 4, 6, 9 }), output); + output.clear(); + + std::copy(taref.rbegin(), taref.rend(), std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 9, 6, 4, 3, 3 }), output); + output.clear(); + + ASSERT_EQ(input.size(), taref.size()); + ASSERT_EQ(input.empty(), taref.empty()); + ASSERT_EQ(input.front().value - 1, taref.front()); + ASSERT_EQ(input.back().value - 1, taref.back()); + + for (size_t i = 0; i != input.size(); ++i) { + ASSERT_EQ(input[i].value - 1, taref[i]); + } +} + +TEST(TransformArrayRef, ConstAndNonConstRef) { + struct Ref { + int& operator()(ValueHolder& h) const { return h.value; } + const int& operator()(const ValueHolder& h) const { return h.value; } + }; + Ref ref; + std::vector<ValueHolder> input({ 1, 0, 1, 0, 3, 1 }); + std::vector<int> output; + + auto taref = MakeTransformArrayRef(input, ref); + static_assert(std::is_same<int, decltype(taref)::value_type>::value, "value_type"); + static_assert(std::is_same<int*, decltype(taref)::pointer>::value, "pointer"); + static_assert(std::is_same<int&, decltype(taref)::reference>::value, "reference"); + static_assert(std::is_same<const int*, decltype(taref)::const_pointer>::value, "const_pointer"); + static_assert(std::is_same<const int&, decltype(taref)::const_reference>::value, + "const_reference"); + + std::copy(taref.begin(), taref.end(), std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 1, 0, 1, 0, 3, 1 }), output); + output.clear(); + + std::copy(taref.cbegin(), taref.cend(), std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 1, 0, 1, 0, 3, 1 }), output); + output.clear(); + + std::copy(taref.rbegin(), taref.rend(), std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 1, 3, 0, 1, 0, 1 }), output); + output.clear(); + + std::copy(taref.crbegin(), taref.crend(), std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 1, 3, 0, 1, 0, 1 }), output); + output.clear(); + + ASSERT_EQ(input.size(), taref.size()); + ASSERT_EQ(input.empty(), taref.empty()); + ASSERT_EQ(input.front().value, taref.front()); + ASSERT_EQ(input.back().value, taref.back()); + + for (size_t i = 0; i != input.size(); ++i) { + ASSERT_EQ(input[i].value, taref[i]); + } + + // Test writing through the transform iterator. + std::vector<int> transform_input({ 24, 37, 11, 71 }); + std::vector<ValueHolder> transformed(transform_input.size(), 0); + taref = MakeTransformArrayRef(transformed, ref); + for (size_t i = 0; i != transform_input.size(); ++i) { + taref[i] = transform_input[i]; + } + ASSERT_EQ(std::vector<ValueHolder>({ 24, 37, 11, 71 }), transformed); +} + +} // namespace art diff --git a/compiler/utils/transform_iterator.h b/compiler/utils/transform_iterator.h new file mode 100644 index 0000000000..f0769d4800 --- /dev/null +++ b/compiler/utils/transform_iterator.h @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef ART_COMPILER_UTILS_TRANSFORM_ITERATOR_H_ +#define ART_COMPILER_UTILS_TRANSFORM_ITERATOR_H_ + +#include <iterator> +#include <type_traits> + +#include "base/iteration_range.h" + +namespace art { + +// The transform iterator transforms values from the base iterator with a given +// transformation function. It can serve as a replacement for std::transform(), i.e. +// std::copy(MakeTransformIterator(begin, f), MakeTransformIterator(end, f), out) +// is equivalent to +// std::transform(begin, end, f) +// If the function returns an l-value reference or a wrapper that supports assignment, +// the TransformIterator can be used also as an output iterator, i.e. +// std::copy(begin, end, MakeTransformIterator(out, f)) +// is equivalent to +// for (auto it = begin; it != end; ++it) { +// f(*out++) = *it; +// } +template <typename BaseIterator, typename Function> +class TransformIterator { + private: + static_assert(std::is_base_of< + std::input_iterator_tag, + typename std::iterator_traits<BaseIterator>::iterator_category>::value, + "Transform iterator base must be an input iterator."); + + using InputType = + typename std::conditional< + std::is_same<void, typename std::iterator_traits<BaseIterator>::reference>::value, + typename std::iterator_traits<BaseIterator>::value_type, + typename std::iterator_traits<BaseIterator>::reference>::type; + using ResultType = typename std::result_of<Function(InputType)>::type; + + public: + using iterator_category = typename std::iterator_traits<BaseIterator>::iterator_category; + using value_type = + typename std::remove_const<typename std::remove_reference<ResultType>::type>::type; + using difference_type = typename std::iterator_traits<BaseIterator>::difference_type; + using pointer = typename std::conditional< + std::is_reference<ResultType>::value, + typename std::add_pointer<typename std::remove_reference<ResultType>::type>::type, + TransformIterator>::type; + using reference = ResultType; + + TransformIterator(BaseIterator base, Function fn) + : data_(base, fn) { } + + template <typename OtherBI> + TransformIterator(const TransformIterator<OtherBI, Function>& other) + : data_(other.base(), other.GetFunction()) { + } + + TransformIterator& operator++() { + ++data_.base_; + return *this; + } + + TransformIterator& operator++(int) { + TransformIterator tmp(*this); + ++*this; + return tmp; + } + + TransformIterator& operator--() { + static_assert( + std::is_base_of<std::bidirectional_iterator_tag, + typename std::iterator_traits<BaseIterator>::iterator_category>::value, + "BaseIterator must be bidirectional iterator to use operator--()"); + --data_.base_; + return *this; + } + + TransformIterator& operator--(int) { + TransformIterator tmp(*this); + --*this; + return tmp; + } + + reference operator*() const { + return GetFunction()(*base()); + } + + reference operator[](difference_type n) const { + static_assert( + std::is_base_of<std::random_access_iterator_tag, + typename std::iterator_traits<BaseIterator>::iterator_category>::value, + "BaseIterator must be random access iterator to use operator[]"); + return GetFunction()(base()[n]); + } + + TransformIterator operator+(difference_type n) const { + static_assert( + std::is_base_of<std::random_access_iterator_tag, + typename std::iterator_traits<BaseIterator>::iterator_category>::value, + "BaseIterator must be random access iterator to use operator+"); + return TransformIterator(base() + n, GetFunction()); + } + + TransformIterator operator-(difference_type n) const { + static_assert( + std::is_base_of<std::random_access_iterator_tag, + typename std::iterator_traits<BaseIterator>::iterator_category>::value, + "BaseIterator must be random access iterator to use operator-"); + return TransformIterator(base() - n, GetFunction()); + } + + difference_type operator-(const TransformIterator& other) const { + static_assert( + std::is_base_of<std::random_access_iterator_tag, + typename std::iterator_traits<BaseIterator>::iterator_category>::value, + "BaseIterator must be random access iterator to use operator-"); + return base() - other.base(); + } + + // Retrieve the base iterator. + BaseIterator base() const { + return data_.base_; + } + + // Retrieve the transformation function. + const Function& GetFunction() const { + return static_cast<const Function&>(data_); + } + + private: + // Allow EBO for state-less Function. + struct Data : Function { + public: + Data(BaseIterator base, Function fn) : Function(fn), base_(base) { } + + BaseIterator base_; + }; + + Data data_; +}; + +template <typename BaseIterator1, typename BaseIterator2, typename Function> +bool operator==(const TransformIterator<BaseIterator1, Function>& lhs, + const TransformIterator<BaseIterator2, Function>& rhs) { + return lhs.base() == rhs.base(); +} + +template <typename BaseIterator1, typename BaseIterator2, typename Function> +bool operator!=(const TransformIterator<BaseIterator1, Function>& lhs, + const TransformIterator<BaseIterator2, Function>& rhs) { + return !(lhs == rhs); +} + +template <typename BaseIterator, typename Function> +TransformIterator<BaseIterator, Function> MakeTransformIterator(BaseIterator base, Function f) { + return TransformIterator<BaseIterator, Function>(base, f); +} + +template <typename BaseRange, typename Function> +auto MakeTransformRange(BaseRange& range, Function f) { + return MakeIterationRange(MakeTransformIterator(range.begin(), f), + MakeTransformIterator(range.end(), f)); +} + +} // namespace art + +#endif // ART_COMPILER_UTILS_TRANSFORM_ITERATOR_H_ diff --git a/compiler/utils/transform_iterator_test.cc b/compiler/utils/transform_iterator_test.cc new file mode 100644 index 0000000000..dbb4779330 --- /dev/null +++ b/compiler/utils/transform_iterator_test.cc @@ -0,0 +1,533 @@ +/* + * Copyright (C) 2016 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 <algorithm> +#include <forward_list> +#include <list> +#include <type_traits> +#include <vector> + +#include <array> + +#include "gtest/gtest.h" + +#include "utils/transform_iterator.h" + +namespace art { + +namespace { // anonymous namespace + +struct ValueHolder { + // Deliberately not explicit. + ValueHolder(int v) : value(v) { } // NOLINT + int value; +}; + +bool operator==(const ValueHolder& lhs, const ValueHolder& rhs) { + return lhs.value == rhs.value; +} + +} // anonymous namespace + +TEST(TransformIterator, VectorAdd1) { + auto add1 = [](const ValueHolder& h) { return h.value + 1; }; // NOLINT [readability/braces] + std::vector<ValueHolder> input({ 1, 7, 3, 8 }); + std::vector<int> output; + + using vector_titer = decltype(MakeTransformIterator(input.begin(), add1)); + static_assert(std::is_same<std::random_access_iterator_tag, + vector_titer::iterator_category>::value, "category"); + static_assert(std::is_same<int, vector_titer::value_type>::value, "value_type"); + static_assert(std::is_same<vector_titer, vector_titer::pointer>::value, "pointer"); + static_assert(std::is_same<int, vector_titer::reference>::value, "reference"); + + using vector_ctiter = decltype(MakeTransformIterator(input.cbegin(), add1)); + static_assert(std::is_same<std::random_access_iterator_tag, + vector_ctiter::iterator_category>::value, "category"); + static_assert(std::is_same<int, vector_ctiter::value_type>::value, "value_type"); + static_assert(std::is_same<vector_ctiter, vector_ctiter::pointer>::value, "pointer"); + static_assert(std::is_same<int, vector_ctiter::reference>::value, "reference"); + + using vector_rtiter = decltype(MakeTransformIterator(input.rbegin(), add1)); + static_assert(std::is_same<std::random_access_iterator_tag, + vector_rtiter::iterator_category>::value, "category"); + static_assert(std::is_same<int, vector_rtiter::value_type>::value, "value_type"); + static_assert(std::is_same<vector_rtiter, vector_rtiter::pointer>::value, "pointer"); + static_assert(std::is_same<int, vector_rtiter::reference>::value, "reference"); + + using vector_crtiter = decltype(MakeTransformIterator(input.crbegin(), add1)); + static_assert(std::is_same<std::random_access_iterator_tag, + vector_crtiter::iterator_category>::value, "category"); + static_assert(std::is_same<int, vector_crtiter::value_type>::value, "value_type"); + static_assert(std::is_same<vector_crtiter, vector_crtiter::pointer>::value, "pointer"); + static_assert(std::is_same<int, vector_crtiter::reference>::value, "reference"); + + std::copy(MakeTransformIterator(input.begin(), add1), + MakeTransformIterator(input.end(), add1), + std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 2, 8, 4, 9 }), output); + output.clear(); + + std::copy(MakeTransformIterator(input.cbegin(), add1), + MakeTransformIterator(input.cend(), add1), + std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 2, 8, 4, 9 }), output); + output.clear(); + + std::copy(MakeTransformIterator(input.rbegin(), add1), + MakeTransformIterator(input.rend(), add1), + std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 9, 4, 8, 2 }), output); + output.clear(); + + std::copy(MakeTransformIterator(input.crbegin(), add1), + MakeTransformIterator(input.crend(), add1), + std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 9, 4, 8, 2 }), output); + output.clear(); + + for (size_t i = 0; i != input.size(); ++i) { + ASSERT_EQ(input[i].value + 1, MakeTransformIterator(input.begin(), add1)[i]); + ASSERT_EQ(input[i].value + 1, MakeTransformIterator(input.cbegin(), add1)[i]); + ptrdiff_t index_from_rbegin = static_cast<ptrdiff_t>(input.size() - i - 1u); + ASSERT_EQ(input[i].value + 1, MakeTransformIterator(input.rbegin(), add1)[index_from_rbegin]); + ASSERT_EQ(input[i].value + 1, MakeTransformIterator(input.crbegin(), add1)[index_from_rbegin]); + ptrdiff_t index_from_end = -static_cast<ptrdiff_t>(input.size() - i); + ASSERT_EQ(input[i].value + 1, MakeTransformIterator(input.end(), add1)[index_from_end]); + ASSERT_EQ(input[i].value + 1, MakeTransformIterator(input.cend(), add1)[index_from_end]); + ptrdiff_t index_from_rend = -1 - static_cast<ptrdiff_t>(i); + ASSERT_EQ(input[i].value + 1, MakeTransformIterator(input.rend(), add1)[index_from_rend]); + ASSERT_EQ(input[i].value + 1, MakeTransformIterator(input.crend(), add1)[index_from_rend]); + + ASSERT_EQ(MakeTransformIterator(input.begin(), add1) + i, + MakeTransformIterator(input.begin() + i, add1)); + ASSERT_EQ(MakeTransformIterator(input.cbegin(), add1) + i, + MakeTransformIterator(input.cbegin() + i, add1)); + ASSERT_EQ(MakeTransformIterator(input.rbegin(), add1) + i, + MakeTransformIterator(input.rbegin() + i, add1)); + ASSERT_EQ(MakeTransformIterator(input.crbegin(), add1) + i, + MakeTransformIterator(input.crbegin() + i, add1)); + ASSERT_EQ(MakeTransformIterator(input.end(), add1) - i, + MakeTransformIterator(input.end() - i, add1)); + ASSERT_EQ(MakeTransformIterator(input.cend(), add1) - i, + MakeTransformIterator(input.cend() - i, add1)); + ASSERT_EQ(MakeTransformIterator(input.rend(), add1) - i, + MakeTransformIterator(input.rend() - i, add1)); + ASSERT_EQ(MakeTransformIterator(input.crend(), add1) - i, + MakeTransformIterator(input.crend() - i, add1)); + } + ASSERT_EQ(input.end(), + (MakeTransformIterator(input.begin(), add1) + input.size()).base()); + ASSERT_EQ(MakeTransformIterator(input.end(), add1) - MakeTransformIterator(input.begin(), add1), + static_cast<ptrdiff_t>(input.size())); + + // Test iterator->const_iterator conversion and comparison. + auto it = MakeTransformIterator(input.begin(), add1); + decltype(MakeTransformIterator(input.cbegin(), add1)) cit = it; + static_assert(!std::is_same<decltype(it), decltype(cit)>::value, "Types must be different"); + ASSERT_EQ(it, cit); + auto rit = MakeTransformIterator(input.rbegin(), add1); + decltype(MakeTransformIterator(input.crbegin(), add1)) crit(rit); + static_assert(!std::is_same<decltype(rit), decltype(crit)>::value, "Types must be different"); + ASSERT_EQ(rit, crit); +} + +TEST(TransformIterator, ListSub1) { + auto sub1 = [](const ValueHolder& h) { return h.value - 1; }; // NOLINT [readability/braces] + std::list<ValueHolder> input({ 2, 3, 5, 7, 11 }); + std::vector<int> output; + + using list_titer = decltype(MakeTransformIterator(input.begin(), sub1)); + static_assert(std::is_same<std::bidirectional_iterator_tag, + list_titer::iterator_category>::value, "category"); + static_assert(std::is_same<int, list_titer::value_type>::value, "value_type"); + static_assert(std::is_same<list_titer, list_titer::pointer>::value, "pointer"); + static_assert(std::is_same<int, list_titer::reference>::value, "reference"); + + using list_ctiter = decltype(MakeTransformIterator(input.cbegin(), sub1)); + static_assert(std::is_same<std::bidirectional_iterator_tag, + list_ctiter::iterator_category>::value, "category"); + static_assert(std::is_same<int, list_ctiter::value_type>::value, "value_type"); + static_assert(std::is_same<list_ctiter, list_ctiter::pointer>::value, "pointer"); + static_assert(std::is_same<int, list_ctiter::reference>::value, "reference"); + + using list_rtiter = decltype(MakeTransformIterator(input.rbegin(), sub1)); + static_assert(std::is_same<std::bidirectional_iterator_tag, + list_rtiter::iterator_category>::value, "category"); + static_assert(std::is_same<int, list_rtiter::value_type>::value, "value_type"); + static_assert(std::is_same<list_rtiter, list_rtiter::pointer>::value, "pointer"); + static_assert(std::is_same<int, list_rtiter::reference>::value, "reference"); + + using list_crtiter = decltype(MakeTransformIterator(input.crbegin(), sub1)); + static_assert(std::is_same<std::bidirectional_iterator_tag, + list_crtiter::iterator_category>::value, "category"); + static_assert(std::is_same<int, list_crtiter::value_type>::value, "value_type"); + static_assert(std::is_same<list_crtiter, list_crtiter::pointer>::value, "pointer"); + static_assert(std::is_same<int, list_crtiter::reference>::value, "reference"); + + std::copy(MakeTransformIterator(input.begin(), sub1), + MakeTransformIterator(input.end(), sub1), + std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 1, 2, 4, 6, 10 }), output); + output.clear(); + + std::copy(MakeTransformIterator(input.cbegin(), sub1), + MakeTransformIterator(input.cend(), sub1), + std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 1, 2, 4, 6, 10 }), output); + output.clear(); + + std::copy(MakeTransformIterator(input.rbegin(), sub1), + MakeTransformIterator(input.rend(), sub1), + std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 10, 6, 4, 2, 1 }), output); + output.clear(); + + std::copy(MakeTransformIterator(input.crbegin(), sub1), + MakeTransformIterator(input.crend(), sub1), + std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 10, 6, 4, 2, 1 }), output); + output.clear(); + + // Test iterator->const_iterator conversion and comparison. + auto it = MakeTransformIterator(input.begin(), sub1); + decltype(MakeTransformIterator(input.cbegin(), sub1)) cit = it; + static_assert(!std::is_same<decltype(it), decltype(cit)>::value, "Types must be different"); + ASSERT_EQ(it, cit); +} + +TEST(TransformIterator, ForwardListSub1) { + auto mul3 = [](const ValueHolder& h) { return h.value * 3; }; // NOLINT [readability/braces] + std::forward_list<ValueHolder> input({ 1, 1, 2, 3, 5, 8 }); + std::vector<int> output; + + using flist_titer = decltype(MakeTransformIterator(input.begin(), mul3)); + static_assert(std::is_same<std::forward_iterator_tag, + flist_titer::iterator_category>::value, "category"); + static_assert(std::is_same<int, flist_titer::value_type>::value, "value_type"); + static_assert(std::is_same<flist_titer, flist_titer::pointer>::value, "pointer"); + static_assert(std::is_same<int, flist_titer::reference>::value, "reference"); + + using flist_ctiter = decltype(MakeTransformIterator(input.cbegin(), mul3)); + static_assert(std::is_same<std::forward_iterator_tag, + flist_ctiter::iterator_category>::value, "category"); + static_assert(std::is_same<int, flist_ctiter::value_type>::value, "value_type"); + static_assert(std::is_same<flist_ctiter, flist_ctiter::pointer>::value, "pointer"); + static_assert(std::is_same<int, flist_ctiter::reference>::value, "reference"); + + std::copy(MakeTransformIterator(input.begin(), mul3), + MakeTransformIterator(input.end(), mul3), + std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 3, 3, 6, 9, 15, 24 }), output); + output.clear(); + + std::copy(MakeTransformIterator(input.cbegin(), mul3), + MakeTransformIterator(input.cend(), mul3), + std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 3, 3, 6, 9, 15, 24 }), output); + output.clear(); + + // Test iterator->const_iterator conversion and comparison. + auto it = MakeTransformIterator(input.begin(), mul3); + decltype(MakeTransformIterator(input.cbegin(), mul3)) cit = it; + static_assert(!std::is_same<decltype(it), decltype(cit)>::value, "Types must be different"); + ASSERT_EQ(it, cit); +} + +TEST(TransformIterator, VectorConstReference) { + auto ref = [](const ValueHolder& h) -> const int& { return h.value; }; // NOLINT [readability/braces] + std::vector<ValueHolder> input({ 7, 3, 1, 2, 4, 8 }); + std::vector<int> output; + + using vector_titer = decltype(MakeTransformIterator(input.begin(), ref)); + static_assert(std::is_same<std::random_access_iterator_tag, + vector_titer::iterator_category>::value, "category"); + static_assert(std::is_same<int, vector_titer::value_type>::value, "value_type"); + static_assert(std::is_same<const int*, vector_titer::pointer>::value, "pointer"); + static_assert(std::is_same<const int&, vector_titer::reference>::value, "reference"); + + using vector_ctiter = decltype(MakeTransformIterator(input.cbegin(), ref)); + static_assert(std::is_same<std::random_access_iterator_tag, + vector_ctiter::iterator_category>::value, "category"); + static_assert(std::is_same<int, vector_ctiter::value_type>::value, "value_type"); + static_assert(std::is_same<const int*, vector_ctiter::pointer>::value, "pointer"); + static_assert(std::is_same<const int&, vector_ctiter::reference>::value, "reference"); + + using vector_rtiter = decltype(MakeTransformIterator(input.rbegin(), ref)); + static_assert(std::is_same<std::random_access_iterator_tag, + vector_rtiter::iterator_category>::value, "category"); + static_assert(std::is_same<int, vector_rtiter::value_type>::value, "value_type"); + static_assert(std::is_same<const int*, vector_rtiter::pointer>::value, "pointer"); + static_assert(std::is_same<const int&, vector_rtiter::reference>::value, "reference"); + + using vector_crtiter = decltype(MakeTransformIterator(input.crbegin(), ref)); + static_assert(std::is_same<std::random_access_iterator_tag, + vector_crtiter::iterator_category>::value, "category"); + static_assert(std::is_same<int, vector_crtiter::value_type>::value, "value_type"); + static_assert(std::is_same<const int*, vector_crtiter::pointer>::value, "pointer"); + static_assert(std::is_same<const int&, vector_crtiter::reference>::value, "reference"); + + std::copy(MakeTransformIterator(input.begin(), ref), + MakeTransformIterator(input.end(), ref), + std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 7, 3, 1, 2, 4, 8 }), output); + output.clear(); + + std::copy(MakeTransformIterator(input.cbegin(), ref), + MakeTransformIterator(input.cend(), ref), + std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 7, 3, 1, 2, 4, 8 }), output); + output.clear(); + + std::copy(MakeTransformIterator(input.rbegin(), ref), + MakeTransformIterator(input.rend(), ref), + std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 8, 4, 2, 1, 3, 7 }), output); + output.clear(); + + std::copy(MakeTransformIterator(input.crbegin(), ref), + MakeTransformIterator(input.crend(), ref), + std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 8, 4, 2, 1, 3, 7 }), output); + output.clear(); + + for (size_t i = 0; i != input.size(); ++i) { + ASSERT_EQ(input[i].value, MakeTransformIterator(input.begin(), ref)[i]); + ASSERT_EQ(input[i].value, MakeTransformIterator(input.cbegin(), ref)[i]); + ptrdiff_t index_from_rbegin = static_cast<ptrdiff_t>(input.size() - i - 1u); + ASSERT_EQ(input[i].value, MakeTransformIterator(input.rbegin(), ref)[index_from_rbegin]); + ASSERT_EQ(input[i].value, MakeTransformIterator(input.crbegin(), ref)[index_from_rbegin]); + ptrdiff_t index_from_end = -static_cast<ptrdiff_t>(input.size() - i); + ASSERT_EQ(input[i].value, MakeTransformIterator(input.end(), ref)[index_from_end]); + ASSERT_EQ(input[i].value, MakeTransformIterator(input.cend(), ref)[index_from_end]); + ptrdiff_t index_from_rend = -1 - static_cast<ptrdiff_t>(i); + ASSERT_EQ(input[i].value, MakeTransformIterator(input.rend(), ref)[index_from_rend]); + ASSERT_EQ(input[i].value, MakeTransformIterator(input.crend(), ref)[index_from_rend]); + + ASSERT_EQ(MakeTransformIterator(input.begin(), ref) + i, + MakeTransformIterator(input.begin() + i, ref)); + ASSERT_EQ(MakeTransformIterator(input.cbegin(), ref) + i, + MakeTransformIterator(input.cbegin() + i, ref)); + ASSERT_EQ(MakeTransformIterator(input.rbegin(), ref) + i, + MakeTransformIterator(input.rbegin() + i, ref)); + ASSERT_EQ(MakeTransformIterator(input.crbegin(), ref) + i, + MakeTransformIterator(input.crbegin() + i, ref)); + ASSERT_EQ(MakeTransformIterator(input.end(), ref) - i, + MakeTransformIterator(input.end() - i, ref)); + ASSERT_EQ(MakeTransformIterator(input.cend(), ref) - i, + MakeTransformIterator(input.cend() - i, ref)); + ASSERT_EQ(MakeTransformIterator(input.rend(), ref) - i, + MakeTransformIterator(input.rend() - i, ref)); + ASSERT_EQ(MakeTransformIterator(input.crend(), ref) - i, + MakeTransformIterator(input.crend() - i, ref)); + } + ASSERT_EQ(input.end(), + (MakeTransformIterator(input.begin(), ref) + input.size()).base()); + ASSERT_EQ(MakeTransformIterator(input.end(), ref) - MakeTransformIterator(input.begin(), ref), + static_cast<ptrdiff_t>(input.size())); +} + +TEST(TransformIterator, VectorNonConstReference) { + auto ref = [](ValueHolder& h) -> int& { return h.value; }; // NOLINT [readability/braces] + std::vector<ValueHolder> input({ 7, 3, 1, 2, 4, 8 }); + std::vector<int> output; + + using vector_titer = decltype(MakeTransformIterator(input.begin(), ref)); + static_assert(std::is_same<std::random_access_iterator_tag, + vector_titer::iterator_category>::value, "category"); + static_assert(std::is_same<int, vector_titer::value_type>::value, "value_type"); + static_assert(std::is_same<int*, vector_titer::pointer>::value, "pointer"); + static_assert(std::is_same<int&, vector_titer::reference>::value, "reference"); + + using vector_rtiter = decltype(MakeTransformIterator(input.rbegin(), ref)); + static_assert(std::is_same<std::random_access_iterator_tag, + vector_rtiter::iterator_category>::value, "category"); + static_assert(std::is_same<int, vector_rtiter::value_type>::value, "value_type"); + static_assert(std::is_same<int*, vector_rtiter::pointer>::value, "pointer"); + static_assert(std::is_same<int&, vector_rtiter::reference>::value, "reference"); + + std::copy(MakeTransformIterator(input.begin(), ref), + MakeTransformIterator(input.end(), ref), + std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 7, 3, 1, 2, 4, 8 }), output); + output.clear(); + + std::copy(MakeTransformIterator(input.rbegin(), ref), + MakeTransformIterator(input.rend(), ref), + std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 8, 4, 2, 1, 3, 7 }), output); + output.clear(); + + for (size_t i = 0; i != input.size(); ++i) { + ASSERT_EQ(input[i].value, MakeTransformIterator(input.begin(), ref)[i]); + ptrdiff_t index_from_rbegin = static_cast<ptrdiff_t>(input.size() - i - 1u); + ASSERT_EQ(input[i].value, MakeTransformIterator(input.rbegin(), ref)[index_from_rbegin]); + ptrdiff_t index_from_end = -static_cast<ptrdiff_t>(input.size() - i); + ASSERT_EQ(input[i].value, MakeTransformIterator(input.end(), ref)[index_from_end]); + ptrdiff_t index_from_rend = -1 - static_cast<ptrdiff_t>(i); + ASSERT_EQ(input[i].value, MakeTransformIterator(input.rend(), ref)[index_from_rend]); + + ASSERT_EQ(MakeTransformIterator(input.begin(), ref) + i, + MakeTransformIterator(input.begin() + i, ref)); + ASSERT_EQ(MakeTransformIterator(input.rbegin(), ref) + i, + MakeTransformIterator(input.rbegin() + i, ref)); + ASSERT_EQ(MakeTransformIterator(input.end(), ref) - i, + MakeTransformIterator(input.end() - i, ref)); + ASSERT_EQ(MakeTransformIterator(input.rend(), ref) - i, + MakeTransformIterator(input.rend() - i, ref)); + } + ASSERT_EQ(input.end(), + (MakeTransformIterator(input.begin(), ref) + input.size()).base()); + ASSERT_EQ(MakeTransformIterator(input.end(), ref) - MakeTransformIterator(input.begin(), ref), + static_cast<ptrdiff_t>(input.size())); + + // Test writing through the transform iterator. + std::list<int> transform_input({ 1, -1, 2, -2, 3, -3 }); + std::vector<ValueHolder> transformed(transform_input.size(), 0); + std::transform(transform_input.begin(), + transform_input.end(), + MakeTransformIterator(transformed.begin(), ref), + [](int v) { return -2 * v; }); + ASSERT_EQ(std::vector<ValueHolder>({ -2, 2, -4, 4, -6, 6 }), transformed); +} + +TEST(TransformIterator, VectorConstAndNonConstReference) { + struct Ref { + int& operator()(ValueHolder& h) const { return h.value; } + const int& operator()(const ValueHolder& h) const { return h.value; } + }; + Ref ref; + std::vector<ValueHolder> input({ 7, 3, 1, 2, 4, 8 }); + std::vector<int> output; + + using vector_titer = decltype(MakeTransformIterator(input.begin(), ref)); + static_assert(std::is_same<std::random_access_iterator_tag, + vector_titer::iterator_category>::value, "category"); + static_assert(std::is_same<int, vector_titer::value_type>::value, "value_type"); + static_assert(std::is_same<int*, vector_titer::pointer>::value, "pointer"); + static_assert(std::is_same<int&, vector_titer::reference>::value, "reference"); + + using vector_ctiter = decltype(MakeTransformIterator(input.cbegin(), ref)); + static_assert(std::is_same<std::random_access_iterator_tag, + vector_ctiter::iterator_category>::value, "category"); + // static_assert(std::is_same<int, vector_ctiter::value_type>::value, "value_type"); + static_assert(std::is_same<const int*, vector_ctiter::pointer>::value, "pointer"); + static_assert(std::is_same<const int&, vector_ctiter::reference>::value, "reference"); + + using vector_rtiter = decltype(MakeTransformIterator(input.rbegin(), ref)); + static_assert(std::is_same<std::random_access_iterator_tag, + vector_rtiter::iterator_category>::value, "category"); + static_assert(std::is_same<int, vector_rtiter::value_type>::value, "value_type"); + static_assert(std::is_same<int*, vector_rtiter::pointer>::value, "pointer"); + static_assert(std::is_same<int&, vector_rtiter::reference>::value, "reference"); + + using vector_crtiter = decltype(MakeTransformIterator(input.crbegin(), ref)); + static_assert(std::is_same<std::random_access_iterator_tag, + vector_crtiter::iterator_category>::value, "category"); + // static_assert(std::is_same<int, vector_crtiter::value_type>::value, "value_type"); + static_assert(std::is_same<const int*, vector_crtiter::pointer>::value, "pointer"); + static_assert(std::is_same<const int&, vector_crtiter::reference>::value, "reference"); + + std::copy(MakeTransformIterator(input.begin(), ref), + MakeTransformIterator(input.end(), ref), + std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 7, 3, 1, 2, 4, 8 }), output); + output.clear(); + + std::copy(MakeTransformIterator(input.cbegin(), ref), + MakeTransformIterator(input.cend(), ref), + std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 7, 3, 1, 2, 4, 8 }), output); + output.clear(); + + std::copy(MakeTransformIterator(input.rbegin(), ref), + MakeTransformIterator(input.rend(), ref), + std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 8, 4, 2, 1, 3, 7 }), output); + output.clear(); + + std::copy(MakeTransformIterator(input.crbegin(), ref), + MakeTransformIterator(input.crend(), ref), + std::back_inserter(output)); + ASSERT_EQ(std::vector<int>({ 8, 4, 2, 1, 3, 7 }), output); + output.clear(); + + for (size_t i = 0; i != input.size(); ++i) { + ASSERT_EQ(input[i].value, MakeTransformIterator(input.begin(), ref)[i]); + ASSERT_EQ(input[i].value, MakeTransformIterator(input.cbegin(), ref)[i]); + ptrdiff_t index_from_rbegin = static_cast<ptrdiff_t>(input.size() - i - 1u); + ASSERT_EQ(input[i].value, MakeTransformIterator(input.rbegin(), ref)[index_from_rbegin]); + ASSERT_EQ(input[i].value, MakeTransformIterator(input.crbegin(), ref)[index_from_rbegin]); + ptrdiff_t index_from_end = -static_cast<ptrdiff_t>(input.size() - i); + ASSERT_EQ(input[i].value, MakeTransformIterator(input.end(), ref)[index_from_end]); + ASSERT_EQ(input[i].value, MakeTransformIterator(input.cend(), ref)[index_from_end]); + ptrdiff_t index_from_rend = -1 - static_cast<ptrdiff_t>(i); + ASSERT_EQ(input[i].value, MakeTransformIterator(input.rend(), ref)[index_from_rend]); + ASSERT_EQ(input[i].value, MakeTransformIterator(input.crend(), ref)[index_from_rend]); + + ASSERT_EQ(MakeTransformIterator(input.begin(), ref) + i, + MakeTransformIterator(input.begin() + i, ref)); + ASSERT_EQ(MakeTransformIterator(input.cbegin(), ref) + i, + MakeTransformIterator(input.cbegin() + i, ref)); + ASSERT_EQ(MakeTransformIterator(input.rbegin(), ref) + i, + MakeTransformIterator(input.rbegin() + i, ref)); + ASSERT_EQ(MakeTransformIterator(input.crbegin(), ref) + i, + MakeTransformIterator(input.crbegin() + i, ref)); + ASSERT_EQ(MakeTransformIterator(input.end(), ref) - i, + MakeTransformIterator(input.end() - i, ref)); + ASSERT_EQ(MakeTransformIterator(input.cend(), ref) - i, + MakeTransformIterator(input.cend() - i, ref)); + ASSERT_EQ(MakeTransformIterator(input.rend(), ref) - i, + MakeTransformIterator(input.rend() - i, ref)); + ASSERT_EQ(MakeTransformIterator(input.crend(), ref) - i, + MakeTransformIterator(input.crend() - i, ref)); + } + ASSERT_EQ(input.end(), + (MakeTransformIterator(input.begin(), ref) + input.size()).base()); + ASSERT_EQ(MakeTransformIterator(input.end(), ref) - MakeTransformIterator(input.begin(), ref), + static_cast<ptrdiff_t>(input.size())); + + // Test iterator->const_iterator conversion and comparison. + auto it = MakeTransformIterator(input.begin(), ref); + decltype(MakeTransformIterator(input.cbegin(), ref)) cit = it; + static_assert(!std::is_same<decltype(it), decltype(cit)>::value, "Types must be different"); + ASSERT_EQ(it, cit); + auto rit = MakeTransformIterator(input.rbegin(), ref); + decltype(MakeTransformIterator(input.crbegin(), ref)) crit(rit); + static_assert(!std::is_same<decltype(rit), decltype(crit)>::value, "Types must be different"); + ASSERT_EQ(rit, crit); + + // Test writing through the transform iterator. + std::list<int> transform_input({ 42, 73, 11, 17 }); + std::vector<ValueHolder> transformed(transform_input.size(), 0); + std::transform(transform_input.begin(), + transform_input.end(), + MakeTransformIterator(transformed.begin(), ref), + [](int v) { return -v; }); + ASSERT_EQ(std::vector<ValueHolder>({ -42, -73, -11, -17 }), transformed); +} + +TEST(TransformIterator, TransformRange) { + auto ref = [](ValueHolder& h) -> int& { return h.value; }; // NOLINT [readability/braces] + std::vector<ValueHolder> data({ 1, 0, 1, 3, 1, 0 }); + + for (int& v : MakeTransformRange(data, ref)) { + v += 11; + } + ASSERT_EQ(std::vector<ValueHolder>({ 12, 11, 12, 14, 12, 11 }), data); +} + +} // namespace art diff --git a/compiler/utils/x86/assembler_x86.cc b/compiler/utils/x86/assembler_x86.cc index 2203646e77..f931d75e77 100644 --- a/compiler/utils/x86/assembler_x86.cc +++ b/compiler/utils/x86/assembler_x86.cc @@ -1030,6 +1030,14 @@ void X86Assembler::xchgl(Register reg, const Address& address) { } +void X86Assembler::cmpb(const Address& address, const Immediate& imm) { + AssemblerBuffer::EnsureCapacity ensured(&buffer_); + EmitUint8(0x80); + EmitOperand(7, address); + EmitUint8(imm.value() & 0xFF); +} + + void X86Assembler::cmpw(const Address& address, const Immediate& imm) { AssemblerBuffer::EnsureCapacity ensured(&buffer_); EmitUint8(0x66); @@ -1924,15 +1932,16 @@ static dwarf::Reg DWARFReg(Register reg) { constexpr size_t kFramePointerSize = 4; -void X86Assembler::BuildFrame(size_t frame_size, ManagedRegister method_reg, - const std::vector<ManagedRegister>& spill_regs, +void X86Assembler::BuildFrame(size_t frame_size, + ManagedRegister method_reg, + ArrayRef<const ManagedRegister> spill_regs, const ManagedRegisterEntrySpills& entry_spills) { DCHECK_EQ(buffer_.Size(), 0U); // Nothing emitted yet. cfi_.SetCurrentCFAOffset(4); // Return address on stack. CHECK_ALIGNED(frame_size, kStackAlignment); int gpr_count = 0; for (int i = spill_regs.size() - 1; i >= 0; --i) { - Register spill = spill_regs.at(i).AsX86().AsCpuRegister(); + Register spill = spill_regs[i].AsX86().AsCpuRegister(); pushl(spill); gpr_count++; cfi_.AdjustCFAOffset(kFramePointerSize); @@ -1966,7 +1975,7 @@ void X86Assembler::BuildFrame(size_t frame_size, ManagedRegister method_reg, } } -void X86Assembler::RemoveFrame(size_t frame_size, const std::vector<ManagedRegister>& spill_regs) { +void X86Assembler::RemoveFrame(size_t frame_size, ArrayRef<const ManagedRegister> spill_regs) { CHECK_ALIGNED(frame_size, kStackAlignment); cfi_.RememberState(); // -kFramePointerSize for ArtMethod*. @@ -1974,7 +1983,7 @@ void X86Assembler::RemoveFrame(size_t frame_size, const std::vector<ManagedRegis addl(ESP, Immediate(adjust)); cfi_.AdjustCFAOffset(-adjust); for (size_t i = 0; i < spill_regs.size(); ++i) { - Register spill = spill_regs.at(i).AsX86().AsCpuRegister(); + Register spill = spill_regs[i].AsX86().AsCpuRegister(); popl(spill); cfi_.AdjustCFAOffset(-static_cast<int>(kFramePointerSize)); cfi_.Restore(DWARFReg(spill)); diff --git a/compiler/utils/x86/assembler_x86.h b/compiler/utils/x86/assembler_x86.h index 8567ad2a17..fa616620b6 100644 --- a/compiler/utils/x86/assembler_x86.h +++ b/compiler/utils/x86/assembler_x86.h @@ -479,6 +479,7 @@ class X86Assembler FINAL : public Assembler { void xchgl(Register dst, Register src); void xchgl(Register reg, const Address& address); + void cmpb(const Address& address, const Immediate& imm); void cmpw(const Address& address, const Immediate& imm); void cmpl(Register reg, const Immediate& imm); @@ -632,12 +633,13 @@ class X86Assembler FINAL : public Assembler { // // Emit code that will create an activation on the stack - void BuildFrame(size_t frame_size, ManagedRegister method_reg, - const std::vector<ManagedRegister>& callee_save_regs, + void BuildFrame(size_t frame_size, + ManagedRegister method_reg, + ArrayRef<const ManagedRegister> callee_save_regs, const ManagedRegisterEntrySpills& entry_spills) OVERRIDE; // Emit code that will remove an activation from the stack - void RemoveFrame(size_t frame_size, const std::vector<ManagedRegister>& callee_save_regs) + void RemoveFrame(size_t frame_size, ArrayRef<const ManagedRegister> callee_save_regs) OVERRIDE; void IncreaseFrameSize(size_t adjust) OVERRIDE; diff --git a/compiler/utils/x86/assembler_x86_test.cc b/compiler/utils/x86/assembler_x86_test.cc index 1d1df6e447..28043c9380 100644 --- a/compiler/utils/x86/assembler_x86_test.cc +++ b/compiler/utils/x86/assembler_x86_test.cc @@ -389,4 +389,10 @@ TEST_F(AssemblerX86Test, NearLabel) { DriverStr(expected, "near_label"); } +TEST_F(AssemblerX86Test, Cmpb) { + GetAssembler()->cmpb(x86::Address(x86::EDI, 128), x86::Immediate(0)); + const char* expected = "cmpb $0, 128(%EDI)\n"; + DriverStr(expected, "cmpb"); +} + } // namespace art diff --git a/compiler/utils/x86/managed_register_x86.h b/compiler/utils/x86/managed_register_x86.h index fc20d7e208..c0c2b650e9 100644 --- a/compiler/utils/x86/managed_register_x86.h +++ b/compiler/utils/x86/managed_register_x86.h @@ -89,64 +89,64 @@ const int kNumberOfAllocIds = kNumberOfCpuAllocIds + kNumberOfXmmAllocIds + // There is a one-to-one mapping between ManagedRegister and register id. class X86ManagedRegister : public ManagedRegister { public: - ByteRegister AsByteRegister() const { + constexpr ByteRegister AsByteRegister() const { CHECK(IsCpuRegister()); CHECK_LT(AsCpuRegister(), ESP); // ESP, EBP, ESI and EDI cannot be encoded as byte registers. return static_cast<ByteRegister>(id_); } - Register AsCpuRegister() const { + constexpr Register AsCpuRegister() const { CHECK(IsCpuRegister()); return static_cast<Register>(id_); } - XmmRegister AsXmmRegister() const { + constexpr XmmRegister AsXmmRegister() const { CHECK(IsXmmRegister()); return static_cast<XmmRegister>(id_ - kNumberOfCpuRegIds); } - X87Register AsX87Register() const { + constexpr X87Register AsX87Register() const { CHECK(IsX87Register()); return static_cast<X87Register>(id_ - (kNumberOfCpuRegIds + kNumberOfXmmRegIds)); } - Register AsRegisterPairLow() const { + constexpr Register AsRegisterPairLow() const { CHECK(IsRegisterPair()); // Appropriate mapping of register ids allows to use AllocIdLow(). return FromRegId(AllocIdLow()).AsCpuRegister(); } - Register AsRegisterPairHigh() const { + constexpr Register AsRegisterPairHigh() const { CHECK(IsRegisterPair()); // Appropriate mapping of register ids allows to use AllocIdHigh(). return FromRegId(AllocIdHigh()).AsCpuRegister(); } - RegisterPair AsRegisterPair() const { + constexpr RegisterPair AsRegisterPair() const { CHECK(IsRegisterPair()); return static_cast<RegisterPair>(id_ - (kNumberOfCpuRegIds + kNumberOfXmmRegIds + kNumberOfX87RegIds)); } - bool IsCpuRegister() const { + constexpr bool IsCpuRegister() const { CHECK(IsValidManagedRegister()); return (0 <= id_) && (id_ < kNumberOfCpuRegIds); } - bool IsXmmRegister() const { + constexpr bool IsXmmRegister() const { CHECK(IsValidManagedRegister()); const int test = id_ - kNumberOfCpuRegIds; return (0 <= test) && (test < kNumberOfXmmRegIds); } - bool IsX87Register() const { + constexpr bool IsX87Register() const { CHECK(IsValidManagedRegister()); const int test = id_ - (kNumberOfCpuRegIds + kNumberOfXmmRegIds); return (0 <= test) && (test < kNumberOfX87RegIds); } - bool IsRegisterPair() const { + constexpr bool IsRegisterPair() const { CHECK(IsValidManagedRegister()); const int test = id_ - (kNumberOfCpuRegIds + kNumberOfXmmRegIds + kNumberOfX87RegIds); @@ -160,33 +160,33 @@ class X86ManagedRegister : public ManagedRegister { // then false is returned. bool Overlaps(const X86ManagedRegister& other) const; - static X86ManagedRegister FromCpuRegister(Register r) { + static constexpr X86ManagedRegister FromCpuRegister(Register r) { CHECK_NE(r, kNoRegister); return FromRegId(r); } - static X86ManagedRegister FromXmmRegister(XmmRegister r) { + static constexpr X86ManagedRegister FromXmmRegister(XmmRegister r) { CHECK_NE(r, kNoXmmRegister); return FromRegId(r + kNumberOfCpuRegIds); } - static X86ManagedRegister FromX87Register(X87Register r) { + static constexpr X86ManagedRegister FromX87Register(X87Register r) { CHECK_NE(r, kNoX87Register); return FromRegId(r + kNumberOfCpuRegIds + kNumberOfXmmRegIds); } - static X86ManagedRegister FromRegisterPair(RegisterPair r) { + static constexpr X86ManagedRegister FromRegisterPair(RegisterPair r) { CHECK_NE(r, kNoRegisterPair); return FromRegId(r + (kNumberOfCpuRegIds + kNumberOfXmmRegIds + kNumberOfX87RegIds)); } private: - bool IsValidManagedRegister() const { + constexpr bool IsValidManagedRegister() const { return (0 <= id_) && (id_ < kNumberOfRegIds); } - int RegId() const { + constexpr int RegId() const { CHECK(!IsNoRegister()); return id_; } @@ -202,9 +202,9 @@ class X86ManagedRegister : public ManagedRegister { friend class ManagedRegister; - explicit X86ManagedRegister(int reg_id) : ManagedRegister(reg_id) {} + explicit constexpr X86ManagedRegister(int reg_id) : ManagedRegister(reg_id) {} - static X86ManagedRegister FromRegId(int reg_id) { + static constexpr X86ManagedRegister FromRegId(int reg_id) { X86ManagedRegister reg(reg_id); CHECK(reg.IsValidManagedRegister()); return reg; @@ -215,7 +215,7 @@ std::ostream& operator<<(std::ostream& os, const X86ManagedRegister& reg); } // namespace x86 -inline x86::X86ManagedRegister ManagedRegister::AsX86() const { +constexpr inline x86::X86ManagedRegister ManagedRegister::AsX86() const { x86::X86ManagedRegister reg(id_); CHECK(reg.IsNoRegister() || reg.IsValidManagedRegister()); return reg; diff --git a/compiler/utils/x86_64/assembler_x86_64.cc b/compiler/utils/x86_64/assembler_x86_64.cc index 32eb4a37bf..3046710603 100644 --- a/compiler/utils/x86_64/assembler_x86_64.cc +++ b/compiler/utils/x86_64/assembler_x86_64.cc @@ -1224,6 +1224,16 @@ void X86_64Assembler::xchgl(CpuRegister reg, const Address& address) { } +void X86_64Assembler::cmpb(const Address& address, const Immediate& imm) { + AssemblerBuffer::EnsureCapacity ensured(&buffer_); + CHECK(imm.is_int32()); + EmitOptionalRex32(address); + EmitUint8(0x80); + EmitOperand(7, address); + EmitUint8(imm.value() & 0xFF); +} + + void X86_64Assembler::cmpw(const Address& address, const Immediate& imm) { AssemblerBuffer::EnsureCapacity ensured(&buffer_); CHECK(imm.is_int32()); @@ -2638,15 +2648,16 @@ static dwarf::Reg DWARFReg(FloatRegister reg) { constexpr size_t kFramePointerSize = 8; -void X86_64Assembler::BuildFrame(size_t frame_size, ManagedRegister method_reg, - const std::vector<ManagedRegister>& spill_regs, +void X86_64Assembler::BuildFrame(size_t frame_size, + ManagedRegister method_reg, + ArrayRef<const ManagedRegister> spill_regs, const ManagedRegisterEntrySpills& entry_spills) { DCHECK_EQ(buffer_.Size(), 0U); // Nothing emitted yet. cfi_.SetCurrentCFAOffset(8); // Return address on stack. CHECK_ALIGNED(frame_size, kStackAlignment); int gpr_count = 0; for (int i = spill_regs.size() - 1; i >= 0; --i) { - x86_64::X86_64ManagedRegister spill = spill_regs.at(i).AsX86_64(); + x86_64::X86_64ManagedRegister spill = spill_regs[i].AsX86_64(); if (spill.IsCpuRegister()) { pushq(spill.AsCpuRegister()); gpr_count++; @@ -2664,7 +2675,7 @@ void X86_64Assembler::BuildFrame(size_t frame_size, ManagedRegister method_reg, // spill xmms int64_t offset = rest_of_frame; for (int i = spill_regs.size() - 1; i >= 0; --i) { - x86_64::X86_64ManagedRegister spill = spill_regs.at(i).AsX86_64(); + x86_64::X86_64ManagedRegister spill = spill_regs[i].AsX86_64(); if (spill.IsXmmRegister()) { offset -= sizeof(double); movsd(Address(CpuRegister(RSP), offset), spill.AsXmmRegister()); @@ -2697,15 +2708,14 @@ void X86_64Assembler::BuildFrame(size_t frame_size, ManagedRegister method_reg, } } -void X86_64Assembler::RemoveFrame(size_t frame_size, - const std::vector<ManagedRegister>& spill_regs) { +void X86_64Assembler::RemoveFrame(size_t frame_size, ArrayRef<const ManagedRegister> spill_regs) { CHECK_ALIGNED(frame_size, kStackAlignment); cfi_.RememberState(); int gpr_count = 0; // unspill xmms int64_t offset = static_cast<int64_t>(frame_size) - (spill_regs.size() * kFramePointerSize) - 2 * kFramePointerSize; for (size_t i = 0; i < spill_regs.size(); ++i) { - x86_64::X86_64ManagedRegister spill = spill_regs.at(i).AsX86_64(); + x86_64::X86_64ManagedRegister spill = spill_regs[i].AsX86_64(); if (spill.IsXmmRegister()) { offset += sizeof(double); movsd(spill.AsXmmRegister(), Address(CpuRegister(RSP), offset)); @@ -2718,7 +2728,7 @@ void X86_64Assembler::RemoveFrame(size_t frame_size, addq(CpuRegister(RSP), Immediate(adjust)); cfi_.AdjustCFAOffset(-adjust); for (size_t i = 0; i < spill_regs.size(); ++i) { - x86_64::X86_64ManagedRegister spill = spill_regs.at(i).AsX86_64(); + x86_64::X86_64ManagedRegister spill = spill_regs[i].AsX86_64(); if (spill.IsCpuRegister()) { popq(spill.AsCpuRegister()); cfi_.AdjustCFAOffset(-static_cast<int>(kFramePointerSize)); diff --git a/compiler/utils/x86_64/assembler_x86_64.h b/compiler/utils/x86_64/assembler_x86_64.h index 92c7d0ab99..361f73cffa 100644 --- a/compiler/utils/x86_64/assembler_x86_64.h +++ b/compiler/utils/x86_64/assembler_x86_64.h @@ -506,6 +506,7 @@ class X86_64Assembler FINAL : public Assembler { void xchgq(CpuRegister dst, CpuRegister src); void xchgl(CpuRegister reg, const Address& address); + void cmpb(const Address& address, const Immediate& imm); void cmpw(const Address& address, const Immediate& imm); void cmpl(CpuRegister reg, const Immediate& imm); @@ -703,12 +704,13 @@ class X86_64Assembler FINAL : public Assembler { // // Emit code that will create an activation on the stack - void BuildFrame(size_t frame_size, ManagedRegister method_reg, - const std::vector<ManagedRegister>& callee_save_regs, + void BuildFrame(size_t frame_size, + ManagedRegister method_reg, + ArrayRef<const ManagedRegister> callee_save_regs, const ManagedRegisterEntrySpills& entry_spills) OVERRIDE; // Emit code that will remove an activation from the stack - void RemoveFrame(size_t frame_size, const std::vector<ManagedRegister>& callee_save_regs) + void RemoveFrame(size_t frame_size, ArrayRef<const ManagedRegister> callee_save_regs) OVERRIDE; void IncreaseFrameSize(size_t adjust) OVERRIDE; diff --git a/compiler/utils/x86_64/assembler_x86_64_test.cc b/compiler/utils/x86_64/assembler_x86_64_test.cc index b19e616dd6..788c7253cf 100644 --- a/compiler/utils/x86_64/assembler_x86_64_test.cc +++ b/compiler/utils/x86_64/assembler_x86_64_test.cc @@ -37,7 +37,7 @@ TEST(AssemblerX86_64, CreateBuffer) { ASSERT_EQ(static_cast<size_t>(5), buffer.Size()); } -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID static constexpr size_t kRandomIterations = 1000; // Devices might be puny, don't stress them... #else static constexpr size_t kRandomIterations = 100000; // Hosts are pretty powerful. @@ -1498,9 +1498,11 @@ std::string buildframe_test_fn(AssemblerX86_64Test::Base* assembler_test ATTRIBU // TODO: more interesting spill registers / entry spills. // Two random spill regs. - std::vector<ManagedRegister> spill_regs; - spill_regs.push_back(ManagedFromCpu(x86_64::R10)); - spill_regs.push_back(ManagedFromCpu(x86_64::RSI)); + const ManagedRegister raw_spill_regs[] = { + ManagedFromCpu(x86_64::R10), + ManagedFromCpu(x86_64::RSI) + }; + ArrayRef<const ManagedRegister> spill_regs(raw_spill_regs); // Three random entry spills. ManagedRegisterEntrySpills entry_spills; @@ -1543,9 +1545,11 @@ std::string removeframe_test_fn(AssemblerX86_64Test::Base* assembler_test ATTRIB // TODO: more interesting spill registers / entry spills. // Two random spill regs. - std::vector<ManagedRegister> spill_regs; - spill_regs.push_back(ManagedFromCpu(x86_64::R10)); - spill_regs.push_back(ManagedFromCpu(x86_64::RSI)); + const ManagedRegister raw_spill_regs[] = { + ManagedFromCpu(x86_64::R10), + ManagedFromCpu(x86_64::RSI) + }; + ArrayRef<const ManagedRegister> spill_regs(raw_spill_regs); size_t frame_size = 10 * kStackAlignment; assembler->RemoveFrame(10 * kStackAlignment, spill_regs); @@ -1637,4 +1641,11 @@ TEST_F(AssemblerX86_64Test, Repecmpsq) { DriverStr(expected, "Repecmpsq"); } +TEST_F(AssemblerX86_64Test, Cmpb) { + GetAssembler()->cmpb(x86_64::Address(x86_64::CpuRegister(x86_64::RDI), 128), + x86_64::Immediate(0)); + const char* expected = "cmpb $0, 128(%RDI)\n"; + DriverStr(expected, "cmpb"); +} + } // namespace art diff --git a/compiler/utils/x86_64/constants_x86_64.h b/compiler/utils/x86_64/constants_x86_64.h index 0c782d46cd..37db6b1543 100644 --- a/compiler/utils/x86_64/constants_x86_64.h +++ b/compiler/utils/x86_64/constants_x86_64.h @@ -29,15 +29,15 @@ namespace x86_64 { class CpuRegister { public: - explicit CpuRegister(Register r) : reg_(r) {} - explicit CpuRegister(int r) : reg_(Register(r)) {} - Register AsRegister() const { + explicit constexpr CpuRegister(Register r) : reg_(r) {} + explicit constexpr CpuRegister(int r) : reg_(Register(r)) {} + constexpr Register AsRegister() const { return reg_; } - uint8_t LowBits() const { + constexpr uint8_t LowBits() const { return reg_ & 7; } - bool NeedsRex() const { + constexpr bool NeedsRex() const { return reg_ > 7; } private: @@ -47,15 +47,15 @@ std::ostream& operator<<(std::ostream& os, const CpuRegister& reg); class XmmRegister { public: - explicit XmmRegister(FloatRegister r) : reg_(r) {} - explicit XmmRegister(int r) : reg_(FloatRegister(r)) {} - FloatRegister AsFloatRegister() const { + explicit constexpr XmmRegister(FloatRegister r) : reg_(r) {} + explicit constexpr XmmRegister(int r) : reg_(FloatRegister(r)) {} + constexpr FloatRegister AsFloatRegister() const { return reg_; } - uint8_t LowBits() const { + constexpr uint8_t LowBits() const { return reg_ & 7; } - bool NeedsRex() const { + constexpr bool NeedsRex() const { return reg_ > 7; } private: diff --git a/compiler/utils/x86_64/managed_register_x86_64.h b/compiler/utils/x86_64/managed_register_x86_64.h index c4228c1139..32af672670 100644 --- a/compiler/utils/x86_64/managed_register_x86_64.h +++ b/compiler/utils/x86_64/managed_register_x86_64.h @@ -88,52 +88,52 @@ const int kNumberOfAllocIds = kNumberOfCpuAllocIds + kNumberOfXmmAllocIds + // There is a one-to-one mapping between ManagedRegister and register id. class X86_64ManagedRegister : public ManagedRegister { public: - CpuRegister AsCpuRegister() const { + constexpr CpuRegister AsCpuRegister() const { CHECK(IsCpuRegister()); return CpuRegister(static_cast<Register>(id_)); } - XmmRegister AsXmmRegister() const { + constexpr XmmRegister AsXmmRegister() const { CHECK(IsXmmRegister()); return XmmRegister(static_cast<FloatRegister>(id_ - kNumberOfCpuRegIds)); } - X87Register AsX87Register() const { + constexpr X87Register AsX87Register() const { CHECK(IsX87Register()); return static_cast<X87Register>(id_ - (kNumberOfCpuRegIds + kNumberOfXmmRegIds)); } - CpuRegister AsRegisterPairLow() const { + constexpr CpuRegister AsRegisterPairLow() const { CHECK(IsRegisterPair()); // Appropriate mapping of register ids allows to use AllocIdLow(). return FromRegId(AllocIdLow()).AsCpuRegister(); } - CpuRegister AsRegisterPairHigh() const { + constexpr CpuRegister AsRegisterPairHigh() const { CHECK(IsRegisterPair()); // Appropriate mapping of register ids allows to use AllocIdHigh(). return FromRegId(AllocIdHigh()).AsCpuRegister(); } - bool IsCpuRegister() const { + constexpr bool IsCpuRegister() const { CHECK(IsValidManagedRegister()); return (0 <= id_) && (id_ < kNumberOfCpuRegIds); } - bool IsXmmRegister() const { + constexpr bool IsXmmRegister() const { CHECK(IsValidManagedRegister()); const int test = id_ - kNumberOfCpuRegIds; return (0 <= test) && (test < kNumberOfXmmRegIds); } - bool IsX87Register() const { + constexpr bool IsX87Register() const { CHECK(IsValidManagedRegister()); const int test = id_ - (kNumberOfCpuRegIds + kNumberOfXmmRegIds); return (0 <= test) && (test < kNumberOfX87RegIds); } - bool IsRegisterPair() const { + constexpr bool IsRegisterPair() const { CHECK(IsValidManagedRegister()); const int test = id_ - (kNumberOfCpuRegIds + kNumberOfXmmRegIds + kNumberOfX87RegIds); @@ -147,32 +147,32 @@ class X86_64ManagedRegister : public ManagedRegister { // then false is returned. bool Overlaps(const X86_64ManagedRegister& other) const; - static X86_64ManagedRegister FromCpuRegister(Register r) { + static constexpr X86_64ManagedRegister FromCpuRegister(Register r) { CHECK_NE(r, kNoRegister); return FromRegId(r); } - static X86_64ManagedRegister FromXmmRegister(FloatRegister r) { + static constexpr X86_64ManagedRegister FromXmmRegister(FloatRegister r) { return FromRegId(r + kNumberOfCpuRegIds); } - static X86_64ManagedRegister FromX87Register(X87Register r) { + static constexpr X86_64ManagedRegister FromX87Register(X87Register r) { CHECK_NE(r, kNoX87Register); return FromRegId(r + kNumberOfCpuRegIds + kNumberOfXmmRegIds); } - static X86_64ManagedRegister FromRegisterPair(RegisterPair r) { + static constexpr X86_64ManagedRegister FromRegisterPair(RegisterPair r) { CHECK_NE(r, kNoRegisterPair); return FromRegId(r + (kNumberOfCpuRegIds + kNumberOfXmmRegIds + kNumberOfX87RegIds)); } private: - bool IsValidManagedRegister() const { + constexpr bool IsValidManagedRegister() const { return (0 <= id_) && (id_ < kNumberOfRegIds); } - int RegId() const { + constexpr int RegId() const { CHECK(!IsNoRegister()); return id_; } @@ -188,9 +188,9 @@ class X86_64ManagedRegister : public ManagedRegister { friend class ManagedRegister; - explicit X86_64ManagedRegister(int reg_id) : ManagedRegister(reg_id) {} + explicit constexpr X86_64ManagedRegister(int reg_id) : ManagedRegister(reg_id) {} - static X86_64ManagedRegister FromRegId(int reg_id) { + static constexpr X86_64ManagedRegister FromRegId(int reg_id) { X86_64ManagedRegister reg(reg_id); CHECK(reg.IsValidManagedRegister()); return reg; @@ -201,7 +201,7 @@ std::ostream& operator<<(std::ostream& os, const X86_64ManagedRegister& reg); } // namespace x86_64 -inline x86_64::X86_64ManagedRegister ManagedRegister::AsX86_64() const { +constexpr inline x86_64::X86_64ManagedRegister ManagedRegister::AsX86_64() const { x86_64::X86_64ManagedRegister reg(id_); CHECK(reg.IsNoRegister() || reg.IsValidManagedRegister()); return reg; diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index 370583e3ba..3db85c1842 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -685,12 +685,6 @@ class Dex2Oat FINAL { Usage("Can't have both --image and (--app-image-fd or --app-image-file)"); } - if (IsBootImage()) { - // We need the boot image to always be debuggable. - // TODO: Remove this once we better deal with full frame deoptimization. - compiler_options_->debuggable_ = true; - } - if (oat_filenames_.empty() && oat_fd_ == -1) { Usage("Output must be supplied with either --oat-file or --oat-fd"); } @@ -1269,6 +1263,21 @@ class Dex2Oat FINAL { CHECK(runtime != nullptr); std::set<DexCacheResolvedClasses> resolved_classes( profile_compilation_info_->GetResolvedClasses()); + + // Filter out class path classes since we don't want to include these in the image. + std::unordered_set<std::string> dex_files_locations; + for (const DexFile* dex_file : dex_files_) { + dex_files_locations.insert(dex_file->GetLocation()); + } + for (auto it = resolved_classes.begin(); it != resolved_classes.end(); ) { + if (dex_files_locations.find(it->GetDexLocation()) == dex_files_locations.end()) { + VLOG(compiler) << "Removed profile samples for non-app dex file " << it->GetDexLocation(); + it = resolved_classes.erase(it); + } else { + ++it; + } + } + image_classes_.reset(new std::unordered_set<std::string>( runtime->GetClassLinker()->GetClassDescriptorsForProfileKeys(resolved_classes))); VLOG(compiler) << "Loaded " << image_classes_->size() @@ -1312,7 +1321,7 @@ class Dex2Oat FINAL { if (IsBootImage() && image_filenames_.size() > 1) { // If we're compiling the boot image, store the boot classpath into the Key-Value store. // We need this for the multi-image case. - key_value_store_->Put(OatHeader::kBootClassPath, GetMultiImageBootClassPath()); + key_value_store_->Put(OatHeader::kBootClassPathKey, GetMultiImageBootClassPath()); } if (!IsBootImage()) { @@ -1348,12 +1357,22 @@ class Dex2Oat FINAL { // Open dex files for class path. const std::vector<std::string> class_path_locations = GetClassPathLocations(runtime_->GetClassPathString()); - OpenClassPathFiles(class_path_locations, &class_path_files_); + OpenClassPathFiles(class_path_locations, + &class_path_files_, + &opened_oat_files_, + runtime_->GetInstructionSet()); // Store the classpath we have right now. std::vector<const DexFile*> class_path_files = MakeNonOwningPointerVector(class_path_files_); - key_value_store_->Put(OatHeader::kClassPathKey, - OatFile::EncodeDexFileDependencies(class_path_files)); + std::string encoded_class_path; + if (class_path_locations.size() == 1 && + class_path_locations[0] == OatFile::kSpecialSharedLibrary) { + // When passing the special shared library as the classpath, it is the only path. + encoded_class_path = OatFile::kSpecialSharedLibrary; + } else { + encoded_class_path = OatFile::EncodeDexFileDependencies(class_path_files); + } + key_value_store_->Put(OatHeader::kClassPathKey, encoded_class_path); } // Now that we have finalized key_value_store_, start writing the oat file. @@ -1437,26 +1456,8 @@ class Dex2Oat FINAL { for (const auto& dex_file : dex_files_) { ScopedObjectAccess soa(self); dex_caches_.push_back(soa.AddLocalReference<jobject>( - class_linker->RegisterDexFile(*dex_file, Runtime::Current()->GetLinearAlloc()))); - } - - /* - * If we're not in interpret-only or verify-none or verify-at-runtime or verify-profile mode, - * go ahead and compile small applications. Don't bother to check if we're doing the image. - */ - if (!IsBootImage() && - compiler_options_->IsCompilationEnabled() && - compiler_kind_ == Compiler::kQuick) { - size_t num_methods = 0; - for (size_t i = 0; i != dex_files_.size(); ++i) { - const DexFile* dex_file = dex_files_[i]; - CHECK(dex_file != nullptr); - num_methods += dex_file->NumMethodIds(); - } - if (num_methods <= compiler_options_->GetNumDexMethodsThreshold()) { - compiler_options_->SetCompilerFilter(CompilerFilter::kSpeed); - VLOG(compiler) << "Below method threshold, compiling anyways"; - } + class_linker->RegisterDexFile(*dex_file, + soa.Decode<mirror::ClassLoader*>(class_loader_)))); } return true; @@ -1526,6 +1527,7 @@ class Dex2Oat FINAL { instruction_set_, instruction_set_features_.get(), IsBootImage(), + IsAppImage(), image_classes_.release(), compiled_classes_.release(), /* compiled_methods */ nullptr, @@ -1963,14 +1965,37 @@ class Dex2Oat FINAL { return parsed; } - // Opens requested class path files and appends them to opened_dex_files. + // Opens requested class path files and appends them to opened_dex_files. If the dex files have + // been stripped, this opens them from their oat files and appends them to opened_oat_files. static void OpenClassPathFiles(const std::vector<std::string>& class_path_locations, - std::vector<std::unique_ptr<const DexFile>>* opened_dex_files) { - DCHECK(opened_dex_files != nullptr) << "OpenClassPathFiles out-param is nullptr"; + std::vector<std::unique_ptr<const DexFile>>* opened_dex_files, + std::vector<std::unique_ptr<OatFile>>* opened_oat_files, + InstructionSet isa) { + DCHECK(opened_dex_files != nullptr) << "OpenClassPathFiles dex out-param is nullptr"; + DCHECK(opened_oat_files != nullptr) << "OpenClassPathFiles oat out-param is nullptr"; for (const std::string& location : class_path_locations) { + // Stop early if we detect the special shared library, which may be passed as the classpath + // for dex2oat when we want to skip the shared libraries check. + if (location == OatFile::kSpecialSharedLibrary) { + break; + } std::string error_msg; if (!DexFile::Open(location.c_str(), location.c_str(), &error_msg, opened_dex_files)) { - LOG(WARNING) << "Failed to open dex file '" << location << "': " << error_msg; + // If we fail to open the dex file because it's been stripped, try to open the dex file + // from its corresponding oat file. + OatFileAssistant oat_file_assistant(location.c_str(), isa, false, false); + std::unique_ptr<OatFile> oat_file(oat_file_assistant.GetBestOatFile()); + if (oat_file == nullptr) { + LOG(WARNING) << "Failed to open dex file and associated oat file for '" << location + << "': " << error_msg; + } else { + std::vector<std::unique_ptr<const DexFile>> oat_dex_files = + oat_file_assistant.LoadDexFiles(*oat_file, location.c_str()); + opened_oat_files->push_back(std::move(oat_file)); + opened_dex_files->insert(opened_dex_files->end(), + std::make_move_iterator(oat_dex_files.begin()), + std::make_move_iterator(oat_dex_files.end())); + } } } } @@ -2428,6 +2453,7 @@ class Dex2Oat FINAL { bool multi_image_; bool is_host_; std::string android_root_; + // Dex files we are compiling, does not include the class path dex files. std::vector<const DexFile*> dex_files_; std::string no_inline_from_string_; std::vector<jobject> dex_caches_; @@ -2440,6 +2466,7 @@ class Dex2Oat FINAL { std::unique_ptr<CompilerDriver> driver_; std::vector<std::unique_ptr<MemMap>> opened_dex_files_maps_; + std::vector<std::unique_ptr<OatFile>> opened_oat_files_; std::vector<std::unique_ptr<const DexFile>> opened_dex_files_; std::vector<const DexFile*> no_inline_from_dex_files_; diff --git a/disassembler/disassembler.h b/disassembler/disassembler.h index b99e5c2df4..b08031587f 100644 --- a/disassembler/disassembler.h +++ b/disassembler/disassembler.h @@ -31,16 +31,23 @@ class DisassemblerOptions { // Should the disassembler print absolute or relative addresses. const bool absolute_addresses_; - // Base addess for calculating relative code offsets when absolute_addresses_ is false. + // Base address for calculating relative code offsets when absolute_addresses_ is false. const uint8_t* const base_address_; + // End address (exclusive); + const uint8_t* const end_address_; + // If set, the disassembler is allowed to look at load targets in literal // pools. const bool can_read_literals_; - DisassemblerOptions(bool absolute_addresses, const uint8_t* base_address, + DisassemblerOptions(bool absolute_addresses, + const uint8_t* base_address, + const uint8_t* end_address, bool can_read_literals) - : absolute_addresses_(absolute_addresses), base_address_(base_address), + : absolute_addresses_(absolute_addresses), + base_address_(base_address), + end_address_(end_address), can_read_literals_(can_read_literals) {} private: diff --git a/disassembler/disassembler_arm.cc b/disassembler/disassembler_arm.cc index 77efb6be29..286faf215a 100644 --- a/disassembler/disassembler_arm.cc +++ b/disassembler/disassembler_arm.cc @@ -418,7 +418,12 @@ std::ostream& operator<<(std::ostream& os, T2LitType type) { return os << static_cast<int>(type); } -void DumpThumb2Literal(std::ostream& args, const uint8_t* instr_ptr, uint32_t U, uint32_t imm32, +void DumpThumb2Literal(std::ostream& args, + const uint8_t* instr_ptr, + const uintptr_t lo_adr, + const uintptr_t hi_adr, + uint32_t U, + uint32_t imm32, T2LitType type) { // Literal offsets (imm32) are not required to be aligned so we may need unaligned access. typedef const int16_t unaligned_int16_t __attribute__ ((aligned (1))); @@ -428,8 +433,16 @@ void DumpThumb2Literal(std::ostream& args, const uint8_t* instr_ptr, uint32_t U, typedef const int64_t unaligned_int64_t __attribute__ ((aligned (1))); typedef const uint64_t unaligned_uint64_t __attribute__ ((aligned (1))); + // Get address of literal. Bail if not within expected buffer range to + // avoid trying to fetch invalid literals (we can encounter this when + // interpreting raw data as instructions). uintptr_t pc = RoundDown(reinterpret_cast<intptr_t>(instr_ptr) + 4, 4); uintptr_t lit_adr = U ? pc + imm32 : pc - imm32; + if (lit_adr < lo_adr || lit_adr >= hi_adr) { + args << " ; (?)"; + return; + } + args << " ; "; switch (type) { case kT2LitUByte: @@ -482,6 +495,10 @@ size_t DisassemblerArm::DumpThumb32(std::ostream& os, const uint8_t* instr_ptr) return DumpThumb16(os, instr_ptr); } + // Set valid address range of backing buffer. + const uintptr_t lo_adr = reinterpret_cast<intptr_t>(GetDisassemblerOptions()->base_address_); + const uintptr_t hi_adr = reinterpret_cast<intptr_t>(GetDisassemblerOptions()->end_address_); + uint32_t op2 = (instr >> 20) & 0x7F; std::ostringstream opcode; std::ostringstream args; @@ -824,7 +841,7 @@ size_t DisassemblerArm::DumpThumb32(std::ostream& os, const uint8_t* instr_ptr) args << d << ", [" << Rn << ", #" << ((U == 1) ? "" : "-") << (imm8 << 2) << "]"; if (Rn.r == 15 && U == 1) { - DumpThumb2Literal(args, instr_ptr, U, imm8 << 2, kT2LitHexLong); + DumpThumb2Literal(args, instr_ptr, lo_adr, hi_adr, U, imm8 << 2, kT2LitHexLong); } } else if (Rn.r == 13 && W == 1 && U == L) { // VPUSH/VPOP opcode << (L == 1 ? "vpop" : "vpush"); @@ -1262,10 +1279,10 @@ size_t DisassemblerArm::DumpThumb32(std::ostream& os, const uint8_t* instr_ptr) imm32 = (S << 20) | (J2 << 19) | (J1 << 18) | (imm6 << 12) | (imm11 << 1); imm32 = (imm32 << 11) >> 11; // sign extend 21 bit immediate. } else { - uint32_t I1 = ~(J1 ^ S); - uint32_t I2 = ~(J2 ^ S); + uint32_t I1 = (J1 ^ S) ^ 1; + uint32_t I2 = (J2 ^ S) ^ 1; imm32 = (S << 24) | (I1 << 23) | (I2 << 22) | (imm10 << 12) | (imm11 << 1); - imm32 = (imm32 << 8) >> 8; // sign extend 24 bit immediate. + imm32 = (imm32 << 7) >> 7; // sign extend 25 bit immediate. } opcode << ".w"; DumpBranchTarget(args, instr_ptr + 4, imm32); @@ -1410,7 +1427,7 @@ size_t DisassemblerArm::DumpThumb32(std::ostream& os, const uint8_t* instr_ptr) }; DCHECK_LT(op2 >> 1, arraysize(lit_type)); DCHECK_NE(lit_type[op2 >> 1], kT2LitInvalid); - DumpThumb2Literal(args, instr_ptr, U, imm12, lit_type[op2 >> 1]); + DumpThumb2Literal(args, instr_ptr, lo_adr, hi_adr, U, imm12, lit_type[op2 >> 1]); } } else if ((instr & 0xFC0) == 0) { opcode << ldr_str << sign << type << ".w"; @@ -1711,10 +1728,13 @@ size_t DisassemblerArm::DumpThumb16(std::ostream& os, const uint8_t* instr_ptr) break; } } else if (opcode1 == 0x12 || opcode1 == 0x13) { // 01001x + const uintptr_t lo_adr = reinterpret_cast<intptr_t>(GetDisassemblerOptions()->base_address_); + const uintptr_t hi_adr = reinterpret_cast<intptr_t>(GetDisassemblerOptions()->end_address_); ThumbRegister Rt(instr, 8); uint16_t imm8 = instr & 0xFF; opcode << "ldr"; args << Rt << ", [pc, #" << (imm8 << 2) << "]"; + DumpThumb2Literal(args, instr_ptr, lo_adr, hi_adr, /*U*/ 1u, imm8 << 2, kT2LitHexWord); } else if ((opcode1 >= 0x14 && opcode1 <= 0x17) || // 0101xx (opcode1 >= 0x18 && opcode1 <= 0x1f) || // 011xxx (opcode1 >= 0x20 && opcode1 <= 0x27)) { // 100xxx diff --git a/disassembler/disassembler_arm64.cc b/disassembler/disassembler_arm64.cc index 5f8871470d..6a9afe5740 100644 --- a/disassembler/disassembler_arm64.cc +++ b/disassembler/disassembler_arm64.cc @@ -63,9 +63,17 @@ void CustomDisassembler::VisitLoadLiteral(const vixl::Instruction* instr) { return; } + // Get address of literal. Bail if not within expected buffer range to + // avoid trying to fetch invalid literals (we can encounter this when + // interpreting raw data as instructions). void* data_address = instr->LiteralAddress<void*>(); - vixl::Instr op = instr->Mask(vixl::LoadLiteralMask); + if (data_address < base_address_ || data_address >= end_address_) { + AppendToOutput(" (?)"); + return; + } + // Output information on literal. + vixl::Instr op = instr->Mask(vixl::LoadLiteralMask); switch (op) { case vixl::LDR_w_lit: case vixl::LDR_x_lit: diff --git a/disassembler/disassembler_arm64.h b/disassembler/disassembler_arm64.h index 44fa53f9f6..a4e5ee8a43 100644 --- a/disassembler/disassembler_arm64.h +++ b/disassembler/disassembler_arm64.h @@ -30,8 +30,11 @@ namespace arm64 { class CustomDisassembler FINAL : public vixl::Disassembler { public: - explicit CustomDisassembler(DisassemblerOptions* options) : - vixl::Disassembler(), read_literals_(options->can_read_literals_) { + explicit CustomDisassembler(DisassemblerOptions* options) + : vixl::Disassembler(), + read_literals_(options->can_read_literals_), + base_address_(options->base_address_), + end_address_(options->end_address_) { if (!options->absolute_addresses_) { MapCodeAddress(0, reinterpret_cast<const vixl::Instruction*>(options->base_address_)); } @@ -55,6 +58,10 @@ class CustomDisassembler FINAL : public vixl::Disassembler { // true | 0x72681558: 1c000acb ldr s11, pc+344 (addr 0x726816b0) // false | 0x72681558: 1c000acb ldr s11, pc+344 (addr 0x726816b0) (3.40282e+38) const bool read_literals_; + + // Valid address range: [base_address_, end_address_) + const void* const base_address_; + const void* const end_address_; }; class DisassemblerArm64 FINAL : public Disassembler { diff --git a/libart_fake/Android.mk b/libart_fake/Android.mk new file mode 100644 index 0000000000..ed868a5bd5 --- /dev/null +++ b/libart_fake/Android.mk @@ -0,0 +1,34 @@ +# Copyright (C) 2016 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. +# + +LOCAL_PATH := $(my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := libart_fake +LOCAL_INSTALLED_MODULE_STEM := libart.so +LOCAL_SDK_VERSION := 9 +LOCAL_CPP_EXTENSION := .cc +LOCAL_SRC_FILES := fake.cc +LOCAL_SHARED_LIBRARIES := liblog + +ifdef TARGET_2ND_ARCH + LOCAL_MODULE_PATH_32 := $(TARGET_OUT)/fake-libs + LOCAL_MODULE_PATH_64 := $(TARGET_OUT)/fake-libs64 +else + LOCAL_MODULE_PATH := $(TARGET_OUT)/fake-libs +endif + +include $(BUILD_SHARED_LIBRARY) diff --git a/libart_fake/README.md b/libart_fake/README.md new file mode 100644 index 0000000000..6e3621e55c --- /dev/null +++ b/libart_fake/README.md @@ -0,0 +1,5 @@ +libart_fake +==== + +A fake libart made to satisfy some misbehaving apps that will attempt to link +against libart.so. diff --git a/libart_fake/fake.cc b/libart_fake/fake.cc new file mode 100644 index 0000000000..884242101d --- /dev/null +++ b/libart_fake/fake.cc @@ -0,0 +1,46 @@ +/* + * Copyright 2016 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. + */ + +#define LOG_TAG "libart_fake" + +#include <android/log.h> + +#define LOGIT(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) +namespace art { +class Dbg { + public: + void SuspendVM(); + void ResumeVM(); +}; + +class FaultManager { + public: + void EnsureArtActionInFrontOfSignalChain(); +}; + +void Dbg::SuspendVM() { + LOGIT("Linking to and calling into libart.so internal functions is not supported. " + "This call to '%s' is being ignored.", __func__); +} +void Dbg::ResumeVM() { + LOGIT("Linking to and calling into libart.so internal functions is not supported. " + "This call to '%s' is being ignored.", __func__); +} +void FaultManager::EnsureArtActionInFrontOfSignalChain() { + LOGIT("Linking to and calling into libart.so internal functions is not supported. " + "This call to '%s' is being ignored.", __func__); +} +}; // namespace art diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc index 3c6a05d97b..aa4635d6a4 100644 --- a/oatdump/oatdump.cc +++ b/oatdump/oatdump.cc @@ -59,6 +59,7 @@ #include "stack_map.h" #include "ScopedLocalRef.h" #include "thread_list.h" +#include "type_lookup_table.h" #include "verifier/method_verifier.h" #include "well_known_classes.h" @@ -334,6 +335,7 @@ class OatDumper { disassembler_(Disassembler::Create(instruction_set_, new DisassemblerOptions(options_.absolute_addresses_, oat_file.Begin(), + oat_file.End(), true /* can_read_literals_ */))) { CHECK(options_.class_loader_ != nullptr); CHECK(options_.class_filter_ != nullptr); @@ -573,8 +575,15 @@ class OatDumper { os << StringPrintf("location: %s\n", oat_dex_file.GetDexFileLocation().c_str()); os << StringPrintf("checksum: 0x%08x\n", oat_dex_file.GetDexFileLocationChecksum()); - // Create the verifier early. + // Print embedded dex file data range. + const uint8_t* const oat_file_begin = oat_dex_file.GetOatFile()->Begin(); + const uint8_t* const dex_file_pointer = oat_dex_file.GetDexFilePointer(); + uint32_t dex_offset = dchecked_integral_cast<uint32_t>(dex_file_pointer - oat_file_begin); + os << StringPrintf("dex-file: 0x%08x..0x%08x\n", + dex_offset, + dchecked_integral_cast<uint32_t>(dex_offset + oat_dex_file.FileSize() - 1)); + // Create the dex file early. A lot of print-out things depend on it. std::string error_msg; const DexFile* const dex_file = OpenDexFile(&oat_dex_file, &error_msg); if (dex_file == nullptr) { @@ -583,6 +592,16 @@ class OatDumper { return false; } + // Print lookup table, if it exists. + if (oat_dex_file.GetLookupTableData() != nullptr) { + uint32_t table_offset = dchecked_integral_cast<uint32_t>( + oat_dex_file.GetLookupTableData() - oat_file_begin); + uint32_t table_size = TypeLookupTable::RawDataLength(*dex_file); + os << StringPrintf("type-table: 0x%08x..0x%08x\n", + table_offset, + table_offset + table_size - 1); + } + VariableIndentationOutputStream vios(&os); ScopedIndentation indent1(&vios); for (size_t class_def_index = 0; @@ -1099,8 +1118,7 @@ class OatDumper { ScopedObjectAccess soa(Thread::Current()); Runtime* const runtime = Runtime::Current(); Handle<mirror::DexCache> dex_cache( - hs->NewHandle(runtime->GetClassLinker()->RegisterDexFile(*dex_file, - runtime->GetLinearAlloc()))); + hs->NewHandle(runtime->GetClassLinker()->RegisterDexFile(*dex_file, nullptr))); DCHECK(options_.class_loader_ != nullptr); return verifier::MethodVerifier::VerifyMethodAndDump( soa.Self(), vios, dex_method_idx, dex_file, dex_cache, *options_.class_loader_, @@ -1416,11 +1434,10 @@ class ImageDumper { indent_os << "\n"; // TODO: Dump fields. // Dump methods after. - const auto& methods_section = image_header_.GetMethodsSection(); DumpArtMethodVisitor visitor(this); - methods_section.VisitPackedArtMethods(&visitor, - image_space_.Begin(), - image_header_.GetPointerSize()); + image_header_.VisitPackedArtMethods(&visitor, + image_space_.Begin(), + image_header_.GetPointerSize()); // Dump the large objects separately. heap->GetLargeObjectsSpace()->GetLiveBitmap()->Walk(ImageDumper::Callback, this); indent_os << "\n"; @@ -1779,6 +1796,7 @@ class ImageDumper { DCHECK(method != nullptr); const void* quick_oat_code_begin = GetQuickOatCodeBegin(method); const void* quick_oat_code_end = GetQuickOatCodeEnd(method); + const size_t pointer_size = image_header_.GetPointerSize(); OatQuickMethodHeader* method_header = reinterpret_cast<OatQuickMethodHeader*>( reinterpret_cast<uintptr_t>(quick_oat_code_begin) - sizeof(OatQuickMethodHeader)); if (method->IsNative()) { @@ -1792,13 +1810,16 @@ class ImageDumper { image_header_.GetPointerSize())) { indent_os << StringPrintf("OAT CODE: %p\n", quick_oat_code_begin); } - } else if (method->IsAbstract() || - method->IsCalleeSaveMethod() || - method->IsResolutionMethod() || - (method == Runtime::Current()->GetImtConflictMethod()) || - method->IsImtUnimplementedMethod() || - method->IsClassInitializer()) { + } else if (method->IsAbstract() || method->IsClassInitializer()) { // Don't print information for these. + } else if (method->IsRuntimeMethod()) { + ImtConflictTable* table = method->GetImtConflictTable(image_header_.GetPointerSize()); + if (table != nullptr) { + indent_os << "IMT conflict table " << table << " method: "; + for (size_t i = 0, count = table->NumEntries(pointer_size); i < count; ++i) { + indent_os << PrettyMethod(table->GetImplementationMethod(i, pointer_size)) << " "; + } + } } else { const DexFile::CodeItem* code_item = method->GetCodeItem(); size_t dex_instruction_bytes = code_item->insns_size_in_code_units_ * 2; @@ -2261,7 +2282,7 @@ static int DumpOatWithRuntime(Runtime* runtime, OatFile* oat_file, OatDumperOpti std::string error_msg; const DexFile* const dex_file = OpenDexFile(odf, &error_msg); CHECK(dex_file != nullptr) << error_msg; - class_linker->RegisterDexFile(*dex_file, runtime->GetLinearAlloc()); + class_linker->RegisterDexFile(*dex_file, nullptr); class_path.push_back(dex_file); } diff --git a/patchoat/patchoat.cc b/patchoat/patchoat.cc index a1b3c9e12e..0a7ffda3b4 100644 --- a/patchoat/patchoat.cc +++ b/patchoat/patchoat.cc @@ -472,8 +472,7 @@ class PatchOatArtFieldVisitor : public ArtFieldVisitor { void PatchOat::PatchArtFields(const ImageHeader* image_header) { PatchOatArtFieldVisitor visitor(this); - const auto& section = image_header->GetImageSection(ImageHeader::kSectionArtFields); - section.VisitPackedArtFields(&visitor, heap_->Begin()); + image_header->VisitPackedArtFields(&visitor, heap_->Begin()); } class PatchOatArtMethodVisitor : public ArtMethodVisitor { @@ -490,10 +489,20 @@ class PatchOatArtMethodVisitor : public ArtMethodVisitor { }; void PatchOat::PatchArtMethods(const ImageHeader* image_header) { - const auto& section = image_header->GetMethodsSection(); const size_t pointer_size = InstructionSetPointerSize(isa_); PatchOatArtMethodVisitor visitor(this); - section.VisitPackedArtMethods(&visitor, heap_->Begin(), pointer_size); + image_header->VisitPackedArtMethods(&visitor, heap_->Begin(), pointer_size); +} + +void PatchOat::PatchImtConflictTables(const ImageHeader* image_header) { + const size_t pointer_size = InstructionSetPointerSize(isa_); + // We can safely walk target image since the conflict tables are independent. + image_header->VisitPackedImtConflictTables( + [this](ArtMethod* method) { + return RelocatedAddressOfPointer(method); + }, + image_->Begin(), + pointer_size); } class FixupRootVisitor : public RootVisitor { @@ -627,6 +636,7 @@ bool PatchOat::PatchImage(bool primary_image) { PatchArtFields(image_header); PatchArtMethods(image_header); + PatchImtConflictTables(image_header); PatchInternedStrings(image_header); PatchClassTable(image_header); // Patch dex file int/long arrays which point to ArtFields. @@ -650,12 +660,6 @@ bool PatchOat::PatchImage(bool primary_image) { return true; } -bool PatchOat::InHeap(mirror::Object* o) { - uintptr_t begin = reinterpret_cast<uintptr_t>(heap_->Begin()); - uintptr_t end = reinterpret_cast<uintptr_t>(heap_->End()); - uintptr_t obj = reinterpret_cast<uintptr_t>(o); - return o == nullptr || (begin <= obj && obj < end); -} void PatchOat::PatchVisitor::operator() (mirror::Object* obj, MemberOffset off, bool is_static_unused ATTRIBUTE_UNUSED) const { @@ -668,7 +672,8 @@ void PatchOat::PatchVisitor::operator() (mirror::Class* cls ATTRIBUTE_UNUSED, mirror::Reference* ref) const { MemberOffset off = mirror::Reference::ReferentOffset(); mirror::Object* referent = ref->GetReferent(); - DCHECK(patcher_->InHeap(referent)) << "Referent is not in the heap."; + DCHECK(referent == nullptr || + Runtime::Current()->GetHeap()->ObjectIsInBootImageSpace(referent)) << referent; mirror::Object* moved_object = patcher_->RelocatedAddressOfPointer(referent); copy_->SetFieldObjectWithoutWriteBarrier<false, true, kVerifyNone>(off, moved_object); } @@ -730,6 +735,7 @@ void PatchOat::FixupMethod(ArtMethod* object, ArtMethod* copy) { RelocatedAddressOfPointer(object->GetDexCacheResolvedTypes(pointer_size)), pointer_size); copy->SetEntryPointFromQuickCompiledCodePtrSize(RelocatedAddressOfPointer( object->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size)), pointer_size); + // No special handling for IMT conflict table since all pointers are moved by the same offset. copy->SetEntryPointFromJniPtrSize(RelocatedAddressOfPointer( object->GetEntryPointFromJniPtrSize(pointer_size)), pointer_size); } diff --git a/patchoat/patchoat.h b/patchoat/patchoat.h index a6a8feeb3c..3ef837fde9 100644 --- a/patchoat/patchoat.h +++ b/patchoat/patchoat.h @@ -106,7 +106,6 @@ class PatchOat { SHARED_REQUIRES(Locks::mutator_lock_); void FixupMethod(ArtMethod* object, ArtMethod* copy) SHARED_REQUIRES(Locks::mutator_lock_); - bool InHeap(mirror::Object*); // Patches oat in place, modifying the oat_file given to the constructor. bool PatchElf(); @@ -118,6 +117,8 @@ class PatchOat { bool PatchImage(bool primary_image) SHARED_REQUIRES(Locks::mutator_lock_); void PatchArtFields(const ImageHeader* image_header) SHARED_REQUIRES(Locks::mutator_lock_); void PatchArtMethods(const ImageHeader* image_header) SHARED_REQUIRES(Locks::mutator_lock_); + void PatchImtConflictTables(const ImageHeader* image_header) + SHARED_REQUIRES(Locks::mutator_lock_); void PatchInternedStrings(const ImageHeader* image_header) SHARED_REQUIRES(Locks::mutator_lock_); void PatchClassTable(const ImageHeader* image_header) diff --git a/profman/profile_assistant.cc b/profman/profile_assistant.cc index 58e8a3a7d1..a25460e427 100644 --- a/profman/profile_assistant.cc +++ b/profman/profile_assistant.cc @@ -21,44 +21,41 @@ namespace art { -// Minimum number of new methods that profiles must contain to enable recompilation. +// Minimum number of new methods/classes that profiles +// must contain to enable recompilation. static constexpr const uint32_t kMinNewMethodsForCompilation = 10; +static constexpr const uint32_t kMinNewClassesForCompilation = 10; ProfileAssistant::ProcessingResult ProfileAssistant::ProcessProfilesInternal( const std::vector<ScopedFlock>& profile_files, const ScopedFlock& reference_profile_file) { DCHECK(!profile_files.empty()); - std::vector<ProfileCompilationInfo> new_info(profile_files.size()); - bool should_compile = false; - // Read the main profile files. - for (size_t i = 0; i < new_info.size(); i++) { - if (!new_info[i].Load(profile_files[i].GetFile()->Fd())) { - LOG(WARNING) << "Could not load profile file at index " << i; - return kErrorBadProfiles; - } - // Do we have enough new profiled methods that will make the compilation worthwhile? - should_compile |= (new_info[i].GetNumberOfMethods() > kMinNewMethodsForCompilation); - } - - if (!should_compile) { - return kSkipCompilation; - } - - // Merge information. ProfileCompilationInfo info; + // Load the reference profile. if (!info.Load(reference_profile_file.GetFile()->Fd())) { LOG(WARNING) << "Could not load reference profile file"; return kErrorBadProfiles; } - for (size_t i = 0; i < new_info.size(); i++) { - // Merge all data into a single object. - if (!info.Load(new_info[i])) { - LOG(WARNING) << "Could not merge profile data at index " << i; + // Store the current state of the reference profile before merging with the current profiles. + uint32_t number_of_methods = info.GetNumberOfMethods(); + uint32_t number_of_classes = info.GetNumberOfResolvedClasses(); + + // Merge all current profiles. + for (size_t i = 0; i < profile_files.size(); i++) { + if (!info.Load(profile_files[i].GetFile()->Fd())) { + LOG(WARNING) << "Could not load profile file at index " << i; return kErrorBadProfiles; } } + + // Check if there is enough new information added by the current profiles. + if (((info.GetNumberOfMethods() - number_of_methods) < kMinNewMethodsForCompilation) && + ((info.GetNumberOfResolvedClasses() - number_of_classes) < kMinNewClassesForCompilation)) { + return kSkipCompilation; + } + // We were successful in merging all profile information. Update the reference profile. if (!reference_profile_file.GetFile()->ClearContent()) { PLOG(WARNING) << "Could not clear reference profile file"; diff --git a/profman/profile_assistant_test.cc b/profman/profile_assistant_test.cc index b0d5df2b3b..462c39735e 100644 --- a/profman/profile_assistant_test.cc +++ b/profman/profile_assistant_test.cc @@ -29,6 +29,7 @@ class ProfileAssistantTest : public CommonRuntimeTest { void SetupProfile(const std::string& id, uint32_t checksum, uint16_t number_of_methods, + uint16_t number_of_classes, const ScratchFile& profile, ProfileCompilationInfo* info, uint16_t start_method_index = 0) { @@ -40,6 +41,10 @@ class ProfileAssistantTest : public CommonRuntimeTest { ASSERT_TRUE(info->AddMethodIndex(dex_location1, dex_location_checksum1, i)); ASSERT_TRUE(info->AddMethodIndex(dex_location2, dex_location_checksum2, i)); } + for (uint16_t i = 0; i < number_of_classes; i++) { + ASSERT_TRUE(info->AddClassIndex(dex_location1, dex_location_checksum1, i)); + } + ASSERT_TRUE(info->Save(GetFd(profile))); ASSERT_EQ(0, profile.GetFile()->Flush()); ASSERT_TRUE(profile.GetFile()->ResetOffset()); @@ -89,9 +94,9 @@ TEST_F(ProfileAssistantTest, AdviseCompilationEmptyReferences) { const uint16_t kNumberOfMethodsToEnableCompilation = 100; ProfileCompilationInfo info1; - SetupProfile("p1", 1, kNumberOfMethodsToEnableCompilation, profile1, &info1); + SetupProfile("p1", 1, kNumberOfMethodsToEnableCompilation, 0, profile1, &info1); ProfileCompilationInfo info2; - SetupProfile("p2", 2, kNumberOfMethodsToEnableCompilation, profile2, &info2); + SetupProfile("p2", 2, kNumberOfMethodsToEnableCompilation, 0, profile2, &info2); // We should advise compilation. ASSERT_EQ(ProfileAssistant::kCompile, @@ -102,8 +107,8 @@ TEST_F(ProfileAssistantTest, AdviseCompilationEmptyReferences) { ASSERT_TRUE(result.Load(reference_profile_fd)); ProfileCompilationInfo expected; - ASSERT_TRUE(expected.Load(info1)); - ASSERT_TRUE(expected.Load(info2)); + ASSERT_TRUE(expected.MergeWith(info1)); + ASSERT_TRUE(expected.MergeWith(info2)); ASSERT_TRUE(expected.Equals(result)); // The information from profiles must remain the same. @@ -111,6 +116,35 @@ TEST_F(ProfileAssistantTest, AdviseCompilationEmptyReferences) { CheckProfileInfo(profile2, info2); } +// TODO(calin): Add more tests for classes. +TEST_F(ProfileAssistantTest, AdviseCompilationEmptyReferencesBecauseOfClasses) { + ScratchFile profile1; + ScratchFile reference_profile; + + std::vector<int> profile_fds({ + GetFd(profile1)}); + int reference_profile_fd = GetFd(reference_profile); + + const uint16_t kNumberOfClassesToEnableCompilation = 100; + ProfileCompilationInfo info1; + SetupProfile("p1", 1, 0, kNumberOfClassesToEnableCompilation, profile1, &info1); + + // We should advise compilation. + ASSERT_EQ(ProfileAssistant::kCompile, + ProcessProfiles(profile_fds, reference_profile_fd)); + // The resulting compilation info must be equal to the merge of the inputs. + ProfileCompilationInfo result; + ASSERT_TRUE(reference_profile.GetFile()->ResetOffset()); + ASSERT_TRUE(result.Load(reference_profile_fd)); + + ProfileCompilationInfo expected; + ASSERT_TRUE(expected.MergeWith(info1)); + ASSERT_TRUE(expected.Equals(result)); + + // The information from profiles must remain the same. + CheckProfileInfo(profile1, info1); +} + TEST_F(ProfileAssistantTest, AdviseCompilationNonEmptyReferences) { ScratchFile profile1; ScratchFile profile2; @@ -124,15 +158,15 @@ TEST_F(ProfileAssistantTest, AdviseCompilationNonEmptyReferences) { // The new profile info will contain the methods with indices 0-100. const uint16_t kNumberOfMethodsToEnableCompilation = 100; ProfileCompilationInfo info1; - SetupProfile("p1", 1, kNumberOfMethodsToEnableCompilation, profile1, &info1); + SetupProfile("p1", 1, kNumberOfMethodsToEnableCompilation, 0, profile1, &info1); ProfileCompilationInfo info2; - SetupProfile("p2", 2, kNumberOfMethodsToEnableCompilation, profile2, &info2); + SetupProfile("p2", 2, kNumberOfMethodsToEnableCompilation, 0, profile2, &info2); // The reference profile info will contain the methods with indices 50-150. const uint16_t kNumberOfMethodsAlreadyCompiled = 100; ProfileCompilationInfo reference_info; - SetupProfile("p1", 1, kNumberOfMethodsAlreadyCompiled, reference_profile, + SetupProfile("p1", 1, kNumberOfMethodsAlreadyCompiled, 0, reference_profile, &reference_info, kNumberOfMethodsToEnableCompilation / 2); // We should advise compilation. @@ -145,9 +179,9 @@ TEST_F(ProfileAssistantTest, AdviseCompilationNonEmptyReferences) { ASSERT_TRUE(result.Load(reference_profile_fd)); ProfileCompilationInfo expected; - ASSERT_TRUE(expected.Load(info1)); - ASSERT_TRUE(expected.Load(info2)); - ASSERT_TRUE(expected.Load(reference_info)); + ASSERT_TRUE(expected.MergeWith(info1)); + ASSERT_TRUE(expected.MergeWith(info2)); + ASSERT_TRUE(expected.MergeWith(reference_info)); ASSERT_TRUE(expected.Equals(result)); // The information from profiles must remain the same. @@ -167,9 +201,9 @@ TEST_F(ProfileAssistantTest, DoNotAdviseCompilation) { const uint16_t kNumberOfMethodsToSkipCompilation = 1; ProfileCompilationInfo info1; - SetupProfile("p1", 1, kNumberOfMethodsToSkipCompilation, profile1, &info1); + SetupProfile("p1", 1, kNumberOfMethodsToSkipCompilation, 0, profile1, &info1); ProfileCompilationInfo info2; - SetupProfile("p2", 2, kNumberOfMethodsToSkipCompilation, profile2, &info2); + SetupProfile("p2", 2, kNumberOfMethodsToSkipCompilation, 0, profile2, &info2); // We should not advise compilation. ASSERT_EQ(ProfileAssistant::kSkipCompilation, @@ -207,9 +241,9 @@ TEST_F(ProfileAssistantTest, FailProcessingBecauseOfProfiles) { const uint16_t kNumberOfMethodsToEnableCompilation = 100; // Assign different hashes for the same dex file. This will make merging of information to fail. ProfileCompilationInfo info1; - SetupProfile("p1", 1, kNumberOfMethodsToEnableCompilation, profile1, &info1); + SetupProfile("p1", 1, kNumberOfMethodsToEnableCompilation, 0, profile1, &info1); ProfileCompilationInfo info2; - SetupProfile("p1", 2, kNumberOfMethodsToEnableCompilation, profile2, &info2); + SetupProfile("p1", 2, kNumberOfMethodsToEnableCompilation, 0, profile2, &info2); // We should fail processing. ASSERT_EQ(ProfileAssistant::kErrorBadProfiles, @@ -234,9 +268,9 @@ TEST_F(ProfileAssistantTest, FailProcessingBecauseOfReferenceProfiles) { const uint16_t kNumberOfMethodsToEnableCompilation = 100; // Assign different hashes for the same dex file. This will make merging of information to fail. ProfileCompilationInfo info1; - SetupProfile("p1", 1, kNumberOfMethodsToEnableCompilation, profile1, &info1); + SetupProfile("p1", 1, kNumberOfMethodsToEnableCompilation, 0, profile1, &info1); ProfileCompilationInfo reference_info; - SetupProfile("p1", 2, kNumberOfMethodsToEnableCompilation, reference_profile, &reference_info); + SetupProfile("p1", 2, kNumberOfMethodsToEnableCompilation, 0, reference_profile, &reference_info); // We should not advise compilation. ASSERT_TRUE(profile1.GetFile()->ResetOffset()); diff --git a/profman/profman.cc b/profman/profman.cc index 7c9e449ed5..4d9276f15b 100644 --- a/profman/profman.cc +++ b/profman/profman.cc @@ -14,12 +14,14 @@ * limitations under the License. */ +#include "errno.h" #include <stdio.h> #include <stdlib.h> #include <sys/file.h> #include <sys/stat.h> #include <unistd.h> +#include <iostream> #include <string> #include <vector> @@ -68,6 +70,9 @@ NO_RETURN static void Usage(const char *fmt, ...) { UsageError("Command: %s", CommandLine().c_str()); UsageError("Usage: profman [options]..."); UsageError(""); + UsageError(" --dump-info-for=<filename>: dumps the content of the profile file"); + UsageError(" to standard output in a human readable form."); + UsageError(""); UsageError(" --profile-file=<filename>: specify profiler output file to use for compilation."); UsageError(" Can be specified multiple time, in which case the data from the different"); UsageError(" profiles will be aggregated."); @@ -117,9 +122,11 @@ class ProfMan FINAL { const StringPiece option(argv[i]); const bool log_options = false; if (log_options) { - LOG(INFO) << "patchoat: option[" << i << "]=" << argv[i]; + LOG(INFO) << "profman: option[" << i << "]=" << argv[i]; } - if (option.starts_with("--profile-file=")) { + if (option.starts_with("--dump-info-for=")) { + dump_info_for_ = option.substr(strlen("--dump-info-for=")).ToString(); + } else if (option.starts_with("--profile-file=")) { profile_files_.push_back(option.substr(strlen("--profile-file=")).ToString()); } else if (option.starts_with("--profile-file-fd=")) { ParseFdForCollection(option, "--profile-file-fd", &profile_files_fd_); @@ -132,13 +139,23 @@ class ProfMan FINAL { } } - if (profile_files_.empty() && profile_files_fd_.empty()) { + bool has_profiles = !profile_files_.empty() || !profile_files_fd_.empty(); + bool has_reference_profile = !reference_profile_file_.empty() || + (reference_profile_file_fd_ != -1); + + if (!dump_info_for_.empty()) { + if (has_profiles || has_reference_profile) { + Usage("dump-info-for cannot be specified together with other options"); + } + return; + } + if (!has_profiles) { Usage("No profile files specified."); } if (!profile_files_.empty() && !profile_files_fd_.empty()) { Usage("Profile files should not be specified with both --profile-file-fd and --profile-file"); } - if (!reference_profile_file_.empty() && (reference_profile_file_fd_ != -1)) { + if (!has_reference_profile) { Usage("--reference-profile-file-fd should only be supplied with --profile-file-fd"); } if (reference_profile_file_.empty() && (reference_profile_file_fd_ == -1)) { @@ -160,6 +177,26 @@ class ProfMan FINAL { return result; } + int DumpProfileInfo() { + int fd = open(dump_info_for_.c_str(), O_RDWR); + if (fd < 0) { + std::cerr << "Cannot open " << dump_info_for_ << strerror(errno); + return -1; + } + ProfileCompilationInfo info; + if (!info.Load(fd)) { + std::cerr << "Cannot load profile info from " << dump_info_for_; + return -1; + } + std::string dump = info.DumpInfo(/*dex_files*/ nullptr); + std::cout << dump << "\n"; + return 0; + } + + bool ShouldOnlyDumpProfile() { + return !dump_info_for_.empty(); + } + private: static void ParseFdForCollection(const StringPiece& option, const char* arg_name, @@ -178,7 +215,11 @@ class ProfMan FINAL { } void LogCompletionTime() { - LOG(INFO) << "profman took " << PrettyDuration(NanoTime() - start_ns_); + static constexpr uint64_t kLogThresholdTime = MsToNs(100); // 100ms + uint64_t time_taken = NanoTime() - start_ns_; + if (time_taken > kLogThresholdTime) { + LOG(WARNING) << "profman took " << PrettyDuration(time_taken); + } } std::vector<std::string> profile_files_; @@ -186,6 +227,7 @@ class ProfMan FINAL { std::string reference_profile_file_; int reference_profile_file_fd_; uint64_t start_ns_; + std::string dump_info_for_; }; // See ProfileAssistant::ProcessingResult for return codes. @@ -195,6 +237,9 @@ static int profman(int argc, char** argv) { // Parse arguments. Argument mistakes will lead to exit(EXIT_FAILURE) in UsageError. profman.ParseArgs(argc, argv); + if (profman.ShouldOnlyDumpProfile()) { + return profman.DumpProfileInfo(); + } // Process profile information and assess if we need to do a profile guided compilation. // This operation involves I/O. return profman.ProcessProfiles(); diff --git a/runtime/Android.mk b/runtime/Android.mk index c85907987b..aa12c83ceb 100644 --- a/runtime/Android.mk +++ b/runtime/Android.mk @@ -106,7 +106,6 @@ LIBART_COMMON_SRC_FILES := \ jit/debugger_interface.cc \ jit/jit.cc \ jit/jit_code_cache.cc \ - jit/jit_instrumentation.cc \ jit/offline_profiling_info.cc \ jit/profiling_info.cc \ jit/profile_saver.cc \ diff --git a/runtime/arch/arm/entrypoints_init_arm.cc b/runtime/arch/arm/entrypoints_init_arm.cc index e358ff879c..4c68862c7f 100644 --- a/runtime/arch/arm/entrypoints_init_arm.cc +++ b/runtime/arch/arm/entrypoints_init_arm.cc @@ -17,6 +17,7 @@ #include "entrypoints/jni/jni_entrypoints.h" #include "entrypoints/quick/quick_alloc_entrypoints.h" #include "entrypoints/quick/quick_default_externs.h" +#include "entrypoints/quick/quick_default_init_entrypoints.h" #include "entrypoints/quick/quick_entrypoints.h" #include "entrypoints/entrypoint_utils.h" #include "entrypoints/math_entrypoints.h" @@ -47,67 +48,12 @@ extern "C" int __aeabi_idivmod(int32_t, int32_t); // [DIV|REM]_INT[_2ADDR|_LIT8 extern "C" int64_t __aeabi_ldivmod(int64_t, int64_t); void InitEntryPoints(JniEntryPoints* jpoints, QuickEntryPoints* qpoints) { - // JNI - jpoints->pDlsymLookup = art_jni_dlsym_lookup_stub; - - // Alloc - ResetQuickAllocEntryPoints(qpoints); + DefaultInitEntryPoints(jpoints, qpoints); // Cast qpoints->pInstanceofNonTrivial = artIsAssignableFromCode; qpoints->pCheckCast = art_quick_check_cast; - // DexCache - qpoints->pInitializeStaticStorage = art_quick_initialize_static_storage; - qpoints->pInitializeTypeAndVerifyAccess = art_quick_initialize_type_and_verify_access; - qpoints->pInitializeType = art_quick_initialize_type; - qpoints->pResolveString = art_quick_resolve_string; - - // Field - qpoints->pSet8Instance = art_quick_set8_instance; - qpoints->pSet8Static = art_quick_set8_static; - qpoints->pSet16Instance = art_quick_set16_instance; - qpoints->pSet16Static = art_quick_set16_static; - qpoints->pSet32Instance = art_quick_set32_instance; - qpoints->pSet32Static = art_quick_set32_static; - qpoints->pSet64Instance = art_quick_set64_instance; - qpoints->pSet64Static = art_quick_set64_static; - qpoints->pSetObjInstance = art_quick_set_obj_instance; - qpoints->pSetObjStatic = art_quick_set_obj_static; - qpoints->pGetByteInstance = art_quick_get_byte_instance; - qpoints->pGetBooleanInstance = art_quick_get_boolean_instance; - qpoints->pGetShortInstance = art_quick_get_short_instance; - qpoints->pGetCharInstance = art_quick_get_char_instance; - qpoints->pGet32Instance = art_quick_get32_instance; - qpoints->pGet64Instance = art_quick_get64_instance; - qpoints->pGetObjInstance = art_quick_get_obj_instance; - qpoints->pGetByteStatic = art_quick_get_byte_static; - qpoints->pGetBooleanStatic = art_quick_get_boolean_static; - qpoints->pGetShortStatic = art_quick_get_short_static; - qpoints->pGetCharStatic = art_quick_get_char_static; - qpoints->pGet32Static = art_quick_get32_static; - qpoints->pGet64Static = art_quick_get64_static; - qpoints->pGetObjStatic = art_quick_get_obj_static; - - // Array - qpoints->pAputObjectWithNullAndBoundCheck = art_quick_aput_obj_with_null_and_bound_check; - qpoints->pAputObjectWithBoundCheck = art_quick_aput_obj_with_bound_check; - qpoints->pAputObject = art_quick_aput_obj; - qpoints->pHandleFillArrayData = art_quick_handle_fill_data; - - // JNI - qpoints->pJniMethodStart = JniMethodStart; - qpoints->pJniMethodStartSynchronized = JniMethodStartSynchronized; - qpoints->pJniMethodEnd = JniMethodEnd; - qpoints->pJniMethodEndSynchronized = JniMethodEndSynchronized; - qpoints->pJniMethodEndWithReference = JniMethodEndWithReference; - qpoints->pJniMethodEndWithReferenceSynchronized = JniMethodEndWithReferenceSynchronized; - qpoints->pQuickGenericJniTrampoline = art_quick_generic_jni_trampoline; - - // Locks - qpoints->pLockObject = art_quick_lock_object; - qpoints->pUnlockObject = art_quick_unlock_object; - // Math qpoints->pIdivmod = __aeabi_idivmod; qpoints->pLdiv = __aeabi_ldivmod; @@ -151,38 +97,10 @@ void InitEntryPoints(JniEntryPoints* jpoints, QuickEntryPoints* qpoints) { // Intrinsics qpoints->pIndexOf = art_quick_indexof; - qpoints->pStringCompareTo = art_quick_string_compareto; + // The ARM StringCompareTo intrinsic does not call the runtime. + qpoints->pStringCompareTo = nullptr; qpoints->pMemcpy = memcpy; - // Invocation - qpoints->pQuickImtConflictTrampoline = art_quick_imt_conflict_trampoline; - qpoints->pQuickResolutionTrampoline = art_quick_resolution_trampoline; - qpoints->pQuickToInterpreterBridge = art_quick_to_interpreter_bridge; - qpoints->pInvokeDirectTrampolineWithAccessCheck = - art_quick_invoke_direct_trampoline_with_access_check; - qpoints->pInvokeInterfaceTrampolineWithAccessCheck = - art_quick_invoke_interface_trampoline_with_access_check; - qpoints->pInvokeStaticTrampolineWithAccessCheck = - art_quick_invoke_static_trampoline_with_access_check; - qpoints->pInvokeSuperTrampolineWithAccessCheck = - art_quick_invoke_super_trampoline_with_access_check; - qpoints->pInvokeVirtualTrampolineWithAccessCheck = - art_quick_invoke_virtual_trampoline_with_access_check; - - // Thread - qpoints->pTestSuspend = art_quick_test_suspend; - - // Throws - qpoints->pDeliverException = art_quick_deliver_exception; - qpoints->pThrowArrayBounds = art_quick_throw_array_bounds; - qpoints->pThrowDivZero = art_quick_throw_div_zero; - qpoints->pThrowNoSuchMethod = art_quick_throw_no_such_method; - qpoints->pThrowNullPointer = art_quick_throw_null_pointer_exception; - qpoints->pThrowStackOverflow = art_quick_throw_stack_overflow; - - // Deoptimization from compiled code. - qpoints->pDeoptimize = art_quick_deoptimize_from_compiled_code; - // Read barrier. qpoints->pReadBarrierJni = ReadBarrierJni; qpoints->pReadBarrierMark = artReadBarrierMark; diff --git a/runtime/arch/arm/instruction_set_features_arm.cc b/runtime/arch/arm/instruction_set_features_arm.cc index 51f992b056..ffac0307b7 100644 --- a/runtime/arch/arm/instruction_set_features_arm.cc +++ b/runtime/arch/arm/instruction_set_features_arm.cc @@ -16,7 +16,7 @@ #include "instruction_set_features_arm.h" -#if defined(__ANDROID__) && defined(__arm__) +#if defined(ART_TARGET_ANDROID) && defined(__arm__) #include <sys/auxv.h> #include <asm/hwcap.h> #endif @@ -166,7 +166,7 @@ const ArmInstructionSetFeatures* ArmInstructionSetFeatures::FromHwcap() { bool has_div = false; bool has_lpae = false; -#if defined(__ANDROID__) && defined(__arm__) +#if defined(ART_TARGET_ANDROID) && defined(__arm__) uint64_t hwcaps = getauxval(AT_HWCAP); LOG(INFO) << "hwcaps=" << hwcaps; if ((hwcaps & HWCAP_IDIVT) != 0) { diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S index da7db1dbb4..1bba4f9b01 100644 --- a/runtime/arch/arm/quick_entrypoints_arm.S +++ b/runtime/arch/arm/quick_entrypoints_arm.S @@ -544,6 +544,15 @@ ENTRY art_quick_lock_object DELIVER_PENDING_EXCEPTION END art_quick_lock_object +ENTRY art_quick_lock_object_no_inline + SETUP_REFS_ONLY_CALLEE_SAVE_FRAME r1, r2 @ save callee saves in case we block + mov r1, r9 @ pass Thread::Current + bl artLockObjectFromCode @ (Object* obj, Thread*) + RESTORE_REFS_ONLY_CALLEE_SAVE_FRAME + RETURN_IF_RESULT_IS_ZERO + DELIVER_PENDING_EXCEPTION +END art_quick_lock_object_no_inline + /* * Entry from managed code that calls artUnlockObjectFromCode and delivers exception on failure. * r0 holds the possibly null object to lock. @@ -601,6 +610,16 @@ ENTRY art_quick_unlock_object DELIVER_PENDING_EXCEPTION END art_quick_unlock_object +ENTRY art_quick_unlock_object_no_inline + @ save callee saves in case exception allocation triggers GC + SETUP_REFS_ONLY_CALLEE_SAVE_FRAME r1, r2 + mov r1, r9 @ pass Thread::Current + bl artUnlockObjectFromCode @ (Object* obj, Thread*) + RESTORE_REFS_ONLY_CALLEE_SAVE_FRAME + RETURN_IF_RESULT_IS_ZERO + DELIVER_PENDING_EXCEPTION +END art_quick_unlock_object_no_inline + /* * Entry from managed code that calls artIsAssignableFromCode and on failure calls * artThrowClassCastException. @@ -1660,145 +1679,6 @@ ENTRY art_quick_indexof pop {r4, r10-r11, pc} END art_quick_indexof - /* - * String's compareTo. - * - * Requires rARG0/rARG1 to have been previously checked for null. Will - * return negative if this's string is < comp, 0 if they are the - * same and positive if >. - * - * On entry: - * r0: this object pointer - * r1: comp object pointer - * - */ - .extern __memcmp16 -ENTRY art_quick_string_compareto - mov r2, r0 @ this to r2, opening up r0 for return value - sub r0, r2, r1 @ Same? - cbnz r0,1f - bx lr -1: @ Same strings, return. - - push {r4, r7-r12, lr} @ 8 words - keep alignment - .cfi_adjust_cfa_offset 32 - .cfi_rel_offset r4, 0 - .cfi_rel_offset r7, 4 - .cfi_rel_offset r8, 8 - .cfi_rel_offset r9, 12 - .cfi_rel_offset r10, 16 - .cfi_rel_offset r11, 20 - .cfi_rel_offset r12, 24 - .cfi_rel_offset lr, 28 - - ldr r7, [r2, #MIRROR_STRING_COUNT_OFFSET] - ldr r10, [r1, #MIRROR_STRING_COUNT_OFFSET] - add r2, #MIRROR_STRING_VALUE_OFFSET - add r1, #MIRROR_STRING_VALUE_OFFSET - - /* - * At this point, we have: - * value: r2/r1 - * offset: r4/r9 - * count: r7/r10 - * We're going to compute - * r11 <- countDiff - * r10 <- minCount - */ - subs r11, r7, r10 - it ls - movls r10, r7 - - /* - * Note: data pointers point to previous element so we can use pre-index - * mode with base writeback. - */ - subs r2, #2 @ offset to contents[-1] - subs r1, #2 @ offset to contents[-1] - - /* - * At this point we have: - * r2: *this string data - * r1: *comp string data - * r10: iteration count for comparison - * r11: value to return if the first part of the string is equal - * r0: reserved for result - * r3, r4, r7, r8, r9, r12 available for loading string data - */ - - subs r10, #2 - blt .Ldo_remainder2 - - /* - * Unroll the first two checks so we can quickly catch early mismatch - * on long strings (but preserve incoming alignment) - */ - - ldrh r3, [r2, #2]! - ldrh r4, [r1, #2]! - ldrh r7, [r2, #2]! - ldrh r8, [r1, #2]! - subs r0, r3, r4 - it eq - subseq r0, r7, r8 - bne .Ldone - cmp r10, #28 - bgt .Ldo_memcmp16 - subs r10, #3 - blt .Ldo_remainder - -.Lloopback_triple: - ldrh r3, [r2, #2]! - ldrh r4, [r1, #2]! - ldrh r7, [r2, #2]! - ldrh r8, [r1, #2]! - ldrh r9, [r2, #2]! - ldrh r12,[r1, #2]! - subs r0, r3, r4 - it eq - subseq r0, r7, r8 - it eq - subseq r0, r9, r12 - bne .Ldone - subs r10, #3 - bge .Lloopback_triple - -.Ldo_remainder: - adds r10, #3 - beq .Lreturn_diff - -.Lloopback_single: - ldrh r3, [r2, #2]! - ldrh r4, [r1, #2]! - subs r0, r3, r4 - bne .Ldone - subs r10, #1 - bne .Lloopback_single - -.Lreturn_diff: - mov r0, r11 - pop {r4, r7-r12, pc} - -.Ldo_remainder2: - adds r10, #2 - bne .Lloopback_single - mov r0, r11 - pop {r4, r7-r12, pc} - - /* Long string case */ -.Ldo_memcmp16: - mov r7, r11 - add r0, r2, #2 - add r1, r1, #2 - mov r2, r10 - bl __memcmp16 - cmp r0, #0 - it eq - moveq r0, r7 -.Ldone: - pop {r4, r7-r12, pc} -END art_quick_string_compareto - /* Assembly routines used to handle ABI differences. */ /* double fmod(double a, double b) */ @@ -1832,7 +1712,7 @@ ENTRY art_quick_fmodf add sp, #4 .cfi_adjust_cfa_offset -4 pop {pc} -END art_quick_fmod +END art_quick_fmodf /* int64_t art_d2l(double d) */ .extern art_d2l diff --git a/runtime/arch/arm64/entrypoints_init_arm64.cc b/runtime/arch/arm64/entrypoints_init_arm64.cc index 4db941174d..bf0f6470d1 100644 --- a/runtime/arch/arm64/entrypoints_init_arm64.cc +++ b/runtime/arch/arm64/entrypoints_init_arm64.cc @@ -17,6 +17,7 @@ #include "entrypoints/jni/jni_entrypoints.h" #include "entrypoints/quick/quick_alloc_entrypoints.h" #include "entrypoints/quick/quick_default_externs.h" +#include "entrypoints/quick/quick_default_init_entrypoints.h" #include "entrypoints/quick/quick_entrypoints.h" #include "entrypoints/entrypoint_utils.h" #include "entrypoints/math_entrypoints.h" @@ -30,67 +31,12 @@ extern "C" uint32_t artIsAssignableFromCode(const mirror::Class* klass, const mirror::Class* ref_class); void InitEntryPoints(JniEntryPoints* jpoints, QuickEntryPoints* qpoints) { - // JNI - jpoints->pDlsymLookup = art_jni_dlsym_lookup_stub; - - // Alloc - ResetQuickAllocEntryPoints(qpoints); + DefaultInitEntryPoints(jpoints, qpoints); // Cast qpoints->pInstanceofNonTrivial = artIsAssignableFromCode; qpoints->pCheckCast = art_quick_check_cast; - // DexCache - qpoints->pInitializeStaticStorage = art_quick_initialize_static_storage; - qpoints->pInitializeTypeAndVerifyAccess = art_quick_initialize_type_and_verify_access; - qpoints->pInitializeType = art_quick_initialize_type; - qpoints->pResolveString = art_quick_resolve_string; - - // Field - qpoints->pSet8Instance = art_quick_set8_instance; - qpoints->pSet8Static = art_quick_set8_static; - qpoints->pSet16Instance = art_quick_set16_instance; - qpoints->pSet16Static = art_quick_set16_static; - qpoints->pSet32Instance = art_quick_set32_instance; - qpoints->pSet32Static = art_quick_set32_static; - qpoints->pSet64Instance = art_quick_set64_instance; - qpoints->pSet64Static = art_quick_set64_static; - qpoints->pSetObjInstance = art_quick_set_obj_instance; - qpoints->pSetObjStatic = art_quick_set_obj_static; - qpoints->pGetBooleanInstance = art_quick_get_boolean_instance; - qpoints->pGetByteInstance = art_quick_get_byte_instance; - qpoints->pGetCharInstance = art_quick_get_char_instance; - qpoints->pGetShortInstance = art_quick_get_short_instance; - qpoints->pGet32Instance = art_quick_get32_instance; - qpoints->pGet64Instance = art_quick_get64_instance; - qpoints->pGetObjInstance = art_quick_get_obj_instance; - qpoints->pGetBooleanStatic = art_quick_get_boolean_static; - qpoints->pGetByteStatic = art_quick_get_byte_static; - qpoints->pGetCharStatic = art_quick_get_char_static; - qpoints->pGetShortStatic = art_quick_get_short_static; - qpoints->pGet32Static = art_quick_get32_static; - qpoints->pGet64Static = art_quick_get64_static; - qpoints->pGetObjStatic = art_quick_get_obj_static; - - // Array - qpoints->pAputObjectWithNullAndBoundCheck = art_quick_aput_obj_with_null_and_bound_check; - qpoints->pAputObjectWithBoundCheck = art_quick_aput_obj_with_bound_check; - qpoints->pAputObject = art_quick_aput_obj; - qpoints->pHandleFillArrayData = art_quick_handle_fill_data; - - // JNI - qpoints->pJniMethodStart = JniMethodStart; - qpoints->pJniMethodStartSynchronized = JniMethodStartSynchronized; - qpoints->pJniMethodEnd = JniMethodEnd; - qpoints->pJniMethodEndSynchronized = JniMethodEndSynchronized; - qpoints->pJniMethodEndWithReference = JniMethodEndWithReference; - qpoints->pJniMethodEndWithReferenceSynchronized = JniMethodEndWithReferenceSynchronized; - qpoints->pQuickGenericJniTrampoline = art_quick_generic_jni_trampoline; - - // Locks - qpoints->pLockObject = art_quick_lock_object; - qpoints->pUnlockObject = art_quick_unlock_object; - // Math // TODO null entrypoints not needed for ARM64 - generate inline. qpoints->pCmpgDouble = nullptr; @@ -134,38 +80,10 @@ void InitEntryPoints(JniEntryPoints* jpoints, QuickEntryPoints* qpoints) { // Intrinsics qpoints->pIndexOf = art_quick_indexof; - qpoints->pStringCompareTo = art_quick_string_compareto; + // The ARM64 StringCompareTo intrinsic does not call the runtime. + qpoints->pStringCompareTo = nullptr; qpoints->pMemcpy = memcpy; - // Invocation - qpoints->pQuickImtConflictTrampoline = art_quick_imt_conflict_trampoline; - qpoints->pQuickResolutionTrampoline = art_quick_resolution_trampoline; - qpoints->pQuickToInterpreterBridge = art_quick_to_interpreter_bridge; - qpoints->pInvokeDirectTrampolineWithAccessCheck = - art_quick_invoke_direct_trampoline_with_access_check; - qpoints->pInvokeInterfaceTrampolineWithAccessCheck = - art_quick_invoke_interface_trampoline_with_access_check; - qpoints->pInvokeStaticTrampolineWithAccessCheck = - art_quick_invoke_static_trampoline_with_access_check; - qpoints->pInvokeSuperTrampolineWithAccessCheck = - art_quick_invoke_super_trampoline_with_access_check; - qpoints->pInvokeVirtualTrampolineWithAccessCheck = - art_quick_invoke_virtual_trampoline_with_access_check; - - // Thread - qpoints->pTestSuspend = art_quick_test_suspend; - - // Throws - qpoints->pDeliverException = art_quick_deliver_exception; - qpoints->pThrowArrayBounds = art_quick_throw_array_bounds; - qpoints->pThrowDivZero = art_quick_throw_div_zero; - qpoints->pThrowNoSuchMethod = art_quick_throw_no_such_method; - qpoints->pThrowNullPointer = art_quick_throw_null_pointer_exception; - qpoints->pThrowStackOverflow = art_quick_throw_stack_overflow; - - // Deoptimization from compiled code. - qpoints->pDeoptimize = art_quick_deoptimize_from_compiled_code; - // Read barrier. qpoints->pReadBarrierJni = ReadBarrierJni; qpoints->pReadBarrierMark = artReadBarrierMark; diff --git a/runtime/arch/arm64/instruction_set_features_arm64.cc b/runtime/arch/arm64/instruction_set_features_arm64.cc index 613bb5c765..cad13b29d9 100644 --- a/runtime/arch/arm64/instruction_set_features_arm64.cc +++ b/runtime/arch/arm64/instruction_set_features_arm64.cc @@ -39,7 +39,7 @@ const Arm64InstructionSetFeatures* Arm64InstructionSetFeatures::FromVariant( if (!needs_a53_835769_fix) { // Check to see if this is an expected variant. static const char* arm64_known_variants[] = { - "denver64", "kryo" + "denver64", "kryo", "exynos-m1" }; if (!FindVariantInArray(arm64_known_variants, arraysize(arm64_known_variants), variant)) { std::ostringstream os; diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S index 1cdda2d19e..1fba09bae3 100644 --- a/runtime/arch/arm64/quick_entrypoints_arm64.S +++ b/runtime/arch/arm64/quick_entrypoints_arm64.S @@ -1113,6 +1113,14 @@ ENTRY art_quick_lock_object RETURN_IF_W0_IS_ZERO_OR_DELIVER END art_quick_lock_object +ENTRY art_quick_lock_object_no_inline + SETUP_REFS_ONLY_CALLEE_SAVE_FRAME // save callee saves in case we block + mov x1, xSELF // pass Thread::Current + bl artLockObjectFromCode // (Object* obj, Thread*) + RESTORE_REFS_ONLY_CALLEE_SAVE_FRAME + RETURN_IF_W0_IS_ZERO_OR_DELIVER +END art_quick_lock_object_no_inline + /* * Entry from managed code that calls artUnlockObjectFromCode and delivers exception on failure. * x0 holds the possibly null object to lock. @@ -1171,6 +1179,14 @@ ENTRY art_quick_unlock_object RETURN_IF_W0_IS_ZERO_OR_DELIVER END art_quick_unlock_object +ENTRY art_quick_unlock_object_no_inline + SETUP_REFS_ONLY_CALLEE_SAVE_FRAME // save callee saves in case exception allocation triggers GC + mov x1, xSELF // pass Thread::Current + bl artUnlockObjectFromCode // (Object* obj, Thread*) + RESTORE_REFS_ONLY_CALLEE_SAVE_FRAME + RETURN_IF_W0_IS_ZERO_OR_DELIVER +END art_quick_unlock_object_no_inline + /* * Entry from managed code that calls artIsAssignableFromCode and on failure calls * artThrowClassCastException. @@ -2187,108 +2203,3 @@ ENTRY art_quick_indexof asr x0, x0, #1 ret END art_quick_indexof - - /* - * String's compareTo. - * - * TODO: Not very optimized. - * - * On entry: - * x0: this object pointer - * x1: comp object pointer - * - */ - .extern __memcmp16 -ENTRY art_quick_string_compareto - mov x2, x0 // x0 is return, use x2 for first input. - sub x0, x2, x1 // Same string object? - cbnz x0,1f - ret -1: // Different string objects. - - ldr w4, [x2, #MIRROR_STRING_COUNT_OFFSET] - ldr w3, [x1, #MIRROR_STRING_COUNT_OFFSET] - add x2, x2, #MIRROR_STRING_VALUE_OFFSET - add x1, x1, #MIRROR_STRING_VALUE_OFFSET - - /* - * Now: Data* Count - * first arg x2 w4 - * second arg x1 w3 - */ - - // x0 := str1.length(w4) - str2.length(w3). ldr zero-extended w3/w4 into x3/x4. - subs x0, x4, x3 - // Min(count1, count2) into w3. - csel x3, x3, x4, ge - - // TODO: Tune this value. - // Check for long string, do memcmp16 for them. - cmp w3, #28 // Constant from arm32. - bgt .Ldo_memcmp16 - - /* - * Now: - * x2: *first string data - * x1: *second string data - * w3: iteration count - * x0: return value if comparison equal - * x4, x5, x6, x7: free - */ - - // Do a simple unrolled loop. -.Lloop: - // At least two more elements? - subs w3, w3, #2 - b.lt .Lremainder_or_done - - ldrh w4, [x2], #2 - ldrh w5, [x1], #2 - - ldrh w6, [x2], #2 - ldrh w7, [x1], #2 - - subs w4, w4, w5 - b.ne .Lw4_result - - subs w6, w6, w7 - b.ne .Lw6_result - - b .Lloop - -.Lremainder_or_done: - adds w3, w3, #1 - b.eq .Lremainder - ret - -.Lremainder: - ldrh w4, [x2], #2 - ldrh w5, [x1], #2 - subs w4, w4, w5 - b.ne .Lw4_result - ret - -// Result is in w4 -.Lw4_result: - sxtw x0, w4 - ret - -// Result is in w6 -.Lw6_result: - sxtw x0, w6 - ret - -.Ldo_memcmp16: - mov x14, x0 // Save x0 and LR. __memcmp16 does not use these temps. - mov x15, xLR // TODO: Codify and check that? - - mov x0, x2 - uxtw x2, w3 - bl __memcmp16 - - mov xLR, x15 // Restore LR. - - cmp x0, #0 // Check the memcmp difference. - csel x0, x0, x14, ne // x0 := x0 != 0 ? x14(prev x0=length diff) : x1. - ret -END art_quick_string_compareto diff --git a/runtime/arch/instruction_set_features_test.cc b/runtime/arch/instruction_set_features_test.cc index 99c2d4dc74..fb38b47ea7 100644 --- a/runtime/arch/instruction_set_features_test.cc +++ b/runtime/arch/instruction_set_features_test.cc @@ -18,7 +18,7 @@ #include <gtest/gtest.h> -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID #include "cutils/properties.h" #endif @@ -26,7 +26,7 @@ namespace art { -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID #if defined(__aarch64__) TEST(InstructionSetFeaturesTest, DISABLED_FeaturesFromSystemPropertyVariant) { LOG(WARNING) << "Test disabled due to no CPP define for A53 erratum 835769"; @@ -111,7 +111,7 @@ TEST(InstructionSetFeaturesTest, FeaturesFromCpuInfo) { } #endif -#ifndef __ANDROID__ +#ifndef ART_TARGET_ANDROID TEST(InstructionSetFeaturesTest, HostFeaturesFromCppDefines) { std::string error_msg; std::unique_ptr<const InstructionSetFeatures> default_features( diff --git a/runtime/arch/mips/entrypoints_init_mips.cc b/runtime/arch/mips/entrypoints_init_mips.cc index 51eb77f409..45e33a8500 100644 --- a/runtime/arch/mips/entrypoints_init_mips.cc +++ b/runtime/arch/mips/entrypoints_init_mips.cc @@ -59,6 +59,9 @@ extern "C" int64_t __divdi3(int64_t, int64_t); extern "C" int64_t __moddi3(int64_t, int64_t); void InitEntryPoints(JniEntryPoints* jpoints, QuickEntryPoints* qpoints) { + // Note: MIPS has asserts checking for the type of entrypoint. Don't move it + // to InitDefaultEntryPoints(). + // JNI jpoints->pDlsymLookup = art_jni_dlsym_lookup_stub; @@ -167,9 +170,14 @@ void InitEntryPoints(JniEntryPoints* jpoints, QuickEntryPoints* qpoints) { "Non-direct C stub marked direct."); // Locks - qpoints->pLockObject = art_quick_lock_object; + if (UNLIKELY(VLOG_IS_ON(systrace_lock_logging))) { + qpoints->pLockObject = art_quick_lock_object_no_inline; + qpoints->pUnlockObject = art_quick_unlock_object_no_inline; + } else { + qpoints->pLockObject = art_quick_lock_object; + qpoints->pUnlockObject = art_quick_unlock_object; + } static_assert(!IsDirectEntrypoint(kQuickLockObject), "Non-direct C stub marked direct."); - qpoints->pUnlockObject = art_quick_unlock_object; static_assert(!IsDirectEntrypoint(kQuickUnlockObject), "Non-direct C stub marked direct."); // Math diff --git a/runtime/arch/mips/quick_entrypoints_mips.S b/runtime/arch/mips/quick_entrypoints_mips.S index 8939a488e9..3ee26afc4f 100644 --- a/runtime/arch/mips/quick_entrypoints_mips.S +++ b/runtime/arch/mips/quick_entrypoints_mips.S @@ -906,6 +906,16 @@ ENTRY art_quick_lock_object RETURN_IF_ZERO END art_quick_lock_object +ENTRY art_quick_lock_object_no_inline + beqz $a0, .Lart_quick_throw_null_pointer_exception_gp_set + nop + SETUP_REFS_ONLY_CALLEE_SAVE_FRAME # save callee saves in case we block + la $t9, artLockObjectFromCode + jalr $t9 # (Object* obj, Thread*) + move $a1, rSELF # pass Thread::Current + RETURN_IF_ZERO +END art_quick_lock_object_no_inline + /* * Entry from managed code that calls artUnlockObjectFromCode and delivers exception on failure. */ @@ -920,6 +930,16 @@ ENTRY art_quick_unlock_object RETURN_IF_ZERO END art_quick_unlock_object +ENTRY art_quick_unlock_object_no_inline + beqz $a0, .Lart_quick_throw_null_pointer_exception_gp_set + nop + SETUP_REFS_ONLY_CALLEE_SAVE_FRAME # save callee saves in case exception allocation triggers GC + la $t9, artUnlockObjectFromCode + jalr $t9 # (Object* obj, Thread*) + move $a1, rSELF # pass Thread::Current + RETURN_IF_ZERO +END art_quick_unlock_object_no_inline + /* * Entry from managed code that calls artCheckCastFromCode and delivers exception on failure. */ diff --git a/runtime/arch/mips64/entrypoints_init_mips64.cc b/runtime/arch/mips64/entrypoints_init_mips64.cc index 4bdb38e51b..030c12707e 100644 --- a/runtime/arch/mips64/entrypoints_init_mips64.cc +++ b/runtime/arch/mips64/entrypoints_init_mips64.cc @@ -18,6 +18,7 @@ #include "entrypoints/jni/jni_entrypoints.h" #include "entrypoints/quick/quick_alloc_entrypoints.h" #include "entrypoints/quick/quick_default_externs.h" +#include "entrypoints/quick/quick_default_init_entrypoints.h" #include "entrypoints/quick/quick_entrypoints.h" #include "entrypoints/entrypoint_utils.h" #include "entrypoints/math_entrypoints.h" @@ -57,67 +58,12 @@ extern "C" int64_t __divdi3(int64_t, int64_t); extern "C" int64_t __moddi3(int64_t, int64_t); void InitEntryPoints(JniEntryPoints* jpoints, QuickEntryPoints* qpoints) { - // JNI - jpoints->pDlsymLookup = art_jni_dlsym_lookup_stub; - - // Alloc - ResetQuickAllocEntryPoints(qpoints); + DefaultInitEntryPoints(jpoints, qpoints); // Cast qpoints->pInstanceofNonTrivial = artIsAssignableFromCode; qpoints->pCheckCast = art_quick_check_cast; - // DexCache - qpoints->pInitializeStaticStorage = art_quick_initialize_static_storage; - qpoints->pInitializeTypeAndVerifyAccess = art_quick_initialize_type_and_verify_access; - qpoints->pInitializeType = art_quick_initialize_type; - qpoints->pResolveString = art_quick_resolve_string; - - // Field - qpoints->pSet8Instance = art_quick_set8_instance; - qpoints->pSet8Static = art_quick_set8_static; - qpoints->pSet16Instance = art_quick_set16_instance; - qpoints->pSet16Static = art_quick_set16_static; - qpoints->pSet32Instance = art_quick_set32_instance; - qpoints->pSet32Static = art_quick_set32_static; - qpoints->pSet64Instance = art_quick_set64_instance; - qpoints->pSet64Static = art_quick_set64_static; - qpoints->pSetObjInstance = art_quick_set_obj_instance; - qpoints->pSetObjStatic = art_quick_set_obj_static; - qpoints->pGetBooleanInstance = art_quick_get_boolean_instance; - qpoints->pGetByteInstance = art_quick_get_byte_instance; - qpoints->pGetCharInstance = art_quick_get_char_instance; - qpoints->pGetShortInstance = art_quick_get_short_instance; - qpoints->pGet32Instance = art_quick_get32_instance; - qpoints->pGet64Instance = art_quick_get64_instance; - qpoints->pGetObjInstance = art_quick_get_obj_instance; - qpoints->pGetBooleanStatic = art_quick_get_boolean_static; - qpoints->pGetByteStatic = art_quick_get_byte_static; - qpoints->pGetCharStatic = art_quick_get_char_static; - qpoints->pGetShortStatic = art_quick_get_short_static; - qpoints->pGet32Static = art_quick_get32_static; - qpoints->pGet64Static = art_quick_get64_static; - qpoints->pGetObjStatic = art_quick_get_obj_static; - - // Array - qpoints->pAputObjectWithNullAndBoundCheck = art_quick_aput_obj_with_null_and_bound_check; - qpoints->pAputObjectWithBoundCheck = art_quick_aput_obj_with_bound_check; - qpoints->pAputObject = art_quick_aput_obj; - qpoints->pHandleFillArrayData = art_quick_handle_fill_data; - - // JNI - qpoints->pJniMethodStart = JniMethodStart; - qpoints->pJniMethodStartSynchronized = JniMethodStartSynchronized; - qpoints->pJniMethodEnd = JniMethodEnd; - qpoints->pJniMethodEndSynchronized = JniMethodEndSynchronized; - qpoints->pJniMethodEndWithReference = JniMethodEndWithReference; - qpoints->pJniMethodEndWithReferenceSynchronized = JniMethodEndWithReferenceSynchronized; - qpoints->pQuickGenericJniTrampoline = art_quick_generic_jni_trampoline; - - // Locks - qpoints->pLockObject = art_quick_lock_object; - qpoints->pUnlockObject = art_quick_unlock_object; - // Math qpoints->pCmpgDouble = CmpgDouble; qpoints->pCmpgFloat = CmpgFloat; @@ -144,35 +90,6 @@ void InitEntryPoints(JniEntryPoints* jpoints, QuickEntryPoints* qpoints) { qpoints->pStringCompareTo = art_quick_string_compareto; qpoints->pMemcpy = memcpy; - // Invocation - qpoints->pQuickImtConflictTrampoline = art_quick_imt_conflict_trampoline; - qpoints->pQuickResolutionTrampoline = art_quick_resolution_trampoline; - qpoints->pQuickToInterpreterBridge = art_quick_to_interpreter_bridge; - qpoints->pInvokeDirectTrampolineWithAccessCheck = - art_quick_invoke_direct_trampoline_with_access_check; - qpoints->pInvokeInterfaceTrampolineWithAccessCheck = - art_quick_invoke_interface_trampoline_with_access_check; - qpoints->pInvokeStaticTrampolineWithAccessCheck = - art_quick_invoke_static_trampoline_with_access_check; - qpoints->pInvokeSuperTrampolineWithAccessCheck = - art_quick_invoke_super_trampoline_with_access_check; - qpoints->pInvokeVirtualTrampolineWithAccessCheck = - art_quick_invoke_virtual_trampoline_with_access_check; - - // Thread - qpoints->pTestSuspend = art_quick_test_suspend; - - // Throws - qpoints->pDeliverException = art_quick_deliver_exception; - qpoints->pThrowArrayBounds = art_quick_throw_array_bounds; - qpoints->pThrowDivZero = art_quick_throw_div_zero; - qpoints->pThrowNoSuchMethod = art_quick_throw_no_such_method; - qpoints->pThrowNullPointer = art_quick_throw_null_pointer_exception; - qpoints->pThrowStackOverflow = art_quick_throw_stack_overflow; - - // Deoptimization from compiled code. - qpoints->pDeoptimize = art_quick_deoptimize_from_compiled_code; - // TODO - use lld/scd instructions for Mips64 // Atomic 64-bit load/store qpoints->pA64Load = QuasiAtomic::Read64; diff --git a/runtime/arch/mips64/quick_entrypoints_mips64.S b/runtime/arch/mips64/quick_entrypoints_mips64.S index 5d0c94c637..8f1a35a693 100644 --- a/runtime/arch/mips64/quick_entrypoints_mips64.S +++ b/runtime/arch/mips64/quick_entrypoints_mips64.S @@ -971,6 +971,15 @@ ENTRY art_quick_lock_object RETURN_IF_ZERO END art_quick_lock_object +ENTRY art_quick_lock_object_no_inline + beq $a0, $zero, .Lart_quick_throw_null_pointer_exception_gp_set + nop + SETUP_REFS_ONLY_CALLEE_SAVE_FRAME # save callee saves in case we block + jal artLockObjectFromCode # (Object* obj, Thread*) + move $a1, rSELF # pass Thread::Current + RETURN_IF_ZERO +END art_quick_lock_object_no_inline + /* * Entry from managed code that calls artUnlockObjectFromCode and delivers exception on failure. */ @@ -984,6 +993,15 @@ ENTRY art_quick_unlock_object RETURN_IF_ZERO END art_quick_unlock_object +ENTRY art_quick_unlock_object_no_inline + beq $a0, $zero, .Lart_quick_throw_null_pointer_exception_gp_set + nop + SETUP_REFS_ONLY_CALLEE_SAVE_FRAME # save callee saves in case exception allocation triggers GC + jal artUnlockObjectFromCode # (Object* obj, Thread*) + move $a1, rSELF # pass Thread::Current + RETURN_IF_ZERO +END art_quick_unlock_object_no_inline + /* * Entry from managed code that calls artCheckCastFromCode and delivers exception on failure. */ diff --git a/runtime/arch/stub_test.cc b/runtime/arch/stub_test.cc index 75d9073cfb..a7d6d6f793 100644 --- a/runtime/arch/stub_test.cc +++ b/runtime/arch/stub_test.cc @@ -1205,8 +1205,9 @@ TEST_F(StubTest, AllocObjectArray) { TEST_F(StubTest, StringCompareTo) { -#if defined(__i386__) || defined(__arm__) || defined(__aarch64__) || \ - defined(__mips__) || (defined(__x86_64__) && !defined(__APPLE__)) + // There is no StringCompareTo runtime entrypoint for __arm__ or __aarch64__. +#if defined(__i386__) || defined(__mips__) || \ + (defined(__x86_64__) && !defined(__APPLE__)) // TODO: Check the "Unresolved" allocation stubs Thread* self = Thread::Current(); @@ -2010,14 +2011,14 @@ TEST_F(StubTest, DISABLED_IMT) { // that will create it: the runtime stub expects to be called by compiled code. LinearAlloc* linear_alloc = Runtime::Current()->GetLinearAlloc(); ArtMethod* conflict_method = Runtime::Current()->CreateImtConflictMethod(linear_alloc); - static ImtConflictTable::Entry empty_entry = { nullptr, nullptr }; - ImtConflictTable* empty_conflict_table = reinterpret_cast<ImtConflictTable*>(&empty_entry); + ImtConflictTable* empty_conflict_table = + Runtime::Current()->GetClassLinker()->CreateImtConflictTable(/*count*/0u, linear_alloc); void* data = linear_alloc->Alloc( self, - ImtConflictTable::ComputeSizeWithOneMoreEntry(empty_conflict_table)); + ImtConflictTable::ComputeSizeWithOneMoreEntry(empty_conflict_table, sizeof(void*))); ImtConflictTable* new_table = new (data) ImtConflictTable( - empty_conflict_table, inf_contains, contains_amethod); - conflict_method->SetImtConflictTable(new_table); + empty_conflict_table, inf_contains, contains_amethod, sizeof(void*)); + conflict_method->SetImtConflictTable(new_table, sizeof(void*)); size_t result = Invoke3WithReferrerAndHidden(reinterpret_cast<size_t>(conflict_method), diff --git a/runtime/arch/x86/entrypoints_init_x86.cc b/runtime/arch/x86/entrypoints_init_x86.cc index e593f39fd8..15a857146b 100644 --- a/runtime/arch/x86/entrypoints_init_x86.cc +++ b/runtime/arch/x86/entrypoints_init_x86.cc @@ -17,6 +17,7 @@ #include "entrypoints/jni/jni_entrypoints.h" #include "entrypoints/quick/quick_alloc_entrypoints.h" #include "entrypoints/quick/quick_default_externs.h" +#include "entrypoints/quick/quick_default_init_entrypoints.h" #include "entrypoints/quick/quick_entrypoints.h" #include "entrypoints/runtime_asm_entrypoints.h" #include "interpreter/interpreter.h" @@ -33,67 +34,12 @@ extern "C" mirror::Object* art_quick_read_barrier_slow(mirror::Object*, mirror:: extern "C" mirror::Object* art_quick_read_barrier_for_root_slow(GcRoot<mirror::Object>*); void InitEntryPoints(JniEntryPoints* jpoints, QuickEntryPoints* qpoints) { - // JNI - jpoints->pDlsymLookup = art_jni_dlsym_lookup_stub; - - // Alloc - ResetQuickAllocEntryPoints(qpoints); + DefaultInitEntryPoints(jpoints, qpoints); // Cast qpoints->pInstanceofNonTrivial = art_quick_is_assignable; qpoints->pCheckCast = art_quick_check_cast; - // DexCache - qpoints->pInitializeStaticStorage = art_quick_initialize_static_storage; - qpoints->pInitializeTypeAndVerifyAccess = art_quick_initialize_type_and_verify_access; - qpoints->pInitializeType = art_quick_initialize_type; - qpoints->pResolveString = art_quick_resolve_string; - - // Field - qpoints->pSet8Instance = art_quick_set8_instance; - qpoints->pSet8Static = art_quick_set8_static; - qpoints->pSet16Instance = art_quick_set16_instance; - qpoints->pSet16Static = art_quick_set16_static; - qpoints->pSet32Instance = art_quick_set32_instance; - qpoints->pSet32Static = art_quick_set32_static; - qpoints->pSet64Instance = art_quick_set64_instance; - qpoints->pSet64Static = art_quick_set64_static; - qpoints->pSetObjInstance = art_quick_set_obj_instance; - qpoints->pSetObjStatic = art_quick_set_obj_static; - qpoints->pGetByteInstance = art_quick_get_byte_instance; - qpoints->pGetBooleanInstance = art_quick_get_boolean_instance; - qpoints->pGetShortInstance = art_quick_get_short_instance; - qpoints->pGetCharInstance = art_quick_get_char_instance; - qpoints->pGet32Instance = art_quick_get32_instance; - qpoints->pGet64Instance = art_quick_get64_instance; - qpoints->pGetObjInstance = art_quick_get_obj_instance; - qpoints->pGetByteStatic = art_quick_get_byte_static; - qpoints->pGetBooleanStatic = art_quick_get_boolean_static; - qpoints->pGetShortStatic = art_quick_get_short_static; - qpoints->pGetCharStatic = art_quick_get_char_static; - qpoints->pGet32Static = art_quick_get32_static; - qpoints->pGet64Static = art_quick_get64_static; - qpoints->pGetObjStatic = art_quick_get_obj_static; - - // Array - qpoints->pAputObjectWithNullAndBoundCheck = art_quick_aput_obj_with_null_and_bound_check; - qpoints->pAputObjectWithBoundCheck = art_quick_aput_obj_with_bound_check; - qpoints->pAputObject = art_quick_aput_obj; - qpoints->pHandleFillArrayData = art_quick_handle_fill_data; - - // JNI - qpoints->pJniMethodStart = JniMethodStart; - qpoints->pJniMethodStartSynchronized = JniMethodStartSynchronized; - qpoints->pJniMethodEnd = JniMethodEnd; - qpoints->pJniMethodEndSynchronized = JniMethodEndSynchronized; - qpoints->pJniMethodEndWithReference = JniMethodEndWithReference; - qpoints->pJniMethodEndWithReferenceSynchronized = JniMethodEndWithReferenceSynchronized; - qpoints->pQuickGenericJniTrampoline = art_quick_generic_jni_trampoline; - - // Locks - qpoints->pLockObject = art_quick_lock_object; - qpoints->pUnlockObject = art_quick_unlock_object; - // More math. qpoints->pCos = cos; qpoints->pSin = sin; @@ -128,35 +74,6 @@ void InitEntryPoints(JniEntryPoints* jpoints, QuickEntryPoints* qpoints) { qpoints->pStringCompareTo = art_quick_string_compareto; qpoints->pMemcpy = art_quick_memcpy; - // Invocation - qpoints->pQuickImtConflictTrampoline = art_quick_imt_conflict_trampoline; - qpoints->pQuickResolutionTrampoline = art_quick_resolution_trampoline; - qpoints->pQuickToInterpreterBridge = art_quick_to_interpreter_bridge; - qpoints->pInvokeDirectTrampolineWithAccessCheck = - art_quick_invoke_direct_trampoline_with_access_check; - qpoints->pInvokeInterfaceTrampolineWithAccessCheck = - art_quick_invoke_interface_trampoline_with_access_check; - qpoints->pInvokeStaticTrampolineWithAccessCheck = - art_quick_invoke_static_trampoline_with_access_check; - qpoints->pInvokeSuperTrampolineWithAccessCheck = - art_quick_invoke_super_trampoline_with_access_check; - qpoints->pInvokeVirtualTrampolineWithAccessCheck = - art_quick_invoke_virtual_trampoline_with_access_check; - - // Thread - qpoints->pTestSuspend = art_quick_test_suspend; - - // Throws - qpoints->pDeliverException = art_quick_deliver_exception; - qpoints->pThrowArrayBounds = art_quick_throw_array_bounds; - qpoints->pThrowDivZero = art_quick_throw_div_zero; - qpoints->pThrowNoSuchMethod = art_quick_throw_no_such_method; - qpoints->pThrowNullPointer = art_quick_throw_null_pointer_exception; - qpoints->pThrowStackOverflow = art_quick_throw_stack_overflow; - - // Deoptimize - qpoints->pDeoptimize = art_quick_deoptimize_from_compiled_code; - // Read barrier. qpoints->pReadBarrierJni = ReadBarrierJni; qpoints->pReadBarrierMark = art_quick_read_barrier_mark; diff --git a/runtime/arch/x86/instruction_set_features_x86.cc b/runtime/arch/x86/instruction_set_features_x86.cc index b97a8dbbc1..0093e82008 100644 --- a/runtime/arch/x86/instruction_set_features_x86.cc +++ b/runtime/arch/x86/instruction_set_features_x86.cc @@ -45,11 +45,6 @@ static constexpr const char* x86_variants_with_sse4_2[] = { "silvermont", }; -static constexpr const char* x86_variants_prefer_locked_add_sync[] = { - "atom", - "silvermont", -}; - static constexpr const char* x86_variants_with_popcnt[] = { "silvermont", }; @@ -69,10 +64,6 @@ const X86InstructionSetFeatures* X86InstructionSetFeatures::FromVariant( bool has_AVX = false; bool has_AVX2 = false; - bool prefers_locked_add = FindVariantInArray(x86_variants_prefer_locked_add_sync, - arraysize(x86_variants_prefer_locked_add_sync), - variant); - bool has_POPCNT = FindVariantInArray(x86_variants_with_popcnt, arraysize(x86_variants_with_popcnt), variant); @@ -86,10 +77,10 @@ const X86InstructionSetFeatures* X86InstructionSetFeatures::FromVariant( if (x86_64) { return new X86_64InstructionSetFeatures(smp, has_SSSE3, has_SSE4_1, has_SSE4_2, has_AVX, - has_AVX2, prefers_locked_add, has_POPCNT); + has_AVX2, has_POPCNT); } else { return new X86InstructionSetFeatures(smp, has_SSSE3, has_SSE4_1, has_SSE4_2, has_AVX, - has_AVX2, prefers_locked_add, has_POPCNT); + has_AVX2, has_POPCNT); } } @@ -101,16 +92,13 @@ const X86InstructionSetFeatures* X86InstructionSetFeatures::FromBitmap(uint32_t bool has_SSE4_2 = (bitmap & kSse4_2Bitfield) != 0; bool has_AVX = (bitmap & kAvxBitfield) != 0; bool has_AVX2 = (bitmap & kAvxBitfield) != 0; - bool prefers_locked_add = (bitmap & kPrefersLockedAdd) != 0; bool has_POPCNT = (bitmap & kPopCntBitfield) != 0; if (x86_64) { return new X86_64InstructionSetFeatures(smp, has_SSSE3, has_SSE4_1, has_SSE4_2, - has_AVX, has_AVX2, prefers_locked_add, - has_POPCNT); + has_AVX, has_AVX2, has_POPCNT); } else { return new X86InstructionSetFeatures(smp, has_SSSE3, has_SSE4_1, has_SSE4_2, - has_AVX, has_AVX2, prefers_locked_add, - has_POPCNT); + has_AVX, has_AVX2, has_POPCNT); } } @@ -147,9 +135,6 @@ const X86InstructionSetFeatures* X86InstructionSetFeatures::FromCppDefines(bool const bool has_AVX2 = true; #endif - // No #define for memory synchronization preference. - const bool prefers_locked_add = false; - #ifndef __POPCNT__ const bool has_POPCNT = false; #else @@ -158,10 +143,10 @@ const X86InstructionSetFeatures* X86InstructionSetFeatures::FromCppDefines(bool if (x86_64) { return new X86_64InstructionSetFeatures(smp, has_SSSE3, has_SSE4_1, has_SSE4_2, has_AVX, - has_AVX2, prefers_locked_add, has_POPCNT); + has_AVX2, has_POPCNT); } else { return new X86InstructionSetFeatures(smp, has_SSSE3, has_SSE4_1, has_SSE4_2, has_AVX, - has_AVX2, prefers_locked_add, has_POPCNT); + has_AVX2, has_POPCNT); } } @@ -174,8 +159,6 @@ const X86InstructionSetFeatures* X86InstructionSetFeatures::FromCpuInfo(bool x86 bool has_SSE4_2 = false; bool has_AVX = false; bool has_AVX2 = false; - // No cpuinfo for memory synchronization preference. - const bool prefers_locked_add = false; bool has_POPCNT = false; std::ifstream in("/proc/cpuinfo"); @@ -217,10 +200,10 @@ const X86InstructionSetFeatures* X86InstructionSetFeatures::FromCpuInfo(bool x86 } if (x86_64) { return new X86_64InstructionSetFeatures(smp, has_SSSE3, has_SSE4_1, has_SSE4_2, has_AVX, - has_AVX2, prefers_locked_add, has_POPCNT); + has_AVX2, has_POPCNT); } else { return new X86InstructionSetFeatures(smp, has_SSSE3, has_SSE4_1, has_SSE4_2, has_AVX, - has_AVX2, prefers_locked_add, has_POPCNT); + has_AVX2, has_POPCNT); } } @@ -245,7 +228,6 @@ bool X86InstructionSetFeatures::Equals(const InstructionSetFeatures* other) cons (has_SSE4_2_ == other_as_x86->has_SSE4_2_) && (has_AVX_ == other_as_x86->has_AVX_) && (has_AVX2_ == other_as_x86->has_AVX2_) && - (prefers_locked_add_ == other_as_x86->prefers_locked_add_) && (has_POPCNT_ == other_as_x86->has_POPCNT_); } @@ -256,7 +238,6 @@ uint32_t X86InstructionSetFeatures::AsBitmap() const { (has_SSE4_2_ ? kSse4_2Bitfield : 0) | (has_AVX_ ? kAvxBitfield : 0) | (has_AVX2_ ? kAvx2Bitfield : 0) | - (prefers_locked_add_ ? kPrefersLockedAdd : 0) | (has_POPCNT_ ? kPopCntBitfield : 0); } @@ -292,11 +273,6 @@ std::string X86InstructionSetFeatures::GetFeatureString() const { } else { result += ",-avx2"; } - if (prefers_locked_add_) { - result += ",lock_add"; - } else { - result += ",-lock_add"; - } if (has_POPCNT_) { result += ",popcnt"; } else { @@ -313,7 +289,6 @@ const InstructionSetFeatures* X86InstructionSetFeatures::AddFeaturesFromSplitStr bool has_SSE4_2 = has_SSE4_2_; bool has_AVX = has_AVX_; bool has_AVX2 = has_AVX2_; - bool prefers_locked_add = prefers_locked_add_; bool has_POPCNT = has_POPCNT_; for (auto i = features.begin(); i != features.end(); i++) { std::string feature = Trim(*i); @@ -337,10 +312,6 @@ const InstructionSetFeatures* X86InstructionSetFeatures::AddFeaturesFromSplitStr has_AVX2 = true; } else if (feature == "-avx2") { has_AVX2 = false; - } else if (feature == "lock_add") { - prefers_locked_add = true; - } else if (feature == "-lock_add") { - prefers_locked_add = false; } else if (feature == "popcnt") { has_POPCNT = true; } else if (feature == "-popcnt") { @@ -352,10 +323,10 @@ const InstructionSetFeatures* X86InstructionSetFeatures::AddFeaturesFromSplitStr } if (x86_64) { return new X86_64InstructionSetFeatures(smp, has_SSSE3, has_SSE4_1, has_SSE4_2, has_AVX, - has_AVX2, prefers_locked_add, has_POPCNT); + has_AVX2, has_POPCNT); } else { return new X86InstructionSetFeatures(smp, has_SSSE3, has_SSE4_1, has_SSE4_2, has_AVX, - has_AVX2, prefers_locked_add, has_POPCNT); + has_AVX2, has_POPCNT); } } diff --git a/runtime/arch/x86/instruction_set_features_x86.h b/runtime/arch/x86/instruction_set_features_x86.h index 1819654bef..2aa8ae6055 100644 --- a/runtime/arch/x86/instruction_set_features_x86.h +++ b/runtime/arch/x86/instruction_set_features_x86.h @@ -60,8 +60,6 @@ class X86InstructionSetFeatures : public InstructionSetFeatures { bool HasSSE4_1() const { return has_SSE4_1_; } - bool PrefersLockedAddSynchronization() const { return prefers_locked_add_; } - bool HasPopCnt() const { return has_POPCNT_; } protected: @@ -77,16 +75,13 @@ class X86InstructionSetFeatures : public InstructionSetFeatures { bool x86_64, std::string* error_msg) const; X86InstructionSetFeatures(bool smp, bool has_SSSE3, bool has_SSE4_1, bool has_SSE4_2, - bool has_AVX, bool has_AVX2, - bool prefers_locked_add, - bool has_POPCNT) + bool has_AVX, bool has_AVX2, bool has_POPCNT) : InstructionSetFeatures(smp), has_SSSE3_(has_SSSE3), has_SSE4_1_(has_SSE4_1), has_SSE4_2_(has_SSE4_2), has_AVX_(has_AVX), has_AVX2_(has_AVX2), - prefers_locked_add_(prefers_locked_add), has_POPCNT_(has_POPCNT) { } @@ -99,8 +94,7 @@ class X86InstructionSetFeatures : public InstructionSetFeatures { kSse4_2Bitfield = 8, kAvxBitfield = 16, kAvx2Bitfield = 32, - kPrefersLockedAdd = 64, - kPopCntBitfield = 128, + kPopCntBitfield = 64, }; const bool has_SSSE3_; // x86 128bit SIMD - Supplemental SSE. @@ -108,7 +102,6 @@ class X86InstructionSetFeatures : public InstructionSetFeatures { const bool has_SSE4_2_; // x86 128bit SIMD SSE4.2. const bool has_AVX_; // x86 256bit SIMD AVX. const bool has_AVX2_; // x86 256bit SIMD AVX 2.0. - const bool prefers_locked_add_; // x86 use locked add for memory synchronization. const bool has_POPCNT_; // x86 population count DISALLOW_COPY_AND_ASSIGN(X86InstructionSetFeatures); diff --git a/runtime/arch/x86/instruction_set_features_x86_test.cc b/runtime/arch/x86/instruction_set_features_x86_test.cc index a062c12892..9e154c6ecf 100644 --- a/runtime/arch/x86/instruction_set_features_x86_test.cc +++ b/runtime/arch/x86/instruction_set_features_x86_test.cc @@ -27,7 +27,7 @@ TEST(X86InstructionSetFeaturesTest, X86FeaturesFromDefaultVariant) { ASSERT_TRUE(x86_features.get() != nullptr) << error_msg; EXPECT_EQ(x86_features->GetInstructionSet(), kX86); EXPECT_TRUE(x86_features->Equals(x86_features.get())); - EXPECT_STREQ("smp,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,-lock_add,-popcnt", + EXPECT_STREQ("smp,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,-popcnt", x86_features->GetFeatureString().c_str()); EXPECT_EQ(x86_features->AsBitmap(), 1U); } @@ -40,9 +40,9 @@ TEST(X86InstructionSetFeaturesTest, X86FeaturesFromAtomVariant) { ASSERT_TRUE(x86_features.get() != nullptr) << error_msg; EXPECT_EQ(x86_features->GetInstructionSet(), kX86); EXPECT_TRUE(x86_features->Equals(x86_features.get())); - EXPECT_STREQ("smp,ssse3,-sse4.1,-sse4.2,-avx,-avx2,lock_add,-popcnt", + EXPECT_STREQ("smp,ssse3,-sse4.1,-sse4.2,-avx,-avx2,-popcnt", x86_features->GetFeatureString().c_str()); - EXPECT_EQ(x86_features->AsBitmap(), 67U); + EXPECT_EQ(x86_features->AsBitmap(), 3U); // Build features for a 32-bit x86 default processor. std::unique_ptr<const InstructionSetFeatures> x86_default_features( @@ -50,7 +50,7 @@ TEST(X86InstructionSetFeaturesTest, X86FeaturesFromAtomVariant) { ASSERT_TRUE(x86_default_features.get() != nullptr) << error_msg; EXPECT_EQ(x86_default_features->GetInstructionSet(), kX86); EXPECT_TRUE(x86_default_features->Equals(x86_default_features.get())); - EXPECT_STREQ("smp,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,-lock_add,-popcnt", + EXPECT_STREQ("smp,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,-popcnt", x86_default_features->GetFeatureString().c_str()); EXPECT_EQ(x86_default_features->AsBitmap(), 1U); @@ -60,9 +60,9 @@ TEST(X86InstructionSetFeaturesTest, X86FeaturesFromAtomVariant) { ASSERT_TRUE(x86_64_features.get() != nullptr) << error_msg; EXPECT_EQ(x86_64_features->GetInstructionSet(), kX86_64); EXPECT_TRUE(x86_64_features->Equals(x86_64_features.get())); - EXPECT_STREQ("smp,ssse3,-sse4.1,-sse4.2,-avx,-avx2,lock_add,-popcnt", + EXPECT_STREQ("smp,ssse3,-sse4.1,-sse4.2,-avx,-avx2,-popcnt", x86_64_features->GetFeatureString().c_str()); - EXPECT_EQ(x86_64_features->AsBitmap(), 67U); + EXPECT_EQ(x86_64_features->AsBitmap(), 3U); EXPECT_FALSE(x86_64_features->Equals(x86_features.get())); EXPECT_FALSE(x86_64_features->Equals(x86_default_features.get())); @@ -77,9 +77,9 @@ TEST(X86InstructionSetFeaturesTest, X86FeaturesFromSilvermontVariant) { ASSERT_TRUE(x86_features.get() != nullptr) << error_msg; EXPECT_EQ(x86_features->GetInstructionSet(), kX86); EXPECT_TRUE(x86_features->Equals(x86_features.get())); - EXPECT_STREQ("smp,ssse3,sse4.1,sse4.2,-avx,-avx2,lock_add,popcnt", + EXPECT_STREQ("smp,ssse3,sse4.1,sse4.2,-avx,-avx2,popcnt", x86_features->GetFeatureString().c_str()); - EXPECT_EQ(x86_features->AsBitmap(), 207U); + EXPECT_EQ(x86_features->AsBitmap(), 79U); // Build features for a 32-bit x86 default processor. std::unique_ptr<const InstructionSetFeatures> x86_default_features( @@ -87,7 +87,7 @@ TEST(X86InstructionSetFeaturesTest, X86FeaturesFromSilvermontVariant) { ASSERT_TRUE(x86_default_features.get() != nullptr) << error_msg; EXPECT_EQ(x86_default_features->GetInstructionSet(), kX86); EXPECT_TRUE(x86_default_features->Equals(x86_default_features.get())); - EXPECT_STREQ("smp,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,-lock_add,-popcnt", + EXPECT_STREQ("smp,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,-popcnt", x86_default_features->GetFeatureString().c_str()); EXPECT_EQ(x86_default_features->AsBitmap(), 1U); @@ -97,9 +97,9 @@ TEST(X86InstructionSetFeaturesTest, X86FeaturesFromSilvermontVariant) { ASSERT_TRUE(x86_64_features.get() != nullptr) << error_msg; EXPECT_EQ(x86_64_features->GetInstructionSet(), kX86_64); EXPECT_TRUE(x86_64_features->Equals(x86_64_features.get())); - EXPECT_STREQ("smp,ssse3,sse4.1,sse4.2,-avx,-avx2,lock_add,popcnt", + EXPECT_STREQ("smp,ssse3,sse4.1,sse4.2,-avx,-avx2,popcnt", x86_64_features->GetFeatureString().c_str()); - EXPECT_EQ(x86_64_features->AsBitmap(), 207U); + EXPECT_EQ(x86_64_features->AsBitmap(), 79U); EXPECT_FALSE(x86_64_features->Equals(x86_features.get())); EXPECT_FALSE(x86_64_features->Equals(x86_default_features.get())); diff --git a/runtime/arch/x86/quick_entrypoints_x86.S b/runtime/arch/x86/quick_entrypoints_x86.S index 551ec6880d..485da9fe33 100644 --- a/runtime/arch/x86/quick_entrypoints_x86.S +++ b/runtime/arch/x86/quick_entrypoints_x86.S @@ -897,8 +897,123 @@ DEFINE_FUNCTION art_quick_alloc_object_rosalloc RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER // return or deliver exception END_FUNCTION art_quick_alloc_object_rosalloc -GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT(_tlab, TLAB) -GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT(_region_tlab, RegionTLAB) +// The common fast path code for art_quick_alloc_object_tlab and art_quick_alloc_object_region_tlab. +// +// EAX: type_idx/return_value, ECX: ArtMethod*, EDX: the class. +MACRO1(ALLOC_OBJECT_TLAB_FAST_PATH, slowPathLabel) + testl %edx, %edx // Check null class + jz VAR(slowPathLabel) + // Check class status. + cmpl LITERAL(MIRROR_CLASS_STATUS_INITIALIZED), MIRROR_CLASS_STATUS_OFFSET(%edx) + jne VAR(slowPathLabel) + // No fake dependence needed on x86 + // between status and flags load, + // since each load is a load-acquire, + // no loads reordering. + // Check access flags has + // kAccClassIsFinalizable + testl LITERAL(ACCESS_FLAGS_CLASS_IS_FINALIZABLE), MIRROR_CLASS_ACCESS_FLAGS_OFFSET(%edx) + jnz VAR(slowPathLabel) + movl %fs:THREAD_SELF_OFFSET, %ebx // ebx = thread + movl THREAD_LOCAL_END_OFFSET(%ebx), %edi // Load thread_local_end. + subl THREAD_LOCAL_POS_OFFSET(%ebx), %edi // Compute the remaining buffer size. + movl MIRROR_CLASS_OBJECT_SIZE_OFFSET(%edx), %esi // Load the object size. + cmpl %edi, %esi // Check if it fits. OK to do this + // before rounding up the object size + // assuming the buf size alignment. + ja VAR(slowPathLabel) + addl LITERAL(OBJECT_ALIGNMENT_MASK), %esi // Align the size by 8. (addr + 7) & ~7. + andl LITERAL(OBJECT_ALIGNMENT_MASK_TOGGLED), %esi + movl THREAD_LOCAL_POS_OFFSET(%ebx), %eax // Load thread_local_pos + // as allocated object. + addl %eax, %esi // Add the object size. + movl %esi, THREAD_LOCAL_POS_OFFSET(%ebx) // Update thread_local_pos. + addl LITERAL(1), THREAD_LOCAL_OBJECTS_OFFSET(%ebx) // Increase thread_local_objects. + // Store the class pointer in the header. + // No fence needed for x86. + POISON_HEAP_REF edx + movl %edx, MIRROR_OBJECT_CLASS_OFFSET(%eax) + POP edi + POP esi + ret // Fast path succeeded. +END_MACRO + +// The common slow path code for art_quick_alloc_object_tlab and art_quick_alloc_object_region_tlab. +MACRO1(ALLOC_OBJECT_TLAB_SLOW_PATH, cxx_name) + POP edi + POP esi + SETUP_REFS_ONLY_CALLEE_SAVE_FRAME ebx, ebx // save ref containing registers for GC + // Outgoing argument set up + PUSH eax // alignment padding + pushl %fs:THREAD_SELF_OFFSET // pass Thread::Current() + CFI_ADJUST_CFA_OFFSET(4) + PUSH ecx + PUSH eax + call CALLVAR(cxx_name) // cxx_name(arg0, arg1, Thread*) + addl LITERAL(16), %esp + CFI_ADJUST_CFA_OFFSET(-16) + RESTORE_REFS_ONLY_CALLEE_SAVE_FRAME // restore frame up to return address + RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER // return or deliver exception +END_MACRO + +// A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT(_tlab, TLAB). +DEFINE_FUNCTION art_quick_alloc_object_tlab + // Fast path tlab allocation. + // EAX: uint32_t type_idx/return value, ECX: ArtMethod*. + // EBX, EDX: free. +#if defined(USE_READ_BARRIER) + int3 + int3 +#endif + PUSH esi + PUSH edi + movl ART_METHOD_DEX_CACHE_TYPES_OFFSET_32(%ecx), %edx // Load dex cache resolved types array + // Might need to break down into multiple instructions to get the base address in a register. + // Load the class + movl 0(%edx, %eax, COMPRESSED_REFERENCE_SIZE), %edx + ALLOC_OBJECT_TLAB_FAST_PATH .Lart_quick_alloc_object_tlab_slow_path +.Lart_quick_alloc_object_tlab_slow_path: + ALLOC_OBJECT_TLAB_SLOW_PATH artAllocObjectFromCodeTLAB +END_FUNCTION art_quick_alloc_object_tlab + +// A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT(_region_tlab, RegionTLAB). +DEFINE_FUNCTION art_quick_alloc_object_region_tlab + // Fast path region tlab allocation. + // EAX: uint32_t type_idx/return value, ECX: ArtMethod*. + // EBX, EDX: free. +#if !defined(USE_READ_BARRIER) + int3 + int3 +#endif + PUSH esi + PUSH edi + movl ART_METHOD_DEX_CACHE_TYPES_OFFSET_32(%ecx), %edx // Load dex cache resolved types array + // Might need to break down into multiple instructions to get the base address in a register. + // Load the class + movl 0(%edx, %eax, COMPRESSED_REFERENCE_SIZE), %edx + // Read barrier for class load. + cmpl LITERAL(0), %fs:THREAD_IS_GC_MARKING_OFFSET + jne .Lart_quick_alloc_object_region_tlab_class_load_read_barrier_slow_path +.Lart_quick_alloc_object_region_tlab_class_load_read_barrier_slow_path_exit: + ALLOC_OBJECT_TLAB_FAST_PATH .Lart_quick_alloc_object_region_tlab_slow_path +.Lart_quick_alloc_object_region_tlab_class_load_read_barrier_slow_path: + // The read barrier slow path. Mark the class. + PUSH eax + PUSH ecx + // Outgoing argument set up + subl MACRO_LITERAL(8), %esp // Alignment padding + CFI_ADJUST_CFA_OFFSET(8) + PUSH edx // Pass the class as the first param. + call SYMBOL(artReadBarrierMark) // cxx_name(mirror::Object* obj) + movl %eax, %edx + addl MACRO_LITERAL(12), %esp + CFI_ADJUST_CFA_OFFSET(-12) + POP ecx + POP eax + jmp .Lart_quick_alloc_object_region_tlab_class_load_read_barrier_slow_path_exit +.Lart_quick_alloc_object_region_tlab_slow_path: + ALLOC_OBJECT_TLAB_SLOW_PATH artAllocObjectFromCodeRegionTLAB +END_FUNCTION art_quick_alloc_object_region_tlab ONE_ARG_DOWNCALL art_quick_resolve_string, artResolveStringFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER ONE_ARG_DOWNCALL art_quick_initialize_static_storage, artInitializeStaticStorageFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER @@ -960,6 +1075,22 @@ DEFINE_FUNCTION art_quick_lock_object RETURN_IF_EAX_ZERO END_FUNCTION art_quick_lock_object +DEFINE_FUNCTION art_quick_lock_object_no_inline + SETUP_REFS_ONLY_CALLEE_SAVE_FRAME ebx, ebx // save ref containing registers for GC + // Outgoing argument set up + subl LITERAL(8), %esp // alignment padding + CFI_ADJUST_CFA_OFFSET(8) + pushl %fs:THREAD_SELF_OFFSET // pass Thread::Current() + CFI_ADJUST_CFA_OFFSET(4) + PUSH eax // pass object + call SYMBOL(artLockObjectFromCode) // artLockObjectFromCode(object, Thread*) + addl LITERAL(16), %esp // pop arguments + CFI_ADJUST_CFA_OFFSET(-16) + RESTORE_REFS_ONLY_CALLEE_SAVE_FRAME // restore frame up to return address + RETURN_IF_EAX_ZERO +END_FUNCTION art_quick_lock_object_no_inline + + DEFINE_FUNCTION art_quick_unlock_object testl %eax, %eax // null check object/eax jz .Lslow_unlock @@ -1015,6 +1146,21 @@ DEFINE_FUNCTION art_quick_unlock_object RETURN_IF_EAX_ZERO END_FUNCTION art_quick_unlock_object +DEFINE_FUNCTION art_quick_unlock_object_no_inline + SETUP_REFS_ONLY_CALLEE_SAVE_FRAME ebx, ebx // save ref containing registers for GC + // Outgoing argument set up + subl LITERAL(8), %esp // alignment padding + CFI_ADJUST_CFA_OFFSET(8) + pushl %fs:THREAD_SELF_OFFSET // pass Thread::Current() + CFI_ADJUST_CFA_OFFSET(4) + PUSH eax // pass object + call SYMBOL(artUnlockObjectFromCode) // artUnlockObjectFromCode(object, Thread*) + addl LITERAL(16), %esp // pop arguments + CFI_ADJUST_CFA_OFFSET(-16) + RESTORE_REFS_ONLY_CALLEE_SAVE_FRAME // restore frame up to return address + RETURN_IF_EAX_ZERO +END_FUNCTION art_quick_unlock_object_no_inline + DEFINE_FUNCTION art_quick_is_assignable PUSH eax // alignment padding PUSH ecx // pass arg2 - obj->klass diff --git a/runtime/arch/x86/thread_x86.cc b/runtime/arch/x86/thread_x86.cc index 3d19f06092..c39d122434 100644 --- a/runtime/arch/x86/thread_x86.cc +++ b/runtime/arch/x86/thread_x86.cc @@ -45,16 +45,17 @@ void Thread::InitCpu() { MutexLock mu(nullptr, *Locks::modify_ldt_lock_); const uintptr_t base = reinterpret_cast<uintptr_t>(this); - const size_t limit = kPageSize; + const size_t limit = sizeof(Thread); const int contents = MODIFY_LDT_CONTENTS_DATA; const int seg_32bit = 1; const int read_exec_only = 0; - const int limit_in_pages = 0; + const int limit_in_pages = 1; const int seg_not_present = 0; const int useable = 1; - int entry_number = -1; + int entry_number; + uint16_t table_indicator; #if defined(__APPLE__) descriptor_table_entry_t entry; @@ -77,41 +78,52 @@ void Thread::InitCpu() { if (entry_number == -1) { PLOG(FATAL) << "i386_set_ldt failed"; } + + table_indicator = 1 << 2; // LDT #else - // Read current LDT entries. - static_assert(static_cast<size_t>(LDT_ENTRY_SIZE) == sizeof(uint64_t), - "LDT_ENTRY_SIZE is different from sizeof(uint64_t)."); - std::vector<uint64_t> ldt(LDT_ENTRIES); - size_t ldt_size(sizeof(uint64_t) * ldt.size()); - memset(&ldt[0], 0, ldt_size); - // TODO: why doesn't this return LDT_ENTRY_SIZE * LDT_ENTRIES for the main thread? - syscall(__NR_modify_ldt, 0, &ldt[0], ldt_size); - - // Find the first empty slot. - for (entry_number = 0; entry_number < LDT_ENTRIES && ldt[entry_number] != 0; ++entry_number) { + // We use a GDT entry on Linux. + user_desc gdt_entry; + memset(&gdt_entry, 0, sizeof(gdt_entry)); + + // On Linux, there are 3 TLS GDT entries. We use one of those to to store our segment descriptor + // data. + // + // This entry must be shared, as the kernel only guarantees three TLS entries. For simplicity + // (and locality), use this local global, which practically becomes readonly after the first + // (startup) thread of the runtime has been initialized (during Runtime::Start()). + // + // We also share this between all runtimes in the process. This is both for simplicity (one + // well-known slot) as well as to avoid the three-slot limitation. Downside is that we cannot + // free the slot when it is known that a runtime stops. + static unsigned int gdt_entry_number = -1; + + if (gdt_entry_number == static_cast<unsigned int>(-1)) { + gdt_entry.entry_number = -1; // Let the kernel choose. + } else { + gdt_entry.entry_number = gdt_entry_number; } - if (entry_number >= LDT_ENTRIES) { - LOG(FATAL) << "Failed to find a free LDT slot"; + gdt_entry.base_addr = base; + gdt_entry.limit = limit; + gdt_entry.seg_32bit = seg_32bit; + gdt_entry.contents = contents; + gdt_entry.read_exec_only = read_exec_only; + gdt_entry.limit_in_pages = limit_in_pages; + gdt_entry.seg_not_present = seg_not_present; + gdt_entry.useable = useable; + int rc = syscall(__NR_set_thread_area, &gdt_entry); + if (rc != -1) { + entry_number = gdt_entry.entry_number; + if (gdt_entry_number == static_cast<unsigned int>(-1)) { + gdt_entry_number = entry_number; // Save the kernel-assigned entry number. + } + } else { + PLOG(FATAL) << "set_thread_area failed"; + UNREACHABLE(); } - - // Update LDT entry. - user_desc ldt_entry; - memset(&ldt_entry, 0, sizeof(ldt_entry)); - ldt_entry.entry_number = entry_number; - ldt_entry.base_addr = base; - ldt_entry.limit = limit; - ldt_entry.seg_32bit = seg_32bit; - ldt_entry.contents = contents; - ldt_entry.read_exec_only = read_exec_only; - ldt_entry.limit_in_pages = limit_in_pages; - ldt_entry.seg_not_present = seg_not_present; - ldt_entry.useable = useable; - CHECK_EQ(0, syscall(__NR_modify_ldt, 1, &ldt_entry, sizeof(ldt_entry))); - entry_number = ldt_entry.entry_number; + table_indicator = 0; // GDT #endif - // Change %fs to be new LDT entry. - uint16_t table_indicator = 1 << 2; // LDT + // Change %fs to be new DT entry. uint16_t rpl = 3; // Requested privilege level uint16_t selector = (entry_number << 3) | table_indicator | rpl; __asm__ __volatile__("movw %w0, %%fs" @@ -163,13 +175,18 @@ void Thread::CleanupCpu() { UNUSED(selector); // i386_set_ldt(selector >> 3, 0, 1); #else - user_desc ldt_entry; - memset(&ldt_entry, 0, sizeof(ldt_entry)); - ldt_entry.entry_number = selector >> 3; - ldt_entry.contents = MODIFY_LDT_CONTENTS_DATA; - ldt_entry.seg_not_present = 1; - - syscall(__NR_modify_ldt, 1, &ldt_entry, sizeof(ldt_entry)); + // Note if we wanted to clean up the GDT entry, we would do that here, when the *last* thread + // is being deleted. But see the comment on gdt_entry_number. Code would look like this: + // + // user_desc gdt_entry; + // memset(&gdt_entry, 0, sizeof(gdt_entry)); + // gdt_entry.entry_number = selector >> 3; + // gdt_entry.contents = MODIFY_LDT_CONTENTS_DATA; + // // "Empty" = Delete = seg_not_present==1 && read_exec_only==1. + // gdt_entry.seg_not_present = 1; + // gdt_entry.read_exec_only = 1; + // syscall(__NR_set_thread_area, &gdt_entry); + UNUSED(selector); #endif } diff --git a/runtime/arch/x86_64/asm_support_x86_64.h b/runtime/arch/x86_64/asm_support_x86_64.h index eddd17206c..48bec73239 100644 --- a/runtime/arch/x86_64/asm_support_x86_64.h +++ b/runtime/arch/x86_64/asm_support_x86_64.h @@ -19,8 +19,8 @@ #include "asm_support.h" -#define FRAME_SIZE_SAVE_ALL_CALLEE_SAVE 64 + 4*8 -#define FRAME_SIZE_REFS_ONLY_CALLEE_SAVE 64 + 4*8 -#define FRAME_SIZE_REFS_AND_ARGS_CALLEE_SAVE 176 + 4*8 +#define FRAME_SIZE_SAVE_ALL_CALLEE_SAVE (64 + 4*8) +#define FRAME_SIZE_REFS_ONLY_CALLEE_SAVE (64 + 4*8) +#define FRAME_SIZE_REFS_AND_ARGS_CALLEE_SAVE (176 + 4*8) #endif // ART_RUNTIME_ARCH_X86_64_ASM_SUPPORT_X86_64_H_ diff --git a/runtime/arch/x86_64/entrypoints_init_x86_64.cc b/runtime/arch/x86_64/entrypoints_init_x86_64.cc index 0a5d14a163..bd6df700d0 100644 --- a/runtime/arch/x86_64/entrypoints_init_x86_64.cc +++ b/runtime/arch/x86_64/entrypoints_init_x86_64.cc @@ -17,6 +17,9 @@ #include "entrypoints/jni/jni_entrypoints.h" #include "entrypoints/quick/quick_alloc_entrypoints.h" #include "entrypoints/quick/quick_default_externs.h" +#if !defined(__APPLE__) +#include "entrypoints/quick/quick_default_init_entrypoints.h" +#endif #include "entrypoints/quick/quick_entrypoints.h" #include "entrypoints/math_entrypoints.h" #include "entrypoints/runtime_asm_entrypoints.h" @@ -38,67 +41,12 @@ void InitEntryPoints(JniEntryPoints* jpoints, QuickEntryPoints* qpoints) { UNUSED(jpoints, qpoints); UNIMPLEMENTED(FATAL); #else - // JNI - jpoints->pDlsymLookup = art_jni_dlsym_lookup_stub; - - // Alloc - ResetQuickAllocEntryPoints(qpoints); + DefaultInitEntryPoints(jpoints, qpoints); // Cast qpoints->pInstanceofNonTrivial = art_quick_assignable_from_code; qpoints->pCheckCast = art_quick_check_cast; - // DexCache - qpoints->pInitializeStaticStorage = art_quick_initialize_static_storage; - qpoints->pInitializeTypeAndVerifyAccess = art_quick_initialize_type_and_verify_access; - qpoints->pInitializeType = art_quick_initialize_type; - qpoints->pResolveString = art_quick_resolve_string; - - // Field - qpoints->pSet8Instance = art_quick_set8_instance; - qpoints->pSet8Static = art_quick_set8_static; - qpoints->pSet16Instance = art_quick_set16_instance; - qpoints->pSet16Static = art_quick_set16_static; - qpoints->pSet32Instance = art_quick_set32_instance; - qpoints->pSet32Static = art_quick_set32_static; - qpoints->pSet64Instance = art_quick_set64_instance; - qpoints->pSet64Static = art_quick_set64_static; - qpoints->pSetObjInstance = art_quick_set_obj_instance; - qpoints->pSetObjStatic = art_quick_set_obj_static; - qpoints->pGetByteInstance = art_quick_get_byte_instance; - qpoints->pGetBooleanInstance = art_quick_get_boolean_instance; - qpoints->pGetShortInstance = art_quick_get_short_instance; - qpoints->pGetCharInstance = art_quick_get_char_instance; - qpoints->pGet32Instance = art_quick_get32_instance; - qpoints->pGet64Instance = art_quick_get64_instance; - qpoints->pGetObjInstance = art_quick_get_obj_instance; - qpoints->pGetByteStatic = art_quick_get_byte_static; - qpoints->pGetBooleanStatic = art_quick_get_boolean_static; - qpoints->pGetShortStatic = art_quick_get_short_static; - qpoints->pGetCharStatic = art_quick_get_char_static; - qpoints->pGet32Static = art_quick_get32_static; - qpoints->pGet64Static = art_quick_get64_static; - qpoints->pGetObjStatic = art_quick_get_obj_static; - - // Array - qpoints->pAputObjectWithNullAndBoundCheck = art_quick_aput_obj_with_null_and_bound_check; - qpoints->pAputObjectWithBoundCheck = art_quick_aput_obj_with_bound_check; - qpoints->pAputObject = art_quick_aput_obj; - qpoints->pHandleFillArrayData = art_quick_handle_fill_data; - - // JNI - qpoints->pJniMethodStart = JniMethodStart; - qpoints->pJniMethodStartSynchronized = JniMethodStartSynchronized; - qpoints->pJniMethodEnd = JniMethodEnd; - qpoints->pJniMethodEndSynchronized = JniMethodEndSynchronized; - qpoints->pJniMethodEndWithReference = JniMethodEndWithReference; - qpoints->pJniMethodEndWithReferenceSynchronized = JniMethodEndWithReferenceSynchronized; - qpoints->pQuickGenericJniTrampoline = art_quick_generic_jni_trampoline; - - // Locks - qpoints->pLockObject = art_quick_lock_object; - qpoints->pUnlockObject = art_quick_unlock_object; - // More math. qpoints->pCos = cos; qpoints->pSin = sin; @@ -132,35 +80,6 @@ void InitEntryPoints(JniEntryPoints* jpoints, QuickEntryPoints* qpoints) { qpoints->pStringCompareTo = art_quick_string_compareto; qpoints->pMemcpy = art_quick_memcpy; - // Invocation - qpoints->pQuickImtConflictTrampoline = art_quick_imt_conflict_trampoline; - qpoints->pQuickResolutionTrampoline = art_quick_resolution_trampoline; - qpoints->pQuickToInterpreterBridge = art_quick_to_interpreter_bridge; - qpoints->pInvokeDirectTrampolineWithAccessCheck = - art_quick_invoke_direct_trampoline_with_access_check; - qpoints->pInvokeInterfaceTrampolineWithAccessCheck = - art_quick_invoke_interface_trampoline_with_access_check; - qpoints->pInvokeStaticTrampolineWithAccessCheck = - art_quick_invoke_static_trampoline_with_access_check; - qpoints->pInvokeSuperTrampolineWithAccessCheck = - art_quick_invoke_super_trampoline_with_access_check; - qpoints->pInvokeVirtualTrampolineWithAccessCheck = - art_quick_invoke_virtual_trampoline_with_access_check; - - // Thread - qpoints->pTestSuspend = art_quick_test_suspend; - - // Throws - qpoints->pDeliverException = art_quick_deliver_exception; - qpoints->pThrowArrayBounds = art_quick_throw_array_bounds; - qpoints->pThrowDivZero = art_quick_throw_div_zero; - qpoints->pThrowNoSuchMethod = art_quick_throw_no_such_method; - qpoints->pThrowNullPointer = art_quick_throw_null_pointer_exception; - qpoints->pThrowStackOverflow = art_quick_throw_stack_overflow; - - // Deoptimize - qpoints->pDeoptimize = art_quick_deoptimize_from_compiled_code; - // Read barrier. qpoints->pReadBarrierJni = ReadBarrierJni; qpoints->pReadBarrierMark = art_quick_read_barrier_mark; diff --git a/runtime/arch/x86_64/instruction_set_features_x86_64.h b/runtime/arch/x86_64/instruction_set_features_x86_64.h index aba72348f8..0840f89a21 100644 --- a/runtime/arch/x86_64/instruction_set_features_x86_64.h +++ b/runtime/arch/x86_64/instruction_set_features_x86_64.h @@ -74,10 +74,9 @@ class X86_64InstructionSetFeatures FINAL : public X86InstructionSetFeatures { private: X86_64InstructionSetFeatures(bool smp, bool has_SSSE3, bool has_SSE4_1, bool has_SSE4_2, - bool has_AVX, bool has_AVX2, bool prefers_locked_add, - bool has_POPCNT) + bool has_AVX, bool has_AVX2, bool has_POPCNT) : X86InstructionSetFeatures(smp, has_SSSE3, has_SSE4_1, has_SSE4_2, has_AVX, - has_AVX2, prefers_locked_add, has_POPCNT) { + has_AVX2, has_POPCNT) { } friend class X86InstructionSetFeatures; diff --git a/runtime/arch/x86_64/instruction_set_features_x86_64_test.cc b/runtime/arch/x86_64/instruction_set_features_x86_64_test.cc index 78aeacf214..f2b2cd85c5 100644 --- a/runtime/arch/x86_64/instruction_set_features_x86_64_test.cc +++ b/runtime/arch/x86_64/instruction_set_features_x86_64_test.cc @@ -27,7 +27,7 @@ TEST(X86_64InstructionSetFeaturesTest, X86Features) { ASSERT_TRUE(x86_64_features.get() != nullptr) << error_msg; EXPECT_EQ(x86_64_features->GetInstructionSet(), kX86_64); EXPECT_TRUE(x86_64_features->Equals(x86_64_features.get())); - EXPECT_STREQ("smp,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,-lock_add,-popcnt", + EXPECT_STREQ("smp,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,-popcnt", x86_64_features->GetFeatureString().c_str()); EXPECT_EQ(x86_64_features->AsBitmap(), 1U); } diff --git a/runtime/arch/x86_64/quick_entrypoints_x86_64.S b/runtime/arch/x86_64/quick_entrypoints_x86_64.S index 26e668e7ae..8064ed696f 100644 --- a/runtime/arch/x86_64/quick_entrypoints_x86_64.S +++ b/runtime/arch/x86_64/quick_entrypoints_x86_64.S @@ -894,57 +894,107 @@ DEFINE_FUNCTION art_quick_alloc_object_rosalloc RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER // return or deliver exception END_FUNCTION art_quick_alloc_object_rosalloc -// A handle-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT(_tlab, TLAB). -DEFINE_FUNCTION art_quick_alloc_object_tlab - // Fast path tlab allocation. - // RDI: uint32_t type_idx, RSI: ArtMethod* - // RDX, RCX, R8, R9: free. RAX: return val. - // TODO: Add read barrier when this function is used. - // Note this function can/should implement read barrier fast path only - // (no read barrier slow path) because this is the fast path of tlab allocation. - // We can fall back to the allocation slow path to do the read barrier slow path. -#if defined(USE_READ_BARRIER) - int3 - int3 -#endif - // Might need a special macro since rsi and edx is 32b/64b mismatched. - movq ART_METHOD_DEX_CACHE_TYPES_OFFSET_64(%rsi), %rdx // Load dex cache resolved types array - // TODO: Add read barrier when this function is used. - // Might need to break down into multiple instructions to get the base address in a register. - // Load the class - movl 0(%rdx, %rdi, COMPRESSED_REFERENCE_SIZE), %edx +// The common fast path code for art_quick_alloc_object_tlab and art_quick_alloc_object_region_tlab. +// +// RDI: type_idx, RSI: ArtMethod*, RDX/EDX: the class, RAX: return value. +// RCX: scratch, r8: Thread::Current(). +MACRO1(ALLOC_OBJECT_TLAB_FAST_PATH, slowPathLabel) testl %edx, %edx // Check null class - jz .Lart_quick_alloc_object_tlab_slow_path + jz RAW_VAR(slowPathLabel) // Check class status. cmpl LITERAL(MIRROR_CLASS_STATUS_INITIALIZED), MIRROR_CLASS_STATUS_OFFSET(%rdx) - jne .Lart_quick_alloc_object_tlab_slow_path - // Check access flags has kAccClassIsFinalizable + jne RAW_VAR(slowPathLabel) + // No fake dependence needed on x86 + // between status and flags load, + // since each load is a load-acquire, + // no loads reordering. + // Check access flags has + // kAccClassIsFinalizable testl LITERAL(ACCESS_FLAGS_CLASS_IS_FINALIZABLE), MIRROR_CLASS_ACCESS_FLAGS_OFFSET(%rdx) - jnz .Lart_quick_alloc_object_tlab_slow_path + jnz RAW_VAR(slowPathLabel) + movq %gs:THREAD_SELF_OFFSET, %r8 // r8 = thread + movq THREAD_LOCAL_END_OFFSET(%r8), %rax // Load thread_local_end. + subq THREAD_LOCAL_POS_OFFSET(%r8), %rax // Compute the remaining buffer size. movl MIRROR_CLASS_OBJECT_SIZE_OFFSET(%rdx), %ecx // Load the object size. + cmpq %rax, %rcx // Check if it fits. OK to do this + // before rounding up the object size + // assuming the buf size alignment. + ja RAW_VAR(slowPathLabel) addl LITERAL(OBJECT_ALIGNMENT_MASK), %ecx // Align the size by 8. (addr + 7) & ~7. andl LITERAL(OBJECT_ALIGNMENT_MASK_TOGGLED), %ecx - movq %gs:THREAD_SELF_OFFSET, %r8 // r8 = thread - movq THREAD_LOCAL_POS_OFFSET(%r8), %rax // Load thread_local_pos. + movq THREAD_LOCAL_POS_OFFSET(%r8), %rax // Load thread_local_pos + // as allocated object. addq %rax, %rcx // Add the object size. - cmpq THREAD_LOCAL_END_OFFSET(%r8), %rcx // Check if it fits. - ja .Lart_quick_alloc_object_tlab_slow_path movq %rcx, THREAD_LOCAL_POS_OFFSET(%r8) // Update thread_local_pos. - addq LITERAL(1), THREAD_LOCAL_OBJECTS_OFFSET(%r8) // Increment thread_local_objects. + addq LITERAL(1), THREAD_LOCAL_OBJECTS_OFFSET(%r8) // Increase thread_local_objects. // Store the class pointer in the header. // No fence needed for x86. + POISON_HEAP_REF edx movl %edx, MIRROR_OBJECT_CLASS_OFFSET(%rax) ret // Fast path succeeded. -.Lart_quick_alloc_object_tlab_slow_path: +END_MACRO + +// The common slow path code for art_quick_alloc_object_tlab and art_quick_alloc_object_region_tlab. +MACRO1(ALLOC_OBJECT_TLAB_SLOW_PATH, cxx_name) SETUP_REFS_ONLY_CALLEE_SAVE_FRAME // save ref containing registers for GC // Outgoing argument set up movq %gs:THREAD_SELF_OFFSET, %rdx // pass Thread::Current() - call SYMBOL(artAllocObjectFromCodeTLAB) // cxx_name(arg0, arg1, Thread*) + call VAR(cxx_name) // cxx_name(arg0, arg1, Thread*) RESTORE_REFS_ONLY_CALLEE_SAVE_FRAME // restore frame up to return address RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER // return or deliver exception +END_MACRO + +// A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT(_tlab, TLAB). +DEFINE_FUNCTION art_quick_alloc_object_tlab + // Fast path tlab allocation. + // RDI: uint32_t type_idx, RSI: ArtMethod* + // RDX, RCX, R8, R9: free. RAX: return val. +#if defined(USE_READ_BARRIER) + int3 + int3 +#endif + // Might need a special macro since rsi and edx is 32b/64b mismatched. + movq ART_METHOD_DEX_CACHE_TYPES_OFFSET_64(%rsi), %rdx // Load dex cache resolved types array + // Might need to break down into multiple instructions to get the base address in a register. + // Load the class + movl 0(%rdx, %rdi, COMPRESSED_REFERENCE_SIZE), %edx + ALLOC_OBJECT_TLAB_FAST_PATH .Lart_quick_alloc_object_tlab_slow_path +.Lart_quick_alloc_object_tlab_slow_path: + ALLOC_OBJECT_TLAB_SLOW_PATH artAllocObjectFromCodeTLAB END_FUNCTION art_quick_alloc_object_tlab -GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT(_region_tlab, RegionTLAB) +// A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT(_region_tlab, RegionTLAB). +DEFINE_FUNCTION art_quick_alloc_object_region_tlab + // Fast path region tlab allocation. + // RDI: uint32_t type_idx, RSI: ArtMethod* + // RDX, RCX, R8, R9: free. RAX: return val. +#if !defined(USE_READ_BARRIER) + int3 + int3 +#endif + // Might need a special macro since rsi and edx is 32b/64b mismatched. + movq ART_METHOD_DEX_CACHE_TYPES_OFFSET_64(%rsi), %rdx // Load dex cache resolved types array + // Might need to break down into multiple instructions to get the base address in a register. + // Load the class + movl 0(%rdx, %rdi, COMPRESSED_REFERENCE_SIZE), %edx + cmpl LITERAL(0), %gs:THREAD_IS_GC_MARKING_OFFSET + jne .Lart_quick_alloc_object_region_tlab_class_load_read_barrier_slow_path +.Lart_quick_alloc_object_region_tlab_class_load_read_barrier_slow_path_exit: + ALLOC_OBJECT_TLAB_FAST_PATH .Lart_quick_alloc_object_region_tlab_slow_path +.Lart_quick_alloc_object_region_tlab_class_load_read_barrier_slow_path: + // The read barrier slow path. Mark the class. + PUSH rdi + PUSH rsi + // Outgoing argument set up + movq %rdx, %rdi // Pass the class as the first param. + call SYMBOL(artReadBarrierMark) // cxx_name(mirror::Object* obj) + movq %rax, %rdx + POP rsi + POP rdi + jmp .Lart_quick_alloc_object_region_tlab_class_load_read_barrier_slow_path_exit +.Lart_quick_alloc_object_region_tlab_slow_path: + ALLOC_OBJECT_TLAB_SLOW_PATH artAllocObjectFromCodeRegionTLAB +END_FUNCTION art_quick_alloc_object_region_tlab ONE_ARG_DOWNCALL art_quick_resolve_string, artResolveStringFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER ONE_ARG_DOWNCALL art_quick_initialize_static_storage, artInitializeStaticStorageFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER @@ -994,6 +1044,14 @@ DEFINE_FUNCTION art_quick_lock_object RETURN_IF_EAX_ZERO END_FUNCTION art_quick_lock_object +DEFINE_FUNCTION art_quick_lock_object_no_inline + SETUP_REFS_ONLY_CALLEE_SAVE_FRAME + movq %gs:THREAD_SELF_OFFSET, %rsi // pass Thread::Current() + call SYMBOL(artLockObjectFromCode) // artLockObjectFromCode(object, Thread*) + RESTORE_REFS_ONLY_CALLEE_SAVE_FRAME // restore frame up to return address + RETURN_IF_EAX_ZERO +END_FUNCTION art_quick_lock_object_no_inline + DEFINE_FUNCTION art_quick_unlock_object testl %edi, %edi // null check object/edi jz .Lslow_unlock @@ -1037,6 +1095,14 @@ DEFINE_FUNCTION art_quick_unlock_object RETURN_IF_EAX_ZERO END_FUNCTION art_quick_unlock_object +DEFINE_FUNCTION art_quick_unlock_object_no_inline + SETUP_REFS_ONLY_CALLEE_SAVE_FRAME + movq %gs:THREAD_SELF_OFFSET, %rsi // pass Thread::Current() + call SYMBOL(artUnlockObjectFromCode) // artUnlockObjectFromCode(object, Thread*) + RESTORE_REFS_ONLY_CALLEE_SAVE_FRAME // restore frame up to return address + RETURN_IF_EAX_ZERO +END_FUNCTION art_quick_unlock_object_no_inline + DEFINE_FUNCTION art_quick_check_cast PUSH rdi // Save args for exc PUSH rsi diff --git a/runtime/art_field-inl.h b/runtime/art_field-inl.h index d91149701e..98d33453e2 100644 --- a/runtime/art_field-inl.h +++ b/runtime/art_field-inl.h @@ -122,21 +122,21 @@ inline void ArtField::SetObj(mirror::Object* object, mirror::Object* new_value) #define FIELD_GET(object, type) \ DCHECK_EQ(Primitive::kPrim ## type, GetTypeAsPrimitiveType()) << PrettyField(this); \ - DCHECK(object != nullptr) << PrettyField(this); \ - DCHECK(!IsStatic() || (object == GetDeclaringClass()) || !Runtime::Current()->IsStarted()); \ + DCHECK((object) != nullptr) << PrettyField(this); \ + DCHECK(!IsStatic() || ((object) == GetDeclaringClass()) || !Runtime::Current()->IsStarted()); \ if (UNLIKELY(IsVolatile())) { \ - return object->GetField ## type ## Volatile(GetOffset()); \ + return (object)->GetField ## type ## Volatile(GetOffset()); \ } \ - return object->GetField ## type(GetOffset()); + return (object)->GetField ## type(GetOffset()); #define FIELD_SET(object, type, value) \ DCHECK_EQ(Primitive::kPrim ## type, GetTypeAsPrimitiveType()) << PrettyField(this); \ - DCHECK(object != nullptr) << PrettyField(this); \ - DCHECK(!IsStatic() || (object == GetDeclaringClass()) || !Runtime::Current()->IsStarted()); \ + DCHECK((object) != nullptr) << PrettyField(this); \ + DCHECK(!IsStatic() || ((object) == GetDeclaringClass()) || !Runtime::Current()->IsStarted()); \ if (UNLIKELY(IsVolatile())) { \ - object->SetField ## type ## Volatile<kTransactionActive>(GetOffset(), value); \ + (object)->SetField ## type ## Volatile<kTransactionActive>(GetOffset(), value); \ } else { \ - object->SetField ## type<kTransactionActive>(GetOffset(), value); \ + (object)->SetField ## type<kTransactionActive>(GetOffset(), value); \ } inline uint8_t ArtField::GetBoolean(mirror::Object* object) { diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h index 6449efad78..7647ad6e57 100644 --- a/runtime/art_method-inl.h +++ b/runtime/art_method-inl.h @@ -456,13 +456,18 @@ void ArtMethod::VisitRoots(RootVisitorType& visitor, size_t pointer_size) { interface_method->VisitRoots(visitor, pointer_size); } visitor.VisitRoot(declaring_class_.AddressWithoutBarrier()); - // Runtime methods and native methods use the same field as the profiling info for - // storing their own data (jni entrypoint for native methods, and ImtConflictTable for - // some runtime methods). - if (!IsNative() && !IsRuntimeMethod()) { - ProfilingInfo* profiling_info = GetProfilingInfo(pointer_size); - if (profiling_info != nullptr) { - profiling_info->VisitRoots(visitor); + // We know we don't have profiling information if the class hasn't been verified. Note + // that this check also ensures the IsNative call can be made, as IsNative expects a fully + // created class (and not a retired one). + if (klass->IsVerified()) { + // Runtime methods and native methods use the same field as the profiling info for + // storing their own data (jni entrypoint for native methods, and ImtConflictTable for + // some runtime methods). + if (!IsNative() && !IsRuntimeMethod()) { + ProfilingInfo* profiling_info = GetProfilingInfo(pointer_size); + if (profiling_info != nullptr) { + profiling_info->VisitRoots(visitor); + } } } } diff --git a/runtime/art_method.cc b/runtime/art_method.cc index 34d19d151b..1790df6be4 100644 --- a/runtime/art_method.cc +++ b/runtime/art_method.cc @@ -253,14 +253,17 @@ void ArtMethod::Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue* Runtime* runtime = Runtime::Current(); // Call the invoke stub, passing everything as arguments. // If the runtime is not yet started or it is required by the debugger, then perform the - // Invocation by the interpreter. + // Invocation by the interpreter, explicitly forcing interpretation over JIT to prevent + // cycling around the various JIT/Interpreter methods that handle method invocation. if (UNLIKELY(!runtime->IsStarted() || Dbg::IsForcedInterpreterNeededForCalling(self, this))) { if (IsStatic()) { - art::interpreter::EnterInterpreterFromInvoke(self, this, nullptr, args, result); + art::interpreter::EnterInterpreterFromInvoke( + self, this, nullptr, args, result, /*stay_in_interpreter*/ true); } else { mirror::Object* receiver = reinterpret_cast<StackReference<mirror::Object>*>(&args[0])->AsMirrorPtr(); - art::interpreter::EnterInterpreterFromInvoke(self, this, receiver, args + 1, result); + art::interpreter::EnterInterpreterFromInvoke( + self, this, receiver, args + 1, result, /*stay_in_interpreter*/ true); } } else { DCHECK_EQ(runtime->GetClassLinker()->GetImagePointerSize(), sizeof(void*)); @@ -276,7 +279,7 @@ void ArtMethod::Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue* // Ensure that we won't be accidentally calling quick compiled code when -Xint. if (kIsDebugBuild && runtime->GetInstrumentation()->IsForcedInterpretOnly()) { - CHECK(!runtime->UseJit()); + CHECK(!runtime->UseJitCompilation()); const void* oat_quick_code = runtime->GetClassLinker()->GetOatMethodQuickCodeFor(this); CHECK(oat_quick_code == nullptr || oat_quick_code != GetEntryPointFromQuickCompiledCode()) << "Don't call compiled code when -Xint " << PrettyMethod(this); @@ -481,7 +484,7 @@ void ArtMethod::CopyFrom(ArtMethod* src, size_t image_pointer_size) { // to the JIT code, but this would require taking the JIT code cache lock to notify // it, which we do not want at this level. Runtime* runtime = Runtime::Current(); - if (runtime->GetJit() != nullptr) { + if (runtime->UseJitCompilation()) { if (runtime->GetJit()->GetCodeCache()->ContainsPc(GetEntryPointFromQuickCompiledCode())) { SetEntryPointFromQuickCompiledCodePtrSize(GetQuickToInterpreterBridge(), image_pointer_size); } diff --git a/runtime/art_method.h b/runtime/art_method.h index d1ef019428..a012a5a9ca 100644 --- a/runtime/art_method.h +++ b/runtime/art_method.h @@ -41,6 +41,7 @@ class ShadowFrame; namespace mirror { class Array; class Class; +class IfTable; class PointerArray; } // namespace mirror @@ -50,66 +51,151 @@ class PointerArray; // with the last entry being null to make an assembly implementation of a lookup // faster. class ImtConflictTable { + enum MethodIndex { + kMethodInterface, + kMethodImplementation, + kMethodCount, // Number of elements in enum. + }; + public: // Build a new table copying `other` and adding the new entry formed of // the pair { `interface_method`, `implementation_method` } ImtConflictTable(ImtConflictTable* other, ArtMethod* interface_method, - ArtMethod* implementation_method) { - size_t index = 0; - while (other->entries_[index].interface_method != nullptr) { - entries_[index] = other->entries_[index]; - index++; + ArtMethod* implementation_method, + size_t pointer_size) { + const size_t count = other->NumEntries(pointer_size); + for (size_t i = 0; i < count; ++i) { + SetInterfaceMethod(i, pointer_size, other->GetInterfaceMethod(i, pointer_size)); + SetImplementationMethod(i, pointer_size, other->GetImplementationMethod(i, pointer_size)); } - entries_[index].interface_method = interface_method; - entries_[index].implementation_method = implementation_method; + SetInterfaceMethod(count, pointer_size, interface_method); + SetImplementationMethod(count, pointer_size, implementation_method); // Add the null marker. - entries_[index + 1].interface_method = nullptr; - entries_[index + 1].implementation_method = nullptr; + SetInterfaceMethod(count + 1, pointer_size, nullptr); + SetImplementationMethod(count + 1, pointer_size, nullptr); + } + + // num_entries excludes the header. + ImtConflictTable(size_t num_entries, size_t pointer_size) { + SetInterfaceMethod(num_entries, pointer_size, nullptr); + SetImplementationMethod(num_entries, pointer_size, nullptr); + } + + // Set an entry at an index. + void SetInterfaceMethod(size_t index, size_t pointer_size, ArtMethod* method) { + SetMethod(index * kMethodCount + kMethodInterface, pointer_size, method); + } + + void SetImplementationMethod(size_t index, size_t pointer_size, ArtMethod* method) { + SetMethod(index * kMethodCount + kMethodImplementation, pointer_size, method); + } + + ArtMethod* GetInterfaceMethod(size_t index, size_t pointer_size) const { + return GetMethod(index * kMethodCount + kMethodInterface, pointer_size); + } + + ArtMethod* GetImplementationMethod(size_t index, size_t pointer_size) const { + return GetMethod(index * kMethodCount + kMethodImplementation, pointer_size); + } + + // Visit all of the entries. + // NO_THREAD_SAFETY_ANALYSIS for calling with held locks. Visitor is passed a pair of ArtMethod* + // and also returns one. The order is <interface, implementation>. + template<typename Visitor> + void Visit(const Visitor& visitor, size_t pointer_size) NO_THREAD_SAFETY_ANALYSIS { + uint32_t table_index = 0; + for (;;) { + ArtMethod* interface_method = GetInterfaceMethod(table_index, pointer_size); + if (interface_method == nullptr) { + break; + } + ArtMethod* implementation_method = GetImplementationMethod(table_index, pointer_size); + auto input = std::make_pair(interface_method, implementation_method); + std::pair<ArtMethod*, ArtMethod*> updated = visitor(input); + if (input.first != updated.first) { + SetInterfaceMethod(table_index, pointer_size, updated.first); + } + if (input.second != updated.second) { + SetImplementationMethod(table_index, pointer_size, updated.second); + } + ++table_index; + } } // Lookup the implementation ArtMethod associated to `interface_method`. Return null // if not found. - ArtMethod* Lookup(ArtMethod* interface_method) const { + ArtMethod* Lookup(ArtMethod* interface_method, size_t pointer_size) const { uint32_t table_index = 0; - ArtMethod* current_interface_method; - while ((current_interface_method = entries_[table_index].interface_method) != nullptr) { + for (;;) { + ArtMethod* current_interface_method = GetInterfaceMethod(table_index, pointer_size); + if (current_interface_method == nullptr) { + break; + } if (current_interface_method == interface_method) { - return entries_[table_index].implementation_method; + return GetImplementationMethod(table_index, pointer_size); } - table_index++; + ++table_index; } return nullptr; } - // Compute the size in bytes taken by this table. - size_t ComputeSize() const { + // Compute the number of entries in this table. + size_t NumEntries(size_t pointer_size) const { uint32_t table_index = 0; - size_t total_size = 0; - while ((entries_[table_index].interface_method) != nullptr) { - total_size += sizeof(Entry); - table_index++; + while (GetInterfaceMethod(table_index, pointer_size) != nullptr) { + ++table_index; } + return table_index; + } + + // Compute the size in bytes taken by this table. + size_t ComputeSize(size_t pointer_size) const { // Add the end marker. - return total_size + sizeof(Entry); + return ComputeSize(NumEntries(pointer_size), pointer_size); } // Compute the size in bytes needed for copying the given `table` and add // one more entry. - static size_t ComputeSizeWithOneMoreEntry(ImtConflictTable* table) { - return table->ComputeSize() + sizeof(Entry); + static size_t ComputeSizeWithOneMoreEntry(ImtConflictTable* table, size_t pointer_size) { + return table->ComputeSize(pointer_size) + EntrySize(pointer_size); } - struct Entry { - ArtMethod* interface_method; - ArtMethod* implementation_method; - }; + // Compute size with a fixed number of entries. + static size_t ComputeSize(size_t num_entries, size_t pointer_size) { + return (num_entries + 1) * EntrySize(pointer_size); // Add one for null terminator. + } + + static size_t EntrySize(size_t pointer_size) { + return pointer_size * static_cast<size_t>(kMethodCount); + } private: + ArtMethod* GetMethod(size_t index, size_t pointer_size) const { + if (pointer_size == 8) { + return reinterpret_cast<ArtMethod*>(static_cast<uintptr_t>(data64_[index])); + } else { + DCHECK_EQ(pointer_size, 4u); + return reinterpret_cast<ArtMethod*>(static_cast<uintptr_t>(data32_[index])); + } + } + + void SetMethod(size_t index, size_t pointer_size, ArtMethod* method) { + if (pointer_size == 8) { + data64_[index] = dchecked_integral_cast<uint64_t>(reinterpret_cast<uintptr_t>(method)); + } else { + DCHECK_EQ(pointer_size, 4u); + data32_[index] = dchecked_integral_cast<uint32_t>(reinterpret_cast<uintptr_t>(method)); + } + } + // Array of entries that the assembly stubs will iterate over. Note that this is // not fixed size, and we allocate data prior to calling the constructor // of ImtConflictTable. - Entry entries_[0]; + union { + uint32_t data32_[0]; + uint64_t data64_[0]; + }; DISALLOW_COPY_AND_ASSIGN(ImtConflictTable); }; @@ -220,6 +306,10 @@ class ArtMethod FINAL { return !IsAbstract() && !IsDefaultConflicting(); } + bool IsCompilable() { + return (GetAccessFlags() & kAccCompileDontBother) == 0; + } + // A default conflict method is a special sentinel method that stands for a conflict between // multiple default methods. It cannot be invoked, throwing an IncompatibleClassChangeError if one // attempts to do so. @@ -261,6 +351,12 @@ class ArtMethod FINAL { SetAccessFlags(GetAccessFlags() | kAccSkipAccessChecks); } + // Should this method be run in the interpreter and count locks (e.g., failed structured- + // locking verification)? + bool MustCountLocks() { + return (GetAccessFlags() & kAccMustCountLocks) != 0; + } + // Returns true if this method could be overridden by a default method. bool IsOverridableByDefaultMethod() SHARED_REQUIRES(Locks::mutator_lock_); @@ -347,7 +443,6 @@ class ArtMethod FINAL { // Find the method that this method overrides. ArtMethod* FindOverriddenMethod(size_t pointer_size) - REQUIRES(Roles::uninterruptible_) SHARED_REQUIRES(Locks::mutator_lock_); // Find the method index for this method within other_dexfile. If this method isn't present then @@ -413,8 +508,8 @@ class ArtMethod FINAL { return reinterpret_cast<ImtConflictTable*>(GetEntryPointFromJniPtrSize(pointer_size)); } - ALWAYS_INLINE void SetImtConflictTable(ImtConflictTable* table) { - SetEntryPointFromJniPtrSize(table, sizeof(void*)); + ALWAYS_INLINE void SetImtConflictTable(ImtConflictTable* table, size_t pointer_size) { + SetEntryPointFromJniPtrSize(table, pointer_size); } ALWAYS_INLINE void SetProfilingInfo(ProfilingInfo* info) { diff --git a/runtime/asm_support.h b/runtime/asm_support.h index d27d2f6c91..8eb3742b61 100644 --- a/runtime/asm_support.h +++ b/runtime/asm_support.h @@ -20,7 +20,7 @@ #if defined(__cplusplus) #include "art_method.h" #include "gc/allocator/rosalloc.h" -#include "jit/jit_instrumentation.h" +#include "jit/jit.h" #include "lock_word.h" #include "mirror/class.h" #include "mirror/string.h" @@ -396,10 +396,10 @@ ADD_TEST_EQ(THREAD_SUSPEND_REQUEST, static_cast<int32_t>(art::kSuspendRequest)) #define THREAD_CHECKPOINT_REQUEST 2 ADD_TEST_EQ(THREAD_CHECKPOINT_REQUEST, static_cast<int32_t>(art::kCheckpointRequest)) -#define JIT_CHECK_OSR -1 +#define JIT_CHECK_OSR (-1) ADD_TEST_EQ(JIT_CHECK_OSR, static_cast<int32_t>(art::jit::kJitCheckForOSR)) -#define JIT_HOTNESS_DISABLE -2 +#define JIT_HOTNESS_DISABLE (-2) ADD_TEST_EQ(JIT_HOTNESS_DISABLE, static_cast<int32_t>(art::jit::kJitHotnessDisabled)) #if defined(__cplusplus) diff --git a/runtime/base/arena_allocator.cc b/runtime/base/arena_allocator.cc index d951089bb2..b84e29f7ce 100644 --- a/runtime/base/arena_allocator.cc +++ b/runtime/base/arena_allocator.cc @@ -162,6 +162,7 @@ Arena::Arena() : bytes_allocated_(0), next_(nullptr) { MallocArena::MallocArena(size_t size) { memory_ = reinterpret_cast<uint8_t*>(calloc(1, size)); + CHECK(memory_ != nullptr); // Abort on OOM. size_ = size; } @@ -319,15 +320,27 @@ void* ArenaAllocator::AllocWithMemoryTool(size_t bytes, ArenaAllocKind kind) { // mark only the actually allocated memory as defined. That leaves red zones // and padding between allocations marked as inaccessible. size_t rounded_bytes = RoundUp(bytes + kMemoryToolRedZoneBytes, 8); - if (UNLIKELY(ptr_ + rounded_bytes > end_)) { - // Obtain a new block. - ObtainNewArenaForAllocation(rounded_bytes); - CHECK(ptr_ != nullptr); - MEMORY_TOOL_MAKE_NOACCESS(ptr_, end_ - ptr_); - } ArenaAllocatorStats::RecordAlloc(rounded_bytes, kind); - uint8_t* ret = ptr_; - ptr_ += rounded_bytes; + uint8_t* ret; + if (UNLIKELY(rounded_bytes > static_cast<size_t>(end_ - ptr_))) { + ret = AllocFromNewArena(rounded_bytes); + uint8_t* noaccess_begin = ret + bytes; + uint8_t* noaccess_end; + if (ret == arena_head_->Begin()) { + DCHECK(ptr_ - rounded_bytes == ret); + noaccess_end = end_; + } else { + // We're still using the old arena but `ret` comes from a new one just after it. + DCHECK(arena_head_->next_ != nullptr); + DCHECK(ret == arena_head_->next_->Begin()); + DCHECK_EQ(rounded_bytes, arena_head_->next_->GetBytesAllocated()); + noaccess_end = arena_head_->next_->End(); + } + MEMORY_TOOL_MAKE_NOACCESS(noaccess_begin, noaccess_end - noaccess_begin); + } else { + ret = ptr_; + ptr_ += rounded_bytes; + } MEMORY_TOOL_MAKE_DEFINED(ret, bytes); // Check that the memory is already zeroed out. DCHECK(std::all_of(ret, ret + bytes, [](uint8_t val) { return val == 0u; })); @@ -340,14 +353,27 @@ ArenaAllocator::~ArenaAllocator() { pool_->FreeArenaChain(arena_head_); } -void ArenaAllocator::ObtainNewArenaForAllocation(size_t allocation_size) { - UpdateBytesAllocated(); - Arena* new_arena = pool_->AllocArena(std::max(Arena::kDefaultSize, allocation_size)); - new_arena->next_ = arena_head_; - arena_head_ = new_arena; - // Update our internal data structures. - ptr_ = begin_ = new_arena->Begin(); - end_ = new_arena->End(); +uint8_t* ArenaAllocator::AllocFromNewArena(size_t bytes) { + Arena* new_arena = pool_->AllocArena(std::max(Arena::kDefaultSize, bytes)); + DCHECK(new_arena != nullptr); + DCHECK_LE(bytes, new_arena->Size()); + if (static_cast<size_t>(end_ - ptr_) > new_arena->Size() - bytes) { + // The old arena has more space remaining than the new one, so keep using it. + // This can happen when the requested size is over half of the default size. + DCHECK(arena_head_ != nullptr); + new_arena->bytes_allocated_ = bytes; // UpdateBytesAllocated() on the new_arena. + new_arena->next_ = arena_head_->next_; + arena_head_->next_ = new_arena; + } else { + UpdateBytesAllocated(); + new_arena->next_ = arena_head_; + arena_head_ = new_arena; + // Update our internal data structures. + begin_ = new_arena->Begin(); + ptr_ = begin_ + bytes; + end_ = new_arena->End(); + } + return new_arena->Begin(); } bool ArenaAllocator::Contains(const void* ptr) const { diff --git a/runtime/base/arena_allocator.h b/runtime/base/arena_allocator.h index 52a1002e05..6c1a8984cd 100644 --- a/runtime/base/arena_allocator.h +++ b/runtime/base/arena_allocator.h @@ -234,6 +234,8 @@ class Arena { friend class ScopedArenaAllocator; template <bool kCount> friend class ArenaAllocatorStatsImpl; + friend class ArenaAllocatorTest; + private: DISALLOW_COPY_AND_ASSIGN(Arena); }; @@ -303,14 +305,10 @@ class ArenaAllocator return AllocWithMemoryTool(bytes, kind); } bytes = RoundUp(bytes, kAlignment); - if (UNLIKELY(ptr_ + bytes > end_)) { - // Obtain a new block. - ObtainNewArenaForAllocation(bytes); - if (UNLIKELY(ptr_ == nullptr)) { - return nullptr; - } - } ArenaAllocatorStats::RecordAlloc(bytes, kind); + if (UNLIKELY(bytes > static_cast<size_t>(end_ - ptr_))) { + return AllocFromNewArena(bytes); + } uint8_t* ret = ptr_; ptr_ += bytes; return ret; @@ -350,10 +348,6 @@ class ArenaAllocator return static_cast<T*>(Alloc(length * sizeof(T), kind)); } - void* AllocWithMemoryTool(size_t bytes, ArenaAllocKind kind); - - void ObtainNewArenaForAllocation(size_t allocation_size); - size_t BytesAllocated() const; MemStats GetMemStats() const; @@ -369,6 +363,9 @@ class ArenaAllocator bool Contains(const void* ptr) const; private: + void* AllocWithMemoryTool(size_t bytes, ArenaAllocKind kind); + uint8_t* AllocFromNewArena(size_t bytes); + static constexpr size_t kAlignment = 8; void UpdateBytesAllocated(); @@ -382,6 +379,8 @@ class ArenaAllocator template <typename U> friend class ArenaAllocatorAdapter; + friend class ArenaAllocatorTest; + DISALLOW_COPY_AND_ASSIGN(ArenaAllocator); }; // ArenaAllocator diff --git a/runtime/base/arena_allocator_test.cc b/runtime/base/arena_allocator_test.cc new file mode 100644 index 0000000000..9de3cc4312 --- /dev/null +++ b/runtime/base/arena_allocator_test.cc @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2013 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 "base/arena_allocator.h" +#include "base/arena_bit_vector.h" +#include "gtest/gtest.h" + +namespace art { + +class ArenaAllocatorTest : public testing::Test { + protected: + size_t NumberOfArenas(ArenaAllocator* arena) { + size_t result = 0u; + for (Arena* a = arena->arena_head_; a != nullptr; a = a->next_) { + ++result; + } + return result; + } +}; + +TEST_F(ArenaAllocatorTest, Test) { + ArenaPool pool; + ArenaAllocator arena(&pool); + ArenaBitVector bv(&arena, 10, true); + bv.SetBit(5); + EXPECT_EQ(1U, bv.GetStorageSize()); + bv.SetBit(35); + EXPECT_EQ(2U, bv.GetStorageSize()); +} + +TEST_F(ArenaAllocatorTest, MakeDefined) { + // Regression test to make sure we mark the allocated area defined. + ArenaPool pool; + static constexpr size_t kSmallArraySize = 10; + static constexpr size_t kLargeArraySize = 50; + uint32_t* small_array; + { + // Allocate a small array from an arena and release it. + ArenaAllocator arena(&pool); + small_array = arena.AllocArray<uint32_t>(kSmallArraySize); + ASSERT_EQ(0u, small_array[kSmallArraySize - 1u]); + } + { + // Reuse the previous arena and allocate more than previous allocation including red zone. + ArenaAllocator arena(&pool); + uint32_t* large_array = arena.AllocArray<uint32_t>(kLargeArraySize); + ASSERT_EQ(0u, large_array[kLargeArraySize - 1u]); + // Verify that the allocation was made on the same arena. + ASSERT_EQ(small_array, large_array); + } +} + +TEST_F(ArenaAllocatorTest, LargeAllocations) { + { + ArenaPool pool; + ArenaAllocator arena(&pool); + // Note: Leaving some space for memory tool red zones. + void* alloc1 = arena.Alloc(Arena::kDefaultSize * 5 / 8); + void* alloc2 = arena.Alloc(Arena::kDefaultSize * 2 / 8); + ASSERT_NE(alloc1, alloc2); + ASSERT_EQ(1u, NumberOfArenas(&arena)); + } + { + ArenaPool pool; + ArenaAllocator arena(&pool); + void* alloc1 = arena.Alloc(Arena::kDefaultSize * 13 / 16); + void* alloc2 = arena.Alloc(Arena::kDefaultSize * 11 / 16); + ASSERT_NE(alloc1, alloc2); + ASSERT_EQ(2u, NumberOfArenas(&arena)); + void* alloc3 = arena.Alloc(Arena::kDefaultSize * 7 / 16); + ASSERT_NE(alloc1, alloc3); + ASSERT_NE(alloc2, alloc3); + ASSERT_EQ(3u, NumberOfArenas(&arena)); + } + { + ArenaPool pool; + ArenaAllocator arena(&pool); + void* alloc1 = arena.Alloc(Arena::kDefaultSize * 13 / 16); + void* alloc2 = arena.Alloc(Arena::kDefaultSize * 9 / 16); + ASSERT_NE(alloc1, alloc2); + ASSERT_EQ(2u, NumberOfArenas(&arena)); + // Note: Leaving some space for memory tool red zones. + void* alloc3 = arena.Alloc(Arena::kDefaultSize * 5 / 16); + ASSERT_NE(alloc1, alloc3); + ASSERT_NE(alloc2, alloc3); + ASSERT_EQ(2u, NumberOfArenas(&arena)); + } + { + ArenaPool pool; + ArenaAllocator arena(&pool); + void* alloc1 = arena.Alloc(Arena::kDefaultSize * 9 / 16); + void* alloc2 = arena.Alloc(Arena::kDefaultSize * 13 / 16); + ASSERT_NE(alloc1, alloc2); + ASSERT_EQ(2u, NumberOfArenas(&arena)); + // Note: Leaving some space for memory tool red zones. + void* alloc3 = arena.Alloc(Arena::kDefaultSize * 5 / 16); + ASSERT_NE(alloc1, alloc3); + ASSERT_NE(alloc2, alloc3); + ASSERT_EQ(2u, NumberOfArenas(&arena)); + } + { + ArenaPool pool; + ArenaAllocator arena(&pool); + // Note: Leaving some space for memory tool red zones. + for (size_t i = 0; i != 15; ++i) { + arena.Alloc(Arena::kDefaultSize * 1 / 16); // Allocate 15 times from the same arena. + ASSERT_EQ(i + 1u, NumberOfArenas(&arena)); + arena.Alloc(Arena::kDefaultSize * 17 / 16); // Allocate a separate arena. + ASSERT_EQ(i + 2u, NumberOfArenas(&arena)); + } + } +} + +} // namespace art diff --git a/runtime/base/bit_vector.h b/runtime/base/bit_vector.h index 424ebb70f6..56090672ce 100644 --- a/runtime/base/bit_vector.h +++ b/runtime/base/bit_vector.h @@ -111,6 +111,20 @@ class BitVector { const BitVector* const bit_vector_; }; + // MoveConstructible but not MoveAssignable, CopyConstructible or CopyAssignable. + + BitVector(const BitVector& other) = delete; + BitVector& operator=(const BitVector& other) = delete; + + BitVector(BitVector&& other) + : storage_(other.storage_), + storage_size_(other.storage_size_), + allocator_(other.allocator_), + expandable_(other.expandable_) { + other.storage_ = nullptr; + other.storage_size_ = 0u; + } + BitVector(uint32_t start_bits, bool expandable, Allocator* allocator); diff --git a/runtime/base/histogram-inl.h b/runtime/base/histogram-inl.h index c7a0ba2dfd..4af47d170a 100644 --- a/runtime/base/histogram-inl.h +++ b/runtime/base/histogram-inl.h @@ -202,9 +202,13 @@ inline void Histogram<Value>::PrintConfidenceIntervals(std::ostream &os, double template <class Value> inline void Histogram<Value>::PrintMemoryUse(std::ostream &os) const { - os << Name() - << ": Avg: " << PrettySize(Mean()) << " Max: " - << PrettySize(Max()) << " Min: " << PrettySize(Min()) << "\n"; + os << Name(); + if (sample_size_ != 0u) { + os << ": Avg: " << PrettySize(Mean()) << " Max: " + << PrettySize(Max()) << " Min: " << PrettySize(Min()) << "\n"; + } else { + os << ": <no data>\n"; + } } template <class Value> diff --git a/runtime/base/histogram.h b/runtime/base/histogram.h index bcb7b3b769..0e3bc8e1b4 100644 --- a/runtime/base/histogram.h +++ b/runtime/base/histogram.h @@ -85,6 +85,10 @@ template <class Value> class Histogram { return max_value_added_; } + Value BucketWidth() const { + return bucket_width_; + } + const std::string& Name() const { return name_; } diff --git a/runtime/base/logging.cc b/runtime/base/logging.cc index 212e5bd922..3ee15a2469 100644 --- a/runtime/base/logging.cc +++ b/runtime/base/logging.cc @@ -26,7 +26,7 @@ #include "utils.h" // Headers for LogMessage::LogLine. -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID #include "cutils/log.h" #else #include <sys/types.h> @@ -47,7 +47,7 @@ static std::unique_ptr<std::string> gProgramInvocationShortName; // Print INTERNAL_FATAL messages directly instead of at destruction time. This only works on the // host right now: for the device, a stream buf collating output into lines and calling LogLine or // lower-level logging is necessary. -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID static constexpr bool kPrintInternalFatalDirectly = false; #else static constexpr bool kPrintInternalFatalDirectly = !kIsTargetBuild; @@ -235,7 +235,7 @@ std::ostream& LogMessage::stream() { return data_->GetBuffer(); } -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID static const android_LogPriority kLogSeverityToAndroidLogPriority[] = { ANDROID_LOG_VERBOSE, // NONE, use verbose as stand-in, will never be printed. ANDROID_LOG_VERBOSE, ANDROID_LOG_DEBUG, ANDROID_LOG_INFO, ANDROID_LOG_WARN, @@ -251,7 +251,7 @@ void LogMessage::LogLine(const char* file, unsigned int line, LogSeverity log_se return; } -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID const char* tag = ProgramInvocationShortName(); int priority = kLogSeverityToAndroidLogPriority[static_cast<size_t>(log_severity)]; if (priority == ANDROID_LOG_FATAL) { @@ -274,7 +274,7 @@ void LogMessage::LogLineLowStack(const char* file, unsigned int line, LogSeverit return; } -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID // Use android_writeLog() to avoid stack-based buffers used by android_printLog(). const char* tag = ProgramInvocationShortName(); int priority = kLogSeverityToAndroidLogPriority[static_cast<size_t>(log_severity)]; @@ -311,7 +311,7 @@ void LogMessage::LogLineLowStack(const char* file, unsigned int line, LogSeverit TEMP_FAILURE_RETRY(write(STDERR_FILENO, "] ", 2)); TEMP_FAILURE_RETRY(write(STDERR_FILENO, message, strlen(message))); TEMP_FAILURE_RETRY(write(STDERR_FILENO, "\n", 1)); -#endif +#endif // ART_TARGET_ANDROID } ScopedLogSeverity::ScopedLogSeverity(LogSeverity level) { diff --git a/runtime/base/logging.h b/runtime/base/logging.h index 97280c3a03..6323eee53a 100644 --- a/runtime/base/logging.h +++ b/runtime/base/logging.h @@ -56,6 +56,7 @@ struct LogVerbosity { bool threads; bool verifier; bool image; + bool systrace_lock_logging; // Enabled with "-verbose:sys-locks". }; // Global log verbosity setting, initialized by InitLogging. @@ -139,11 +140,11 @@ extern const char* ProgramInvocationShortName(); // Helper for CHECK_STRxx(s1,s2) macros. #define CHECK_STROP(s1, s2, sense) \ - if (UNLIKELY((strcmp(s1, s2) == 0) != sense)) \ + if (UNLIKELY((strcmp(s1, s2) == 0) != (sense))) \ LOG(::art::FATAL) << "Check failed: " \ - << "\"" << s1 << "\"" \ - << (sense ? " == " : " != ") \ - << "\"" << s2 << "\"" + << "\"" << (s1) << "\"" \ + << ((sense) ? " == " : " != ") \ + << "\"" << (s2) << "\"" // Check for string (const char*) equality between s1 and s2, LOG(FATAL) if not. #define CHECK_STREQ(s1, s2) CHECK_STROP(s1, s2, true) @@ -155,7 +156,7 @@ extern const char* ProgramInvocationShortName(); int rc = call args; \ if (rc != 0) { \ errno = rc; \ - PLOG(::art::FATAL) << # call << " failed for " << what; \ + PLOG(::art::FATAL) << # call << " failed for " << (what); \ } \ } while (false) @@ -197,14 +198,14 @@ static constexpr bool kEnableDChecks = true; // types of LHS and RHS. template <typename LHS, typename RHS> struct EagerEvaluator { - EagerEvaluator(LHS l, RHS r) : lhs(l), rhs(r) { } + constexpr EagerEvaluator(LHS l, RHS r) : lhs(l), rhs(r) { } LHS lhs; RHS rhs; }; // Helper function for CHECK_xx. template <typename LHS, typename RHS> -static inline EagerEvaluator<LHS, RHS> MakeEagerEvaluator(LHS lhs, RHS rhs) { +static inline constexpr EagerEvaluator<LHS, RHS> MakeEagerEvaluator(LHS lhs, RHS rhs) { return EagerEvaluator<LHS, RHS>(lhs, rhs); } diff --git a/runtime/base/macros.h b/runtime/base/macros.h index dc692d2b75..3c43253e67 100644 --- a/runtime/base/macros.h +++ b/runtime/base/macros.h @@ -75,7 +75,7 @@ template<typename T> ART_FRIEND_TEST(test_set_name, individual_test) ALWAYS_INLINE void* operator new(size_t, void* ptr) noexcept { return ptr; } \ ALWAYS_INLINE void operator delete(void*, void*) noexcept { } \ private: \ - void* operator new(size_t) = delete + void* operator new(size_t) = delete // NOLINT // The arraysize(arr) macro returns the # of elements in an array arr. // The expression is a compile-time constant, and therefore can be @@ -135,13 +135,13 @@ char (&ArraySizeHelper(T (&array)[N]))[N]; #define ARRAYSIZE_UNSAFE(a) \ ((sizeof(a) / sizeof(*(a))) / static_cast<size_t>(!(sizeof(a) % sizeof(*(a))))) -#define SIZEOF_MEMBER(t, f) sizeof((reinterpret_cast<t*>(4096))->f) +#define SIZEOF_MEMBER(t, f) sizeof((reinterpret_cast<t*>(4096))->f) // NOLINT #define OFFSETOF_MEMBER(t, f) \ - (reinterpret_cast<const char*>(&reinterpret_cast<t*>(16)->f) - reinterpret_cast<const char*>(16)) // NOLINT + (reinterpret_cast<uintptr_t>(&reinterpret_cast<t*>(16)->f) - static_cast<uintptr_t>(16u)) // NOLINT -#define OFFSETOF_VOLATILE_MEMBER(t, f) \ - (reinterpret_cast<volatile char*>(&reinterpret_cast<t*>(16)->f) - reinterpret_cast<volatile char*>(16)) // NOLINT +#define OFFSETOF_MEMBERPTR(t, f) \ + (reinterpret_cast<uintptr_t>(&(reinterpret_cast<t*>(16)->*f)) - static_cast<uintptr_t>(16)) // NOLINT #define PACKED(x) __attribute__ ((__aligned__(x), __packed__)) diff --git a/runtime/base/mutex.cc b/runtime/base/mutex.cc index 620bf9c8b7..6f689d7fee 100644 --- a/runtime/base/mutex.cc +++ b/runtime/base/mutex.cc @@ -49,7 +49,7 @@ Mutex* Locks::modify_ldt_lock_ = nullptr; MutatorMutex* Locks::mutator_lock_ = nullptr; Mutex* Locks::profiler_lock_ = nullptr; ReaderWriterMutex* Locks::oat_file_manager_lock_ = nullptr; -ReaderWriterMutex* Locks::oat_file_count_lock_ = nullptr; +Mutex* Locks::host_dlopen_handles_lock_ = nullptr; Mutex* Locks::reference_processor_lock_ = nullptr; Mutex* Locks::reference_queue_cleared_references_lock_ = nullptr; Mutex* Locks::reference_queue_finalizer_references_lock_ = nullptr; @@ -953,7 +953,7 @@ void Locks::Init() { DCHECK(deoptimization_lock_ != nullptr); DCHECK(heap_bitmap_lock_ != nullptr); DCHECK(oat_file_manager_lock_ != nullptr); - DCHECK(oat_file_count_lock_ != nullptr); + DCHECK(host_dlopen_handles_lock_ != nullptr); DCHECK(intern_table_lock_ != nullptr); DCHECK(jni_libraries_lock_ != nullptr); DCHECK(logging_lock_ != nullptr); @@ -971,7 +971,7 @@ void Locks::Init() { instrument_entrypoints_lock_ = new Mutex("instrument entrypoint lock", current_lock_level); #define UPDATE_CURRENT_LOCK_LEVEL(new_level) \ - if (new_level >= current_lock_level) { \ + if ((new_level) >= current_lock_level) { \ /* Do not use CHECKs or FATAL here, abort_lock_ is not setup yet. */ \ fprintf(stderr, "New local level %d is not less than current level %d\n", \ new_level, current_lock_level); \ @@ -1042,9 +1042,9 @@ void Locks::Init() { DCHECK(oat_file_manager_lock_ == nullptr); oat_file_manager_lock_ = new ReaderWriterMutex("OatFile manager lock", current_lock_level); - UPDATE_CURRENT_LOCK_LEVEL(kOatFileCountLock); - DCHECK(oat_file_count_lock_ == nullptr); - oat_file_count_lock_ = new ReaderWriterMutex("OatFile count lock", current_lock_level); + UPDATE_CURRENT_LOCK_LEVEL(kHostDlOpenHandlesLock); + DCHECK(host_dlopen_handles_lock_ == nullptr); + host_dlopen_handles_lock_ = new Mutex("host dlopen handles lock", current_lock_level); UPDATE_CURRENT_LOCK_LEVEL(kInternTableLock); DCHECK(intern_table_lock_ == nullptr); diff --git a/runtime/base/mutex.h b/runtime/base/mutex.h index 17e0339561..3d7624d979 100644 --- a/runtime/base/mutex.h +++ b/runtime/base/mutex.h @@ -76,7 +76,6 @@ enum LockLevel { kReferenceQueueClearedReferencesLock, kReferenceProcessorLock, kJitDebugInterfaceLock, - kJitCodeCacheLock, kAllocSpaceLock, kBumpPointerSpaceBlockLock, kArenaPoolLock, @@ -84,11 +83,12 @@ enum LockLevel { kDexFileToMethodInlinerMapLock, kInternTableLock, kOatFileSecondaryLookupLock, - kOatFileCountLock, + kHostDlOpenHandlesLock, kOatFileManagerLock, kTracingUniqueMethodsLock, kTracingStreamingLock, kDeoptimizedMethodsLock, + kJitCodeCacheLock, kClassLoaderClassesLock, kDefaultMutexLevel, kMarkSweepLargeObjectLock, @@ -651,11 +651,11 @@ class Locks { // Guards opened oat files in OatFileManager. static ReaderWriterMutex* oat_file_manager_lock_ ACQUIRED_AFTER(modify_ldt_lock_); - // Guards opened oat files in OatFileManager. - static ReaderWriterMutex* oat_file_count_lock_ ACQUIRED_AFTER(oat_file_manager_lock_); + // Guards dlopen_handles_ in DlOpenOatFile. + static Mutex* host_dlopen_handles_lock_ ACQUIRED_AFTER(oat_file_manager_lock_); // Guards intern table. - static Mutex* intern_table_lock_ ACQUIRED_AFTER(oat_file_count_lock_); + static Mutex* intern_table_lock_ ACQUIRED_AFTER(host_dlopen_handles_lock_); // Guards reference processor. static Mutex* reference_processor_lock_ ACQUIRED_AFTER(intern_table_lock_); diff --git a/runtime/check_jni.cc b/runtime/check_jni.cc index beabce36fb..96fa53cb52 100644 --- a/runtime/check_jni.cc +++ b/runtime/check_jni.cc @@ -1176,14 +1176,16 @@ class ScopedCheck { return false; } - // Get the *correct* JNIEnv by going through our TLS pointer. + // Get the current thread's JNIEnv by going through our TLS pointer. JNIEnvExt* threadEnv = self->GetJniEnv(); // Verify that the current thread is (a) attached and (b) associated with // this particular instance of JNIEnv. if (env != threadEnv) { + // Get the thread owning the JNIEnv that's being used. + Thread* envThread = reinterpret_cast<JNIEnvExt*>(env)->self; AbortF("thread %s using JNIEnv* from thread %s", - ToStr<Thread>(*self).c_str(), ToStr<Thread>(*self).c_str()); + ToStr<Thread>(*self).c_str(), ToStr<Thread>(*envThread).c_str()); return false; } @@ -2427,19 +2429,20 @@ class CheckJNI { Primitive::kPrimDouble)); } +// NOLINT added to avoid wrong warning/fix from clang-tidy. #define PRIMITIVE_ARRAY_FUNCTIONS(ctype, name, ptype) \ - static ctype* Get##name##ArrayElements(JNIEnv* env, ctype##Array array, jboolean* is_copy) { \ - return reinterpret_cast<ctype*>( \ + static ctype* Get##name##ArrayElements(JNIEnv* env, ctype##Array array, jboolean* is_copy) { /* NOLINT */ \ + return reinterpret_cast<ctype*>( /* NOLINT */ \ GetPrimitiveArrayElements(__FUNCTION__, ptype, env, array, is_copy)); \ } \ \ - static void Release##name##ArrayElements(JNIEnv* env, ctype##Array array, ctype* elems, \ + static void Release##name##ArrayElements(JNIEnv* env, ctype##Array array, ctype* elems, /* NOLINT */ \ jint mode) { \ ReleasePrimitiveArrayElements(__FUNCTION__, ptype, env, array, elems, mode); \ } \ \ static void Get##name##ArrayRegion(JNIEnv* env, ctype##Array array, jsize start, jsize len, \ - ctype* buf) { \ + ctype* buf) { /* NOLINT */ \ GetPrimitiveArrayRegion(__FUNCTION__, ptype, env, array, start, len, buf); \ } \ \ diff --git a/runtime/class_linker-inl.h b/runtime/class_linker-inl.h index 7e8a4a4fcd..f3e260be56 100644 --- a/runtime/class_linker-inl.h +++ b/runtime/class_linker-inl.h @@ -116,9 +116,10 @@ inline ArtMethod* ClassLinker::GetResolvedMethod(uint32_t method_idx, ArtMethod* return resolved_method; } -inline mirror::Class* ClassLinker::ResolveReferencedClassOfMethod(Thread* self, - uint32_t method_idx, - ArtMethod* referrer) { +inline mirror::Class* ClassLinker::ResolveReferencedClassOfMethod( + uint32_t method_idx, + Handle<mirror::DexCache> dex_cache, + Handle<mirror::ClassLoader> class_loader) { // NB: We cannot simply use `GetResolvedMethod(method_idx, ...)->GetDeclaringClass()`. This is // because if we did so than an invoke-super could be incorrectly dispatched in cases where // GetMethodId(method_idx).class_idx_ refers to a non-interface, non-direct-superclass @@ -127,15 +128,11 @@ inline mirror::Class* ClassLinker::ResolveReferencedClassOfMethod(Thread* self, // interface (either miranda, default or conflict) we would incorrectly assume that is what we // want to invoke on, instead of the 'concrete' implementation that the direct superclass // contains. - mirror::Class* declaring_class = referrer->GetDeclaringClass(); - StackHandleScope<2> hs(self); - Handle<mirror::DexCache> h_dex_cache(hs.NewHandle(declaring_class->GetDexCache())); - const DexFile* dex_file = h_dex_cache->GetDexFile(); + const DexFile* dex_file = dex_cache->GetDexFile(); const DexFile::MethodId& method = dex_file->GetMethodId(method_idx); - mirror::Class* resolved_type = h_dex_cache->GetResolvedType(method.class_idx_); + mirror::Class* resolved_type = dex_cache->GetResolvedType(method.class_idx_); if (UNLIKELY(resolved_type == nullptr)) { - Handle<mirror::ClassLoader> class_loader(hs.NewHandle(declaring_class->GetClassLoader())); - resolved_type = ResolveType(*dex_file, method.class_idx_, h_dex_cache, class_loader); + resolved_type = ResolveType(*dex_file, method.class_idx_, dex_cache, class_loader); } return resolved_type; } diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 1e7ee65eac..15cc6343e8 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -52,6 +52,7 @@ #include "gc/accounting/card_table-inl.h" #include "gc/accounting/heap_bitmap-inl.h" #include "gc/heap.h" +#include "gc/scoped_gc_critical_section.h" #include "gc/space/image_space.h" #include "handle_scope-inl.h" #include "image-inl.h" @@ -687,6 +688,9 @@ bool ClassLinker::InitWithoutImage(std::vector<std::unique_ptr<const DexFile>> b self->AssertNoPendingException(); } + // Create conflict tables that depend on the class linker. + runtime->FixupConflictTables(); + FinishInit(self); VLOG(startup) << "ClassLinker::InitFromCompiler exiting"; @@ -773,9 +777,13 @@ static void SanityCheckArtMethod(ArtMethod* m, bool contains = false; for (gc::space::ImageSpace* space : spaces) { auto& header = space->GetImageHeader(); - auto& methods = header.GetMethodsSection(); - auto offset = reinterpret_cast<uint8_t*>(m) - space->Begin(); - contains |= methods.Contains(offset); + size_t offset = reinterpret_cast<uint8_t*>(m) - space->Begin(); + + const ImageSection& methods = header.GetMethodsSection(); + contains = contains || methods.Contains(offset); + + const ImageSection& runtime_methods = header.GetRuntimeMethodsSection(); + contains = contains || runtime_methods.Contains(offset); } CHECK(contains) << m << " not found"; } @@ -1056,9 +1064,8 @@ bool ClassLinker::InitFromBootImage(std::string* error_msg) { return true; } -static bool IsBootClassLoader(ScopedObjectAccessAlreadyRunnable& soa, - mirror::ClassLoader* class_loader) - SHARED_REQUIRES(Locks::mutator_lock_) { +bool ClassLinker::IsBootClassLoader(ScopedObjectAccessAlreadyRunnable& soa, + mirror::ClassLoader* class_loader) { return class_loader == nullptr || class_loader->GetClass() == soa.Decode<mirror::Class*>(WellKnownClasses::java_lang_BootClassLoader); @@ -1099,7 +1106,7 @@ static bool FlattenPathClassLoader(mirror::ClassLoader* class_loader, soa.DecodeField(WellKnownClasses::dalvik_system_DexPathList_dexElements); CHECK(dex_path_list_field != nullptr); CHECK(dex_elements_field != nullptr); - while (!IsBootClassLoader(soa, class_loader)) { + while (!ClassLinker::IsBootClassLoader(soa, class_loader)) { if (class_loader->GetClass() != soa.Decode<mirror::Class*>(WellKnownClasses::dalvik_system_PathClassLoader)) { *error_msg = StringPrintf("Unknown class loader type %s", PrettyTypeOf(class_loader).c_str()); @@ -1341,7 +1348,8 @@ bool ClassLinker::UpdateAppImageClassLoadersAndDexCaches( for (int32_t j = 0; j < static_cast<int32_t>(num_types); j++) { // The image space is not yet added to the heap, avoid read barriers. mirror::Class* klass = types[j].Read(); - if (klass != nullptr) { + // There may also be boot image classes, + if (space->HasAddress(klass)) { DCHECK_NE(klass->GetStatus(), mirror::Class::kStatusError); // Update the class loader from the one in the image class loader to the one that loaded // the app image. @@ -1380,6 +1388,9 @@ bool ClassLinker::UpdateAppImageClassLoadersAndDexCaches( VLOG(image) << PrettyMethod(&m); } } + } else { + DCHECK(klass == nullptr || heap->ObjectIsInBootImageSpace(klass)) + << klass << " " << PrettyClass(klass); } } } @@ -1387,10 +1398,10 @@ bool ClassLinker::UpdateAppImageClassLoadersAndDexCaches( for (int32_t j = 0; j < static_cast<int32_t>(num_types); j++) { // The image space is not yet added to the heap, avoid read barriers. mirror::Class* klass = types[j].Read(); - if (klass != nullptr) { + if (space->HasAddress(klass)) { DCHECK_NE(klass->GetStatus(), mirror::Class::kStatusError); if (kIsDebugBuild) { - if (new_class_set != nullptr) { + if (new_class_set != nullptr) { auto it = new_class_set->Find(GcRoot<mirror::Class>(klass)); DCHECK(it != new_class_set->end()); DCHECK_EQ(it->Read(), klass); @@ -1438,20 +1449,14 @@ bool ClassLinker::UpdateAppImageClassLoadersAndDexCaches( if (*out_forward_dex_cache_array) { ScopedTrace timing("Fixup ArtMethod dex cache arrays"); FixupArtMethodArrayVisitor visitor(header); - header.GetImageSection(ImageHeader::kSectionArtMethods).VisitPackedArtMethods( - &visitor, - space->Begin(), - sizeof(void*)); + header.VisitPackedArtMethods(&visitor, space->Begin(), sizeof(void*)); Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(class_loader.Get()); } if (kVerifyArtMethodDeclaringClasses) { ScopedTrace timing("Verify declaring classes"); ReaderMutexLock rmu(self, *Locks::heap_bitmap_lock_); VerifyDeclaringClassVisitor visitor; - header.GetImageSection(ImageHeader::kSectionArtMethods).VisitPackedArtMethods( - &visitor, - space->Begin(), - sizeof(void*)); + header.VisitPackedArtMethods(&visitor, space->Begin(), sizeof(void*)); } return true; } @@ -1661,6 +1666,10 @@ bool ClassLinker::AddImageSpace( // resolve the same way, simply flatten the hierarchy in the way the resolution order would be, // and check that the dex file names are the same. for (mirror::ClassLoader* image_class_loader : image_class_loaders) { + if (IsBootClassLoader(soa, image_class_loader)) { + // The dex cache can reference types from the boot class loader. + continue; + } std::list<mirror::String*> image_dex_file_names; std::string temp_error_msg; if (!FlattenPathClassLoader(image_class_loader, &image_dex_file_names, &temp_error_msg)) { @@ -1729,9 +1738,8 @@ bool ClassLinker::AddImageSpace( // Set entry point to interpreter if in InterpretOnly mode. if (!runtime->IsAotCompiler() && runtime->GetInstrumentation()->InterpretOnly()) { - const ImageSection& methods = header.GetMethodsSection(); SetInterpreterEntrypointArtMethodVisitor visitor(image_pointer_size_); - methods.VisitPackedArtMethods(&visitor, space->Begin(), image_pointer_size_); + header.VisitPackedArtMethods(&visitor, space->Begin(), image_pointer_size_); } ClassTable* class_table = nullptr; @@ -1800,10 +1808,7 @@ bool ClassLinker::AddImageSpace( // This verification needs to happen after the classes have been added to the class loader. // Since it ensures classes are in the class table. VerifyClassInTableArtMethodVisitor visitor2(class_table); - header.GetImageSection(ImageHeader::kSectionArtMethods).VisitPackedArtMethods( - &visitor2, - space->Begin(), - sizeof(void*)); + header.VisitPackedArtMethods(&visitor2, space->Begin(), sizeof(void*)); } VLOG(class_linker) << "Adding image space took " << PrettyDuration(NanoTime() - start_time); return true; @@ -2035,6 +2040,7 @@ void ClassLinker::DeleteClassLoader(Thread* self, const ClassLoaderData& data) { Runtime* const runtime = Runtime::Current(); JavaVMExt* const vm = runtime->GetJavaVM(); vm->DeleteWeakGlobalRef(self, data.weak_root); + // Notify the JIT that we need to remove the methods and/or profiling info. if (runtime->GetJit() != nullptr) { jit::JitCodeCache* code_cache = runtime->GetJit()->GetCodeCache(); if (code_cache != nullptr) { @@ -2086,6 +2092,21 @@ mirror::DexCache* ClassLinker::AllocDexCache(Thread* self, reinterpret_cast<ArtMethod**>(raw_arrays + layout.MethodsOffset()); ArtField** fields = (dex_file.NumFieldIds() == 0u) ? nullptr : reinterpret_cast<ArtField**>(raw_arrays + layout.FieldsOffset()); + if (kIsDebugBuild) { + // Sanity check to make sure all the dex cache arrays are empty. b/28992179 + for (size_t i = 0; i < dex_file.NumStringIds(); ++i) { + CHECK(strings[i].Read<kWithoutReadBarrier>() == nullptr); + } + for (size_t i = 0; i < dex_file.NumTypeIds(); ++i) { + CHECK(types[i].Read<kWithoutReadBarrier>() == nullptr); + } + for (size_t i = 0; i < dex_file.NumMethodIds(); ++i) { + CHECK(mirror::DexCache::GetElementPtrSize(methods, i, image_pointer_size_) == nullptr); + } + for (size_t i = 0; i < dex_file.NumFieldIds(); ++i) { + CHECK(mirror::DexCache::GetElementPtrSize(fields, i, image_pointer_size_) == nullptr); + } + } dex_cache->Init(&dex_file, location.Get(), strings, @@ -2447,9 +2468,7 @@ mirror::Class* ClassLinker::DefineClass(Thread* self, self->AssertPendingOOMException(); return nullptr; } - mirror::DexCache* dex_cache = RegisterDexFile( - dex_file, - GetOrCreateAllocatorForClassLoader(class_loader.Get())); + mirror::DexCache* dex_cache = RegisterDexFile(dex_file, class_loader.Get()); if (dex_cache == nullptr) { self->AssertPendingOOMException(); return nullptr; @@ -2752,7 +2771,7 @@ bool ClassLinker::ShouldUseInterpreterEntrypoint(ArtMethod* method, const void* } if (runtime->IsNativeDebuggable()) { - DCHECK(runtime->UseJit() && runtime->GetJit()->JitAtFirstUse()); + DCHECK(runtime->UseJitCompilation() && runtime->GetJit()->JitAtFirstUse()); // If we are doing native debugging, ignore application's AOT code, // since we want to JIT it with extra stackmaps for native debugging. // On the other hand, keep all AOT code from the boot image, since the @@ -3208,7 +3227,8 @@ void ClassLinker::RegisterDexFileLocked(const DexFile& dex_file, dex_caches_.push_back(data); } -mirror::DexCache* ClassLinker::RegisterDexFile(const DexFile& dex_file, LinearAlloc* linear_alloc) { +mirror::DexCache* ClassLinker::RegisterDexFile(const DexFile& dex_file, + mirror::ClassLoader* class_loader) { Thread* self = Thread::Current(); { ReaderMutexLock mu(self, dex_lock_); @@ -3217,21 +3237,31 @@ mirror::DexCache* ClassLinker::RegisterDexFile(const DexFile& dex_file, LinearAl return dex_cache; } } + LinearAlloc* const linear_alloc = GetOrCreateAllocatorForClassLoader(class_loader); + DCHECK(linear_alloc != nullptr); + ClassTable* table; + { + WriterMutexLock mu(self, *Locks::classlinker_classes_lock_); + table = InsertClassTableForClassLoader(class_loader); + } // Don't alloc while holding the lock, since allocation may need to // suspend all threads and another thread may need the dex_lock_ to // get to a suspend point. StackHandleScope<1> hs(self); Handle<mirror::DexCache> h_dex_cache(hs.NewHandle(AllocDexCache(self, dex_file, linear_alloc))); - WriterMutexLock mu(self, dex_lock_); - mirror::DexCache* dex_cache = FindDexCacheLocked(self, dex_file, true); - if (dex_cache != nullptr) { - return dex_cache; - } - if (h_dex_cache.Get() == nullptr) { - self->AssertPendingOOMException(); - return nullptr; + { + WriterMutexLock mu(self, dex_lock_); + mirror::DexCache* dex_cache = FindDexCacheLocked(self, dex_file, true); + if (dex_cache != nullptr) { + return dex_cache; + } + if (h_dex_cache.Get() == nullptr) { + self->AssertPendingOOMException(); + return nullptr; + } + RegisterDexFileLocked(dex_file, h_dex_cache); } - RegisterDexFileLocked(dex_file, h_dex_cache); + table->InsertStrongRoot(h_dex_cache.Get()); return h_dex_cache.Get(); } @@ -4442,7 +4472,20 @@ bool ClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass, // We failed to verify, expect either the klass to be erroneous or verification failed at // compile time. if (klass->IsErroneous()) { - CHECK(self->IsExceptionPending()); + // The class is erroneous. This may be a verifier error, or another thread attempted + // verification and/or initialization and failed. We can distinguish those cases by + // whether an exception is already pending. + if (self->IsExceptionPending()) { + // Check that it's a VerifyError. + DCHECK_EQ("java.lang.Class<java.lang.VerifyError>", + PrettyClass(self->GetException()->GetClass())); + } else { + // Check that another thread attempted initialization. + DCHECK_NE(0, klass->GetClinitThreadId()); + DCHECK_NE(self->GetTid(), klass->GetClinitThreadId()); + // Need to rethrow the previous failure now. + ThrowEarlierClassFailure(klass.Get(), true); + } VlogClassInitializationFailure(klass); } else { CHECK(Runtime::Current()->IsAotCompiler()); @@ -4452,6 +4495,14 @@ bool ClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass, } else { self->AssertNoPendingException(); } + + // A separate thread could have moved us all the way to initialized. A "simple" example + // involves a subclass of the current class being initialized at the same time (which + // will implicitly initialize the superclass, if scheduled that way). b/28254258 + DCHECK_NE(mirror::Class::kStatusError, klass->GetStatus()); + if (klass->IsInitialized()) { + return true; + } } // If the class is kStatusInitializing, either this thread is @@ -5298,6 +5349,19 @@ bool ClassLinker::LoadSuperAndInterfaces(Handle<mirror::Class> klass, const DexF const DexFile::ClassDef& class_def = dex_file.GetClassDef(klass->GetDexClassDefIndex()); uint16_t super_class_idx = class_def.superclass_idx_; if (super_class_idx != DexFile::kDexNoIndex16) { + // Check that a class does not inherit from itself directly. + // + // TODO: This is a cheap check to detect the straightforward case + // of a class extending itself (b/28685551), but we should do a + // proper cycle detection on loaded classes, to detect all cases + // of class circularity errors (b/28830038). + if (super_class_idx == class_def.class_idx_) { + ThrowClassCircularityError(klass.Get(), + "Class %s extends itself", + PrettyDescriptor(klass.Get()).c_str()); + return false; + } + mirror::Class* super_class = ResolveType(dex_file, super_class_idx, klass.Get()); if (super_class == nullptr) { DCHECK(Thread::Current()->IsExceptionPending()); @@ -5940,16 +6004,49 @@ ClassLinker::DefaultMethodSearchResult ClassLinker::FindDefaultMethodImplementat } } -// Sets imt_ref appropriately for LinkInterfaceMethods. -// If there is no method in the imt location of imt_ref it will store the given method there. -// Otherwise it will set the conflict method which will figure out which method to use during -// runtime. -static void SetIMTRef(ArtMethod* unimplemented_method, - ArtMethod* imt_conflict_method, - size_t image_pointer_size, - ArtMethod* current_method, - /*out*/ArtMethod** imt_ref) - SHARED_REQUIRES(Locks::mutator_lock_) { +ArtMethod* ClassLinker::AddMethodToConflictTable(mirror::Class* klass, + ArtMethod* conflict_method, + ArtMethod* interface_method, + ArtMethod* method, + bool force_new_conflict_method) { + ImtConflictTable* current_table = conflict_method->GetImtConflictTable(sizeof(void*)); + Runtime* const runtime = Runtime::Current(); + LinearAlloc* linear_alloc = GetAllocatorForClassLoader(klass->GetClassLoader()); + bool new_entry = conflict_method == runtime->GetImtConflictMethod() || force_new_conflict_method; + + // Create a new entry if the existing one is the shared conflict method. + ArtMethod* new_conflict_method = new_entry + ? runtime->CreateImtConflictMethod(linear_alloc) + : conflict_method; + + // Allocate a new table. Note that we will leak this table at the next conflict, + // but that's a tradeoff compared to making the table fixed size. + void* data = linear_alloc->Alloc( + Thread::Current(), ImtConflictTable::ComputeSizeWithOneMoreEntry(current_table, + image_pointer_size_)); + if (data == nullptr) { + LOG(ERROR) << "Failed to allocate conflict table"; + return conflict_method; + } + ImtConflictTable* new_table = new (data) ImtConflictTable(current_table, + interface_method, + method, + image_pointer_size_); + + // Do a fence to ensure threads see the data in the table before it is assigned + // to the conflict method. + // Note that there is a race in the presence of multiple threads and we may leak + // memory from the LinearAlloc, but that's a tradeoff compared to using + // atomic operations. + QuasiAtomic::ThreadFenceRelease(); + new_conflict_method->SetImtConflictTable(new_table, image_pointer_size_); + return new_conflict_method; +} + +void ClassLinker::SetIMTRef(ArtMethod* unimplemented_method, + ArtMethod* imt_conflict_method, + ArtMethod* current_method, + /*out*/ArtMethod** imt_ref) { // Place method in imt if entry is empty, place conflict otherwise. if (*imt_ref == unimplemented_method) { *imt_ref = current_method; @@ -5959,9 +6056,9 @@ static void SetIMTRef(ArtMethod* unimplemented_method, // Note that we have checked IsRuntimeMethod, as there may be multiple different // conflict methods. MethodNameAndSignatureComparator imt_comparator( - (*imt_ref)->GetInterfaceMethodIfProxy(image_pointer_size)); + (*imt_ref)->GetInterfaceMethodIfProxy(image_pointer_size_)); if (imt_comparator.HasSameNameAndSignature( - current_method->GetInterfaceMethodIfProxy(image_pointer_size))) { + current_method->GetInterfaceMethodIfProxy(image_pointer_size_))) { *imt_ref = current_method; } else { *imt_ref = imt_conflict_method; @@ -5974,6 +6071,151 @@ static void SetIMTRef(ArtMethod* unimplemented_method, } } +void ClassLinker::FillIMTAndConflictTables(mirror::Class* klass) { + DCHECK(klass->ShouldHaveEmbeddedImtAndVTable()) << PrettyClass(klass); + DCHECK(!klass->IsTemp()) << PrettyClass(klass); + ArtMethod* imt[mirror::Class::kImtSize]; + Runtime* const runtime = Runtime::Current(); + ArtMethod* const unimplemented_method = runtime->GetImtUnimplementedMethod(); + ArtMethod* const conflict_method = runtime->GetImtConflictMethod(); + std::fill_n(imt, arraysize(imt), unimplemented_method); + if (klass->GetIfTable() != nullptr) { + FillIMTFromIfTable(klass->GetIfTable(), + unimplemented_method, + conflict_method, + klass, + true, + false, + &imt[0]); + } + for (size_t i = 0; i < mirror::Class::kImtSize; ++i) { + klass->SetEmbeddedImTableEntry(i, imt[i], image_pointer_size_); + } +} + +static inline uint32_t GetIMTIndex(ArtMethod* interface_method) + SHARED_REQUIRES(Locks::mutator_lock_) { + return interface_method->GetDexMethodIndex() % mirror::Class::kImtSize; +} + +ImtConflictTable* ClassLinker::CreateImtConflictTable(size_t count, + LinearAlloc* linear_alloc, + size_t image_pointer_size) { + void* data = linear_alloc->Alloc(Thread::Current(), + ImtConflictTable::ComputeSize(count, + image_pointer_size)); + return (data != nullptr) ? new (data) ImtConflictTable(count, image_pointer_size) : nullptr; +} + +ImtConflictTable* ClassLinker::CreateImtConflictTable(size_t count, LinearAlloc* linear_alloc) { + return CreateImtConflictTable(count, linear_alloc, image_pointer_size_); +} + +void ClassLinker::FillIMTFromIfTable(mirror::IfTable* if_table, + ArtMethod* unimplemented_method, + ArtMethod* imt_conflict_method, + mirror::Class* klass, + bool create_conflict_tables, + bool ignore_copied_methods, + ArtMethod** imt) { + uint32_t conflict_counts[mirror::Class::kImtSize] = {}; + for (size_t i = 0, length = if_table->Count(); i < length; ++i) { + mirror::Class* interface = if_table->GetInterface(i); + const size_t num_virtuals = interface->NumVirtualMethods(); + const size_t method_array_count = if_table->GetMethodArrayCount(i); + // Virtual methods can be larger than the if table methods if there are default methods. + DCHECK_GE(num_virtuals, method_array_count); + if (kIsDebugBuild) { + if (klass->IsInterface()) { + DCHECK_EQ(method_array_count, 0u); + } else { + DCHECK_EQ(interface->NumDeclaredVirtualMethods(), method_array_count); + } + } + if (method_array_count == 0) { + continue; + } + auto* method_array = if_table->GetMethodArray(i); + for (size_t j = 0; j < method_array_count; ++j) { + ArtMethod* implementation_method = + method_array->GetElementPtrSize<ArtMethod*>(j, image_pointer_size_); + if (ignore_copied_methods && implementation_method->IsCopied()) { + continue; + } + DCHECK(implementation_method != nullptr); + // Miranda methods cannot be used to implement an interface method, but they are safe to put + // in the IMT since their entrypoint is the interface trampoline. If we put any copied methods + // or interface methods in the IMT here they will not create extra conflicts since we compare + // names and signatures in SetIMTRef. + ArtMethod* interface_method = interface->GetVirtualMethod(j, image_pointer_size_); + const uint32_t imt_index = GetIMTIndex(interface_method); + + // There is only any conflicts if all of the interface methods for an IMT slot don't have + // the same implementation method, keep track of this to avoid creating a conflict table in + // this case. + + // Conflict table size for each IMT slot. + ++conflict_counts[imt_index]; + + SetIMTRef(unimplemented_method, + imt_conflict_method, + implementation_method, + /*out*/&imt[imt_index]); + } + } + + if (create_conflict_tables) { + // Create the conflict tables. + LinearAlloc* linear_alloc = GetAllocatorForClassLoader(klass->GetClassLoader()); + for (size_t i = 0; i < mirror::Class::kImtSize; ++i) { + size_t conflicts = conflict_counts[i]; + if (imt[i] == imt_conflict_method) { + ImtConflictTable* new_table = CreateImtConflictTable(conflicts, linear_alloc); + if (new_table != nullptr) { + ArtMethod* new_conflict_method = + Runtime::Current()->CreateImtConflictMethod(linear_alloc); + new_conflict_method->SetImtConflictTable(new_table, image_pointer_size_); + imt[i] = new_conflict_method; + } else { + LOG(ERROR) << "Failed to allocate conflict table"; + imt[i] = imt_conflict_method; + } + } else { + DCHECK_NE(imt[i], imt_conflict_method); + } + } + + for (size_t i = 0, length = if_table->Count(); i < length; ++i) { + mirror::Class* interface = if_table->GetInterface(i); + const size_t method_array_count = if_table->GetMethodArrayCount(i); + // Virtual methods can be larger than the if table methods if there are default methods. + if (method_array_count == 0) { + continue; + } + auto* method_array = if_table->GetMethodArray(i); + for (size_t j = 0; j < method_array_count; ++j) { + ArtMethod* implementation_method = + method_array->GetElementPtrSize<ArtMethod*>(j, image_pointer_size_); + if (ignore_copied_methods && implementation_method->IsCopied()) { + continue; + } + DCHECK(implementation_method != nullptr); + ArtMethod* interface_method = interface->GetVirtualMethod(j, image_pointer_size_); + const uint32_t imt_index = GetIMTIndex(interface_method); + if (!imt[imt_index]->IsRuntimeMethod() || + imt[imt_index] == unimplemented_method || + imt[imt_index] == imt_conflict_method) { + continue; + } + ImtConflictTable* table = imt[imt_index]->GetImtConflictTable(image_pointer_size_); + const size_t num_entries = table->NumEntries(image_pointer_size_); + table->SetInterfaceMethod(num_entries, image_pointer_size_, interface_method); + table->SetImplementationMethod(num_entries, image_pointer_size_, implementation_method); + } + } + } +} + // Simple helper function that checks that no subtypes of 'val' are contained within the 'classes' // set. static bool NotSubinterfaceOfAny(const std::unordered_set<mirror::Class*>& classes, @@ -6209,48 +6451,28 @@ static void SanityCheckVTable(Handle<mirror::Class> klass, uint32_t pointer_size } } -static void FillImtFromSuperClass(Handle<mirror::Class> klass, - Handle<mirror::IfTable> iftable, - ArtMethod* unimplemented_method, - ArtMethod* imt_conflict_method, - ArtMethod** out_imt, - size_t pointer_size) SHARED_REQUIRES(Locks::mutator_lock_) { +void ClassLinker::FillImtFromSuperClass(Handle<mirror::Class> klass, + ArtMethod* unimplemented_method, + ArtMethod* imt_conflict_method, + ArtMethod** imt) { DCHECK(klass->HasSuperClass()); mirror::Class* super_class = klass->GetSuperClass(); if (super_class->ShouldHaveEmbeddedImtAndVTable()) { for (size_t i = 0; i < mirror::Class::kImtSize; ++i) { - out_imt[i] = super_class->GetEmbeddedImTableEntry(i, pointer_size); + imt[i] = super_class->GetEmbeddedImTableEntry(i, image_pointer_size_); } } else { // No imt in the super class, need to reconstruct from the iftable. mirror::IfTable* if_table = super_class->GetIfTable(); - const size_t length = super_class->GetIfTableCount(); - for (size_t i = 0; i < length; ++i) { - mirror::Class* interface = iftable->GetInterface(i); - const size_t num_virtuals = interface->NumDeclaredVirtualMethods(); - const size_t method_array_count = if_table->GetMethodArrayCount(i); - DCHECK_EQ(num_virtuals, method_array_count); - if (method_array_count == 0) { - continue; - } - auto* method_array = if_table->GetMethodArray(i); - for (size_t j = 0; j < num_virtuals; ++j) { - auto method = method_array->GetElementPtrSize<ArtMethod*>(j, pointer_size); - DCHECK(method != nullptr) << PrettyClass(super_class); - // Miranda methods cannot be used to implement an interface method and defaults should be - // skipped in case we override it. - if (method->IsDefault() || method->IsMiranda()) { - continue; - } - ArtMethod* interface_method = interface->GetVirtualMethod(j, pointer_size); - uint32_t imt_index = interface_method->GetDexMethodIndex() % mirror::Class::kImtSize; - auto** imt_ref = &out_imt[imt_index]; - if (*imt_ref == unimplemented_method) { - *imt_ref = method; - } else if (*imt_ref != imt_conflict_method) { - *imt_ref = imt_conflict_method; - } - } + if (if_table != nullptr) { + // Ignore copied methods since we will handle these in LinkInterfaceMethods. + FillIMTFromIfTable(if_table, + unimplemented_method, + imt_conflict_method, + klass.Get(), + /*create_conflict_table*/false, + /*ignore_copied_methods*/true, + /*out*/imt); } } } @@ -6293,13 +6515,10 @@ bool ClassLinker::LinkInterfaceMethods( const bool extend_super_iftable = has_superclass; if (has_superclass && fill_tables) { FillImtFromSuperClass(klass, - iftable, unimplemented_method, imt_conflict_method, - out_imt, - image_pointer_size_); + out_imt); } - // Allocate method arrays before since we don't want miss visiting miranda method roots due to // thread suspension. if (fill_tables) { @@ -6383,7 +6602,7 @@ bool ClassLinker::LinkInterfaceMethods( auto* interface_method = iftable->GetInterface(i)->GetVirtualMethod(j, image_pointer_size_); MethodNameAndSignatureComparator interface_name_comparator( interface_method->GetInterfaceMethodIfProxy(image_pointer_size_)); - uint32_t imt_index = interface_method->GetDexMethodIndex() % mirror::Class::kImtSize; + uint32_t imt_index = GetIMTIndex(interface_method); ArtMethod** imt_ptr = &out_imt[imt_index]; // For each method listed in the interface's method list, find the // matching method in our class's method list. We want to favor the @@ -6428,7 +6647,6 @@ bool ClassLinker::LinkInterfaceMethods( // Place method in imt if entry is empty, place conflict otherwise. SetIMTRef(unimplemented_method, imt_conflict_method, - image_pointer_size_, vtable_method, /*out*/imt_ptr); } @@ -6462,6 +6680,17 @@ bool ClassLinker::LinkInterfaceMethods( // The method is not overridable by a default method (i.e. it is directly implemented // in some class). Therefore move onto the next interface method. continue; + } else { + // If the super-classes method is override-able by a default method we need to keep + // track of it since though it is override-able it is not guaranteed to be 'overridden'. + // If it turns out not to be overridden and we did not keep track of it we might add it + // to the vtable twice, causing corruption (vtable entries having inconsistent and + // illegal states, incorrect vtable size, and incorrect or inconsistent iftable entries) + // in this class and any subclasses. + DCHECK(vtable_impl == nullptr || vtable_impl == supers_method) + << "vtable_impl was " << PrettyMethod(vtable_impl) << " and not 'nullptr' or " + << PrettyMethod(supers_method) << " as expected. IFTable appears to be corrupt!"; + vtable_impl = supers_method; } } // If we haven't found it yet we should search through the interfaces for default methods. @@ -6560,7 +6789,6 @@ bool ClassLinker::LinkInterfaceMethods( method_array->SetElementPtrSize(j, current_method, image_pointer_size_); SetIMTRef(unimplemented_method, imt_conflict_method, - image_pointer_size_, current_method, /*out*/imt_ptr); } @@ -6786,7 +7014,13 @@ bool ClassLinker::LinkInterfaceMethods( } } // Put some random garbage in old methods to help find stale pointers. - if (methods != old_methods && old_methods != nullptr) { + if (methods != old_methods && old_methods != nullptr && kIsDebugBuild) { + // Need to make sure the GC is not running since it could be scanning the methods we are + // about to overwrite. + ScopedThreadStateChange tsc(self, kSuspended); + gc::ScopedGCCriticalSection gcs(self, + gc::kGcCauseClassLinker, + gc::kCollectorTypeClassLinker); memset(old_methods, 0xFEu, old_size); } } else { @@ -7631,7 +7865,8 @@ const char* ClassLinker::GetClassRootDescriptor(ClassRoot class_root) { return descriptor; } -jobject ClassLinker::CreatePathClassLoader(Thread* self, std::vector<const DexFile*>& dex_files) { +jobject ClassLinker::CreatePathClassLoader(Thread* self, + const std::vector<const DexFile*>& dex_files) { // SOAAlreadyRunnable is protected, and we need something to add a global reference. // We could move the jobject to the callers, but all call-sites do this... ScopedObjectAccessUnchecked soa(self); @@ -7746,6 +7981,16 @@ void ClassLinker::DropFindArrayClassCache() { find_array_class_cache_next_victim_ = 0; } +void ClassLinker::ClearClassTableStrongRoots() const { + Thread* const self = Thread::Current(); + WriterMutexLock mu(self, *Locks::classlinker_classes_lock_); + for (const ClassLoaderData& data : class_loaders_) { + if (data.class_table != nullptr) { + data.class_table->ClearStrongRoots(); + } + } +} + void ClassLinker::VisitClassLoaders(ClassLoaderVisitor* visitor) const { Thread* const self = Thread::Current(); for (const ClassLoaderData& data : class_loaders_) { @@ -7764,7 +8009,7 @@ void ClassLinker::InsertDexFileInToClassLoader(mirror::Object* dex_file, WriterMutexLock mu(self, *Locks::classlinker_classes_lock_); ClassTable* const table = ClassTableForClassLoader(class_loader); DCHECK(table != nullptr); - if (table->InsertDexFile(dex_file) && class_loader != nullptr) { + if (table->InsertStrongRoot(dex_file) && class_loader != nullptr) { // It was not already inserted, perform the write barrier to let the GC know the class loader's // class table was modified. Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(class_loader); @@ -7814,6 +8059,7 @@ std::set<DexCacheResolvedClasses> ClassLinker::GetResolvedClasses(bool ignore_bo VLOG(class_linker) << "Collecting class profile for dex file " << location << " types=" << num_types << " class_defs=" << num_class_defs; DexCacheResolvedClasses resolved_classes(dex_file->GetLocation(), + dex_file->GetBaseLocation(), dex_file->GetLocationChecksum()); size_t num_resolved = 0; std::unordered_set<uint16_t> class_set; diff --git a/runtime/class_linker.h b/runtime/class_linker.h index 5de502b540..0715babdb6 100644 --- a/runtime/class_linker.h +++ b/runtime/class_linker.h @@ -53,6 +53,7 @@ namespace mirror { class StackTraceElement; } // namespace mirror +class ImtConflictTable; template<class T> class Handle; template<class T> class MutableHandle; class InternTable; @@ -254,7 +255,7 @@ class ClassLinker { SHARED_REQUIRES(Locks::mutator_lock_); // Resolve a Type with the given index from the DexFile, storing the - // result in the DexCache. The referrer is used to identity the + // result in the DexCache. The referrer is used to identify the // target DexCache and ClassLoader to use for resolution. mirror::Class* ResolveType(const DexFile& dex_file, uint16_t type_idx, mirror::Class* referrer) SHARED_REQUIRES(Locks::mutator_lock_) @@ -310,9 +311,9 @@ class ClassLinker { // This returns the class referred to by GetMethodId(method_idx).class_idx_. This might be // different then the declaring class of the resolved method due to copied // miranda/default/conflict methods. - mirror::Class* ResolveReferencedClassOfMethod(Thread* self, - uint32_t method_idx, - ArtMethod* referrer) + mirror::Class* ResolveReferencedClassOfMethod(uint32_t method_idx, + Handle<mirror::DexCache> dex_cache, + Handle<mirror::ClassLoader> class_loader) SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!dex_lock_, !Roles::uninterruptible_); template <ResolveMode kResolveMode> @@ -376,7 +377,8 @@ class ClassLinker { SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!dex_lock_, !Roles::uninterruptible_); - mirror::DexCache* RegisterDexFile(const DexFile& dex_file, LinearAlloc* linear_alloc) + mirror::DexCache* RegisterDexFile(const DexFile& dex_file, + mirror::ClassLoader* class_loader) REQUIRES(!dex_lock_) SHARED_REQUIRES(Locks::mutator_lock_); void RegisterDexFile(const DexFile& dex_file, Handle<mirror::DexCache> dex_cache) @@ -559,7 +561,7 @@ class ClassLinker { // Creates a GlobalRef PathClassLoader that can be used to load classes from the given dex files. // Note: the objects are not completely set up. Do not use this outside of tests and the compiler. - jobject CreatePathClassLoader(Thread* self, std::vector<const DexFile*>& dex_files) + jobject CreatePathClassLoader(Thread* self, const std::vector<const DexFile*>& dex_files) SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!dex_lock_); @@ -610,6 +612,35 @@ class ClassLinker { const std::set<DexCacheResolvedClasses>& classes) REQUIRES(!dex_lock_); + static bool IsBootClassLoader(ScopedObjectAccessAlreadyRunnable& soa, + mirror::ClassLoader* class_loader) + SHARED_REQUIRES(Locks::mutator_lock_); + + ArtMethod* AddMethodToConflictTable(mirror::Class* klass, + ArtMethod* conflict_method, + ArtMethod* interface_method, + ArtMethod* method, + bool force_new_conflict_method) + SHARED_REQUIRES(Locks::mutator_lock_); + + // Create a conflict table with a specified capacity. + ImtConflictTable* CreateImtConflictTable(size_t count, LinearAlloc* linear_alloc); + + // Static version for when the class linker is not yet created. + static ImtConflictTable* CreateImtConflictTable(size_t count, + LinearAlloc* linear_alloc, + size_t pointer_size); + + + // Create the IMT and conflict tables for a class. + void FillIMTAndConflictTables(mirror::Class* klass) SHARED_REQUIRES(Locks::mutator_lock_); + + // Clear class table strong roots (other than classes themselves). This is done by dex2oat to + // allow pruning dex caches. + void ClearClassTableStrongRoots() const + REQUIRES(!Locks::classlinker_classes_lock_) + SHARED_REQUIRES(Locks::mutator_lock_); + struct DexCacheData { // Weak root to the DexCache. Note: Do not decode this unnecessarily or else class unloading may // not work properly. @@ -999,7 +1030,7 @@ class ClassLinker { // Returns null if not found. ClassTable* ClassTableForClassLoader(mirror::ClassLoader* class_loader) - SHARED_REQUIRES(Locks::mutator_lock_, Locks::classlinker_classes_lock_); + SHARED_REQUIRES(Locks::mutator_lock_); // Insert a new class table if not found. ClassTable* InsertClassTableForClassLoader(mirror::ClassLoader* class_loader) @@ -1057,6 +1088,28 @@ class ClassLinker { REQUIRES(!dex_lock_) SHARED_REQUIRES(Locks::mutator_lock_); + // Sets imt_ref appropriately for LinkInterfaceMethods. + // If there is no method in the imt location of imt_ref it will store the given method there. + // Otherwise it will set the conflict method which will figure out which method to use during + // runtime. + void SetIMTRef(ArtMethod* unimplemented_method, + ArtMethod* imt_conflict_method, + ArtMethod* current_method, + /*out*/ArtMethod** imt_ref) SHARED_REQUIRES(Locks::mutator_lock_); + + void FillIMTFromIfTable(mirror::IfTable* if_table, + ArtMethod* unimplemented_method, + ArtMethod* imt_conflict_method, + mirror::Class* klass, + bool create_conflict_tables, + bool ignore_copied_methods, + ArtMethod** imt) SHARED_REQUIRES(Locks::mutator_lock_); + + void FillImtFromSuperClass(Handle<mirror::Class> klass, + ArtMethod* unimplemented_method, + ArtMethod* imt_conflict_method, + ArtMethod** imt) SHARED_REQUIRES(Locks::mutator_lock_); + std::vector<const DexFile*> boot_class_path_; std::vector<std::unique_ptr<const DexFile>> boot_dex_files_; diff --git a/runtime/class_table-inl.h b/runtime/class_table-inl.h index 42e320ae71..d52365df6d 100644 --- a/runtime/class_table-inl.h +++ b/runtime/class_table-inl.h @@ -29,7 +29,7 @@ void ClassTable::VisitRoots(Visitor& visitor) { visitor.VisitRoot(root.AddressWithoutBarrier()); } } - for (GcRoot<mirror::Object>& root : dex_files_) { + for (GcRoot<mirror::Object>& root : strong_roots_) { visitor.VisitRoot(root.AddressWithoutBarrier()); } } @@ -42,7 +42,7 @@ void ClassTable::VisitRoots(const Visitor& visitor) { visitor.VisitRoot(root.AddressWithoutBarrier()); } } - for (GcRoot<mirror::Object>& root : dex_files_) { + for (GcRoot<mirror::Object>& root : strong_roots_) { visitor.VisitRoot(root.AddressWithoutBarrier()); } } diff --git a/runtime/class_table.cc b/runtime/class_table.cc index 8267c68b29..e9154cb400 100644 --- a/runtime/class_table.cc +++ b/runtime/class_table.cc @@ -146,15 +146,15 @@ uint32_t ClassTable::ClassDescriptorHashEquals::operator()(const char* descripto return ComputeModifiedUtf8Hash(descriptor); } -bool ClassTable::InsertDexFile(mirror::Object* dex_file) { +bool ClassTable::InsertStrongRoot(mirror::Object* obj) { WriterMutexLock mu(Thread::Current(), lock_); - DCHECK(dex_file != nullptr); - for (GcRoot<mirror::Object>& root : dex_files_) { - if (root.Read() == dex_file) { + DCHECK(obj != nullptr); + for (GcRoot<mirror::Object>& root : strong_roots_) { + if (root.Read() == obj) { return false; } } - dex_files_.push_back(GcRoot<mirror::Object>(dex_file)); + strong_roots_.push_back(GcRoot<mirror::Object>(obj)); return true; } @@ -189,4 +189,8 @@ void ClassTable::AddClassSet(ClassSet&& set) { classes_.insert(classes_.begin(), std::move(set)); } +void ClassTable::ClearStrongRoots() { + WriterMutexLock mu(Thread::Current(), lock_); + strong_roots_.clear(); +} } // namespace art diff --git a/runtime/class_table.h b/runtime/class_table.h index eb784b5c71..6fb420605c 100644 --- a/runtime/class_table.h +++ b/runtime/class_table.h @@ -133,8 +133,8 @@ class ClassTable { REQUIRES(!lock_) SHARED_REQUIRES(Locks::mutator_lock_); - // Return true if we inserted the dex file, false if it already exists. - bool InsertDexFile(mirror::Object* dex_file) + // Return true if we inserted the strong root, false if it already exists. + bool InsertStrongRoot(mirror::Object* obj) REQUIRES(!lock_) SHARED_REQUIRES(Locks::mutator_lock_); @@ -153,14 +153,24 @@ class ClassTable { REQUIRES(!lock_) SHARED_REQUIRES(Locks::mutator_lock_); + // Clear strong roots (other than classes themselves). + void ClearStrongRoots() + REQUIRES(!lock_) + SHARED_REQUIRES(Locks::mutator_lock_); + + ReaderWriterMutex& GetLock() { + return lock_; + } + private: // Lock to guard inserting and removing. mutable ReaderWriterMutex lock_; // We have a vector to help prevent dirty pages after the zygote forks by calling FreezeSnapshot. std::vector<ClassSet> classes_ GUARDED_BY(lock_); - // Dex files used by the class loader which may not be owned by the class loader. We keep these - // live so that we do not have issues closing any of the dex files. - std::vector<GcRoot<mirror::Object>> dex_files_ GUARDED_BY(lock_); + // Extra strong roots that can be either dex files or dex caches. Dex files used by the class + // loader which may not be owned by the class loader must be held strongly live. Also dex caches + // are held live to prevent them being unloading once they have classes in them. + std::vector<GcRoot<mirror::Object>> strong_roots_ GUARDED_BY(lock_); }; } // namespace art diff --git a/runtime/common_throws.cc b/runtime/common_throws.cc index b4208fe054..75cce424e9 100644 --- a/runtime/common_throws.cc +++ b/runtime/common_throws.cc @@ -137,13 +137,21 @@ void ThrowClassCircularityError(mirror::Class* c) { ThrowException("Ljava/lang/ClassCircularityError;", c, msg.str().c_str()); } +void ThrowClassCircularityError(mirror::Class* c, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + ThrowException("Ljava/lang/ClassCircularityError;", c, fmt, &args); + va_end(args); +} + // ClassFormatError void ThrowClassFormatError(mirror::Class* referrer, const char* fmt, ...) { va_list args; va_start(args, fmt); ThrowException("Ljava/lang/ClassFormatError;", referrer, fmt, &args); - va_end(args);} + va_end(args); +} // IllegalAccessError diff --git a/runtime/common_throws.h b/runtime/common_throws.h index 39c4e52b15..c3a1f09db3 100644 --- a/runtime/common_throws.h +++ b/runtime/common_throws.h @@ -58,6 +58,9 @@ void ThrowArrayStoreException(mirror::Class* element_class, mirror::Class* array void ThrowClassCircularityError(mirror::Class* c) SHARED_REQUIRES(Locks::mutator_lock_) COLD_ATTR; +void ThrowClassCircularityError(mirror::Class* c, const char* fmt, ...) + SHARED_REQUIRES(Locks::mutator_lock_) COLD_ATTR; + // ClassCastException void ThrowClassCastException(mirror::Class* dest_type, mirror::Class* src_type) diff --git a/runtime/compiler_filter.cc b/runtime/compiler_filter.cc index d617caf78c..dc197c1066 100644 --- a/runtime/compiler_filter.cc +++ b/runtime/compiler_filter.cc @@ -20,7 +20,7 @@ namespace art { -bool CompilerFilter::IsCompilationEnabled(Filter filter) { +bool CompilerFilter::IsBytecodeCompilationEnabled(Filter filter) { switch (filter) { case CompilerFilter::kVerifyNone: case CompilerFilter::kVerifyAtRuntime: @@ -39,6 +39,25 @@ bool CompilerFilter::IsCompilationEnabled(Filter filter) { UNREACHABLE(); } +bool CompilerFilter::IsJniCompilationEnabled(Filter filter) { + switch (filter) { + case CompilerFilter::kVerifyNone: + case CompilerFilter::kVerifyAtRuntime: return false; + + case CompilerFilter::kVerifyProfile: + case CompilerFilter::kInterpretOnly: + case CompilerFilter::kSpaceProfile: + case CompilerFilter::kSpace: + case CompilerFilter::kBalanced: + case CompilerFilter::kTime: + case CompilerFilter::kSpeedProfile: + case CompilerFilter::kSpeed: + case CompilerFilter::kEverythingProfile: + case CompilerFilter::kEverything: return true; + } + UNREACHABLE(); +} + bool CompilerFilter::IsVerificationEnabled(Filter filter) { switch (filter) { case CompilerFilter::kVerifyNone: diff --git a/runtime/compiler_filter.h b/runtime/compiler_filter.h index 6289d8a22c..37631cc6d2 100644 --- a/runtime/compiler_filter.h +++ b/runtime/compiler_filter.h @@ -30,10 +30,10 @@ class CompilerFilter FINAL { // Note: Order here matters. Later filter choices are considered "as good // as" earlier filter choices. enum Filter { - kVerifyNone, // Skip verification and compile nothing except JNI stubs. - kVerifyAtRuntime, // Only compile JNI stubs and verify at runtime. - kVerifyProfile, // Verify only the classes in the profile. - kInterpretOnly, // Verify, and compile only JNI stubs. + kVerifyNone, // Skip verification but mark all classes as verified anyway. + kVerifyAtRuntime, // Delay verication to runtime, do not compile anything. + kVerifyProfile, // Verify only the classes in the profile, compile only JNI stubs. + kInterpretOnly, // Verify everything, compile only JNI stubs. kTime, // Compile methods, but minimize compilation time. kSpaceProfile, // Maximize space savings based on profile. kSpace, // Maximize space savings. @@ -44,9 +44,15 @@ class CompilerFilter FINAL { kEverything, // Compile everything capable of being compiled. }; + static const Filter kDefaultCompilerFilter = kSpeed; + + // Returns true if an oat file with this compiler filter contains + // compiled executable code for bytecode. + static bool IsBytecodeCompilationEnabled(Filter filter); + // Returns true if an oat file with this compiler filter contains - // compiled executable code. - static bool IsCompilationEnabled(Filter filter); + // compiled executable code for JNI methods. + static bool IsJniCompilationEnabled(Filter filter); // Returns true if this compiler filter requires running verification. static bool IsVerificationEnabled(Filter filter); diff --git a/runtime/debugger.cc b/runtime/debugger.cc index d8325525ac..5b54f7d4a9 100644 --- a/runtime/debugger.cc +++ b/runtime/debugger.cc @@ -582,7 +582,7 @@ class UpdateEntryPointsClassVisitor : public ClassVisitor { if (Runtime::Current()->GetHeap()->IsInBootImageOatFile(code) && !m.IsNative() && !m.IsProxyMethod()) { - instrumentation_->UpdateMethodsCode(&m, GetQuickToInterpreterBridge()); + instrumentation_->UpdateMethodsCodeFromDebugger(&m, GetQuickToInterpreterBridge()); } } return true; @@ -644,8 +644,7 @@ void Dbg::Disconnected() { LOG(INFO) << "Debugger is no longer active"; - // Suspend all threads and exclusively acquire the mutator lock. Set the state of the thread - // to kRunnable to avoid scoped object access transitions. Remove the debugger as a listener + // Suspend all threads and exclusively acquire the mutator lock. Remove the debugger as a listener // and clear the object registry. Runtime* runtime = Runtime::Current(); Thread* self = Thread::Current(); @@ -655,7 +654,6 @@ void Dbg::Disconnected() { gc::kGcCauseInstrumentation, gc::kCollectorTypeInstrumentation); ScopedSuspendAll ssa(__FUNCTION__); - ThreadState old_state = self->SetStateUnsafe(kRunnable); // Debugger may not be active at this point. if (IsDebuggerActive()) { { @@ -676,7 +674,6 @@ void Dbg::Disconnected() { } gDebuggerActive = false; } - CHECK_EQ(self->SetStateUnsafe(old_state), kRunnable); } { @@ -2362,6 +2359,10 @@ JDWP::ObjectId Dbg::GetThreadId(Thread* thread) { } void Dbg::SuspendVM() { + // Avoid a deadlock between GC and debugger where GC gets suspended during GC. b/25800335. + gc::ScopedGCCriticalSection gcs(Thread::Current(), + gc::kGcCauseDebugger, + gc::kCollectorTypeDebugger); Runtime::Current()->GetThreadList()->SuspendAllForDebugger(); } @@ -4101,6 +4102,8 @@ void Dbg::ExecuteMethodWithoutPendingException(ScopedObjectAccess& soa, DebugInv // Suspend other threads if the invoke is not single-threaded. if ((pReq->options & JDWP::INVOKE_SINGLE_THREADED) == 0) { ScopedThreadSuspension sts(soa.Self(), kWaitingForDebuggerSuspension); + // Avoid a deadlock between GC and debugger where GC gets suspended during GC. b/25800335. + gc::ScopedGCCriticalSection gcs(soa.Self(), gc::kGcCauseDebugger, gc::kCollectorTypeDebugger); VLOG(jdwp) << " Suspending all threads"; Runtime::Current()->GetThreadList()->SuspendAllForDebugger(); } diff --git a/runtime/dex_cache_resolved_classes.h b/runtime/dex_cache_resolved_classes.h index 80c12cb642..0febbedf03 100644 --- a/runtime/dex_cache_resolved_classes.h +++ b/runtime/dex_cache_resolved_classes.h @@ -26,8 +26,11 @@ namespace art { // Data structure for passing around which classes belonging to a dex cache / dex file are resolved. class DexCacheResolvedClasses { public: - DexCacheResolvedClasses(const std::string& dex_location, uint32_t location_checksum) + DexCacheResolvedClasses(const std::string& dex_location, + const std::string& base_location, + uint32_t location_checksum) : dex_location_(dex_location), + base_location_(base_location), location_checksum_(location_checksum) {} // Only compare the key elements, ignore the resolved classes. @@ -35,6 +38,7 @@ class DexCacheResolvedClasses { if (location_checksum_ != other.location_checksum_) { return static_cast<int>(location_checksum_ - other.location_checksum_); } + // Don't need to compare base_location_ since dex_location_ has more info. return dex_location_.compare(other.dex_location_); } @@ -47,6 +51,10 @@ class DexCacheResolvedClasses { return dex_location_; } + const std::string& GetBaseLocation() const { + return base_location_; + } + uint32_t GetLocationChecksum() const { return location_checksum_; } @@ -57,6 +65,7 @@ class DexCacheResolvedClasses { private: const std::string dex_location_; + const std::string base_location_; const uint32_t location_checksum_; // Array of resolved class def indexes. mutable std::unordered_set<uint16_t> classes_; diff --git a/runtime/dex_file.cc b/runtime/dex_file.cc index 63f3f08bad..05c95e069e 100644 --- a/runtime/dex_file.cc +++ b/runtime/dex_file.cc @@ -518,7 +518,7 @@ const DexFile::ClassDef* DexFile::FindClassDef(const char* descriptor, size_t ha return (class_def_idx != DexFile::kDexNoIndex) ? &GetClassDef(class_def_idx) : nullptr; } - // Fast path for rate no class defs case. + // Fast path for rare no class defs case. const uint32_t num_class_defs = NumClassDefs(); if (num_class_defs == 0) { return nullptr; @@ -548,8 +548,8 @@ const DexFile::ClassDef* DexFile::FindClassDef(uint16_t type_idx) const { } const DexFile::FieldId* DexFile::FindFieldId(const DexFile::TypeId& declaring_klass, - const DexFile::StringId& name, - const DexFile::TypeId& type) const { + const DexFile::StringId& name, + const DexFile::TypeId& type) const { // Binary search MethodIds knowing that they are sorted by class_idx, name_idx then proto_idx const uint16_t class_idx = GetIndexForTypeId(declaring_klass); const uint32_t name_idx = GetIndexForStringId(name); @@ -1163,6 +1163,18 @@ static uint64_t ReadUnsignedLong(const uint8_t* ptr, int zwidth, bool fill_on_ri return val; } +// Checks that visibility is as expected. Includes special behavior for M and +// before to allow runtime and build visibility when expecting runtime. +static bool IsVisibilityCompatible(uint32_t actual, uint32_t expected) { + if (expected == DexFile::kDexVisibilityRuntime) { + int32_t sdk_version = Runtime::Current()->GetTargetSdkVersion(); + if (sdk_version > 0 && sdk_version <= 23) { + return actual == DexFile::kDexVisibilityRuntime || actual == DexFile::kDexVisibilityBuild; + } + } + return actual == expected; +} + const DexFile::AnnotationSetItem* DexFile::FindAnnotationSetForField(ArtField* field) const { mirror::Class* klass = field->GetDeclaringClass(); const AnnotationsDirectoryItem* annotations_dir = GetAnnotationsDirectory(*klass->GetClassDef()); @@ -1640,7 +1652,7 @@ const DexFile::AnnotationItem* DexFile::GetAnnotationItemFromAnnotationSet( Handle<mirror::Class> annotation_class) const { for (uint32_t i = 0; i < annotation_set->size_; ++i) { const AnnotationItem* annotation_item = GetAnnotationItem(annotation_set, i); - if (annotation_item->visibility_ != visibility) { + if (!IsVisibilityCompatible(annotation_item->visibility_, visibility)) { continue; } const uint8_t* annotation = annotation_item->annotation_; @@ -1758,6 +1770,8 @@ mirror::ObjectArray<mirror::Object>* DexFile::ProcessAnnotationSet(Handle<mirror uint32_t dest_index = 0; for (uint32_t i = 0; i < size; ++i) { const AnnotationItem* annotation_item = GetAnnotationItem(annotation_set, i); + // Note that we do not use IsVisibilityCompatible here because older code + // was correct for this case. if (annotation_item->visibility_ != visibility) { continue; } @@ -2146,7 +2160,7 @@ const DexFile::AnnotationItem* DexFile::SearchAnnotationSet(const AnnotationSetI const AnnotationItem* result = nullptr; for (uint32_t i = 0; i < annotation_set->size_; ++i) { const AnnotationItem* annotation_item = GetAnnotationItem(annotation_set, i); - if (annotation_item->visibility_ != visibility) { + if (!IsVisibilityCompatible(annotation_item->visibility_, visibility)) { continue; } const uint8_t* annotation = annotation_item->annotation_; diff --git a/runtime/dex_file.h b/runtime/dex_file.h index 3a28422067..638821bfb7 100644 --- a/runtime/dex_file.h +++ b/runtime/dex_file.h @@ -57,6 +57,11 @@ class ZipArchive; // TODO: move all of the macro functionality into the DexCache class. class DexFile { public: + // First Dex format version supporting default methods. + static const uint32_t kDefaultMethodsVersion = 37; + // First Dex format version enforcing class definition ordering rules. + static const uint32_t kClassDefinitionOrderEnforcedVersion = 37; + static const uint8_t kDexMagic[]; static constexpr size_t kNumDexVersions = 2; static constexpr size_t kDexVersionLen = 4; diff --git a/runtime/dex_file_verifier.cc b/runtime/dex_file_verifier.cc index 681c5f977f..1d243490ab 100644 --- a/runtime/dex_file_verifier.cc +++ b/runtime/dex_file_verifier.cc @@ -101,31 +101,31 @@ const DexFile::MethodId* DexFileVerifier::CheckLoadMethodId(uint32_t idx, const } // Helper macro to load string and return false on error. -#define LOAD_STRING(var, idx, error) \ - const char* var = CheckLoadStringByIdx(idx, error); \ - if (UNLIKELY(var == nullptr)) { \ - return false; \ +#define LOAD_STRING(var, idx, error) \ + const char* (var) = CheckLoadStringByIdx(idx, error); \ + if (UNLIKELY((var) == nullptr)) { \ + return false; \ } // Helper macro to load string by type idx and return false on error. -#define LOAD_STRING_BY_TYPE(var, type_idx, error) \ - const char* var = CheckLoadStringByTypeIdx(type_idx, error); \ - if (UNLIKELY(var == nullptr)) { \ - return false; \ +#define LOAD_STRING_BY_TYPE(var, type_idx, error) \ + const char* (var) = CheckLoadStringByTypeIdx(type_idx, error); \ + if (UNLIKELY((var) == nullptr)) { \ + return false; \ } // Helper macro to load method id. Return last parameter on error. -#define LOAD_METHOD(var, idx, error_string, error_stmt) \ - const DexFile::MethodId* var = CheckLoadMethodId(idx, error_string); \ - if (UNLIKELY(var == nullptr)) { \ - error_stmt; \ +#define LOAD_METHOD(var, idx, error_string, error_stmt) \ + const DexFile::MethodId* (var) = CheckLoadMethodId(idx, error_string); \ + if (UNLIKELY((var) == nullptr)) { \ + error_stmt; \ } // Helper macro to load method id. Return last parameter on error. -#define LOAD_FIELD(var, idx, fmt, error_stmt) \ - const DexFile::FieldId* var = CheckLoadFieldId(idx, fmt); \ - if (UNLIKELY(var == nullptr)) { \ - error_stmt; \ +#define LOAD_FIELD(var, idx, fmt, error_stmt) \ + const DexFile::FieldId* (var) = CheckLoadFieldId(idx, fmt); \ + if (UNLIKELY((var) == nullptr)) { \ + error_stmt; \ } bool DexFileVerifier::Verify(const DexFile* dex_file, const uint8_t* begin, size_t size, @@ -251,6 +251,14 @@ bool DexFileVerifier::CheckValidOffsetAndSize(uint32_t offset, return true; } +bool DexFileVerifier::CheckSizeLimit(uint32_t size, uint32_t limit, const char* label) { + if (size > limit) { + ErrorStringPrintf("Size(%u) should not exceed limit(%u) for %s.", size, limit, label); + return false; + } + return true; +} + bool DexFileVerifier::CheckHeader() { // Check file size from the header. uint32_t expected_size = header_->file_size_; @@ -298,10 +306,12 @@ bool DexFileVerifier::CheckHeader() { header_->type_ids_size_, 4, "type-ids") && + CheckSizeLimit(header_->type_ids_size_, DexFile::kDexNoIndex16, "type-ids") && CheckValidOffsetAndSize(header_->proto_ids_off_, header_->proto_ids_size_, 4, "proto-ids") && + CheckSizeLimit(header_->proto_ids_size_, DexFile::kDexNoIndex16, "proto-ids") && CheckValidOffsetAndSize(header_->field_ids_off_, header_->field_ids_size_, 4, @@ -1786,13 +1796,8 @@ bool DexFileVerifier::CheckInterProtoIdItem() { while (curr_it.HasNext() && prev_it.HasNext()) { uint16_t prev_idx = prev_it.GetTypeIdx(); uint16_t curr_idx = curr_it.GetTypeIdx(); - if (prev_idx == DexFile::kDexNoIndex16) { - break; - } - if (UNLIKELY(curr_idx == DexFile::kDexNoIndex16)) { - ErrorStringPrintf("Out-of-order proto_id arguments"); - return false; - } + DCHECK_NE(prev_idx, DexFile::kDexNoIndex16); + DCHECK_NE(curr_idx, DexFile::kDexNoIndex16); if (prev_idx < curr_idx) { break; @@ -1804,6 +1809,12 @@ bool DexFileVerifier::CheckInterProtoIdItem() { prev_it.Next(); curr_it.Next(); } + if (!curr_it.HasNext()) { + // Either a duplicate ProtoId or a ProtoId with a shorter argument list follows + // a ProtoId with a longer one. Both cases are forbidden by the specification. + ErrorStringPrintf("Out-of-order proto_id arguments"); + return false; + } } } @@ -1945,6 +1956,31 @@ bool DexFileVerifier::CheckInterClassDefItem() { } if (item->superclass_idx_ != DexFile::kDexNoIndex16) { + if (header_->GetVersion() >= DexFile::kClassDefinitionOrderEnforcedVersion) { + // Check that a class does not inherit from itself directly (by having + // the same type idx as its super class). + if (UNLIKELY(item->superclass_idx_ == item->class_idx_)) { + ErrorStringPrintf("Class with same type idx as its superclass: '%d'", item->class_idx_); + return false; + } + + // Check that a class is defined after its super class (if the + // latter is defined in the same Dex file). + const DexFile::ClassDef* superclass_def = dex_file_->FindClassDef(item->superclass_idx_); + if (superclass_def != nullptr) { + // The superclass is defined in this Dex file. + if (superclass_def > item) { + // ClassDef item for super class appearing after the class' ClassDef item. + ErrorStringPrintf("Invalid class definition ordering:" + " class with type idx: '%d' defined before" + " superclass with type idx: '%d'", + item->class_idx_, + item->superclass_idx_); + return false; + } + } + } + LOAD_STRING_BY_TYPE(superclass_descriptor, item->superclass_idx_, "inter_class_def_item superclass_idx") if (UNLIKELY(!IsValidDescriptor(superclass_descriptor) || superclass_descriptor[0] != 'L')) { @@ -1953,12 +1989,39 @@ bool DexFileVerifier::CheckInterClassDefItem() { } } + // Check interfaces. const DexFile::TypeList* interfaces = dex_file_->GetInterfacesList(*item); if (interfaces != nullptr) { uint32_t size = interfaces->Size(); - - // Ensure that all interfaces refer to classes (not arrays or primitives). for (uint32_t i = 0; i < size; i++) { + if (header_->GetVersion() >= DexFile::kClassDefinitionOrderEnforcedVersion) { + // Check that a class does not implement itself directly (by having the + // same type idx as one of its immediate implemented interfaces). + if (UNLIKELY(interfaces->GetTypeItem(i).type_idx_ == item->class_idx_)) { + ErrorStringPrintf("Class with same type idx as implemented interface: '%d'", + item->class_idx_); + return false; + } + + // Check that a class is defined after the interfaces it implements + // (if they are defined in the same Dex file). + const DexFile::ClassDef* interface_def = + dex_file_->FindClassDef(interfaces->GetTypeItem(i).type_idx_); + if (interface_def != nullptr) { + // The interface is defined in this Dex file. + if (interface_def > item) { + // ClassDef item for interface appearing after the class' ClassDef item. + ErrorStringPrintf("Invalid class definition ordering:" + " class with type idx: '%d' defined before" + " implemented interface with type idx: '%d'", + item->class_idx_, + interfaces->GetTypeItem(i).type_idx_); + return false; + } + } + } + + // Ensure that the interface refers to a class (not an array nor a primitive type). LOAD_STRING_BY_TYPE(inf_descriptor, interfaces->GetTypeItem(i).type_idx_, "inter_class_def_item interface type_idx") if (UNLIKELY(!IsValidDescriptor(inf_descriptor) || inf_descriptor[0] != 'L')) { @@ -2358,7 +2421,8 @@ static bool CheckAtMostOneOfPublicProtectedPrivate(uint32_t flags) { static std::string GetStringOrError(const uint8_t* const begin, const DexFile::Header* const header, uint32_t string_idx) { - if (header->string_ids_size_ < string_idx) { + // The `string_idx` is not guaranteed to be valid yet. + if (header->string_ids_size_ <= string_idx) { return "(error)"; } @@ -2375,9 +2439,11 @@ static std::string GetStringOrError(const uint8_t* const begin, static std::string GetClassOrError(const uint8_t* const begin, const DexFile::Header* const header, uint32_t class_idx) { - if (header->type_ids_size_ < class_idx) { - return "(error)"; - } + // The `class_idx` is either `FieldId::class_idx_` or `MethodId::class_idx_` and + // it has already been checked in `DexFileVerifier::CheckClassDataItemField()` + // or `DexFileVerifier::CheckClassDataItemMethod()`, respectively, to match + // a valid defining class. + CHECK_LT(class_idx, header->type_ids_size_); const DexFile::TypeId* type_id = reinterpret_cast<const DexFile::TypeId*>(begin + header->type_ids_off_) + class_idx; @@ -2390,9 +2456,8 @@ static std::string GetClassOrError(const uint8_t* const begin, static std::string GetFieldDescriptionOrError(const uint8_t* const begin, const DexFile::Header* const header, uint32_t idx) { - if (header->field_ids_size_ < idx) { - return "(error)"; - } + // The `idx` has already been checked in `DexFileVerifier::CheckClassDataItemField()`. + CHECK_LT(idx, header->field_ids_size_); const DexFile::FieldId* field_id = reinterpret_cast<const DexFile::FieldId*>(begin + header->field_ids_off_) + idx; @@ -2408,9 +2473,8 @@ static std::string GetFieldDescriptionOrError(const uint8_t* const begin, static std::string GetMethodDescriptionOrError(const uint8_t* const begin, const DexFile::Header* const header, uint32_t idx) { - if (header->method_ids_size_ < idx) { - return "(error)"; - } + // The `idx` has already been checked in `DexFileVerifier::CheckClassDataItemMethod()`. + CHECK_LT(idx, header->method_ids_size_); const DexFile::MethodId* method_id = reinterpret_cast<const DexFile::MethodId*>(begin + header->method_ids_off_) + idx; @@ -2465,7 +2529,7 @@ bool DexFileVerifier::CheckFieldAccessFlags(uint32_t idx, GetFieldDescriptionOrError(begin_, header_, idx).c_str(), field_access_flags, PrettyJavaAccessFlags(field_access_flags).c_str()); - if (header_->GetVersion() >= 37) { + if (header_->GetVersion() >= DexFile::kDefaultMethodsVersion) { return false; } else { // Allow in older versions, but warn. @@ -2480,7 +2544,7 @@ bool DexFileVerifier::CheckFieldAccessFlags(uint32_t idx, GetFieldDescriptionOrError(begin_, header_, idx).c_str(), field_access_flags, PrettyJavaAccessFlags(field_access_flags).c_str()); - if (header_->GetVersion() >= 37) { + if (header_->GetVersion() >= DexFile::kDefaultMethodsVersion) { return false; } else { // Allow in older versions, but warn. @@ -2608,7 +2672,13 @@ bool DexFileVerifier::CheckMethodAccessFlags(uint32_t method_index, *error_msg = StringPrintf("Constructor %" PRIu32 "(%s) is not flagged correctly wrt/ static.", method_index, GetMethodDescriptionOrError(begin_, header_, method_index).c_str()); - return false; + if (header_->GetVersion() >= DexFile::kDefaultMethodsVersion) { + return false; + } else { + // Allow in older versions, but warn. + LOG(WARNING) << "This dex file is invalid and will be rejected in the future. Error is: " + << *error_msg; + } } } // Check that static and private methods, as well as constructors, are in the direct methods list, @@ -2628,12 +2698,16 @@ bool DexFileVerifier::CheckMethodAccessFlags(uint32_t method_index, // Interfaces are special. if ((class_access_flags & kAccInterface) != 0) { - // Non-static interface methods must be public. - if ((method_access_flags & (kAccPublic | kAccStatic)) == 0) { + // Non-static interface methods must be public or private. + uint32_t desired_flags = (kAccPublic | kAccStatic); + if (dex_file_->GetVersion() >= DexFile::kDefaultMethodsVersion) { + desired_flags |= kAccPrivate; + } + if ((method_access_flags & desired_flags) == 0) { *error_msg = StringPrintf("Interface virtual method %" PRIu32 "(%s) is not public", method_index, GetMethodDescriptionOrError(begin_, header_, method_index).c_str()); - if (header_->GetVersion() >= 37) { + if (header_->GetVersion() >= DexFile::kDefaultMethodsVersion) { return false; } else { // Allow in older versions, but warn. @@ -2658,7 +2732,13 @@ bool DexFileVerifier::CheckMethodAccessFlags(uint32_t method_index, *error_msg = StringPrintf("Constructor %u(%s) must not be abstract or native", method_index, GetMethodDescriptionOrError(begin_, header_, method_index).c_str()); - return false; + if (header_->GetVersion() >= DexFile::kDefaultMethodsVersion) { + return false; + } else { + // Allow in older versions, but warn. + LOG(WARNING) << "This dex file is invalid and will be rejected in the future. Error is: " + << *error_msg; + } } if ((method_access_flags & kAccAbstract) != 0) { // Abstract methods are not allowed to have the following flags. @@ -2686,7 +2766,7 @@ bool DexFileVerifier::CheckMethodAccessFlags(uint32_t method_index, *error_msg = StringPrintf("Interface method %" PRIu32 "(%s) is not public and abstract", method_index, GetMethodDescriptionOrError(begin_, header_, method_index).c_str()); - if (header_->GetVersion() >= 37) { + if (header_->GetVersion() >= DexFile::kDefaultMethodsVersion) { return false; } else { // Allow in older versions, but warn. diff --git a/runtime/dex_file_verifier.h b/runtime/dex_file_verifier.h index be0e6d83f8..90409db44b 100644 --- a/runtime/dex_file_verifier.h +++ b/runtime/dex_file_verifier.h @@ -49,6 +49,8 @@ class DexFileVerifier { // Checks whether the offset is zero (when size is zero) or that the offset falls within the area // claimed by the file. bool CheckValidOffsetAndSize(uint32_t offset, uint32_t size, size_t alignment, const char* label); + // Checks whether the size is less than the limit. + bool CheckSizeLimit(uint32_t size, uint32_t limit, const char* label); bool CheckIndex(uint32_t field, uint32_t limit, const char* label); bool CheckHeader(); diff --git a/runtime/dex_file_verifier_test.cc b/runtime/dex_file_verifier_test.cc index 344d186ad4..4e53914374 100644 --- a/runtime/dex_file_verifier_test.cc +++ b/runtime/dex_file_verifier_test.cc @@ -57,7 +57,14 @@ static const uint8_t kBase64Map[256] = { 255, 255, 255, 255 }; -static inline uint8_t* DecodeBase64(const char* src, size_t* dst_size) { +// Make the Dex file version 37. +static void MakeDexVersion37(DexFile* dex_file) { + size_t offset = OFFSETOF_MEMBER(DexFile::Header, magic_) + 6; + CHECK_EQ(*(dex_file->Begin() + offset), '5'); + *(const_cast<uint8_t*>(dex_file->Begin()) + offset) = '7'; +} + +static inline std::unique_ptr<uint8_t[]> DecodeBase64(const char* src, size_t* dst_size) { std::vector<uint8_t> tmp; uint32_t t = 0, y = 0; int g = 3; @@ -100,7 +107,7 @@ static inline uint8_t* DecodeBase64(const char* src, size_t* dst_size) { *dst_size = 0; } std::copy(tmp.begin(), tmp.end(), dst.get()); - return dst.release(); + return dst; } static void FixUpChecksum(uint8_t* dex_file) { @@ -113,25 +120,18 @@ static void FixUpChecksum(uint8_t* dex_file) { header->checksum_ = adler_checksum; } -// Custom deleter. Necessary to clean up the memory we use (to be able to mutate). -struct DexFileDeleter { - void operator()(DexFile* in) { - if (in != nullptr) { - delete[] in->Begin(); - delete in; - } - } -}; - -using DexFileUniquePtr = std::unique_ptr<DexFile, DexFileDeleter>; - class DexFileVerifierTest : public CommonRuntimeTest { protected: void VerifyModification(const char* dex_file_base64_content, const char* location, std::function<void(DexFile*)> f, const char* expected_error) { - DexFileUniquePtr dex_file(WrapAsDexFile(dex_file_base64_content)); + size_t length; + std::unique_ptr<uint8_t[]> dex_bytes = DecodeBase64(dex_file_base64_content, &length); + CHECK(dex_bytes != nullptr); + // Note: `dex_file` will be destroyed before `dex_bytes`. + std::unique_ptr<DexFile> dex_file( + new DexFile(dex_bytes.get(), length, "tmp", 0, nullptr, nullptr)); f(dex_file.get()); FixUpChecksum(const_cast<uint8_t*>(dex_file->Begin())); @@ -150,15 +150,6 @@ class DexFileVerifierTest : public CommonRuntimeTest { } } } - - private: - static DexFile* WrapAsDexFile(const char* dex_file_content_in_base_64) { - // Decode base64. - size_t length; - uint8_t* dex_bytes = DecodeBase64(dex_file_content_in_base_64, &length); - CHECK(dex_bytes != nullptr); - return new DexFile(dex_bytes, length, "tmp", 0, nullptr, nullptr); - } }; static std::unique_ptr<const DexFile> OpenDexFileBase64(const char* base64, @@ -193,6 +184,12 @@ static std::unique_ptr<const DexFile> OpenDexFileBase64(const char* base64, return dex_file; } +// To generate a base64 encoded Dex file (such as kGoodTestDex, below) +// from Smali files, use: +// +// smali -o classes.dex class1.smali [class2.smali ...] +// base64 classes.dex >classes.dex.base64 + // For reference. static const char kGoodTestDex[] = "ZGV4CjAzNQDrVbyVkxX1HljTznNf95AglkUAhQuFtmKkAgAAcAAAAHhWNBIAAAAAAAAAAAQCAAAN" @@ -290,7 +287,9 @@ static const char kMethodFlagsTestDex[] = // Find the method data for the first method with the given name (from class 0). Note: the pointer // is to the access flags, so that the caller doesn't have to handle the leb128-encoded method-index // delta. -static const uint8_t* FindMethodData(const DexFile* dex_file, const char* name) { +static const uint8_t* FindMethodData(const DexFile* dex_file, + const char* name, + /*out*/ uint32_t* method_idx = nullptr) { const DexFile::ClassDef& class_def = dex_file->GetClassDef(0); const uint8_t* class_data = dex_file->GetClassData(class_def); @@ -316,6 +315,9 @@ static const uint8_t* FindMethodData(const DexFile* dex_file, const char* name) const DexFile::StringId& string_id = dex_file->GetStringId(name_index); const char* str = dex_file->GetStringData(string_id); if (strcmp(name, str) == 0) { + if (method_idx != nullptr) { + *method_idx = method_index; + } DecodeUnsignedLeb128(&trailing); return trailing; } @@ -449,6 +451,7 @@ TEST_F(DexFileVerifierTest, MethodAccessFlagsConstructors) { kMethodFlagsTestDex, "method_flags_constructor_native_nocode", [&](DexFile* dex_file) { + MakeDexVersion37(dex_file); ApplyMaskToMethodFlags(dex_file, "foo", ~kAccDeclaredSynchronized); ApplyMaskToMethodFlags(dex_file, "bar", ~kAccDeclaredSynchronized); @@ -461,6 +464,7 @@ TEST_F(DexFileVerifierTest, MethodAccessFlagsConstructors) { kMethodFlagsTestDex, "method_flags_constructor_abstract_nocode", [&](DexFile* dex_file) { + MakeDexVersion37(dex_file); ApplyMaskToMethodFlags(dex_file, "foo", ~kAccDeclaredSynchronized); ApplyMaskToMethodFlags(dex_file, "bar", ~kAccDeclaredSynchronized); @@ -521,6 +525,7 @@ TEST_F(DexFileVerifierTest, MethodAccessFlagsConstructors) { kMethodFlagsTestDex, "init_not_allowed_flags", [&](DexFile* dex_file) { + MakeDexVersion37(dex_file); ApplyMaskToMethodFlags(dex_file, "foo", ~kAccDeclaredSynchronized); ApplyMaskToMethodFlags(dex_file, "bar", ~kAccDeclaredSynchronized); @@ -683,6 +688,22 @@ TEST_F(DexFileVerifierTest, MethodAccessFlagsIgnoredOK) { } } +TEST_F(DexFileVerifierTest, B28552165) { + // Regression test for bad error string retrieval in different situations. + // Using invalid access flags to trigger the error. + VerifyModification( + kMethodFlagsTestDex, + "b28552165", + [](DexFile* dex_file) { + OrMaskToMethodFlags(dex_file, "foo", kAccPublic | kAccProtected); + uint32_t method_idx; + FindMethodData(dex_file, "foo", &method_idx); + auto* method_id = const_cast<DexFile::MethodId*>(&dex_file->GetMethodId(method_idx)); + method_id->name_idx_ = dex_file->NumStringIds(); + }, + "Method may have only one of public/protected/private, LMethodFlags;.(error)"); +} + // Set of dex files for interface method tests. As it's not as easy to mutate method names, it's // just easier to break up bad cases. @@ -725,13 +746,6 @@ static uint32_t ApplyMaskShifted(uint32_t src_value, uint32_t mask) { return result; } -// Make the Dex file version 37. -static void MakeDexVersion37(DexFile* dex_file) { - size_t offset = OFFSETOF_MEMBER(DexFile::Header, magic_) + 6; - CHECK_EQ(*(dex_file->Begin() + offset), '5'); - *(const_cast<uint8_t*>(dex_file->Begin()) + offset) = '7'; -} - TEST_F(DexFileVerifierTest, MethodAccessFlagsInterfaces) { VerifyModification( kMethodFlagsInterface, @@ -1436,4 +1450,251 @@ TEST_F(DexFileVerifierTest, SectionAlignment) { } } +// Generated from +// +// .class LOverloading; +// +// .super Ljava/lang/Object; +// +// .method public static foo()V +// .registers 1 +// return-void +// .end method +// +// .method public static foo(I)V +// .registers 1 +// return-void +// .end method +static const char kProtoOrderingTestDex[] = + "ZGV4CjAzNQA1L+ABE6voQ9Lr4Ci//efB53oGnDr5PinsAQAAcAAAAHhWNBIAAAAAAAAAAFgBAAAG" + "AAAAcAAAAAQAAACIAAAAAgAAAJgAAAAAAAAAAAAAAAIAAACwAAAAAQAAAMAAAAAMAQAA4AAAAOAA" + "AADjAAAA8gAAAAYBAAAJAQAADQEAAAAAAAABAAAAAgAAAAMAAAADAAAAAwAAAAAAAAAEAAAAAwAA" + "ABQBAAABAAAABQAAAAEAAQAFAAAAAQAAAAAAAAACAAAAAAAAAP////8AAAAASgEAAAAAAAABSQAN" + "TE92ZXJsb2FkaW5nOwASTGphdmEvbGFuZy9PYmplY3Q7AAFWAAJWSQADZm9vAAAAAQAAAAAAAAAA" + "AAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAOAAAAAQABAAAAAAAAAAAAAQAAAA4AAAACAAAJpAIBCbgC" + "AAAMAAAAAAAAAAEAAAAAAAAAAQAAAAYAAABwAAAAAgAAAAQAAACIAAAAAwAAAAIAAACYAAAABQAA" + "AAIAAACwAAAABgAAAAEAAADAAAAAAiAAAAYAAADgAAAAARAAAAEAAAAUAQAAAxAAAAIAAAAcAQAA" + "ASAAAAIAAAAkAQAAACAAAAEAAABKAQAAABAAAAEAAABYAQAA"; + +TEST_F(DexFileVerifierTest, ProtoOrdering) { + { + // The input dex file should be good before modification. + ScratchFile tmp; + std::string error_msg; + std::unique_ptr<const DexFile> raw(OpenDexFileBase64(kProtoOrderingTestDex, + tmp.GetFilename().c_str(), + &error_msg)); + ASSERT_TRUE(raw.get() != nullptr) << error_msg; + } + + // Modify the order of the ProtoIds for two overloads of "foo" with the + // same return type and one having longer parameter list than the other. + for (size_t i = 0; i != 2; ++i) { + VerifyModification( + kProtoOrderingTestDex, + "proto_ordering", + [i](DexFile* dex_file) { + uint32_t method_idx; + const uint8_t* data = FindMethodData(dex_file, "foo", &method_idx); + CHECK(data != nullptr); + // There should be 2 methods called "foo". + CHECK_LT(method_idx + 1u, dex_file->NumMethodIds()); + CHECK_EQ(dex_file->GetMethodId(method_idx).name_idx_, + dex_file->GetMethodId(method_idx + 1).name_idx_); + CHECK_EQ(dex_file->GetMethodId(method_idx).proto_idx_ + 1u, + dex_file->GetMethodId(method_idx + 1).proto_idx_); + // Their return types should be the same. + uint32_t proto1_idx = dex_file->GetMethodId(method_idx).proto_idx_; + const DexFile::ProtoId& proto1 = dex_file->GetProtoId(proto1_idx); + const DexFile::ProtoId& proto2 = dex_file->GetProtoId(proto1_idx + 1u); + CHECK_EQ(proto1.return_type_idx_, proto2.return_type_idx_); + // And the first should not have any parameters while the second should have some. + CHECK(!DexFileParameterIterator(*dex_file, proto1).HasNext()); + CHECK(DexFileParameterIterator(*dex_file, proto2).HasNext()); + if (i == 0) { + // Swap the proto parameters and shorties to break the ordering. + std::swap(const_cast<uint32_t&>(proto1.parameters_off_), + const_cast<uint32_t&>(proto2.parameters_off_)); + std::swap(const_cast<uint32_t&>(proto1.shorty_idx_), + const_cast<uint32_t&>(proto2.shorty_idx_)); + } else { + // Copy the proto parameters and shorty to create duplicate proto id. + const_cast<uint32_t&>(proto1.parameters_off_) = proto2.parameters_off_; + const_cast<uint32_t&>(proto1.shorty_idx_) = proto2.shorty_idx_; + } + }, + "Out-of-order proto_id arguments"); + } +} + +// To generate a base64 encoded Dex file version 037 from Smali files, use: +// +// smali --api-level 24 -o classes.dex class1.smali [class2.smali ...] +// base64 classes.dex >classes.dex.base64 + +// Dex file version 037 generated from: +// +// .class public LB28685551; +// .super LB28685551; + +static const char kClassExtendsItselfTestDex[] = + "ZGV4CjAzNwDeGbgRg1kb6swszpcTWrrOAALB++F4OPT0AAAAcAAAAHhWNBIAAAAAAAAAAKgAAAAB" + "AAAAcAAAAAEAAAB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAHgAAABcAAAAmAAAAJgA" + "AAAAAAAAAAAAAAEAAAAAAAAAAAAAAP////8AAAAAAAAAAAAAAAALTEIyODY4NTU1MTsAAAAABgAA" + "AAAAAAABAAAAAAAAAAEAAAABAAAAcAAAAAIAAAABAAAAdAAAAAYAAAABAAAAeAAAAAIgAAABAAAA" + "mAAAAAAQAAABAAAAqAAAAA=="; + +TEST_F(DexFileVerifierTest, ClassExtendsItself) { + VerifyModification( + kClassExtendsItselfTestDex, + "class_extends_itself", + [](DexFile* dex_file ATTRIBUTE_UNUSED) { /* empty */ }, + "Class with same type idx as its superclass: '0'"); +} + +// Dex file version 037 generated from: +// +// .class public LFoo; +// .super LBar; +// +// and: +// +// .class public LBar; +// .super LFoo; + +static const char kClassesExtendOneAnotherTestDex[] = + "ZGV4CjAzNwBXHSrwpDMwRBkg+L+JeQCuFNRLhQ86duEcAQAAcAAAAHhWNBIAAAAAAAAAANAAAAAC" + "AAAAcAAAAAIAAAB4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAIAAAABcAAAAwAAAAMAA" + "AADHAAAAAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAA/////wAAAAAAAAAAAAAAAAAAAAABAAAAAQAA" + "AAAAAAD/////AAAAAAAAAAAAAAAABUxCYXI7AAVMRm9vOwAAAAYAAAAAAAAAAQAAAAAAAAABAAAA" + "AgAAAHAAAAACAAAAAgAAAHgAAAAGAAAAAgAAAIAAAAACIAAAAgAAAMAAAAAAEAAAAQAAANAAAAA="; + +TEST_F(DexFileVerifierTest, ClassesExtendOneAnother) { + VerifyModification( + kClassesExtendOneAnotherTestDex, + "classes_extend_one_another", + [](DexFile* dex_file ATTRIBUTE_UNUSED) { /* empty */ }, + "Invalid class definition ordering: class with type idx: '1' defined before" + " superclass with type idx: '0'"); +} + +// Dex file version 037 generated from: +// +// .class public LAll; +// .super LYour; +// +// and: +// +// .class public LYour; +// .super LBase; +// +// and: +// +// .class public LBase; +// .super LAll; + +static const char kCircularClassInheritanceTestDex[] = + "ZGV4CjAzNwBMJxgP0SJz6oLXnKfl+J7lSEORLRwF5LNMAQAAcAAAAHhWNBIAAAAAAAAAAAABAAAD" + "AAAAcAAAAAMAAAB8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAIgAAABkAAAA6AAAAOgA" + "AADvAAAA9wAAAAAAAAABAAAAAgAAAAEAAAABAAAAAAAAAAAAAAD/////AAAAAAAAAAAAAAAAAgAA" + "AAEAAAABAAAAAAAAAP////8AAAAAAAAAAAAAAAAAAAAAAQAAAAIAAAAAAAAA/////wAAAAAAAAAA" + "AAAAAAVMQWxsOwAGTEJhc2U7AAZMWW91cjsAAAYAAAAAAAAAAQAAAAAAAAABAAAAAwAAAHAAAAAC" + "AAAAAwAAAHwAAAAGAAAAAwAAAIgAAAACIAAAAwAAAOgAAAAAEAAAAQAAAAABAAA="; + +TEST_F(DexFileVerifierTest, CircularClassInheritance) { + VerifyModification( + kCircularClassInheritanceTestDex, + "circular_class_inheritance", + [](DexFile* dex_file ATTRIBUTE_UNUSED) { /* empty */ }, + "Invalid class definition ordering: class with type idx: '1' defined before" + " superclass with type idx: '0'"); +} + +// Dex file version 037 generated from: +// +// .class public abstract interface LInterfaceImplementsItself; +// .super Ljava/lang/Object; +// .implements LInterfaceImplementsItself; + +static const char kInterfaceImplementsItselfTestDex[] = + "ZGV4CjAzNwCKKrjatp8XbXl5S/bEVJnqaBhjZkQY4440AQAAcAAAAHhWNBIAAAAAAAAAANwAAAAC" + "AAAAcAAAAAIAAAB4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAIAAAACUAAAAoAAAAKAA" + "AAC9AAAAAAAAAAEAAAAAAAAAAQYAAAEAAADUAAAA/////wAAAAAAAAAAAAAAABtMSW50ZXJmYWNl" + "SW1wbGVtZW50c0l0c2VsZjsAEkxqYXZhL2xhbmcvT2JqZWN0OwAAAAABAAAAAAAAAAcAAAAAAAAA" + "AQAAAAAAAAABAAAAAgAAAHAAAAACAAAAAgAAAHgAAAAGAAAAAQAAAIAAAAACIAAAAgAAAKAAAAAB" + "EAAAAQAAANQAAAAAEAAAAQAAANwAAAA="; + +TEST_F(DexFileVerifierTest, InterfaceImplementsItself) { + VerifyModification( + kInterfaceImplementsItselfTestDex, + "interface_implements_itself", + [](DexFile* dex_file ATTRIBUTE_UNUSED) { /* empty */ }, + "Class with same type idx as implemented interface: '0'"); +} + +// Dex file version 037 generated from: +// +// .class public abstract interface LPing; +// .super Ljava/lang/Object; +// .implements LPong; +// +// and: +// +// .class public abstract interface LPong; +// .super Ljava/lang/Object; +// .implements LPing; + +static const char kInterfacesImplementOneAnotherTestDex[] = + "ZGV4CjAzNwD0Kk9sxlYdg3Dy1Cff0gQCuJAQfEP6ohZUAQAAcAAAAHhWNBIAAAAAAAAAAPwAAAAD" + "AAAAcAAAAAMAAAB8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAIgAAACMAAAAyAAAAMgA" + "AADQAAAA2AAAAAAAAAABAAAAAgAAAAEAAAABBgAAAgAAAOwAAAD/////AAAAAAAAAAAAAAAAAAAA" + "AAEGAAACAAAA9AAAAP////8AAAAAAAAAAAAAAAAGTFBpbmc7AAZMUG9uZzsAEkxqYXZhL2xhbmcv" + "T2JqZWN0OwABAAAAAAAAAAEAAAABAAAABwAAAAAAAAABAAAAAAAAAAEAAAADAAAAcAAAAAIAAAAD" + "AAAAfAAAAAYAAAACAAAAiAAAAAIgAAADAAAAyAAAAAEQAAACAAAA7AAAAAAQAAABAAAA/AAAAA=="; + +TEST_F(DexFileVerifierTest, InterfacesImplementOneAnother) { + VerifyModification( + kInterfacesImplementOneAnotherTestDex, + "interfaces_implement_one_another", + [](DexFile* dex_file ATTRIBUTE_UNUSED) { /* empty */ }, + "Invalid class definition ordering: class with type idx: '1' defined before" + " implemented interface with type idx: '0'"); +} + +// Dex file version 037 generated from: +// +// .class public abstract interface LA; +// .super Ljava/lang/Object; +// .implements LB; +// +// and: +// +// .class public abstract interface LB; +// .super Ljava/lang/Object; +// .implements LC; +// +// and: +// +// .class public abstract interface LC; +// .super Ljava/lang/Object; +// .implements LA; + +static const char kCircularInterfaceImplementationTestDex[] = + "ZGV4CjAzNwCzKmD5Fol6XAU6ichYHcUTIP7Z7MdTcEmEAQAAcAAAAHhWNBIAAAAAAAAAACwBAAAE" + "AAAAcAAAAAQAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAJAAAACUAAAA8AAAAPAA" + "AAD1AAAA+gAAAP8AAAAAAAAAAQAAAAIAAAADAAAAAgAAAAEGAAADAAAAHAEAAP////8AAAAAAAAA" + "AAAAAAABAAAAAQYAAAMAAAAUAQAA/////wAAAAAAAAAAAAAAAAAAAAABBgAAAwAAACQBAAD/////" + "AAAAAAAAAAAAAAAAA0xBOwADTEI7AANMQzsAEkxqYXZhL2xhbmcvT2JqZWN0OwAAAQAAAAIAAAAB" + "AAAAAAAAAAEAAAABAAAABwAAAAAAAAABAAAAAAAAAAEAAAAEAAAAcAAAAAIAAAAEAAAAgAAAAAYA" + "AAADAAAAkAAAAAIgAAAEAAAA8AAAAAEQAAADAAAAFAEAAAAQAAABAAAALAEAAA=="; + +TEST_F(DexFileVerifierTest, CircularInterfaceImplementation) { + VerifyModification( + kCircularInterfaceImplementationTestDex, + "circular_interface_implementation", + [](DexFile* dex_file ATTRIBUTE_UNUSED) { /* empty */ }, + "Invalid class definition ordering: class with type idx: '2' defined before" + " implemented interface with type idx: '0'"); +} + } // namespace art diff --git a/runtime/dex_instruction.cc b/runtime/dex_instruction.cc index 3f621249c5..300e618c82 100644 --- a/runtime/dex_instruction.cc +++ b/runtime/dex_instruction.cc @@ -69,11 +69,11 @@ int const Instruction::kInstructionVerifyFlags[] = { int const Instruction::kInstructionSizeInCodeUnits[] = { #define INSTRUCTION_SIZE(opcode, c, p, format, r, i, a, v) \ - ((opcode == NOP) ? -1 : \ - ((format >= k10x) && (format <= k10t)) ? 1 : \ - ((format >= k20t) && (format <= k25x)) ? 2 : \ - ((format >= k32x) && (format <= k3rc)) ? 3 : \ - (format == k51l) ? 5 : -1), + (((opcode) == NOP) ? -1 : \ + (((format) >= k10x) && ((format) <= k10t)) ? 1 : \ + (((format) >= k20t) && ((format) <= k25x)) ? 2 : \ + (((format) >= k32x) && ((format) <= k3rc)) ? 3 : \ + ((format) == k51l) ? 5 : -1), #include "dex_instruction_list.h" DEX_INSTRUCTION_LIST(INSTRUCTION_SIZE) #undef DEX_INSTRUCTION_LIST diff --git a/runtime/dex_instruction.h b/runtime/dex_instruction.h index 035230eb8c..89c3db6223 100644 --- a/runtime/dex_instruction.h +++ b/runtime/dex_instruction.h @@ -80,7 +80,7 @@ class Instruction { }; enum Code { // private marker to avoid generate-operator-out.py from processing. -#define INSTRUCTION_ENUM(opcode, cname, p, f, r, i, a, v) cname = opcode, +#define INSTRUCTION_ENUM(opcode, cname, p, f, r, i, a, v) cname = (opcode), #include "dex_instruction_list.h" DEX_INSTRUCTION_LIST(INSTRUCTION_ENUM) #undef DEX_INSTRUCTION_LIST diff --git a/runtime/entrypoints/entrypoint_utils-inl.h b/runtime/entrypoints/entrypoint_utils-inl.h index 16fbfaad32..fc6257302a 100644 --- a/runtime/entrypoints/entrypoint_utils-inl.h +++ b/runtime/entrypoints/entrypoint_utils-inl.h @@ -514,12 +514,18 @@ inline ArtMethod* FindMethodFromCode(uint32_t method_idx, mirror::Object** this_ CHECK(self->IsExceptionPending()); return nullptr; } else if (!method_reference_class->IsInterface()) { - // It is not an interface. - mirror::Class* super_class = referring_class->GetSuperClass(); + // It is not an interface. If the referring class is in the class hierarchy of the + // referenced class in the bytecode, we use its super class. Otherwise, we throw + // a NoSuchMethodError. + mirror::Class* super_class = nullptr; + if (method_reference_class->IsAssignableFrom(referring_class)) { + super_class = referring_class->GetSuperClass(); + } uint16_t vtable_index = resolved_method->GetMethodIndex(); if (access_check) { // Check existence of super class. - if (super_class == nullptr || !super_class->HasVTable() || + if (super_class == nullptr || + !super_class->HasVTable() || vtable_index >= static_cast<uint32_t>(super_class->GetVTableLength())) { // Behavior to agree with that of the verifier. ThrowNoSuchMethodError(type, resolved_method->GetDeclaringClass(), @@ -693,8 +699,13 @@ inline ArtMethod* FindMethodFast(uint32_t method_idx, mirror::Object* this_objec // Need to do full type resolution... return nullptr; } else if (!method_reference_class->IsInterface()) { - // It is not an interface. - mirror::Class* super_class = referrer->GetDeclaringClass()->GetSuperClass(); + // It is not an interface. If the referring class is in the class hierarchy of the + // referenced class in the bytecode, we use its super class. Otherwise, we cannot + // resolve the method. + if (!method_reference_class->IsAssignableFrom(referring_class)) { + return nullptr; + } + mirror::Class* super_class = referring_class->GetSuperClass(); if (resolved_method->GetMethodIndex() >= super_class->GetVTableLength()) { // The super class does not have the method. return nullptr; diff --git a/runtime/entrypoints/quick/quick_alloc_entrypoints.cc b/runtime/entrypoints/quick/quick_alloc_entrypoints.cc index 4e4f8510ec..c3b3ac0603 100644 --- a/runtime/entrypoints/quick/quick_alloc_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_alloc_entrypoints.cc @@ -32,7 +32,7 @@ extern "C" mirror::Object* artAllocObjectFromCode ##suffix##suffix2( \ uint32_t type_idx, ArtMethod* method, Thread* self) \ SHARED_REQUIRES(Locks::mutator_lock_) { \ ScopedQuickEntrypointChecks sqec(self); \ - if (kUseTlabFastPath && !instrumented_bool && allocator_type == gc::kAllocatorTypeTLAB) { \ + if (kUseTlabFastPath && !(instrumented_bool) && (allocator_type) == gc::kAllocatorTypeTLAB) { \ mirror::Class* klass = method->GetDexCacheResolvedType<false>(type_idx, sizeof(void*)); \ if (LIKELY(klass != nullptr && klass->IsInitialized() && !klass->IsFinalizable())) { \ size_t byte_count = klass->GetObjectSize(); \ @@ -59,7 +59,7 @@ extern "C" mirror::Object* artAllocObjectFromCodeResolved##suffix##suffix2( \ mirror::Class* klass, ArtMethod* method ATTRIBUTE_UNUSED, Thread* self) \ SHARED_REQUIRES(Locks::mutator_lock_) { \ ScopedQuickEntrypointChecks sqec(self); \ - if (kUseTlabFastPath && !instrumented_bool && allocator_type == gc::kAllocatorTypeTLAB) { \ + if (kUseTlabFastPath && !(instrumented_bool) && (allocator_type) == gc::kAllocatorTypeTLAB) { \ if (LIKELY(klass->IsInitialized())) { \ size_t byte_count = klass->GetObjectSize(); \ byte_count = RoundUp(byte_count, gc::space::BumpPointerSpace::kAlignment); \ @@ -85,7 +85,7 @@ extern "C" mirror::Object* artAllocObjectFromCodeInitialized##suffix##suffix2( \ mirror::Class* klass, ArtMethod* method ATTRIBUTE_UNUSED, Thread* self) \ SHARED_REQUIRES(Locks::mutator_lock_) { \ ScopedQuickEntrypointChecks sqec(self); \ - if (kUseTlabFastPath && !instrumented_bool && allocator_type == gc::kAllocatorTypeTLAB) { \ + if (kUseTlabFastPath && !(instrumented_bool) && (allocator_type) == gc::kAllocatorTypeTLAB) { \ size_t byte_count = klass->GetObjectSize(); \ byte_count = RoundUp(byte_count, gc::space::BumpPointerSpace::kAlignment); \ mirror::Object* obj; \ @@ -136,7 +136,7 @@ extern "C" mirror::Array* artCheckAndAllocArrayFromCode##suffix##suffix2( \ uint32_t type_idx, int32_t component_count, ArtMethod* method, Thread* self) \ SHARED_REQUIRES(Locks::mutator_lock_) { \ ScopedQuickEntrypointChecks sqec(self); \ - if (!instrumented_bool) { \ + if (!(instrumented_bool)) { \ return CheckAndAllocArrayFromCode(type_idx, component_count, method, self, false, allocator_type); \ } else { \ return CheckAndAllocArrayFromCodeInstrumented(type_idx, component_count, method, self, false, allocator_type); \ @@ -146,7 +146,7 @@ extern "C" mirror::Array* artCheckAndAllocArrayFromCodeWithAccessCheck##suffix## uint32_t type_idx, int32_t component_count, ArtMethod* method, Thread* self) \ SHARED_REQUIRES(Locks::mutator_lock_) { \ ScopedQuickEntrypointChecks sqec(self); \ - if (!instrumented_bool) { \ + if (!(instrumented_bool)) { \ return CheckAndAllocArrayFromCode(type_idx, component_count, method, self, true, allocator_type); \ } else { \ return CheckAndAllocArrayFromCodeInstrumented(type_idx, component_count, method, self, true, allocator_type); \ @@ -170,7 +170,7 @@ extern "C" mirror::String* artAllocStringFromCharsFromCode##suffix##suffix2( \ return mirror::String::AllocFromCharArray<instrumented_bool>(self, char_count, handle_array, \ offset, allocator_type); \ } \ -extern "C" mirror::String* artAllocStringFromStringFromCode##suffix##suffix2( \ +extern "C" mirror::String* artAllocStringFromStringFromCode##suffix##suffix2( /* NOLINT */ \ mirror::String* string, Thread* self) \ SHARED_REQUIRES(Locks::mutator_lock_) { \ StackHandleScope<1> hs(self); \ diff --git a/runtime/entrypoints/quick/quick_default_externs.h b/runtime/entrypoints/quick/quick_default_externs.h index 4e01d80312..f3a0d2f3ef 100644 --- a/runtime/entrypoints/quick/quick_default_externs.h +++ b/runtime/entrypoints/quick/quick_default_externs.h @@ -77,6 +77,10 @@ extern "C" void art_quick_handle_fill_data(void*, void*); extern "C" void art_quick_lock_object(art::mirror::Object*); extern "C" void art_quick_unlock_object(art::mirror::Object*); +// Lock entrypoints that do not inline any behavior (e.g., thin-locks). +extern "C" void art_quick_lock_object_no_inline(art::mirror::Object*); +extern "C" void art_quick_unlock_object_no_inline(art::mirror::Object*); + // Math entrypoints. extern "C" int64_t art_quick_d2l(double); extern "C" int64_t art_quick_f2l(float); diff --git a/runtime/entrypoints/quick/quick_default_init_entrypoints.h b/runtime/entrypoints/quick/quick_default_init_entrypoints.h new file mode 100644 index 0000000000..5dafa8b599 --- /dev/null +++ b/runtime/entrypoints/quick/quick_default_init_entrypoints.h @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef ART_RUNTIME_ENTRYPOINTS_QUICK_QUICK_DEFAULT_INIT_ENTRYPOINTS_H_ +#define ART_RUNTIME_ENTRYPOINTS_QUICK_QUICK_DEFAULT_INIT_ENTRYPOINTS_H_ + +#include "base/logging.h" +#include "entrypoints/jni/jni_entrypoints.h" +#include "entrypoints/runtime_asm_entrypoints.h" +#include "quick_alloc_entrypoints.h" +#include "quick_default_externs.h" +#include "quick_entrypoints.h" + +namespace art { + +void DefaultInitEntryPoints(JniEntryPoints* jpoints, QuickEntryPoints* qpoints) { + // JNI + jpoints->pDlsymLookup = art_jni_dlsym_lookup_stub; + + // Alloc + ResetQuickAllocEntryPoints(qpoints); + + // DexCache + qpoints->pInitializeStaticStorage = art_quick_initialize_static_storage; + qpoints->pInitializeTypeAndVerifyAccess = art_quick_initialize_type_and_verify_access; + qpoints->pInitializeType = art_quick_initialize_type; + qpoints->pResolveString = art_quick_resolve_string; + + // Field + qpoints->pSet8Instance = art_quick_set8_instance; + qpoints->pSet8Static = art_quick_set8_static; + qpoints->pSet16Instance = art_quick_set16_instance; + qpoints->pSet16Static = art_quick_set16_static; + qpoints->pSet32Instance = art_quick_set32_instance; + qpoints->pSet32Static = art_quick_set32_static; + qpoints->pSet64Instance = art_quick_set64_instance; + qpoints->pSet64Static = art_quick_set64_static; + qpoints->pSetObjInstance = art_quick_set_obj_instance; + qpoints->pSetObjStatic = art_quick_set_obj_static; + qpoints->pGetByteInstance = art_quick_get_byte_instance; + qpoints->pGetBooleanInstance = art_quick_get_boolean_instance; + qpoints->pGetShortInstance = art_quick_get_short_instance; + qpoints->pGetCharInstance = art_quick_get_char_instance; + qpoints->pGet32Instance = art_quick_get32_instance; + qpoints->pGet64Instance = art_quick_get64_instance; + qpoints->pGetObjInstance = art_quick_get_obj_instance; + qpoints->pGetByteStatic = art_quick_get_byte_static; + qpoints->pGetBooleanStatic = art_quick_get_boolean_static; + qpoints->pGetShortStatic = art_quick_get_short_static; + qpoints->pGetCharStatic = art_quick_get_char_static; + qpoints->pGet32Static = art_quick_get32_static; + qpoints->pGet64Static = art_quick_get64_static; + qpoints->pGetObjStatic = art_quick_get_obj_static; + + // Array + qpoints->pAputObjectWithNullAndBoundCheck = art_quick_aput_obj_with_null_and_bound_check; + qpoints->pAputObjectWithBoundCheck = art_quick_aput_obj_with_bound_check; + qpoints->pAputObject = art_quick_aput_obj; + qpoints->pHandleFillArrayData = art_quick_handle_fill_data; + + // JNI + qpoints->pJniMethodStart = JniMethodStart; + qpoints->pJniMethodStartSynchronized = JniMethodStartSynchronized; + qpoints->pJniMethodEnd = JniMethodEnd; + qpoints->pJniMethodEndSynchronized = JniMethodEndSynchronized; + qpoints->pJniMethodEndWithReference = JniMethodEndWithReference; + qpoints->pJniMethodEndWithReferenceSynchronized = JniMethodEndWithReferenceSynchronized; + qpoints->pQuickGenericJniTrampoline = art_quick_generic_jni_trampoline; + + // Locks + if (UNLIKELY(VLOG_IS_ON(systrace_lock_logging))) { + qpoints->pLockObject = art_quick_lock_object_no_inline; + qpoints->pUnlockObject = art_quick_unlock_object_no_inline; + } else { + qpoints->pLockObject = art_quick_lock_object; + qpoints->pUnlockObject = art_quick_unlock_object; + } + + // Invocation + qpoints->pQuickImtConflictTrampoline = art_quick_imt_conflict_trampoline; + qpoints->pQuickResolutionTrampoline = art_quick_resolution_trampoline; + qpoints->pQuickToInterpreterBridge = art_quick_to_interpreter_bridge; + qpoints->pInvokeDirectTrampolineWithAccessCheck = + art_quick_invoke_direct_trampoline_with_access_check; + qpoints->pInvokeInterfaceTrampolineWithAccessCheck = + art_quick_invoke_interface_trampoline_with_access_check; + qpoints->pInvokeStaticTrampolineWithAccessCheck = + art_quick_invoke_static_trampoline_with_access_check; + qpoints->pInvokeSuperTrampolineWithAccessCheck = + art_quick_invoke_super_trampoline_with_access_check; + qpoints->pInvokeVirtualTrampolineWithAccessCheck = + art_quick_invoke_virtual_trampoline_with_access_check; + + // Thread + qpoints->pTestSuspend = art_quick_test_suspend; + + // Throws + qpoints->pDeliverException = art_quick_deliver_exception; + qpoints->pThrowArrayBounds = art_quick_throw_array_bounds; + qpoints->pThrowDivZero = art_quick_throw_div_zero; + qpoints->pThrowNoSuchMethod = art_quick_throw_no_such_method; + qpoints->pThrowNullPointer = art_quick_throw_null_pointer_exception; + qpoints->pThrowStackOverflow = art_quick_throw_stack_overflow; + + // Deoptimize + qpoints->pDeoptimize = art_quick_deoptimize_from_compiled_code; +}; + +} // namespace art + +#endif // ART_RUNTIME_ENTRYPOINTS_QUICK_QUICK_DEFAULT_INIT_ENTRYPOINTS_H_ diff --git a/runtime/entrypoints/quick/quick_deoptimization_entrypoints.cc b/runtime/entrypoints/quick/quick_deoptimization_entrypoints.cc index c019cae722..f35c2fe75e 100644 --- a/runtime/entrypoints/quick/quick_deoptimization_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_deoptimization_entrypoints.cc @@ -29,39 +29,51 @@ namespace art { -extern "C" NO_RETURN void artDeoptimize(Thread* self) SHARED_REQUIRES(Locks::mutator_lock_) { - ScopedQuickEntrypointChecks sqec(self); - +NO_RETURN static void artDeoptimizeImpl(Thread* self, bool single_frame) + SHARED_REQUIRES(Locks::mutator_lock_) { if (VLOG_IS_ON(deopt)) { - LOG(INFO) << "Deopting:"; - self->Dump(LOG(INFO)); + if (single_frame) { + // Deopt logging will be in DeoptimizeSingleFrame. It is there to take advantage of the + // specialized visitor that will show whether a method is Quick or Shadow. + } else { + LOG(INFO) << "Deopting:"; + self->Dump(LOG(INFO)); + } } self->AssertHasDeoptimizationContext(); - self->SetException(Thread::GetDeoptimizationException()); - self->QuickDeliverException(); + QuickExceptionHandler exception_handler(self, true); + if (single_frame) { + exception_handler.DeoptimizeSingleFrame(); + } else { + exception_handler.DeoptimizeStack(); + } + uintptr_t return_pc = exception_handler.UpdateInstrumentationStack(); + if (exception_handler.IsFullFragmentDone()) { + exception_handler.DoLongJump(true); + } else { + exception_handler.DeoptimizePartialFragmentFixup(return_pc); + // We cannot smash the caller-saves, as we need the ArtMethod in a parameter register that would + // be caller-saved. This has the downside that we cannot track incorrect register usage down the + // line. + exception_handler.DoLongJump(false); + } +} + +extern "C" NO_RETURN void artDeoptimize(Thread* self) SHARED_REQUIRES(Locks::mutator_lock_) { + ScopedQuickEntrypointChecks sqec(self); + artDeoptimizeImpl(self, false); } +// This is called directly from compiled code by an HDepptimize. extern "C" NO_RETURN void artDeoptimizeFromCompiledCode(Thread* self) SHARED_REQUIRES(Locks::mutator_lock_) { ScopedQuickEntrypointChecks sqec(self); - - // Deopt logging will be in DeoptimizeSingleFrame. It is there to take advantage of the - // specialized visitor that will show whether a method is Quick or Shadow. - // Before deoptimizing to interpreter, we must push the deoptimization context. JValue return_value; return_value.SetJ(0); // we never deoptimize from compiled code with an invoke result. self->PushDeoptimizationContext(return_value, false, /* from_code */ true, self->GetException()); - - QuickExceptionHandler exception_handler(self, true); - exception_handler.DeoptimizeSingleFrame(); - exception_handler.UpdateInstrumentationStack(); - exception_handler.DeoptimizeSingleFrameArchDependentFixup(); - // We cannot smash the caller-saves, as we need the ArtMethod in a parameter register that would - // be caller-saved. This has the downside that we cannot track incorrect register usage down the - // line. - exception_handler.DoLongJump(false); + artDeoptimizeImpl(self, true); } } // namespace art diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc index f3e8dbadbe..03771aa80e 100644 --- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc @@ -654,7 +654,7 @@ extern "C" uint64_t artQuickToInterpreterBridge(ArtMethod* method, Thread* self, JValue tmp_value; ShadowFrame* deopt_frame = self->PopStackedShadowFrame( - StackedShadowFrameType::kSingleFrameDeoptimizationShadowFrame, false); + StackedShadowFrameType::kDeoptimizationShadowFrame, false); ManagedStack fragment; DCHECK(!method->IsNative()) << PrettyMethod(method); @@ -667,7 +667,7 @@ extern "C" uint64_t artQuickToInterpreterBridge(ArtMethod* method, Thread* self, JValue result; if (deopt_frame != nullptr) { - // Coming from single-frame deopt. + // Coming from partial-fragment deopt. if (kIsDebugBuild) { // Sanity-check: are the methods as expected? We check that the last shadow frame (the bottom @@ -681,7 +681,7 @@ extern "C" uint64_t artQuickToInterpreterBridge(ArtMethod* method, Thread* self, } if (VLOG_IS_ON(deopt)) { - // Print out the stack to verify that it was a single-frame deopt. + // Print out the stack to verify that it was a partial-fragment deopt. LOG(INFO) << "Continue-ing from deopt. Stack is:"; QuickExceptionHandler::DumpFramesWithType(self, true); } @@ -689,7 +689,6 @@ extern "C" uint64_t artQuickToInterpreterBridge(ArtMethod* method, Thread* self, mirror::Throwable* pending_exception = nullptr; bool from_code = false; self->PopDeoptimizationContext(&result, &pending_exception, /* out */ &from_code); - CHECK(from_code); // Push a transition back into managed code onto the linked list in thread. self->PushManagedStackFragment(&fragment); @@ -755,7 +754,12 @@ extern "C" uint64_t artQuickToInterpreterBridge(ArtMethod* method, Thread* self, // Request a stack deoptimization if needed ArtMethod* caller = QuickArgumentVisitor::GetCallingMethod(sp); - if (UNLIKELY(Dbg::IsForcedInterpreterNeededForUpcall(self, caller))) { + uintptr_t caller_pc = QuickArgumentVisitor::GetCallingPc(sp); + // If caller_pc is the instrumentation exit stub, the stub will check to see if deoptimization + // should be done and it knows the real return pc. + if (UNLIKELY(caller_pc != reinterpret_cast<uintptr_t>(GetQuickInstrumentationExitPc()) && + Dbg::IsForcedInterpreterNeededForUpcall(self, caller) && + Runtime::Current()->IsDeoptimizeable(caller_pc))) { // Push the context of the deoptimization stack so we can restore the return value and the // exception before executing the deoptimized frames. self->PushDeoptimizationContext( @@ -1038,9 +1042,14 @@ extern "C" const void* artQuickResolutionTrampoline( } else { DCHECK_EQ(invoke_type, kSuper); CHECK(caller != nullptr) << invoke_type; + StackHandleScope<2> hs(self); + Handle<mirror::DexCache> dex_cache( + hs.NewHandle(caller->GetDeclaringClass()->GetDexCache())); + Handle<mirror::ClassLoader> class_loader( + hs.NewHandle(caller->GetDeclaringClass()->GetClassLoader())); // TODO Maybe put this into a mirror::Class function. mirror::Class* ref_class = linker->ResolveReferencedClassOfMethod( - self, called_method.dex_method_index, caller); + called_method.dex_method_index, dex_cache, class_loader); if (ref_class->IsInterface()) { called = ref_class->FindVirtualMethodForInterfaceSuper(called, sizeof(void*)); } else { @@ -2169,7 +2178,8 @@ extern "C" TwoWordReturn artInvokeInterfaceTrampoline(uint32_t deadbeef ATTRIBUT imt_index % mirror::Class::kImtSize, sizeof(void*)); if (LIKELY(conflict_method->IsRuntimeMethod())) { ImtConflictTable* current_table = conflict_method->GetImtConflictTable(sizeof(void*)); - method = current_table->Lookup(interface_method); + DCHECK(current_table != nullptr); + method = current_table->Lookup(interface_method, sizeof(void*)); } else { // It seems we aren't really a conflict method! method = cls->FindVirtualMethodForInterface(interface_method, sizeof(void*)); @@ -2220,34 +2230,13 @@ extern "C" TwoWordReturn artInvokeInterfaceTrampoline(uint32_t deadbeef ATTRIBUT ArtMethod* conflict_method = cls->GetEmbeddedImTableEntry( imt_index % mirror::Class::kImtSize, sizeof(void*)); if (conflict_method->IsRuntimeMethod()) { - ImtConflictTable* current_table = conflict_method->GetImtConflictTable(sizeof(void*)); - Runtime* runtime = Runtime::Current(); - LinearAlloc* linear_alloc = (cls->GetClassLoader() == nullptr) - ? runtime->GetLinearAlloc() - : cls->GetClassLoader()->GetAllocator(); - bool is_new_entry = (conflict_method == runtime->GetImtConflictMethod()); - - // Create a new entry if the existing one is the shared conflict method. - ArtMethod* new_conflict_method = is_new_entry - ? runtime->CreateImtConflictMethod(linear_alloc) - : conflict_method; - - // Allocate a new table. Note that we will leak this table at the next conflict, - // but that's a tradeoff compared to making the table fixed size. - void* data = linear_alloc->Alloc( - self, ImtConflictTable::ComputeSizeWithOneMoreEntry(current_table)); - CHECK(data != nullptr) << "Out of memory"; - ImtConflictTable* new_table = new (data) ImtConflictTable( - current_table, interface_method, method); - - // Do a fence to ensure threads see the data in the table before it is assigned - // to the conlict method. - // Note that there is a race in the presence of multiple threads and we may leak - // memory from the LinearAlloc, but that's a tradeoff compared to using - // atomic operations. - QuasiAtomic::ThreadFenceRelease(); - new_conflict_method->SetImtConflictTable(new_table); - if (is_new_entry) { + ArtMethod* new_conflict_method = Runtime::Current()->GetClassLinker()->AddMethodToConflictTable( + cls.Get(), + conflict_method, + interface_method, + method, + /*force_new_conflict_method*/false); + if (new_conflict_method != conflict_method) { // Update the IMT if we create a new conflict method. No fence needed here, as the // data is consistent. cls->SetEmbeddedImTableEntry(imt_index % mirror::Class::kImtSize, diff --git a/runtime/fault_handler.cc b/runtime/fault_handler.cc index 5c5abeb0a6..9f073a63a8 100644 --- a/runtime/fault_handler.cc +++ b/runtime/fault_handler.cc @@ -147,6 +147,10 @@ void FaultManager::Shutdown() { } bool FaultManager::HandleFaultByOtherHandlers(int sig, siginfo_t* info, void* context) { + if (other_handlers_.empty()) { + return false; + } + Thread* self = Thread::Current(); DCHECK(self != nullptr); diff --git a/runtime/gc/allocation_record.cc b/runtime/gc/allocation_record.cc index bd023b34f7..d9f1507afe 100644 --- a/runtime/gc/allocation_record.cc +++ b/runtime/gc/allocation_record.cc @@ -20,7 +20,7 @@ #include "base/stl_util.h" #include "stack.h" -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID #include "cutils/properties.h" #endif @@ -38,7 +38,7 @@ const char* AllocRecord::GetClassDescriptor(std::string* storage) const { } void AllocRecordObjectMap::SetProperties() { -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID // Check whether there's a system property overriding the max number of records. const char* propertyName = "dalvik.vm.allocTrackerMax"; char allocMaxString[PROPERTY_VALUE_MAX]; @@ -88,7 +88,7 @@ void AllocRecordObjectMap::SetProperties() { max_stack_depth_ = value; } } -#endif +#endif // ART_TARGET_ANDROID } AllocRecordObjectMap::~AllocRecordObjectMap() { diff --git a/runtime/gc/allocator/dlmalloc.h b/runtime/gc/allocator/dlmalloc.h index 50e2622e89..c07da5da4d 100644 --- a/runtime/gc/allocator/dlmalloc.h +++ b/runtime/gc/allocator/dlmalloc.h @@ -35,7 +35,7 @@ #include "../../external/dlmalloc/malloc.h" #pragma GCC diagnostic pop -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID // Define dlmalloc routines from bionic that cannot be included directly because of redefining // symbols from the include above. extern "C" void dlmalloc_inspect_all(void(*handler)(void*, void *, size_t, void*), void* arg); diff --git a/runtime/gc/allocator/rosalloc.cc b/runtime/gc/allocator/rosalloc.cc index bd84d0d8e5..375d8699ca 100644 --- a/runtime/gc/allocator/rosalloc.cc +++ b/runtime/gc/allocator/rosalloc.cc @@ -1021,7 +1021,7 @@ size_t RosAlloc::BulkFree(Thread* self, void** ptrs, size_t num_ptrs) { // First mark slots to free in the bulk free bit map without locking the // size bracket locks. On host, unordered_set is faster than vector + flag. -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID std::vector<Run*> runs; #else std::unordered_set<Run*, hash_run, eq_run> runs; @@ -1088,7 +1088,7 @@ size_t RosAlloc::BulkFree(Thread* self, void** ptrs, size_t num_ptrs) { DCHECK_EQ(run->magic_num_, kMagicNum); // Set the bit in the bulk free bit map. freed_bytes += run->AddToBulkFreeList(ptr); -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID if (!run->to_be_bulk_freed_) { run->to_be_bulk_freed_ = true; runs.push_back(run); @@ -1103,7 +1103,7 @@ size_t RosAlloc::BulkFree(Thread* self, void** ptrs, size_t num_ptrs) { // union the bulk free bit map into the thread-local free bit map // (for thread-local runs.) for (Run* run : runs) { -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID DCHECK(run->to_be_bulk_freed_); run->to_be_bulk_freed_ = false; #endif diff --git a/runtime/gc/collector/concurrent_copying-inl.h b/runtime/gc/collector/concurrent_copying-inl.h index 26f5ad3df5..64fa4344d6 100644 --- a/runtime/gc/collector/concurrent_copying-inl.h +++ b/runtime/gc/collector/concurrent_copying-inl.h @@ -28,6 +28,47 @@ namespace art { namespace gc { namespace collector { +inline mirror::Object* ConcurrentCopying::MarkUnevacFromSpaceRegionOrImmuneSpace( + mirror::Object* ref, accounting::ContinuousSpaceBitmap* bitmap) { + // For the Baker-style RB, in a rare case, we could incorrectly change the object from white + // to gray even though the object has already been marked through. This happens if a mutator + // thread gets preempted before the AtomicSetReadBarrierPointer below, GC marks through the + // object (changes it from white to gray and back to white), and the thread runs and + // incorrectly changes it from white to gray. We need to detect such "false gray" cases and + // change the objects back to white at the end of marking. + if (kUseBakerReadBarrier) { + // Test the bitmap first to reduce the chance of false gray cases. + if (bitmap->Test(ref)) { + return ref; + } + } + // This may or may not succeed, which is ok because the object may already be gray. + bool cas_success = false; + if (kUseBakerReadBarrier) { + cas_success = ref->AtomicSetReadBarrierPointer(ReadBarrier::WhitePtr(), + ReadBarrier::GrayPtr()); + } + if (bitmap->AtomicTestAndSet(ref)) { + // Already marked. + if (kUseBakerReadBarrier && + cas_success && + // The object could be white here if a thread gets preempted after a success at the + // above AtomicSetReadBarrierPointer, GC has marked through it, and the thread runs up + // to this point. + ref->GetReadBarrierPointer() == ReadBarrier::GrayPtr()) { + // Register a "false-gray" object to change it from gray to white at the end of marking. + PushOntoFalseGrayStack(ref); + } + } else { + // Newly marked. + if (kUseBakerReadBarrier) { + DCHECK_EQ(ref->GetReadBarrierPointer(), ReadBarrier::GrayPtr()); + } + PushOntoMarkStack(ref); + } + return ref; +} + inline mirror::Object* ConcurrentCopying::Mark(mirror::Object* from_ref) { if (from_ref == nullptr) { return nullptr; @@ -68,21 +109,7 @@ inline mirror::Object* ConcurrentCopying::Mark(mirror::Object* from_ref) { return to_ref; } case space::RegionSpace::RegionType::kRegionTypeUnevacFromSpace: { - // This may or may not succeed, which is ok. - if (kUseBakerReadBarrier) { - from_ref->AtomicSetReadBarrierPointer(ReadBarrier::WhitePtr(), ReadBarrier::GrayPtr()); - } - mirror::Object* to_ref = from_ref; - if (region_space_bitmap_->AtomicTestAndSet(from_ref)) { - // Already marked. - } else { - // Newly marked. - if (kUseBakerReadBarrier) { - DCHECK_EQ(to_ref->GetReadBarrierPointer(), ReadBarrier::GrayPtr()); - } - PushOntoMarkStack(to_ref); - } - return to_ref; + return MarkUnevacFromSpaceRegionOrImmuneSpace(from_ref, region_space_bitmap_); } case space::RegionSpace::RegionType::kRegionTypeNone: return MarkNonMoving(from_ref); diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc index d393f0b1d2..3f8f6284c0 100644 --- a/runtime/gc/collector/concurrent_copying.cc +++ b/runtime/gc/collector/concurrent_copying.cc @@ -165,6 +165,10 @@ void ConcurrentCopying::InitializePhase() { << reinterpret_cast<void*>(region_space_->Limit()); } CheckEmptyMarkStack(); + if (kIsDebugBuild) { + MutexLock mu(Thread::Current(), mark_stack_lock_); + CHECK(false_gray_stack_.empty()); + } immune_spaces_.Reset(); bytes_moved_.StoreRelaxed(0); objects_moved_.StoreRelaxed(0); @@ -247,6 +251,9 @@ class FlipCallback : public Closure { } cc->is_marking_ = true; cc->mark_stack_mode_.StoreRelaxed(ConcurrentCopying::kMarkStackModeThreadLocal); + if (kIsDebugBuild) { + cc->region_space_->AssertAllRegionLiveBytesZeroOrCleared(); + } if (UNLIKELY(Runtime::Current()->IsActiveTransaction())) { CHECK(Runtime::Current()->IsAotCompiler()); TimingLogger::ScopedTiming split2("(Paused)VisitTransactionRoots", cc->GetTimings()); @@ -314,17 +321,7 @@ class ConcurrentCopyingImmuneSpaceObjVisitor { DCHECK(collector_->heap_->GetMarkBitmap()->Test(obj)) << "Immune space object must be already marked"; } - // This may or may not succeed, which is ok. - if (kUseBakerReadBarrier) { - obj->AtomicSetReadBarrierPointer(ReadBarrier::WhitePtr(), ReadBarrier::GrayPtr()); - } - if (cc_bitmap->AtomicTestAndSet(obj)) { - // Already marked. Do nothing. - } else { - // Newly marked. Set the gray bit and push it onto the mark stack. - CHECK(!kUseBakerReadBarrier || obj->GetReadBarrierPointer() == ReadBarrier::GrayPtr()); - collector_->PushOntoMarkStack(obj); - } + collector_->MarkUnevacFromSpaceRegionOrImmuneSpace(obj, cc_bitmap); } private: @@ -459,6 +456,9 @@ void ConcurrentCopying::MarkingPhase() { Runtime::Current()->GetClassLinker()->CleanupClassLoaders(); // Marking is done. Disable marking. DisableMarking(); + if (kUseBakerReadBarrier) { + ProcessFalseGrayStack(); + } CheckEmptyMarkStack(); } @@ -548,6 +548,32 @@ void ConcurrentCopying::DisableMarking() { mark_stack_mode_.StoreSequentiallyConsistent(kMarkStackModeOff); } +void ConcurrentCopying::PushOntoFalseGrayStack(mirror::Object* ref) { + CHECK(kUseBakerReadBarrier); + DCHECK(ref != nullptr); + MutexLock mu(Thread::Current(), mark_stack_lock_); + false_gray_stack_.push_back(ref); +} + +void ConcurrentCopying::ProcessFalseGrayStack() { + CHECK(kUseBakerReadBarrier); + // Change the objects on the false gray stack from gray to white. + MutexLock mu(Thread::Current(), mark_stack_lock_); + for (mirror::Object* obj : false_gray_stack_) { + DCHECK(IsMarked(obj)); + // The object could be white here if a thread got preempted after a success at the + // AtomicSetReadBarrierPointer in Mark(), GC started marking through it (but not finished so + // still gray), and the thread ran to register it onto the false gray stack. + if (obj->GetReadBarrierPointer() == ReadBarrier::GrayPtr()) { + bool success = obj->AtomicSetReadBarrierPointer(ReadBarrier::GrayPtr(), + ReadBarrier::WhitePtr()); + DCHECK(success); + } + } + false_gray_stack_.clear(); +} + + void ConcurrentCopying::IssueEmptyCheckpoint() { Thread* self = Thread::Current(); EmptyCheckpoint check_point(this); @@ -655,8 +681,8 @@ accounting::ObjectStack* ConcurrentCopying::GetLiveStack() { return heap_->live_stack_.get(); } -// The following visitors are that used to verify that there's no -// references to the from-space left after marking. +// The following visitors are used to verify that there's no references to the from-space left after +// marking. class ConcurrentCopyingVerifyNoFromSpaceRefsVisitor : public SingleRootVisitor { public: explicit ConcurrentCopyingVerifyNoFromSpaceRefsVisitor(ConcurrentCopying* collector) @@ -670,20 +696,9 @@ class ConcurrentCopyingVerifyNoFromSpaceRefsVisitor : public SingleRootVisitor { } collector_->AssertToSpaceInvariant(nullptr, MemberOffset(0), ref); if (kUseBakerReadBarrier) { - if (collector_->RegionSpace()->IsInToSpace(ref)) { - CHECK(ref->GetReadBarrierPointer() == nullptr) - << "To-space ref " << ref << " " << PrettyTypeOf(ref) - << " has non-white rb_ptr " << ref->GetReadBarrierPointer(); - } else { - CHECK(ref->GetReadBarrierPointer() == ReadBarrier::BlackPtr() || - (ref->GetReadBarrierPointer() == ReadBarrier::WhitePtr() && - collector_->IsOnAllocStack(ref))) - << "Non-moving/unevac from space ref " << ref << " " << PrettyTypeOf(ref) - << " has non-black rb_ptr " << ref->GetReadBarrierPointer() - << " but isn't on the alloc stack (and has white rb_ptr)." - << " Is it in the non-moving space=" - << (collector_->GetHeap()->GetNonMovingSpace()->HasAddress(ref)); - } + CHECK(ref->GetReadBarrierPointer() == ReadBarrier::WhitePtr()) + << "Ref " << ref << " " << PrettyTypeOf(ref) + << " has non-white rb_ptr " << ref->GetReadBarrierPointer(); } } @@ -749,18 +764,8 @@ class ConcurrentCopyingVerifyNoFromSpaceRefsObjectVisitor { ConcurrentCopyingVerifyNoFromSpaceRefsFieldVisitor visitor(collector); obj->VisitReferences(visitor, visitor); if (kUseBakerReadBarrier) { - if (collector->RegionSpace()->IsInToSpace(obj)) { - CHECK(obj->GetReadBarrierPointer() == nullptr) - << "obj=" << obj << " non-white rb_ptr " << obj->GetReadBarrierPointer(); - } else { - CHECK(obj->GetReadBarrierPointer() == ReadBarrier::BlackPtr() || - (obj->GetReadBarrierPointer() == ReadBarrier::WhitePtr() && - collector->IsOnAllocStack(obj))) - << "Non-moving space/unevac from space ref " << obj << " " << PrettyTypeOf(obj) - << " has non-black rb_ptr " << obj->GetReadBarrierPointer() - << " but isn't on the alloc stack (and has white rb_ptr). Is it in the non-moving space=" - << (collector->GetHeap()->GetNonMovingSpace()->HasAddress(obj)); - } + CHECK(obj->GetReadBarrierPointer() == ReadBarrier::WhitePtr()) + << "obj=" << obj << " non-white rb_ptr " << obj->GetReadBarrierPointer(); } } @@ -1069,7 +1074,6 @@ inline void ConcurrentCopying::ProcessMarkStackRef(mirror::Object* to_ref) { } // Scan ref fields. Scan(to_ref); - // Mark the gray ref as white or black. if (kUseBakerReadBarrier) { DCHECK(to_ref->GetReadBarrierPointer() == ReadBarrier::GrayPtr()) << " " << to_ref << " " << to_ref->GetReadBarrierPointer() @@ -1079,41 +1083,34 @@ inline void ConcurrentCopying::ProcessMarkStackRef(mirror::Object* to_ref) { if (UNLIKELY((to_ref->GetClass<kVerifyNone, kWithoutReadBarrier>()->IsTypeOfReferenceClass() && to_ref->AsReference()->GetReferent<kWithoutReadBarrier>() != nullptr && !IsInToSpace(to_ref->AsReference()->GetReferent<kWithoutReadBarrier>())))) { - // Leave this Reference gray in the queue so that GetReferent() will trigger a read barrier. We - // will change it to black or white later in ReferenceQueue::DequeuePendingReference(). + // Leave this reference gray in the queue so that GetReferent() will trigger a read barrier. We + // will change it to white later in ReferenceQueue::DequeuePendingReference(). DCHECK(to_ref->AsReference()->GetPendingNext() != nullptr) << "Left unenqueued ref gray " << to_ref; } else { - // We may occasionally leave a Reference black or white in the queue if its referent happens to - // be concurrently marked after the Scan() call above has enqueued the Reference, in which case - // the above IsInToSpace() evaluates to true and we change the color from gray to black or white - // here in this else block. + // We may occasionally leave a reference white in the queue if its referent happens to be + // concurrently marked after the Scan() call above has enqueued the Reference, in which case the + // above IsInToSpace() evaluates to true and we change the color from gray to white here in this + // else block. if (kUseBakerReadBarrier) { - if (region_space_->IsInToSpace(to_ref)) { - // If to-space, change from gray to white. - bool success = to_ref->AtomicSetReadBarrierPointer</*kCasRelease*/true>( - ReadBarrier::GrayPtr(), - ReadBarrier::WhitePtr()); - DCHECK(success) << "Must succeed as we won the race."; - DCHECK(to_ref->GetReadBarrierPointer() == ReadBarrier::WhitePtr()); - } else { - // If non-moving space/unevac from space, change from gray - // to black. We can't change gray to white because it's not - // safe to use CAS if two threads change values in opposite - // directions (A->B and B->A). So, we change it to black to - // indicate non-moving objects that have been marked - // through. Note we'd need to change from black to white - // later (concurrently). - bool success = to_ref->AtomicSetReadBarrierPointer</*kCasRelease*/true>( - ReadBarrier::GrayPtr(), - ReadBarrier::BlackPtr()); - DCHECK(success) << "Must succeed as we won the race."; - DCHECK(to_ref->GetReadBarrierPointer() == ReadBarrier::BlackPtr()); - } + bool success = to_ref->AtomicSetReadBarrierPointer</*kCasRelease*/true>( + ReadBarrier::GrayPtr(), + ReadBarrier::WhitePtr()); + DCHECK(success) << "Must succeed as we won the race."; } } #else DCHECK(!kUseBakerReadBarrier); #endif + + if (region_space_->IsInUnevacFromSpace(to_ref)) { + // Add to the live bytes per unevacuated from space. Note this code is always run by the + // GC-running thread (no synchronization required). + DCHECK(region_space_bitmap_->Test(to_ref)); + // Disable the read barrier in SizeOf for performance, which is safe. + size_t obj_size = to_ref->SizeOf<kDefaultVerifyFlags, kWithoutReadBarrier>(); + size_t alloc_size = RoundUp(obj_size, space::RegionSpace::kAlignment); + region_space_->AddLiveBytes(to_ref, alloc_size); + } if (ReadBarrier::kEnableToSpaceInvariantChecks || kIsDebugBuild) { ConcurrentCopyingAssertToSpaceInvariantObjectVisitor visitor(this); visitor(to_ref); @@ -1226,61 +1223,6 @@ void ConcurrentCopying::SweepLargeObjects(bool swap_bitmaps) { RecordFreeLOS(heap_->GetLargeObjectsSpace()->Sweep(swap_bitmaps)); } -class ConcurrentCopyingClearBlackPtrsVisitor { - public: - explicit ConcurrentCopyingClearBlackPtrsVisitor(ConcurrentCopying* cc) - : collector_(cc) {} - void operator()(mirror::Object* obj) const SHARED_REQUIRES(Locks::mutator_lock_) - SHARED_REQUIRES(Locks::heap_bitmap_lock_) { - DCHECK(obj != nullptr); - DCHECK(collector_->heap_->GetMarkBitmap()->Test(obj)) << obj; - DCHECK_EQ(obj->GetReadBarrierPointer(), ReadBarrier::BlackPtr()) << obj; - obj->AtomicSetReadBarrierPointer(ReadBarrier::BlackPtr(), ReadBarrier::WhitePtr()); - DCHECK_EQ(obj->GetReadBarrierPointer(), ReadBarrier::WhitePtr()) << obj; - } - - private: - ConcurrentCopying* const collector_; -}; - -// Clear the black ptrs in non-moving objects back to white. -void ConcurrentCopying::ClearBlackPtrs() { - CHECK(kUseBakerReadBarrier); - TimingLogger::ScopedTiming split("ClearBlackPtrs", GetTimings()); - ConcurrentCopyingClearBlackPtrsVisitor visitor(this); - for (auto& space : heap_->GetContinuousSpaces()) { - if (space == region_space_) { - continue; - } - accounting::ContinuousSpaceBitmap* mark_bitmap = space->GetMarkBitmap(); - if (kVerboseMode) { - LOG(INFO) << "ClearBlackPtrs: " << *space << " bitmap: " << *mark_bitmap; - } - mark_bitmap->VisitMarkedRange(reinterpret_cast<uintptr_t>(space->Begin()), - reinterpret_cast<uintptr_t>(space->Limit()), - visitor); - } - space::LargeObjectSpace* large_object_space = heap_->GetLargeObjectsSpace(); - large_object_space->GetMarkBitmap()->VisitMarkedRange( - reinterpret_cast<uintptr_t>(large_object_space->Begin()), - reinterpret_cast<uintptr_t>(large_object_space->End()), - visitor); - // Objects on the allocation stack? - if (ReadBarrier::kEnableReadBarrierInvariantChecks || kIsDebugBuild) { - size_t count = GetAllocationStack()->Size(); - auto* it = GetAllocationStack()->Begin(); - auto* end = GetAllocationStack()->End(); - for (size_t i = 0; i < count; ++i, ++it) { - CHECK_LT(it, end); - mirror::Object* obj = it->AsMirrorPtr(); - if (obj != nullptr) { - // Must have been cleared above. - CHECK_EQ(obj->GetReadBarrierPointer(), ReadBarrier::WhitePtr()) << obj; - } - } - } -} - void ConcurrentCopying::ReclaimPhase() { TimingLogger::ScopedTiming split("ReclaimPhase", GetTimings()); if (kVerboseMode) { @@ -1338,20 +1280,12 @@ void ConcurrentCopying::ReclaimPhase() { } { - TimingLogger::ScopedTiming split3("ComputeUnevacFromSpaceLiveRatio", GetTimings()); - ComputeUnevacFromSpaceLiveRatio(); - } - - { TimingLogger::ScopedTiming split4("ClearFromSpace", GetTimings()); region_space_->ClearFromSpace(); } { WriterMutexLock mu(self, *Locks::heap_bitmap_lock_); - if (kUseBakerReadBarrier) { - ClearBlackPtrs(); - } Sweep(false); SwapBitmaps(); heap_->UnBindBitmaps(); @@ -1373,39 +1307,6 @@ void ConcurrentCopying::ReclaimPhase() { } } -class ConcurrentCopyingComputeUnevacFromSpaceLiveRatioVisitor { - public: - explicit ConcurrentCopyingComputeUnevacFromSpaceLiveRatioVisitor(ConcurrentCopying* cc) - : collector_(cc) {} - void operator()(mirror::Object* ref) const SHARED_REQUIRES(Locks::mutator_lock_) - SHARED_REQUIRES(Locks::heap_bitmap_lock_) { - DCHECK(ref != nullptr); - DCHECK(collector_->region_space_bitmap_->Test(ref)) << ref; - DCHECK(collector_->region_space_->IsInUnevacFromSpace(ref)) << ref; - if (kUseBakerReadBarrier) { - DCHECK_EQ(ref->GetReadBarrierPointer(), ReadBarrier::BlackPtr()) << ref; - // Clear the black ptr. - ref->AtomicSetReadBarrierPointer(ReadBarrier::BlackPtr(), ReadBarrier::WhitePtr()); - DCHECK_EQ(ref->GetReadBarrierPointer(), ReadBarrier::WhitePtr()) << ref; - } - size_t obj_size = ref->SizeOf(); - size_t alloc_size = RoundUp(obj_size, space::RegionSpace::kAlignment); - collector_->region_space_->AddLiveBytes(ref, alloc_size); - } - - private: - ConcurrentCopying* const collector_; -}; - -// Compute how much live objects are left in regions. -void ConcurrentCopying::ComputeUnevacFromSpaceLiveRatio() { - region_space_->AssertAllRegionLiveBytesZeroOrCleared(); - ConcurrentCopyingComputeUnevacFromSpaceLiveRatioVisitor visitor(this); - region_space_bitmap_->VisitMarkedRange(reinterpret_cast<uintptr_t>(region_space_->Begin()), - reinterpret_cast<uintptr_t>(region_space_->Limit()), - visitor); -} - // Assert the to-space invariant. void ConcurrentCopying::AssertToSpaceInvariant(mirror::Object* obj, MemberOffset offset, mirror::Object* ref) { @@ -1999,19 +1900,7 @@ mirror::Object* ConcurrentCopying::MarkNonMoving(mirror::Object* ref) { DCHECK(heap_mark_bitmap_->GetContinuousSpaceBitmap(ref)->Test(ref)) << "Immune space object must be already marked"; } - // This may or may not succeed, which is ok. - if (kUseBakerReadBarrier) { - ref->AtomicSetReadBarrierPointer(ReadBarrier::WhitePtr(), ReadBarrier::GrayPtr()); - } - if (cc_bitmap->AtomicTestAndSet(ref)) { - // Already marked. - } else { - // Newly marked. - if (kUseBakerReadBarrier) { - DCHECK_EQ(ref->GetReadBarrierPointer(), ReadBarrier::GrayPtr()); - } - PushOntoMarkStack(ref); - } + MarkUnevacFromSpaceRegionOrImmuneSpace(ref, cc_bitmap); } else { // Use the mark bitmap. accounting::ContinuousSpaceBitmap* mark_bitmap = @@ -2024,13 +1913,13 @@ mirror::Object* ConcurrentCopying::MarkNonMoving(mirror::Object* ref) { // Already marked. if (kUseBakerReadBarrier) { DCHECK(ref->GetReadBarrierPointer() == ReadBarrier::GrayPtr() || - ref->GetReadBarrierPointer() == ReadBarrier::BlackPtr()); + ref->GetReadBarrierPointer() == ReadBarrier::WhitePtr()); } } else if (is_los && los_bitmap->Test(ref)) { // Already marked in LOS. if (kUseBakerReadBarrier) { DCHECK(ref->GetReadBarrierPointer() == ReadBarrier::GrayPtr() || - ref->GetReadBarrierPointer() == ReadBarrier::BlackPtr()); + ref->GetReadBarrierPointer() == ReadBarrier::WhitePtr()); } } else { // Not marked. @@ -2046,15 +1935,34 @@ mirror::Object* ConcurrentCopying::MarkNonMoving(mirror::Object* ref) { DCHECK_EQ(ref->GetReadBarrierPointer(), ReadBarrier::WhitePtr()); } } else { + // For the baker-style RB, we need to handle 'false-gray' cases. See the + // kRegionTypeUnevacFromSpace-case comment in Mark(). + if (kUseBakerReadBarrier) { + // Test the bitmap first to reduce the chance of false gray cases. + if ((!is_los && mark_bitmap->Test(ref)) || + (is_los && los_bitmap->Test(ref))) { + return ref; + } + } // Not marked or on the allocation stack. Try to mark it. // This may or may not succeed, which is ok. + bool cas_success = false; if (kUseBakerReadBarrier) { - ref->AtomicSetReadBarrierPointer(ReadBarrier::WhitePtr(), ReadBarrier::GrayPtr()); + cas_success = ref->AtomicSetReadBarrierPointer(ReadBarrier::WhitePtr(), + ReadBarrier::GrayPtr()); } if (!is_los && mark_bitmap->AtomicTestAndSet(ref)) { // Already marked. + if (kUseBakerReadBarrier && cas_success && + ref->GetReadBarrierPointer() == ReadBarrier::GrayPtr()) { + PushOntoFalseGrayStack(ref); + } } else if (is_los && los_bitmap->AtomicTestAndSet(ref)) { // Already marked in LOS. + if (kUseBakerReadBarrier && cas_success && + ref->GetReadBarrierPointer() == ReadBarrier::GrayPtr()) { + PushOntoFalseGrayStack(ref); + } } else { // Newly marked. if (kUseBakerReadBarrier) { diff --git a/runtime/gc/collector/concurrent_copying.h b/runtime/gc/collector/concurrent_copying.h index 76315fe7cc..e9ff618ff3 100644 --- a/runtime/gc/collector/concurrent_copying.h +++ b/runtime/gc/collector/concurrent_copying.h @@ -160,8 +160,6 @@ class ConcurrentCopying : public GarbageCollector { SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_, !mark_stack_lock_); void SweepLargeObjects(bool swap_bitmaps) SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_); - void ClearBlackPtrs() - SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_); void FillWithDummyObject(mirror::Object* dummy_obj, size_t byte_size) SHARED_REQUIRES(Locks::mutator_lock_); mirror::Object* AllocateInSkippedBlock(size_t alloc_size) @@ -185,10 +183,19 @@ class ConcurrentCopying : public GarbageCollector { void ExpandGcMarkStack() SHARED_REQUIRES(Locks::mutator_lock_); mirror::Object* MarkNonMoving(mirror::Object* from_ref) SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!mark_stack_lock_, !skipped_blocks_lock_); + ALWAYS_INLINE mirror::Object* MarkUnevacFromSpaceRegionOrImmuneSpace(mirror::Object* from_ref, + accounting::SpaceBitmap<kObjectAlignment>* bitmap) + SHARED_REQUIRES(Locks::mutator_lock_) + REQUIRES(!mark_stack_lock_, !skipped_blocks_lock_); + void PushOntoFalseGrayStack(mirror::Object* obj) SHARED_REQUIRES(Locks::mutator_lock_) + REQUIRES(!mark_stack_lock_); + void ProcessFalseGrayStack() SHARED_REQUIRES(Locks::mutator_lock_) + REQUIRES(!mark_stack_lock_); space::RegionSpace* region_space_; // The underlying region space. std::unique_ptr<Barrier> gc_barrier_; std::unique_ptr<accounting::ObjectStack> gc_mark_stack_; + std::vector<mirror::Object*> false_gray_stack_ GUARDED_BY(mark_stack_lock_); Mutex mark_stack_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; std::vector<accounting::ObjectStack*> revoked_mark_stacks_ GUARDED_BY(mark_stack_lock_); diff --git a/runtime/gc/collector_type.h b/runtime/gc/collector_type.h index ae412262fc..c6020818ec 100644 --- a/runtime/gc/collector_type.h +++ b/runtime/gc/collector_type.h @@ -44,9 +44,13 @@ enum CollectorType { kCollectorTypeInstrumentation, // Fake collector for adding or removing application image spaces. kCollectorTypeAddRemoveAppImageSpace, + // Fake collector used to implement exclusion between GC and debugger. + kCollectorTypeDebugger, // A homogeneous space compaction collector used in background transition // when both foreground and background collector are CMS. kCollectorTypeHomogeneousSpaceCompact, + // Class linker fake collector. + kCollectorTypeClassLinker, }; std::ostream& operator<<(std::ostream& os, const CollectorType& collector_type); diff --git a/runtime/gc/gc_cause.cc b/runtime/gc/gc_cause.cc index 679432ba89..ad9bb92b17 100644 --- a/runtime/gc/gc_cause.cc +++ b/runtime/gc/gc_cause.cc @@ -35,6 +35,8 @@ const char* PrettyCause(GcCause cause) { case kGcCauseTrim: return "HeapTrim"; case kGcCauseInstrumentation: return "Instrumentation"; case kGcCauseAddRemoveAppImageSpace: return "AddRemoveAppImageSpace"; + case kGcCauseDebugger: return "Debugger"; + case kGcCauseClassLinker: return "ClassLinker"; default: LOG(FATAL) << "Unreachable"; UNREACHABLE(); diff --git a/runtime/gc/gc_cause.h b/runtime/gc/gc_cause.h index c6b505c481..797ec3435e 100644 --- a/runtime/gc/gc_cause.h +++ b/runtime/gc/gc_cause.h @@ -43,8 +43,12 @@ enum GcCause { kGcCauseInstrumentation, // Not a real GC cause, used to add or remove app image spaces. kGcCauseAddRemoveAppImageSpace, + // Not a real GC cause, used to implement exclusion between GC and debugger. + kGcCauseDebugger, // GC triggered for background transition when both foreground and background collector are CMS. kGcCauseHomogeneousSpaceCompact, + // Class linker cause, used to guard filling art methods with special values. + kGcCauseClassLinker, }; const char* PrettyCause(GcCause cause); diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc index c2f772f876..cdd5f2e120 100644 --- a/runtime/gc/heap.cc +++ b/runtime/gc/heap.cc @@ -119,6 +119,8 @@ static constexpr uint32_t kAllocSpaceBeginForDeterministicAoT = 0x40000000; // Dump the rosalloc stats on SIGQUIT. static constexpr bool kDumpRosAllocStatsOnSigQuit = false; +static constexpr size_t kNativeAllocationHistogramBuckets = 16; + static inline bool CareAboutPauseTimes() { return Runtime::Current()->InJankPerceptibleProcessState(); } @@ -186,6 +188,11 @@ Heap::Heap(size_t initial_size, total_objects_freed_ever_(0), num_bytes_allocated_(0), native_bytes_allocated_(0), + native_histogram_lock_("Native allocation lock"), + native_allocation_histogram_("Native allocation sizes", + 1U, + kNativeAllocationHistogramBuckets), + native_free_histogram_("Native free sizes", 1U, kNativeAllocationHistogramBuckets), num_bytes_freed_revoke_(0), verify_missing_card_marks_(false), verify_system_weaks_(false), @@ -304,7 +311,7 @@ Heap::Heap(size_t initial_size, const OatHeader& boot_oat_header = boot_oat_file->GetOatHeader(); const char* boot_classpath = - boot_oat_header.GetStoreValueByKey(OatHeader::kBootClassPath); + boot_oat_header.GetStoreValueByKey(OatHeader::kBootClassPathKey); if (boot_classpath == nullptr) { continue; } @@ -1185,6 +1192,20 @@ void Heap::DumpGcPerformanceInfo(std::ostream& os) { rosalloc_space_->DumpStats(os); } + { + MutexLock mu(Thread::Current(), native_histogram_lock_); + if (native_allocation_histogram_.SampleSize() > 0u) { + os << "Histogram of native allocation "; + native_allocation_histogram_.DumpBins(os); + os << " bucket size " << native_allocation_histogram_.BucketWidth() << "\n"; + } + if (native_free_histogram_.SampleSize() > 0u) { + os << "Histogram of native free "; + native_free_histogram_.DumpBins(os); + os << " bucket size " << native_free_histogram_.BucketWidth() << "\n"; + } + } + BaseMutex::DumpAll(os); } @@ -2687,8 +2708,8 @@ collector::GcType Heap::CollectGarbageInternal(collector::GcType gc_type, concurrent_start_bytes_ = std::numeric_limits<size_t>::max(); } - if ((gc_type == collector::kGcTypeFull) && runtime->UseJit()) { - // It's time to clear all inline caches, in case some classes can be unloaded. + // It's time to clear all inline caches, in case some classes can be unloaded. + if ((gc_type == collector::kGcTypeFull) && (runtime->GetJit() != nullptr)) { runtime->GetJit()->GetCodeCache()->ClearGcRootsInInlineCaches(self); } @@ -3848,6 +3869,10 @@ void Heap::RunFinalization(JNIEnv* env, uint64_t timeout) { void Heap::RegisterNativeAllocation(JNIEnv* env, size_t bytes) { Thread* self = ThreadForEnv(env); + { + MutexLock mu(self, native_histogram_lock_); + native_allocation_histogram_.AddValue(bytes); + } if (native_need_to_run_finalization_) { RunFinalization(env, kNativeAllocationFinalizeTimeout); UpdateMaxNativeFootprint(); @@ -3892,6 +3917,10 @@ void Heap::RegisterNativeAllocation(JNIEnv* env, size_t bytes) { void Heap::RegisterNativeFree(JNIEnv* env, size_t bytes) { size_t expected_size; + { + MutexLock mu(Thread::Current(), native_histogram_lock_); + native_free_histogram_.AddValue(bytes); + } do { expected_size = native_bytes_allocated_.LoadRelaxed(); if (UNLIKELY(bytes > expected_size)) { diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h index fada1a2212..6fb048a5d7 100644 --- a/runtime/gc/heap.h +++ b/runtime/gc/heap.h @@ -128,7 +128,7 @@ class Heap { static constexpr size_t kDefaultMinFree = kDefaultMaxFree / 4; static constexpr size_t kDefaultLongPauseLogThreshold = MsToNs(5); static constexpr size_t kDefaultLongGCLogThreshold = MsToNs(100); - static constexpr size_t kDefaultTLABSize = 256 * KB; + static constexpr size_t kDefaultTLABSize = 32 * KB; static constexpr double kDefaultTargetUtilization = 0.5; static constexpr double kDefaultHeapGrowthMultiplier = 2.0; // Primitive arrays larger than this size are put in the large object space. @@ -241,9 +241,9 @@ class Heap { SHARED_REQUIRES(Locks::mutator_lock_); void RegisterNativeAllocation(JNIEnv* env, size_t bytes) - REQUIRES(!*gc_complete_lock_, !*pending_task_lock_); + REQUIRES(!*gc_complete_lock_, !*pending_task_lock_, !native_histogram_lock_); void RegisterNativeFree(JNIEnv* env, size_t bytes) - REQUIRES(!*gc_complete_lock_, !*pending_task_lock_); + REQUIRES(!*gc_complete_lock_, !*pending_task_lock_, !native_histogram_lock_); // Change the allocator, updates entrypoints. void ChangeAllocator(AllocatorType allocator) @@ -532,7 +532,7 @@ class Heap { space::Space* FindSpaceFromObject(const mirror::Object*, bool fail_ok) const SHARED_REQUIRES(Locks::mutator_lock_); - void DumpForSigQuit(std::ostream& os) REQUIRES(!*gc_complete_lock_); + void DumpForSigQuit(std::ostream& os) REQUIRES(!*gc_complete_lock_, !native_histogram_lock_); // Do a pending collector transition. void DoPendingCollectorTransition() REQUIRES(!*gc_complete_lock_); @@ -654,7 +654,8 @@ class Heap { std::string SafePrettyTypeOf(mirror::Object* obj) NO_THREAD_SAFETY_ANALYSIS; // GC performance measuring - void DumpGcPerformanceInfo(std::ostream& os) REQUIRES(!*gc_complete_lock_); + void DumpGcPerformanceInfo(std::ostream& os) + REQUIRES(!*gc_complete_lock_, !native_histogram_lock_); void ResetGcPerformanceInfo() REQUIRES(!*gc_complete_lock_); // Thread pool. @@ -1156,6 +1157,11 @@ class Heap { // Bytes which are allocated and managed by native code but still need to be accounted for. Atomic<size_t> native_bytes_allocated_; + // Native allocation stats. + Mutex native_histogram_lock_; + Histogram<uint64_t> native_allocation_histogram_; + Histogram<uint64_t> native_free_histogram_; + // Number of bytes freed by thread local buffer revokes. This will // cancel out the ahead-of-time bulk counting of bytes allocated in // rosalloc thread-local buffers. It is temporarily accumulated diff --git a/runtime/gc/reference_queue.cc b/runtime/gc/reference_queue.cc index 03ab9a1a73..6088a43ab1 100644 --- a/runtime/gc/reference_queue.cc +++ b/runtime/gc/reference_queue.cc @@ -68,31 +68,19 @@ mirror::Reference* ReferenceQueue::DequeuePendingReference() { Heap* heap = Runtime::Current()->GetHeap(); if (kUseBakerOrBrooksReadBarrier && heap->CurrentCollectorType() == kCollectorTypeCC && heap->ConcurrentCopyingCollector()->IsActive()) { - // Change the gray ptr we left in ConcurrentCopying::ProcessMarkStackRef() to black or white. + // Change the gray ptr we left in ConcurrentCopying::ProcessMarkStackRef() to white. // We check IsActive() above because we don't want to do this when the zygote compaction // collector (SemiSpace) is running. CHECK(ref != nullptr); collector::ConcurrentCopying* concurrent_copying = heap->ConcurrentCopyingCollector(); - const bool is_moving = concurrent_copying->RegionSpace()->IsInToSpace(ref); - if (ref->GetReadBarrierPointer() == ReadBarrier::GrayPtr()) { - if (is_moving) { - ref->AtomicSetReadBarrierPointer(ReadBarrier::GrayPtr(), ReadBarrier::WhitePtr()); - CHECK_EQ(ref->GetReadBarrierPointer(), ReadBarrier::WhitePtr()); - } else { - ref->AtomicSetReadBarrierPointer(ReadBarrier::GrayPtr(), ReadBarrier::BlackPtr()); - CHECK_EQ(ref->GetReadBarrierPointer(), ReadBarrier::BlackPtr()); - } + mirror::Object* rb_ptr = ref->GetReadBarrierPointer(); + if (rb_ptr == ReadBarrier::GrayPtr()) { + ref->AtomicSetReadBarrierPointer(ReadBarrier::GrayPtr(), ReadBarrier::WhitePtr()); + CHECK_EQ(ref->GetReadBarrierPointer(), ReadBarrier::WhitePtr()); } else { - // In ConcurrentCopying::ProcessMarkStackRef() we may leave a black or white Reference in the - // queue and find it here, which is OK. Check that the color makes sense depending on whether - // the Reference is moving or not and that the referent has been marked. - if (is_moving) { - CHECK_EQ(ref->GetReadBarrierPointer(), ReadBarrier::WhitePtr()) - << "ref=" << ref << " rb_ptr=" << ref->GetReadBarrierPointer(); - } else { - CHECK_EQ(ref->GetReadBarrierPointer(), ReadBarrier::BlackPtr()) - << "ref=" << ref << " rb_ptr=" << ref->GetReadBarrierPointer(); - } + // In ConcurrentCopying::ProcessMarkStackRef() we may leave a white reference in the queue and + // find it here, which is OK. + CHECK_EQ(rb_ptr, ReadBarrier::WhitePtr()) << "ref=" << ref << " rb_ptr=" << rb_ptr; mirror::Object* referent = ref->GetReferent<kWithoutReadBarrier>(); // The referent could be null if it's cleared by a mutator (Reference.clear()). if (referent != nullptr) { diff --git a/runtime/gc/scoped_gc_critical_section.cc b/runtime/gc/scoped_gc_critical_section.cc index e7786a1546..b5eb9795de 100644 --- a/runtime/gc/scoped_gc_critical_section.cc +++ b/runtime/gc/scoped_gc_critical_section.cc @@ -38,4 +38,3 @@ ScopedGCCriticalSection::~ScopedGCCriticalSection() { } // namespace gc } // namespace art - diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc index d386c74354..78c570fa99 100644 --- a/runtime/gc/space/image_space.cc +++ b/runtime/gc/space/image_space.cc @@ -880,7 +880,7 @@ class FixupObjectVisitor : public FixupVisitor { class ForwardObjectAdapter { public: - ALWAYS_INLINE ForwardObjectAdapter(const FixupVisitor* visitor) : visitor_(visitor) {} + ALWAYS_INLINE explicit ForwardObjectAdapter(const FixupVisitor* visitor) : visitor_(visitor) {} template <typename T> ALWAYS_INLINE T* operator()(T* src) const { @@ -893,7 +893,7 @@ class ForwardObjectAdapter { class ForwardCodeAdapter { public: - ALWAYS_INLINE ForwardCodeAdapter(const FixupVisitor* visitor) + ALWAYS_INLINE explicit ForwardCodeAdapter(const FixupVisitor* visitor) : visitor_(visitor) {} template <typename T> @@ -914,10 +914,26 @@ class FixupArtMethodVisitor : public FixupVisitor, public ArtMethodVisitor { pointer_size_(pointer_size) {} virtual void Visit(ArtMethod* method) NO_THREAD_SAFETY_ANALYSIS { - if (fixup_heap_objects_) { - method->UpdateObjectsForImageRelocation(ForwardObjectAdapter(this), pointer_size_); + // TODO: Separate visitor for runtime vs normal methods. + if (UNLIKELY(method->IsRuntimeMethod())) { + ImtConflictTable* table = method->GetImtConflictTable(pointer_size_); + if (table != nullptr) { + ImtConflictTable* new_table = ForwardObject(table); + if (table != new_table) { + method->SetImtConflictTable(new_table, pointer_size_); + } + } + const void* old_code = method->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size_); + const void* new_code = ForwardCode(old_code); + if (old_code != new_code) { + method->SetEntryPointFromQuickCompiledCodePtrSize(new_code, pointer_size_); + } + } else { + if (fixup_heap_objects_) { + method->UpdateObjectsForImageRelocation(ForwardObjectAdapter(this), pointer_size_); + } + method->UpdateEntrypoints<kWithoutReadBarrier>(ForwardCodeAdapter(this), pointer_size_); } - method->UpdateEntrypoints<kWithoutReadBarrier>(ForwardCodeAdapter(this), pointer_size_); } private: @@ -1018,6 +1034,7 @@ static bool RelocateInPlace(ImageHeader& image_header, const ImageSection& objects_section = image_header.GetImageSection(ImageHeader::kSectionObjects); uintptr_t objects_begin = reinterpret_cast<uintptr_t>(target_base + objects_section.Offset()); uintptr_t objects_end = reinterpret_cast<uintptr_t>(target_base + objects_section.End()); + FixupObjectAdapter fixup_adapter(boot_image, boot_oat, app_image, app_oat); if (fixup_image) { // Two pass approach, fix up all classes first, then fix up non class-objects. // The visited bitmap is used to ensure that pointer arrays are not forwarded twice. @@ -1037,7 +1054,6 @@ static bool RelocateInPlace(ImageHeader& image_header, ScopedObjectAccess soa(Thread::Current()); timing.NewTiming("Fixup objects"); bitmap->VisitMarkedRange(objects_begin, objects_end, fixup_object_visitor); - FixupObjectAdapter fixup_adapter(boot_image, boot_oat, app_image, app_oat); // Fixup image roots. CHECK(app_image.InSource(reinterpret_cast<uintptr_t>( image_header.GetImageRoots<kWithoutReadBarrier>()))); @@ -1104,19 +1120,18 @@ static bool RelocateInPlace(ImageHeader& image_header, boot_oat, app_image, app_oat); - image_header.GetImageSection(ImageHeader::kSectionArtMethods).VisitPackedArtMethods( - &method_visitor, - target_base, - pointer_size); + image_header.VisitPackedArtMethods(&method_visitor, target_base, pointer_size); } if (fixup_image) { { // Only touches objects in the app image, no need for mutator lock. TimingLogger::ScopedTiming timing("Fixup fields", &logger); FixupArtFieldVisitor field_visitor(boot_image, boot_oat, app_image, app_oat); - image_header.GetImageSection(ImageHeader::kSectionArtFields).VisitPackedArtFields( - &field_visitor, - target_base); + image_header.VisitPackedArtFields(&field_visitor, target_base); + } + { + TimingLogger::ScopedTiming timing("Fixup conflict tables", &logger); + image_header.VisitPackedImtConflictTables(fixup_adapter, target_base, pointer_size); } // In the app image case, the image methods are actually in the boot image. image_header.RelocateImageMethods(boot_image.Delta()); diff --git a/runtime/gc/space/malloc_space.h b/runtime/gc/space/malloc_space.h index 4e56c4a429..c6b28706a6 100644 --- a/runtime/gc/space/malloc_space.h +++ b/runtime/gc/space/malloc_space.h @@ -39,7 +39,7 @@ class ZygoteSpace; int rc = call args; \ if (UNLIKELY(rc != 0)) { \ errno = rc; \ - PLOG(FATAL) << # call << " failed for " << what; \ + PLOG(FATAL) << # call << " failed for " << (what); \ } \ } while (false) diff --git a/runtime/gc/space/region_space.cc b/runtime/gc/space/region_space.cc index 9a2d0c6d37..5d710bf49b 100644 --- a/runtime/gc/space/region_space.cc +++ b/runtime/gc/space/region_space.cc @@ -216,17 +216,6 @@ void RegionSpace::ClearFromSpace() { evac_region_ = nullptr; } -void RegionSpace::AssertAllRegionLiveBytesZeroOrCleared() { - if (kIsDebugBuild) { - MutexLock mu(Thread::Current(), region_lock_); - for (size_t i = 0; i < num_regions_; ++i) { - Region* r = ®ions_[i]; - size_t live_bytes = r->LiveBytes(); - CHECK(live_bytes == 0U || live_bytes == static_cast<size_t>(-1)) << live_bytes; - } - } -} - void RegionSpace::LogFragmentationAllocFailure(std::ostream& os, size_t /* failed_alloc_bytes */) { size_t max_contiguous_allocation = 0; diff --git a/runtime/gc/space/region_space.h b/runtime/gc/space/region_space.h index 14e800595c..4e8dfe820d 100644 --- a/runtime/gc/space/region_space.h +++ b/runtime/gc/space/region_space.h @@ -215,7 +215,16 @@ class RegionSpace FINAL : public ContinuousMemMapAllocSpace { reg->AddLiveBytes(alloc_size); } - void AssertAllRegionLiveBytesZeroOrCleared() REQUIRES(!region_lock_); + void AssertAllRegionLiveBytesZeroOrCleared() REQUIRES(!region_lock_) { + if (kIsDebugBuild) { + MutexLock mu(Thread::Current(), region_lock_); + for (size_t i = 0; i < num_regions_; ++i) { + Region* r = ®ions_[i]; + size_t live_bytes = r->LiveBytes(); + CHECK(live_bytes == 0U || live_bytes == static_cast<size_t>(-1)) << live_bytes; + } + } + } void RecordAlloc(mirror::Object* ref) REQUIRES(!region_lock_); bool AllocNewTlab(Thread* self) REQUIRES(!region_lock_); diff --git a/runtime/globals.h b/runtime/globals.h index e7ea6f3788..477cbdf5d4 100644 --- a/runtime/globals.h +++ b/runtime/globals.h @@ -51,11 +51,31 @@ static constexpr bool kIsDebugBuild = false; static constexpr bool kIsDebugBuild = true; #endif -// Whether or not this is a target (vs host) build. Useful in conditionals where ART_TARGET isn't. +// ART_TARGET - Defined for target builds of ART. +// ART_TARGET_LINUX - Defined for target Linux builds of ART. +// ART_TARGET_ANDROID - Defined for target Android builds of ART. +// Note: Either ART_TARGET_LINUX or ART_TARGET_ANDROID need to be set when ART_TARGET is set. +// Note: When ART_TARGET_LINUX is defined mem_map.h will not be using Ashmem for memory mappings +// (usually only available on Android kernels). #if defined(ART_TARGET) +// Useful in conditionals where ART_TARGET isn't. static constexpr bool kIsTargetBuild = true; +#if defined(ART_TARGET_LINUX) +static constexpr bool kIsTargetLinux = true; +#elif defined(ART_TARGET_ANDROID) +static constexpr bool kIsTargetLinux = false; +#else +#error "Either ART_TARGET_LINUX or ART_TARGET_ANDROID needs to be defined for target builds." +#endif #else static constexpr bool kIsTargetBuild = false; +#if defined(ART_TARGET_LINUX) +#error "ART_TARGET_LINUX defined for host build." +#elif defined(ART_TARGET_ANDROID) +#error "ART_TARGET_ANDROID defined for host build." +#else +static constexpr bool kIsTargetLinux = false; +#endif #endif // Garbage collector constants. diff --git a/runtime/hprof/hprof.cc b/runtime/hprof/hprof.cc index 3885c605bd..9895395169 100644 --- a/runtime/hprof/hprof.cc +++ b/runtime/hprof/hprof.cc @@ -505,6 +505,7 @@ class Hprof : public SingleRootVisitor { // Walk the roots and the heap. output_->StartNewRecord(HPROF_TAG_HEAP_DUMP_SEGMENT, kHprofTime); + simple_roots_.clear(); runtime->VisitRoots(this); runtime->VisitImageRoots(this); runtime->GetHeap()->VisitObjectsPaused(VisitObjectCallback, this); @@ -884,6 +885,14 @@ class Hprof : public SingleRootVisitor { gc::EqAllocRecordTypesPtr<gc::AllocRecordStackTraceElement>> frames_; std::unordered_map<const mirror::Object*, const gc::AllocRecordStackTrace*> allocation_records_; + // Set used to keep track of what simple root records we have already + // emitted, to avoid emitting duplicate entries. The simple root records are + // those that contain no other information than the root type and the object + // id. A pair of root type and object id is packed into a uint64_t, with + // the root type in the upper 32 bits and the object id in the lower 32 + // bits. + std::unordered_set<uint64_t> simple_roots_; + friend class GcRootVisitor; DISALLOW_COPY_AND_ASSIGN(Hprof); }; @@ -962,10 +971,14 @@ void Hprof::MarkRootObject(const mirror::Object* obj, jobject jni_obj, HprofHeap case HPROF_ROOT_MONITOR_USED: case HPROF_ROOT_INTERNED_STRING: case HPROF_ROOT_DEBUGGER: - case HPROF_ROOT_VM_INTERNAL: - __ AddU1(heap_tag); - __ AddObjectId(obj); + case HPROF_ROOT_VM_INTERNAL: { + uint64_t key = (static_cast<uint64_t>(heap_tag) << 32) | PointerToLowMemUInt32(obj); + if (simple_roots_.insert(key).second) { + __ AddU1(heap_tag); + __ AddObjectId(obj); + } break; + } // ID: object ID // ID: JNI global ref ID diff --git a/runtime/image-inl.h b/runtime/image-inl.h index e3307d87b6..ea75a622c7 100644 --- a/runtime/image-inl.h +++ b/runtime/image-inl.h @@ -19,6 +19,8 @@ #include "image.h" +#include "art_method.h" + namespace art { template <ReadBarrierOption kReadBarrierOption> @@ -42,6 +44,20 @@ inline mirror::ObjectArray<mirror::Object>* ImageHeader::GetImageRoots() const { return image_roots; } +template <typename Visitor> +inline void ImageHeader::VisitPackedImtConflictTables(const Visitor& visitor, + uint8_t* base, + size_t pointer_size) const { + const ImageSection& section = GetImageSection(kSectionIMTConflictTables); + for (size_t pos = 0; pos < section.Size(); ) { + auto* table = reinterpret_cast<ImtConflictTable*>(base + section.Offset() + pos); + table->Visit([&visitor](const std::pair<ArtMethod*, ArtMethod*>& methods) { + return std::make_pair(visitor(methods.first), visitor(methods.second)); + }, pointer_size); + pos += table->ComputeSize(pointer_size); + } +} + } // namespace art #endif // ART_RUNTIME_IMAGE_INL_H_ diff --git a/runtime/image.cc b/runtime/image.cc index 1f54e3e6ae..a9552c27d3 100644 --- a/runtime/image.cc +++ b/runtime/image.cc @@ -24,7 +24,7 @@ namespace art { const uint8_t ImageHeader::kImageMagic[] = { 'a', 'r', 't', '\n' }; -const uint8_t ImageHeader::kImageVersion[] = { '0', '2', '7', '\0' }; +const uint8_t ImageHeader::kImageVersion[] = { '0', '2', '9', '\0' }; ImageHeader::ImageHeader(uint32_t image_begin, uint32_t image_size, @@ -147,9 +147,10 @@ std::ostream& operator<<(std::ostream& os, const ImageSection& section) { return os << "size=" << section.Size() << " range=" << section.Offset() << "-" << section.End(); } -void ImageSection::VisitPackedArtFields(ArtFieldVisitor* visitor, uint8_t* base) const { - for (size_t pos = 0; pos < Size(); ) { - auto* array = reinterpret_cast<LengthPrefixedArray<ArtField>*>(base + Offset() + pos); +void ImageHeader::VisitPackedArtFields(ArtFieldVisitor* visitor, uint8_t* base) const { + const ImageSection& fields = GetFieldsSection(); + for (size_t pos = 0; pos < fields.Size(); ) { + auto* array = reinterpret_cast<LengthPrefixedArray<ArtField>*>(base + fields.Offset() + pos); for (size_t i = 0; i < array->size(); ++i) { visitor->Visit(&array->At(i, sizeof(ArtField))); } @@ -157,18 +158,25 @@ void ImageSection::VisitPackedArtFields(ArtFieldVisitor* visitor, uint8_t* base) } } -void ImageSection::VisitPackedArtMethods(ArtMethodVisitor* visitor, - uint8_t* base, - size_t pointer_size) const { +void ImageHeader::VisitPackedArtMethods(ArtMethodVisitor* visitor, + uint8_t* base, + size_t pointer_size) const { const size_t method_alignment = ArtMethod::Alignment(pointer_size); const size_t method_size = ArtMethod::Size(pointer_size); - for (size_t pos = 0; pos < Size(); ) { - auto* array = reinterpret_cast<LengthPrefixedArray<ArtMethod>*>(base + Offset() + pos); + const ImageSection& methods = GetMethodsSection(); + for (size_t pos = 0; pos < methods.Size(); ) { + auto* array = reinterpret_cast<LengthPrefixedArray<ArtMethod>*>(base + methods.Offset() + pos); for (size_t i = 0; i < array->size(); ++i) { visitor->Visit(&array->At(i, method_size, method_alignment)); } pos += array->ComputeSize(array->size(), method_size, method_alignment); } + const ImageSection& runtime_methods = GetRuntimeMethodsSection(); + for (size_t pos = 0; pos < runtime_methods.Size(); ) { + auto* method = reinterpret_cast<ArtMethod*>(base + runtime_methods.Offset() + pos); + visitor->Visit(method); + pos += method_size; + } } } // namespace art diff --git a/runtime/image.h b/runtime/image.h index 8e5dbad57d..2ea9af7728 100644 --- a/runtime/image.h +++ b/runtime/image.h @@ -64,12 +64,6 @@ class PACKED(4) ImageSection { return offset - offset_ < size_; } - // Visit ArtMethods in the section starting at base. - void VisitPackedArtMethods(ArtMethodVisitor* visitor, uint8_t* base, size_t pointer_size) const; - - // Visit ArtMethods in the section starting at base. - void VisitPackedArtFields(ArtFieldVisitor* visitor, uint8_t* base) const; - private: uint32_t offset_; uint32_t size_; @@ -200,6 +194,8 @@ class PACKED(4) ImageHeader { kSectionObjects, kSectionArtFields, kSectionArtMethods, + kSectionRuntimeMethods, + kSectionIMTConflictTables, kSectionDexCacheArrays, kSectionInternedStrings, kSectionClassTable, @@ -211,10 +207,19 @@ class PACKED(4) ImageHeader { void SetImageMethod(ImageMethod index, ArtMethod* method); const ImageSection& GetImageSection(ImageSections index) const; + const ImageSection& GetMethodsSection() const { return GetImageSection(kSectionArtMethods); } + const ImageSection& GetRuntimeMethodsSection() const { + return GetImageSection(kSectionRuntimeMethods); + } + + const ImageSection& GetFieldsSection() const { + return GetImageSection(ImageHeader::kSectionArtFields); + } + template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier> mirror::Object* GetImageRoot(ImageRoot image_root) const SHARED_REQUIRES(Locks::mutator_lock_); @@ -265,6 +270,19 @@ class PACKED(4) ImageHeader { return boot_image_size_ != 0u; } + // Visit ArtMethods in the section starting at base. Includes runtime methods. + // TODO: Delete base parameter if it is always equal to GetImageBegin. + void VisitPackedArtMethods(ArtMethodVisitor* visitor, uint8_t* base, size_t pointer_size) const; + + // Visit ArtMethods in the section starting at base. + // TODO: Delete base parameter if it is always equal to GetImageBegin. + void VisitPackedArtFields(ArtFieldVisitor* visitor, uint8_t* base) const; + + template <typename Visitor> + void VisitPackedImtConflictTables(const Visitor& visitor, + uint8_t* base, + size_t pointer_size) const; + private: static const uint8_t kImageMagic[4]; static const uint8_t kImageVersion[4]; diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc index 34bc458575..7dfc83fd82 100644 --- a/runtime/instrumentation.cc +++ b/runtime/instrumentation.cc @@ -687,8 +687,7 @@ void Instrumentation::ResetQuickAllocEntryPoints() { } } -void Instrumentation::UpdateMethodsCode(ArtMethod* method, const void* quick_code) { - DCHECK(method->GetDeclaringClass()->IsResolved()); +void Instrumentation::UpdateMethodsCodeImpl(ArtMethod* method, const void* quick_code) { const void* new_quick_code; if (LIKELY(!instrumentation_stubs_installed_)) { new_quick_code = quick_code; @@ -710,6 +709,18 @@ void Instrumentation::UpdateMethodsCode(ArtMethod* method, const void* quick_cod UpdateEntrypoints(method, new_quick_code); } +void Instrumentation::UpdateMethodsCode(ArtMethod* method, const void* quick_code) { + DCHECK(method->GetDeclaringClass()->IsResolved()); + UpdateMethodsCodeImpl(method, quick_code); +} + +void Instrumentation::UpdateMethodsCodeFromDebugger(ArtMethod* method, const void* quick_code) { + // When debugger attaches, we may update the entry points of all methods of a class + // to the interpreter bridge. A method's declaring class might not be in resolved + // state yet in that case. + UpdateMethodsCodeImpl(method, quick_code); +} + bool Instrumentation::AddDeoptimizedMethod(ArtMethod* method) { if (IsDeoptimizedMethod(method)) { // Already in the map. Return. @@ -1077,7 +1088,7 @@ TwoWordReturn Instrumentation::PopInstrumentationStackFrame(Thread* self, uintpt bool deoptimize = (visitor.caller != nullptr) && (interpreter_stubs_installed_ || IsDeoptimized(visitor.caller) || Dbg::IsForcedInterpreterNeededForUpcall(self, visitor.caller)); - if (deoptimize) { + if (deoptimize && Runtime::Current()->IsDeoptimizeable(*return_pc)) { if (kVerboseInstrumentation) { LOG(INFO) << StringPrintf("Deoptimizing %s by returning from %s with result %#" PRIx64 " in ", PrettyMethod(visitor.caller).c_str(), @@ -1099,7 +1110,7 @@ TwoWordReturn Instrumentation::PopInstrumentationStackFrame(Thread* self, uintpt } } -void Instrumentation::PopMethodForUnwind(Thread* self, bool is_deoptimization) const { +uintptr_t Instrumentation::PopMethodForUnwind(Thread* self, bool is_deoptimization) const { // Do the pop. std::deque<instrumentation::InstrumentationStackFrame>* stack = self->GetInstrumentationStack(); CHECK_GT(stack->size(), 0U); @@ -1123,6 +1134,7 @@ void Instrumentation::PopMethodForUnwind(Thread* self, bool is_deoptimization) c uint32_t dex_pc = DexFile::kDexNoIndex; MethodUnwindEvent(self, instrumentation_frame.this_object_, method, dex_pc); } + return instrumentation_frame.return_pc_; } std::string InstrumentationStackFrame::Dump() const { diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h index a4c3d41537..49dd060db1 100644 --- a/runtime/instrumentation.h +++ b/runtime/instrumentation.h @@ -227,6 +227,10 @@ class Instrumentation { void UpdateMethodsCode(ArtMethod* method, const void* quick_code) SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!deoptimized_methods_lock_); + // Update the code of a method respecting any installed stubs from debugger. + void UpdateMethodsCodeFromDebugger(ArtMethod* method, const void* quick_code) + SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!deoptimized_methods_lock_); + // Get the quick code for the given method. More efficient than asking the class linker as it // will short-cut to GetCode if instrumentation and static method resolution stubs aren't // installed. @@ -398,7 +402,8 @@ class Instrumentation { SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!deoptimized_methods_lock_); // Pops an instrumentation frame from the current thread and generate an unwind event. - void PopMethodForUnwind(Thread* self, bool is_deoptimization) const + // Returns the return pc for the instrumentation frame that's popped. + uintptr_t PopMethodForUnwind(Thread* self, bool is_deoptimization) const SHARED_REQUIRES(Locks::mutator_lock_); // Call back for configure stubs. @@ -493,6 +498,9 @@ class Instrumentation { SHARED_REQUIRES(Locks::mutator_lock_, deoptimized_methods_lock_); bool IsDeoptimizedMethodsEmpty() const SHARED_REQUIRES(Locks::mutator_lock_, deoptimized_methods_lock_); + void UpdateMethodsCodeImpl(ArtMethod* method, const void* quick_code) + SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!deoptimized_methods_lock_); + // Have we hijacked ArtMethod::code_ so that it calls instrumentation/interpreter code? bool instrumentation_stubs_installed_; diff --git a/runtime/interpreter/interpreter.cc b/runtime/interpreter/interpreter.cc index baf4afea18..8c42b3abce 100644 --- a/runtime/interpreter/interpreter.cc +++ b/runtime/interpreter/interpreter.cc @@ -264,12 +264,12 @@ JValue ExecuteGotoImpl<false, true>(Thread* self, const DexFile::CodeItem* code_ ShadowFrame& shadow_frame, JValue result_register); #endif -static JValue Execute(Thread* self, const DexFile::CodeItem* code_item, ShadowFrame& shadow_frame, - JValue result_register) - SHARED_REQUIRES(Locks::mutator_lock_); - -static inline JValue Execute(Thread* self, const DexFile::CodeItem* code_item, - ShadowFrame& shadow_frame, JValue result_register) { +static inline JValue Execute( + Thread* self, + const DexFile::CodeItem* code_item, + ShadowFrame& shadow_frame, + JValue result_register, + bool stay_in_interpreter = false) SHARED_REQUIRES(Locks::mutator_lock_) { DCHECK(!shadow_frame.GetMethod()->IsAbstract()); DCHECK(!shadow_frame.GetMethod()->IsNative()); if (LIKELY(shadow_frame.GetDexPC() == 0)) { // Entering the method, but not via deoptimization. @@ -284,24 +284,34 @@ static inline JValue Execute(Thread* self, const DexFile::CodeItem* code_item, method, 0); } - jit::Jit* jit = Runtime::Current()->GetJit(); - if (jit != nullptr && jit->CanInvokeCompiledCode(method)) { - JValue result; + if (!stay_in_interpreter) { + jit::Jit* jit = Runtime::Current()->GetJit(); + if (jit != nullptr) { + jit->MethodEntered(self, shadow_frame.GetMethod()); + if (jit->CanInvokeCompiledCode(method)) { + JValue result; - // Pop the shadow frame before calling into compiled code. - self->PopShadowFrame(); - ArtInterpreterToCompiledCodeBridge(self, code_item, &shadow_frame, &result); - // Push the shadow frame back as the caller will expect it. - self->PushShadowFrame(&shadow_frame); + // Pop the shadow frame before calling into compiled code. + self->PopShadowFrame(); + ArtInterpreterToCompiledCodeBridge(self, nullptr, code_item, &shadow_frame, &result); + // Push the shadow frame back as the caller will expect it. + self->PushShadowFrame(&shadow_frame); - return result; + return result; + } + } } } shadow_frame.GetMethod()->GetDeclaringClass()->AssertInitializedOrInitializingInThread(self); + // Lock counting is a special version of accessibility checks, and for simplicity and + // reduction of template parameters, we gate it behind access-checks mode. + ArtMethod* method = shadow_frame.GetMethod(); + DCHECK(!method->SkipAccessChecks() || !method->MustCountLocks()); + bool transaction_active = Runtime::Current()->IsActiveTransaction(); - if (LIKELY(shadow_frame.GetMethod()->SkipAccessChecks())) { + if (LIKELY(method->SkipAccessChecks())) { // Enter the "without access check" interpreter. if (kInterpreterImplKind == kMterpImplKind) { if (transaction_active) { @@ -379,7 +389,8 @@ static inline JValue Execute(Thread* self, const DexFile::CodeItem* code_item, } void EnterInterpreterFromInvoke(Thread* self, ArtMethod* method, Object* receiver, - uint32_t* args, JValue* result) { + uint32_t* args, JValue* result, + bool stay_in_interpreter) { DCHECK_EQ(self, Thread::Current()); bool implicit_check = !Runtime::Current()->ExplicitStackOverflowChecks(); if (UNLIKELY(__builtin_frame_address(0) < self->GetStackEndForInterpreter(implicit_check))) { @@ -454,7 +465,7 @@ void EnterInterpreterFromInvoke(Thread* self, ArtMethod* method, Object* receive } } if (LIKELY(!method->IsNative())) { - JValue r = Execute(self, code_item, *shadow_frame, JValue()); + JValue r = Execute(self, code_item, *shadow_frame, JValue(), stay_in_interpreter); if (result != nullptr) { *result = r; } @@ -473,6 +484,36 @@ void EnterInterpreterFromInvoke(Thread* self, ArtMethod* method, Object* receive self->PopShadowFrame(); } +static bool IsStringInit(const Instruction* instr, ArtMethod* caller) + SHARED_REQUIRES(Locks::mutator_lock_) { + if (instr->Opcode() == Instruction::INVOKE_DIRECT || + instr->Opcode() == Instruction::INVOKE_DIRECT_RANGE) { + // Instead of calling ResolveMethod() which has suspend point and can trigger + // GC, look up the callee method symbolically. + uint16_t callee_method_idx = (instr->Opcode() == Instruction::INVOKE_DIRECT_RANGE) ? + instr->VRegB_3rc() : instr->VRegB_35c(); + const DexFile* dex_file = caller->GetDexFile(); + const DexFile::MethodId& method_id = dex_file->GetMethodId(callee_method_idx); + const char* class_name = dex_file->StringByTypeIdx(method_id.class_idx_); + const char* method_name = dex_file->GetMethodName(method_id); + // Compare method's class name and method name against string init. + // It's ok since it's not allowed to create your own java/lang/String. + // TODO: verify that assumption. + if ((strcmp(class_name, "Ljava/lang/String;") == 0) && + (strcmp(method_name, "<init>") == 0)) { + return true; + } + } + return false; +} + +static int16_t GetReceiverRegisterForStringInit(const Instruction* instr) { + DCHECK(instr->Opcode() == Instruction::INVOKE_DIRECT_RANGE || + instr->Opcode() == Instruction::INVOKE_DIRECT); + return (instr->Opcode() == Instruction::INVOKE_DIRECT_RANGE) ? + instr->VRegC_3rc() : instr->VRegC_35c(); +} + void EnterInterpreterFromDeoptimize(Thread* self, ShadowFrame* shadow_frame, bool from_code, @@ -484,6 +525,10 @@ void EnterInterpreterFromDeoptimize(Thread* self, // Are we executing the first shadow frame? bool first = true; while (shadow_frame != nullptr) { + // We do not want to recover lock state for lock counting when deoptimizing. Currently, + // the compiler should not have compiled a method that failed structured-locking checks. + DCHECK(!shadow_frame->GetMethod()->MustCountLocks()); + self->SetTopOfShadowStack(shadow_frame); const DexFile::CodeItem* code_item = shadow_frame->GetMethod()->GetCodeItem(); const uint32_t dex_pc = shadow_frame->GetDexPC(); @@ -503,8 +548,40 @@ void EnterInterpreterFromDeoptimize(Thread* self, // instruction, as it already executed. // TODO: should be tested more once b/17586779 is fixed. const Instruction* instr = Instruction::At(&code_item->insns_[dex_pc]); - DCHECK(instr->IsInvoke()); - new_dex_pc = dex_pc + instr->SizeInCodeUnits(); + if (instr->IsInvoke()) { + if (IsStringInit(instr, shadow_frame->GetMethod())) { + uint16_t this_obj_vreg = GetReceiverRegisterForStringInit(instr); + // Move the StringFactory.newStringFromChars() result into the register representing + // "this object" when invoking the string constructor in the original dex instruction. + // Also move the result into all aliases. + DCHECK(value.GetL()->IsString()); + SetStringInitValueToAllAliases(shadow_frame, this_obj_vreg, value); + // Calling string constructor in the original dex code doesn't generate a result value. + value.SetJ(0); + } + new_dex_pc = dex_pc + instr->SizeInCodeUnits(); + } else if (instr->Opcode() == Instruction::NEW_INSTANCE) { + // It's possible to deoptimize at a NEW_INSTANCE dex instruciton that's for a + // java string, which is turned into a call into StringFactory.newEmptyString(); + // Move the StringFactory.newEmptyString() result into the destination register. + DCHECK(value.GetL()->IsString()); + shadow_frame->SetVRegReference(instr->VRegA_21c(), value.GetL()); + // new-instance doesn't generate a result value. + value.SetJ(0); + // Skip the dex instruction since we essentially come back from an invocation. + new_dex_pc = dex_pc + instr->SizeInCodeUnits(); + if (kIsDebugBuild) { + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + // This is a suspend point. But it's ok since value has been set into shadow_frame. + mirror::Class* klass = class_linker->ResolveType( + instr->VRegB_21c(), shadow_frame->GetMethod()); + DCHECK(klass->IsStringClass()); + } + } else { + CHECK(false) << "Unexpected instruction opcode " << instr->Opcode() + << " at dex_pc " << dex_pc + << " of method: " << PrettyMethod(shadow_frame->GetMethod(), false); + } } else { // Nothing to do, the dex_pc is the one at which the code requested // the deoptimization. @@ -532,6 +609,10 @@ JValue EnterInterpreterFromEntryPoint(Thread* self, const DexFile::CodeItem* cod return JValue(); } + jit::Jit* jit = Runtime::Current()->GetJit(); + if (jit != nullptr) { + jit->NotifyCompiledCodeToInterpreterTransition(self, shadow_frame->GetMethod()); + } return Execute(self, code_item, *shadow_frame, JValue()); } diff --git a/runtime/interpreter/interpreter.h b/runtime/interpreter/interpreter.h index 6353a9b7bf..bf4bcff856 100644 --- a/runtime/interpreter/interpreter.h +++ b/runtime/interpreter/interpreter.h @@ -33,8 +33,11 @@ class Thread; namespace interpreter { // Called by ArtMethod::Invoke, shadow frames arguments are taken from the args array. +// The optional stay_in_interpreter parameter (false by default) can be used by clients to +// explicitly force interpretation in the remaining path that implements method invocation. extern void EnterInterpreterFromInvoke(Thread* self, ArtMethod* method, - mirror::Object* receiver, uint32_t* args, JValue* result) + mirror::Object* receiver, uint32_t* args, JValue* result, + bool stay_in_interpreter = false) SHARED_REQUIRES(Locks::mutator_lock_); // 'from_code' denotes whether the deoptimization was explicitly triggered by compiled code. diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc index 3453abcd64..53d5e43989 100644 --- a/runtime/interpreter/interpreter_common.cc +++ b/runtime/interpreter/interpreter_common.cc @@ -503,6 +503,7 @@ static inline bool DoCallCommon(ArtMethod* called_method, uint32_t vregC) ALWAYS_INLINE; void ArtInterpreterToCompiledCodeBridge(Thread* self, + ArtMethod* caller, const DexFile::CodeItem* code_item, ShadowFrame* shadow_frame, JValue* result) @@ -530,11 +531,39 @@ void ArtInterpreterToCompiledCodeBridge(Thread* self, uint16_t arg_offset = (code_item == nullptr) ? 0 : code_item->registers_size_ - code_item->ins_size_; + jit::Jit* jit = Runtime::Current()->GetJit(); + if (jit != nullptr && caller != nullptr) { + jit->NotifyInterpreterToCompiledCodeTransition(self, caller); + } method->Invoke(self, shadow_frame->GetVRegArgs(arg_offset), (shadow_frame->NumberOfVRegs() - arg_offset) * sizeof(uint32_t), result, method->GetInterfaceMethodIfProxy(sizeof(void*))->GetShorty()); } +void SetStringInitValueToAllAliases(ShadowFrame* shadow_frame, + uint16_t this_obj_vreg, + JValue result) + SHARED_REQUIRES(Locks::mutator_lock_) { + Object* existing = shadow_frame->GetVRegReference(this_obj_vreg); + if (existing == nullptr) { + // If it's null, we come from compiled code that was deoptimized. Nothing to do, + // as the compiler verified there was no alias. + // Set the new string result of the StringFactory. + shadow_frame->SetVRegReference(this_obj_vreg, result.GetL()); + return; + } + // Set the string init result into all aliases. + for (uint32_t i = 0, e = shadow_frame->NumberOfVRegs(); i < e; ++i) { + if (shadow_frame->GetVRegReference(i) == existing) { + DCHECK_EQ(shadow_frame->GetVRegReference(i), + reinterpret_cast<mirror::Object*>(shadow_frame->GetVReg(i))); + shadow_frame->SetVRegReference(i, result.GetL()); + DCHECK_EQ(shadow_frame->GetVRegReference(i), + reinterpret_cast<mirror::Object*>(shadow_frame->GetVReg(i))); + } + } +} + template <bool is_range, bool do_assignability_check, size_t kVarArgMax> @@ -726,31 +755,15 @@ static inline bool DoCallCommon(ArtMethod* called_method, target->GetEntryPointFromQuickCompiledCode())) { ArtInterpreterToInterpreterBridge(self, code_item, new_shadow_frame, result); } else { - ArtInterpreterToCompiledCodeBridge(self, code_item, new_shadow_frame, result); + ArtInterpreterToCompiledCodeBridge( + self, shadow_frame.GetMethod(), code_item, new_shadow_frame, result); } } else { UnstartedRuntime::Invoke(self, code_item, new_shadow_frame, result, first_dest_reg); } if (string_init && !self->IsExceptionPending()) { - mirror::Object* existing = shadow_frame.GetVRegReference(string_init_vreg_this); - if (existing == nullptr) { - // If it's null, we come from compiled code that was deoptimized. Nothing to do, - // as the compiler verified there was no alias. - // Set the new string result of the StringFactory. - shadow_frame.SetVRegReference(string_init_vreg_this, result->GetL()); - } else { - // Replace the fake string that was allocated with the StringFactory result. - for (uint32_t i = 0; i < shadow_frame.NumberOfVRegs(); ++i) { - if (shadow_frame.GetVRegReference(i) == existing) { - DCHECK_EQ(shadow_frame.GetVRegReference(i), - reinterpret_cast<mirror::Object*>(shadow_frame.GetVReg(i))); - shadow_frame.SetVRegReference(i, result->GetL()); - DCHECK_EQ(shadow_frame.GetVRegReference(i), - reinterpret_cast<mirror::Object*>(shadow_frame.GetVReg(i))); - } - } - } + SetStringInitValueToAllAliases(&shadow_frame, string_init_vreg_this, *result); } return !self->IsExceptionPending(); diff --git a/runtime/interpreter/interpreter_common.h b/runtime/interpreter/interpreter_common.h index 19d971ead8..cc470f372b 100644 --- a/runtime/interpreter/interpreter_common.h +++ b/runtime/interpreter/interpreter_common.h @@ -34,6 +34,7 @@ #include "dex_instruction-inl.h" #include "entrypoints/entrypoint_utils-inl.h" #include "handle_scope-inl.h" +#include "jit/jit.h" #include "lambda/art_lambda_method.h" #include "lambda/box_table.h" #include "lambda/closure.h" @@ -94,7 +95,9 @@ static inline void DoMonitorEnter(Thread* self, StackHandleScope<1> hs(self); Handle<Object> h_ref(hs.NewHandle(ref)); h_ref->MonitorEnter(self); - frame->GetLockCountData().AddMonitor<kMonitorCounting>(self, h_ref.Get()); + if (kMonitorCounting && frame->GetMethod()->MustCountLocks()) { + frame->GetLockCountData().AddMonitor(self, h_ref.Get()); + } } template <bool kMonitorCounting> @@ -106,7 +109,19 @@ static inline void DoMonitorExit(Thread* self, StackHandleScope<1> hs(self); Handle<Object> h_ref(hs.NewHandle(ref)); h_ref->MonitorExit(self); - frame->GetLockCountData().RemoveMonitorOrThrow<kMonitorCounting>(self, h_ref.Get()); + if (kMonitorCounting && frame->GetMethod()->MustCountLocks()) { + frame->GetLockCountData().RemoveMonitorOrThrow(self, h_ref.Get()); + } +} + +template <bool kMonitorCounting> +static inline bool DoMonitorCheckOnExit(Thread* self, ShadowFrame* frame) + NO_THREAD_SAFETY_ANALYSIS + REQUIRES(!Roles::uninterruptible_) { + if (kMonitorCounting && frame->GetMethod()->MustCountLocks()) { + return frame->GetLockCountData().CheckAllMonitorsReleasedOrThrow(self); + } + return true; } void AbortTransactionF(Thread* self, const char* fmt, ...) @@ -628,6 +643,15 @@ static inline bool DoInvoke(Thread* self, ShadowFrame& shadow_frame, const Instr result->SetJ(0); return false; } else { + jit::Jit* jit = Runtime::Current()->GetJit(); + if (jit != nullptr) { + if (type == kVirtual || type == kInterface) { + jit->InvokeVirtualOrInterface( + self, receiver, sf_method, shadow_frame.GetDexPC(), called_method); + } + jit->AddSamples(self, sf_method, 1, /*with_backedges*/false); + } + // TODO: Remove the InvokeVirtualOrInterface instrumentation, as it was only used by the JIT. if (type == kVirtual || type == kInterface) { instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation(); if (UNLIKELY(instrumentation->HasInvokeVirtualOrInterfaceListeners())) { @@ -667,7 +691,14 @@ static inline bool DoInvokeVirtualQuick(Thread* self, ShadowFrame& shadow_frame, result->SetJ(0); return false; } else { + jit::Jit* jit = Runtime::Current()->GetJit(); + if (jit != nullptr) { + jit->InvokeVirtualOrInterface( + self, receiver, shadow_frame.GetMethod(), shadow_frame.GetDexPC(), called_method); + jit->AddSamples(self, shadow_frame.GetMethod(), 1, /*with_backedges*/false); + } instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation(); + // TODO: Remove the InvokeVirtualOrInterface instrumentation, as it was only used by the JIT. if (UNLIKELY(instrumentation->HasInvokeVirtualOrInterfaceListeners())) { instrumentation->InvokeVirtualOrInterface( self, receiver, shadow_frame.GetMethod(), shadow_frame.GetDexPC(), called_method); @@ -984,8 +1015,17 @@ static inline bool IsBackwardBranch(int32_t branch_offset) { return branch_offset <= 0; } -void ArtInterpreterToCompiledCodeBridge(Thread* self, const DexFile::CodeItem* code_item, - ShadowFrame* shadow_frame, JValue* result); +void ArtInterpreterToCompiledCodeBridge(Thread* self, + ArtMethod* caller, + const DexFile::CodeItem* code_item, + ShadowFrame* shadow_frame, + JValue* result); + +// Set string value created from StringFactory.newStringFromXXX() into all aliases of +// StringFactory.newEmptyString(). +void SetStringInitValueToAllAliases(ShadowFrame* shadow_frame, + uint16_t this_obj_vreg, + JValue result); // Explicitly instantiate all DoInvoke functions. #define EXPLICIT_DO_INVOKE_TEMPLATE_DECL(_type, _is_range, _do_check) \ diff --git a/runtime/interpreter/interpreter_goto_table_impl.cc b/runtime/interpreter/interpreter_goto_table_impl.cc index ce698fb688..f03036b6a8 100644 --- a/runtime/interpreter/interpreter_goto_table_impl.cc +++ b/runtime/interpreter/interpreter_goto_table_impl.cc @@ -22,7 +22,6 @@ #include "experimental_flags.h" #include "interpreter_common.h" #include "jit/jit.h" -#include "jit/jit_instrumentation.h" #include "safe_math.h" #include <memory> // std::unique_ptr @@ -67,7 +66,9 @@ namespace interpreter { #define BRANCH_INSTRUMENTATION(offset) \ do { \ - instrumentation->Branch(self, method, dex_pc, offset); \ + if (UNLIKELY(instrumentation->HasBranchListeners())) { \ + instrumentation->Branch(self, method, dex_pc, offset); \ + } \ JValue result; \ if (jit::Jit::MaybeDoOnStackReplacement(self, method, dex_pc, offset, &result)) { \ return result; \ @@ -76,8 +77,8 @@ namespace interpreter { #define HOTNESS_UPDATE() \ do { \ - if (jit_instrumentation_cache != nullptr) { \ - jit_instrumentation_cache->AddSamples(self, method, 1); \ + if (jit != nullptr) { \ + jit->AddSamples(self, method, 1, /*with_backedges*/ true); \ } \ } while (false) @@ -103,8 +104,7 @@ namespace interpreter { } HANDLE_INSTRUCTION_END(); #define HANDLE_MONITOR_CHECKS() \ - if (!shadow_frame.GetLockCountData(). \ - CheckAllMonitorsReleasedOrThrow<do_assignability_check>(self)) { \ + if (!DoMonitorCheckOnExit<do_assignability_check>(self, &shadow_frame)) { \ HANDLE_PENDING_EXCEPTION(); \ } @@ -195,10 +195,6 @@ JValue ExecuteGotoImpl(Thread* self, const DexFile::CodeItem* code_item, ShadowF const auto* const instrumentation = Runtime::Current()->GetInstrumentation(); ArtMethod* method = shadow_frame.GetMethod(); jit::Jit* jit = Runtime::Current()->GetJit(); - jit::JitInstrumentationCache* jit_instrumentation_cache = nullptr; - if (jit != nullptr) { - jit_instrumentation_cache = jit->GetInstrumentationCache(); - } // Jump to first instruction. ADVANCE(0); @@ -2587,7 +2583,7 @@ JValue ExecuteGotoImpl(Thread* self, const DexFile::CodeItem* code_item, ShadowF instrumentation); if (found_dex_pc == DexFile::kDexNoIndex) { // Structured locking is to be enforced for abnormal termination, too. - shadow_frame.GetLockCountData().CheckAllMonitorsReleasedOrThrow<do_assignability_check>(self); + DoMonitorCheckOnExit<do_assignability_check>(self, &shadow_frame); return JValue(); /* Handled in caller. */ } else { int32_t displacement = static_cast<int32_t>(found_dex_pc) - static_cast<int32_t>(dex_pc); diff --git a/runtime/interpreter/interpreter_switch_impl.cc b/runtime/interpreter/interpreter_switch_impl.cc index 442e1915f8..18330babe0 100644 --- a/runtime/interpreter/interpreter_switch_impl.cc +++ b/runtime/interpreter/interpreter_switch_impl.cc @@ -18,7 +18,6 @@ #include "experimental_flags.h" #include "interpreter_common.h" #include "jit/jit.h" -#include "jit/jit_instrumentation.h" #include "safe_math.h" #include <memory> // std::unique_ptr @@ -35,8 +34,7 @@ namespace interpreter { instrumentation); \ if (found_dex_pc == DexFile::kDexNoIndex) { \ /* Structured locking is to be enforced for abnormal termination, too. */ \ - shadow_frame.GetLockCountData(). \ - CheckAllMonitorsReleasedOrThrow<do_assignability_check>(self); \ + DoMonitorCheckOnExit<do_assignability_check>(self, &shadow_frame); \ if (interpret_one_instruction) { \ /* Signal mterp to return to caller */ \ shadow_frame.SetDexPC(DexFile::kDexNoIndex); \ @@ -58,8 +56,7 @@ namespace interpreter { } while (false) #define HANDLE_MONITOR_CHECKS() \ - if (!shadow_frame.GetLockCountData(). \ - CheckAllMonitorsReleasedOrThrow<do_assignability_check>(self)) { \ + if (!DoMonitorCheckOnExit<do_assignability_check>(self, &shadow_frame)) { \ HANDLE_PENDING_EXCEPTION(); \ } @@ -74,7 +71,9 @@ namespace interpreter { #define BRANCH_INSTRUMENTATION(offset) \ do { \ - instrumentation->Branch(self, method, dex_pc, offset); \ + if (UNLIKELY(instrumentation->HasBranchListeners())) { \ + instrumentation->Branch(self, method, dex_pc, offset); \ + } \ JValue result; \ if (jit::Jit::MaybeDoOnStackReplacement(self, method, dex_pc, offset, &result)) { \ if (interpret_one_instruction) { \ @@ -87,8 +86,8 @@ namespace interpreter { #define HOTNESS_UPDATE() \ do { \ - if (jit_instrumentation_cache != nullptr) { \ - jit_instrumentation_cache->AddSamples(self, method, 1); \ + if (jit != nullptr) { \ + jit->AddSamples(self, method, 1, /*with_backedges*/ true); \ } \ } while (false) @@ -115,10 +114,6 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, uint16_t inst_data; ArtMethod* method = shadow_frame.GetMethod(); jit::Jit* jit = Runtime::Current()->GetJit(); - jit::JitInstrumentationCache* jit_instrumentation_cache = nullptr; - if (jit != nullptr) { - jit_instrumentation_cache = jit->GetInstrumentationCache(); - } // TODO: collapse capture-variable+create-lambda into one opcode, then we won't need // to keep this live for the scope of the entire function call. diff --git a/runtime/interpreter/mterp/mips/bincmp.S b/runtime/interpreter/mterp/mips/bincmp.S index 70057f6792..68df5c3ff0 100644 --- a/runtime/interpreter/mterp/mips/bincmp.S +++ b/runtime/interpreter/mterp/mips/bincmp.S @@ -1,7 +1,6 @@ /* - * Generic two-operand compare-and-branch operation. Provide a "revcmp" - * fragment that specifies the *reverse* comparison to perform, e.g. - * for "if-le" you would use "gt". + * Generic two-operand compare-and-branch operation. Provide a "condition" + * fragment that specifies the comparison to perform. * * For: if-eq, if-ne, if-lt, if-ge, if-gt, if-le */ @@ -9,29 +8,11 @@ GET_OPA4(a0) # a0 <- A+ GET_OPB(a1) # a1 <- B GET_VREG(a3, a1) # a3 <- vB - GET_VREG(a2, a0) # a2 <- vA - b${revcmp} a2, a3, 1f # branch to 1 if comparison failed + GET_VREG(a0, a0) # a0 <- vA FETCH_S(rINST, 1) # rINST<- branch offset, in code units - b 2f -1: - li rINST, 2 # rINST- BYTE branch dist for not-taken -2: -#if MTERP_PROFILE_BRANCHES - EXPORT_PC() - move a0, rSELF - addu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - JAL(MterpProfileBranch) # (self, shadow_frame, offset) - bnez v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - addu a2, rINST, rINST # convert to bytes - FETCH_ADVANCE_INST_RB(a2) # update rPC, load rINST - bgez a2, .L_${opcode}_finish - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue - -%break - -.L_${opcode}_finish: + b${condition} a0, a3, MterpCommonTakenBranchNoFlags # compare (vA, vB) + li t0, JIT_CHECK_OSR + beq rPROFILE, t0, .L_check_not_taken_osr + FETCH_ADVANCE_INST(2) # advance rPC, load rINST GET_INST_OPCODE(t0) # extract opcode from rINST GOTO_OPCODE(t0) # jump to next instruction diff --git a/runtime/interpreter/mterp/mips/entry.S b/runtime/interpreter/mterp/mips/entry.S index 5771a4f402..c806a679b9 100644 --- a/runtime/interpreter/mterp/mips/entry.S +++ b/runtime/interpreter/mterp/mips/entry.S @@ -60,6 +60,12 @@ ExecuteMterpImpl: /* Starting ibase */ lw rIBASE, THREAD_CURRENT_IBASE_OFFSET(rSELF) + /* Set up for backwards branches & osr profiling */ + lw a0, OFF_FP_METHOD(rFP) + addu a1, rFP, OFF_FP_SHADOWFRAME + JAL(MterpSetUpHotnessCountdown) # (method, shadow_frame) + move rPROFILE, v0 # Starting hotness countdown to rPROFILE + /* start executing the instruction at rPC */ FETCH_INST() # load rINST from rPC GET_INST_OPCODE(t0) # extract opcode from rINST diff --git a/runtime/interpreter/mterp/mips/footer.S b/runtime/interpreter/mterp/mips/footer.S index 083dc15205..1363751566 100644 --- a/runtime/interpreter/mterp/mips/footer.S +++ b/runtime/interpreter/mterp/mips/footer.S @@ -112,20 +112,110 @@ MterpException: /* NOTE: no fallthrough */ /* - * Check for suspend check request. Assumes rINST already loaded, rPC advanced and - * still needs to get the opcode and branch to it, and flags are in lr. + * Common handling for branches with support for Jit profiling. + * On entry: + * rINST <= signed offset + * rPROFILE <= signed hotness countdown (expanded to 32 bits) + * + * We have quite a few different cases for branch profiling, OSR detection and + * suspend check support here. + * + * Taken backward branches: + * If profiling active, do hotness countdown and report if we hit zero. + * If in osr check mode, see if our target is a compiled loop header entry and do OSR if so. + * Is there a pending suspend request? If so, suspend. + * + * Taken forward branches and not-taken backward branches: + * If in osr check mode, see if our target is a compiled loop header entry and do OSR if so. + * + * Our most common case is expected to be a taken backward branch with active jit profiling, + * but no full OSR check and no pending suspend request. + * Next most common case is not-taken branch with no full OSR check. */ -MterpCheckSuspendAndContinue: - lw rIBASE, THREAD_CURRENT_IBASE_OFFSET(rSELF) # refresh rIBASE +MterpCommonTakenBranchNoFlags: + bgtz rINST, .L_forward_branch # don't add forward branches to hotness +/* + * We need to subtract 1 from positive values and we should not see 0 here, + * so we may use the result of the comparison with -1. + */ +#if JIT_CHECK_OSR != -1 +# error "JIT_CHECK_OSR must be -1." +#endif + li t0, JIT_CHECK_OSR + beq rPROFILE, t0, .L_osr_check + blt rPROFILE, t0, .L_resume_backward_branch + subu rPROFILE, 1 + beqz rPROFILE, .L_add_batch # counted down to zero - report +.L_resume_backward_branch: + lw ra, THREAD_FLAGS_OFFSET(rSELF) + REFRESH_IBASE() + addu a2, rINST, rINST # a2<- byte offset + FETCH_ADVANCE_INST_RB(a2) # update rPC, load rINST and ra, (THREAD_SUSPEND_REQUEST | THREAD_CHECKPOINT_REQUEST) - bnez ra, 1f + bnez ra, .L_suspend_request_pending GET_INST_OPCODE(t0) # extract opcode from rINST GOTO_OPCODE(t0) # jump to next instruction -1: + +.L_suspend_request_pending: EXPORT_PC() move a0, rSELF JAL(MterpSuspendCheck) # (self) bnez v0, MterpFallback + REFRESH_IBASE() # might have changed during suspend + GET_INST_OPCODE(t0) # extract opcode from rINST + GOTO_OPCODE(t0) # jump to next instruction + +.L_no_count_backwards: + li t0, JIT_CHECK_OSR # check for possible OSR re-entry + bne rPROFILE, t0, .L_resume_backward_branch +.L_osr_check: + move a0, rSELF + addu a1, rFP, OFF_FP_SHADOWFRAME + move a2, rINST + EXPORT_PC() + JAL(MterpMaybeDoOnStackReplacement) # (self, shadow_frame, offset) + bnez v0, MterpOnStackReplacement + b .L_resume_backward_branch + +.L_forward_branch: + li t0, JIT_CHECK_OSR # check for possible OSR re-entry + beq rPROFILE, t0, .L_check_osr_forward +.L_resume_forward_branch: + add a2, rINST, rINST # a2<- byte offset + FETCH_ADVANCE_INST_RB(a2) # update rPC, load rINST + GET_INST_OPCODE(t0) # extract opcode from rINST + GOTO_OPCODE(t0) # jump to next instruction + +.L_check_osr_forward: + move a0, rSELF + addu a1, rFP, OFF_FP_SHADOWFRAME + move a2, rINST + EXPORT_PC() + JAL(MterpMaybeDoOnStackReplacement) # (self, shadow_frame, offset) + bnez v0, MterpOnStackReplacement + b .L_resume_forward_branch + +.L_add_batch: + addu a1, rFP, OFF_FP_SHADOWFRAME + sh rPROFILE, SHADOWFRAME_HOTNESS_COUNTDOWN_OFFSET(a1) + lw a0, OFF_FP_METHOD(rFP) + move a2, rSELF + JAL(MterpAddHotnessBatch) # (method, shadow_frame, self) + move rPROFILE, v0 # restore new hotness countdown to rPROFILE + b .L_no_count_backwards + +/* + * Entered from the conditional branch handlers when OSR check request active on + * not-taken path. All Dalvik not-taken conditional branch offsets are 2. + */ +.L_check_not_taken_osr: + move a0, rSELF + addu a1, rFP, OFF_FP_SHADOWFRAME + li a2, 2 + EXPORT_PC() + JAL(MterpMaybeDoOnStackReplacement) # (self, shadow_frame, offset) + bnez v0, MterpOnStackReplacement + FETCH_ADVANCE_INST(2) GET_INST_OPCODE(t0) # extract opcode from rINST GOTO_OPCODE(t0) # jump to next instruction @@ -172,6 +262,26 @@ MterpReturn: sw v1, 4(a2) li v0, 1 # signal return to caller. MterpDone: +/* + * At this point, we expect rPROFILE to be non-zero. If negative, hotness is disabled or we're + * checking for OSR. If greater than zero, we might have unreported hotness to register + * (the difference between the ending rPROFILE and the cached hotness counter). rPROFILE + * should only reach zero immediately after a hotness decrement, and is then reset to either + * a negative special state or the new non-zero countdown value. + */ + blez rPROFILE, .L_pop_and_return # if > 0, we may have some counts to report. + +MterpProfileActive: + move rINST, v0 # stash return value + /* Report cached hotness counts */ + lw a0, OFF_FP_METHOD(rFP) + addu a1, rFP, OFF_FP_SHADOWFRAME + move a2, rSELF + sh rPROFILE, SHADOWFRAME_HOTNESS_COUNTDOWN_OFFSET(a1) + JAL(MterpAddHotnessBatch) # (method, shadow_frame, self) + move v0, rINST # restore return value + +.L_pop_and_return: /* Restore from the stack and return. Frame size = STACK_SIZE */ STACK_LOAD_FULL() jalr zero, ra diff --git a/runtime/interpreter/mterp/mips/header.S b/runtime/interpreter/mterp/mips/header.S index 37ab21de5c..a3a67444ab 100644 --- a/runtime/interpreter/mterp/mips/header.S +++ b/runtime/interpreter/mterp/mips/header.S @@ -51,7 +51,11 @@ s2 rSELF self (Thread) pointer s3 rIBASE interpreted instruction base pointer, used for computed goto s4 rINST first 16-bit code unit of current instruction + s5 rOBJ object pointer s6 rREFS base of object references in shadow frame (ideally, we'll get rid of this later). + s7 rTEMP used as temp storage that can survive a function call + s8 rPROFILE branch profiling countdown + */ /* single-purpose registers, given names for clarity */ @@ -63,6 +67,7 @@ #define rOBJ s5 #define rREFS s6 #define rTEMP s7 +#define rPROFILE s8 #define rARG0 a0 #define rARG1 a1 @@ -160,7 +165,7 @@ #define OFF_FP_RESULT_REGISTER OFF_FP(SHADOWFRAME_RESULT_REGISTER_OFFSET) #define OFF_FP_DEX_PC_PTR OFF_FP(SHADOWFRAME_DEX_PC_PTR_OFFSET) #define OFF_FP_CODE_ITEM OFF_FP(SHADOWFRAME_CODE_ITEM_OFFSET) -#define OFF_FP_SHADOWFRAME (-SHADOWFRAME_VREGS_OFFSET) +#define OFF_FP_SHADOWFRAME OFF_FP(0) #define MTERP_PROFILE_BRANCHES 1 #define MTERP_LOGGING 0 @@ -482,3 +487,6 @@ STACK_LOAD(s8, 120); \ STACK_LOAD(ra, 124); \ DELETE_STACK(STACK_SIZE) + +#define REFRESH_IBASE() \ + lw rIBASE, THREAD_CURRENT_IBASE_OFFSET(rSELF) diff --git a/runtime/interpreter/mterp/mips/op_goto.S b/runtime/interpreter/mterp/mips/op_goto.S index d6f21c9b2c..57182a5b59 100644 --- a/runtime/interpreter/mterp/mips/op_goto.S +++ b/runtime/interpreter/mterp/mips/op_goto.S @@ -5,34 +5,6 @@ * double to get a byte offset. */ /* goto +AA */ -#if MTERP_PROFILE_BRANCHES sll a0, rINST, 16 # a0 <- AAxx0000 sra rINST, a0, 24 # rINST <- ssssssAA (sign-extended) - EXPORT_PC() - move a0, rSELF - addu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - JAL(MterpProfileBranch) # (self, shadow_frame, offset) - bnez v0, MterpOnStackReplacement # Note: offset must be in rINST - addu a2, rINST, rINST # a2 <- byte offset - FETCH_ADVANCE_INST_RB(a2) # update rPC, load rINST - /* If backwards branch refresh rIBASE */ - bgez a2, 1f - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue -1: - GET_INST_OPCODE(t0) # extract opcode from rINST - GOTO_OPCODE(t0) # jump to next instruction -#else - sll a0, rINST, 16 # a0 <- AAxx0000 - sra rINST, a0, 24 # rINST <- ssssssAA (sign-extended) - addu a2, rINST, rINST # a2 <- byte offset - FETCH_ADVANCE_INST_RB(a2) # update rPC, load rINST - /* If backwards branch refresh rIBASE */ - bgez a1, 1f - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue -1: - GET_INST_OPCODE(t0) # extract opcode from rINST - GOTO_OPCODE(t0) # jump to next instruction -#endif + b MterpCommonTakenBranchNoFlags diff --git a/runtime/interpreter/mterp/mips/op_goto_16.S b/runtime/interpreter/mterp/mips/op_goto_16.S index cec4432599..06c96cd545 100644 --- a/runtime/interpreter/mterp/mips/op_goto_16.S +++ b/runtime/interpreter/mterp/mips/op_goto_16.S @@ -5,30 +5,5 @@ * double to get a byte offset. */ /* goto/16 +AAAA */ -#if MTERP_PROFILE_BRANCHES FETCH_S(rINST, 1) # rINST <- ssssAAAA (sign-extended) - EXPORT_PC() - move a0, rSELF - addu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - JAL(MterpProfileBranch) # (self, shadow_frame, offset) - bnez v0, MterpOnStackReplacement # Note: offset must be in rINST - addu a1, rINST, rINST # a1 <- byte offset, flags set - FETCH_ADVANCE_INST_RB(a1) # update rPC, load rINST - bgez a1, 1f - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue -1: - GET_INST_OPCODE(t0) # extract opcode from rINST - GOTO_OPCODE(t0) # jump to next instruction -#else - FETCH_S(rINST, 1) # rINST <- ssssAAAA (sign-extended) - addu a1, rINST, rINST # a1 <- byte offset, flags set - FETCH_ADVANCE_INST_RB(a1) # update rPC, load rINST - bgez a1, 1f - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue -1: - GET_INST_OPCODE(t0) # extract opcode from rINST - GOTO_OPCODE(t0) # jump to next instruction -#endif + b MterpCommonTakenBranchNoFlags diff --git a/runtime/interpreter/mterp/mips/op_goto_32.S b/runtime/interpreter/mterp/mips/op_goto_32.S index 083acd1ef9..67f52e9e48 100644 --- a/runtime/interpreter/mterp/mips/op_goto_32.S +++ b/runtime/interpreter/mterp/mips/op_goto_32.S @@ -8,36 +8,8 @@ * our "backward branch" test must be "<=0" instead of "<0". */ /* goto/32 +AAAAAAAA */ -#if MTERP_PROFILE_BRANCHES FETCH(a0, 1) # a0 <- aaaa (lo) FETCH(a1, 2) # a1 <- AAAA (hi) sll a1, a1, 16 or rINST, a0, a1 # rINST <- AAAAaaaa - EXPORT_PC() - move a0, rSELF - addu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - JAL(MterpProfileBranch) # (self, shadow_frame, offset) - bnez v0, MterpOnStackReplacement # Note: offset must be in rINST - addu a1, rINST, rINST # a1 <- byte offset - FETCH_ADVANCE_INST_RB(a1) # update rPC, load rINST - bgtz a1, 1f - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue -1: - GET_INST_OPCODE(t0) # extract opcode from rINST - GOTO_OPCODE(t0) # jump to next instruction -#else - FETCH(a0, 1) # a0 <- aaaa (lo) - FETCH(a1, 2) # a1 <- AAAA (hi) - sll a1, a1, 16 - or rINST, a0, a1 # rINST <- AAAAaaaa - addu a1, rINST, rINST # a1 <- byte offset - FETCH_ADVANCE_INST_RB(a1) # update rPC, load rINST - bgtz a1, 1f - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue -1: - GET_INST_OPCODE(t0) # extract opcode from rINST - GOTO_OPCODE(t0) # jump to next instruction -#endif + b MterpCommonTakenBranchNoFlags diff --git a/runtime/interpreter/mterp/mips/op_if_eq.S b/runtime/interpreter/mterp/mips/op_if_eq.S index e7190d8197..d6f9987186 100644 --- a/runtime/interpreter/mterp/mips/op_if_eq.S +++ b/runtime/interpreter/mterp/mips/op_if_eq.S @@ -1 +1 @@ -%include "mips/bincmp.S" { "revcmp":"ne" } +%include "mips/bincmp.S" { "condition":"eq" } diff --git a/runtime/interpreter/mterp/mips/op_if_eqz.S b/runtime/interpreter/mterp/mips/op_if_eqz.S index 0a78fd98ac..c52b76a755 100644 --- a/runtime/interpreter/mterp/mips/op_if_eqz.S +++ b/runtime/interpreter/mterp/mips/op_if_eqz.S @@ -1 +1 @@ -%include "mips/zcmp.S" { "revcmp":"ne" } +%include "mips/zcmp.S" { "condition":"eq" } diff --git a/runtime/interpreter/mterp/mips/op_if_ge.S b/runtime/interpreter/mterp/mips/op_if_ge.S index b2629ba4e9..bd06ff5ad4 100644 --- a/runtime/interpreter/mterp/mips/op_if_ge.S +++ b/runtime/interpreter/mterp/mips/op_if_ge.S @@ -1 +1 @@ -%include "mips/bincmp.S" { "revcmp":"lt" } +%include "mips/bincmp.S" { "condition":"ge" } diff --git a/runtime/interpreter/mterp/mips/op_if_gez.S b/runtime/interpreter/mterp/mips/op_if_gez.S index b02f67709f..549231a15f 100644 --- a/runtime/interpreter/mterp/mips/op_if_gez.S +++ b/runtime/interpreter/mterp/mips/op_if_gez.S @@ -1 +1 @@ -%include "mips/zcmp.S" { "revcmp":"lt" } +%include "mips/zcmp.S" { "condition":"ge" } diff --git a/runtime/interpreter/mterp/mips/op_if_gt.S b/runtime/interpreter/mterp/mips/op_if_gt.S index f620d4a1fd..0be30912ed 100644 --- a/runtime/interpreter/mterp/mips/op_if_gt.S +++ b/runtime/interpreter/mterp/mips/op_if_gt.S @@ -1 +1 @@ -%include "mips/bincmp.S" { "revcmp":"le" } +%include "mips/bincmp.S" { "condition":"gt" } diff --git a/runtime/interpreter/mterp/mips/op_if_gtz.S b/runtime/interpreter/mterp/mips/op_if_gtz.S index 5e5dd708fa..5c7bcc48b5 100644 --- a/runtime/interpreter/mterp/mips/op_if_gtz.S +++ b/runtime/interpreter/mterp/mips/op_if_gtz.S @@ -1 +1 @@ -%include "mips/zcmp.S" { "revcmp":"le" } +%include "mips/zcmp.S" { "condition":"gt" } diff --git a/runtime/interpreter/mterp/mips/op_if_le.S b/runtime/interpreter/mterp/mips/op_if_le.S index a4e8b1ad51..c35c1a24b7 100644 --- a/runtime/interpreter/mterp/mips/op_if_le.S +++ b/runtime/interpreter/mterp/mips/op_if_le.S @@ -1 +1 @@ -%include "mips/bincmp.S" { "revcmp":"gt" } +%include "mips/bincmp.S" { "condition":"le" } diff --git a/runtime/interpreter/mterp/mips/op_if_lez.S b/runtime/interpreter/mterp/mips/op_if_lez.S index af551a62fd..3dc6543d90 100644 --- a/runtime/interpreter/mterp/mips/op_if_lez.S +++ b/runtime/interpreter/mterp/mips/op_if_lez.S @@ -1 +1 @@ -%include "mips/zcmp.S" { "revcmp":"gt" } +%include "mips/zcmp.S" { "condition":"le" } diff --git a/runtime/interpreter/mterp/mips/op_if_lt.S b/runtime/interpreter/mterp/mips/op_if_lt.S index f33b9a4c05..3f3386c9d2 100644 --- a/runtime/interpreter/mterp/mips/op_if_lt.S +++ b/runtime/interpreter/mterp/mips/op_if_lt.S @@ -1 +1 @@ -%include "mips/bincmp.S" { "revcmp":"ge" } +%include "mips/bincmp.S" { "condition":"lt" } diff --git a/runtime/interpreter/mterp/mips/op_if_ltz.S b/runtime/interpreter/mterp/mips/op_if_ltz.S index 18fcb1d477..e6d6ed6aa6 100644 --- a/runtime/interpreter/mterp/mips/op_if_ltz.S +++ b/runtime/interpreter/mterp/mips/op_if_ltz.S @@ -1 +1 @@ -%include "mips/zcmp.S" { "revcmp":"ge" } +%include "mips/zcmp.S" { "condition":"lt" } diff --git a/runtime/interpreter/mterp/mips/op_if_ne.S b/runtime/interpreter/mterp/mips/op_if_ne.S index e0a102b443..3d7bf350f1 100644 --- a/runtime/interpreter/mterp/mips/op_if_ne.S +++ b/runtime/interpreter/mterp/mips/op_if_ne.S @@ -1 +1 @@ -%include "mips/bincmp.S" { "revcmp":"eq" } +%include "mips/bincmp.S" { "condition":"ne" } diff --git a/runtime/interpreter/mterp/mips/op_if_nez.S b/runtime/interpreter/mterp/mips/op_if_nez.S index d1866a0a02..d121eae930 100644 --- a/runtime/interpreter/mterp/mips/op_if_nez.S +++ b/runtime/interpreter/mterp/mips/op_if_nez.S @@ -1 +1 @@ -%include "mips/zcmp.S" { "revcmp":"eq" } +%include "mips/zcmp.S" { "condition":"ne" } diff --git a/runtime/interpreter/mterp/mips/op_packed_switch.S b/runtime/interpreter/mterp/mips/op_packed_switch.S index 93fae973e5..ffa4f472ef 100644 --- a/runtime/interpreter/mterp/mips/op_packed_switch.S +++ b/runtime/interpreter/mterp/mips/op_packed_switch.S @@ -9,7 +9,6 @@ * for: packed-switch, sparse-switch */ /* op vAA, +BBBB */ -#if MTERP_PROFILE_BRANCHES FETCH(a0, 1) # a0 <- bbbb (lo) FETCH(a1, 2) # a1 <- BBBB (hi) GET_OPA(a3) # a3 <- AA @@ -19,39 +18,4 @@ EAS1(a0, rPC, a0) # a0 <- PC + BBBBbbbb*2 JAL($func) # a0 <- code-unit branch offset move rINST, v0 - EXPORT_PC() - move a0, rSELF - addu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - JAL(MterpProfileBranch) # (self, shadow_frame, offset) - bnez v0, MterpOnStackReplacement # Note: offset must be in rINST - addu a1, rINST, rINST # a1 <- byte offset - FETCH_ADVANCE_INST_RB(a1) # update rPC, load rINST - bgtz a1, .L${opcode}_finish - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue -#else - FETCH(a0, 1) # a0 <- bbbb (lo) - FETCH(a1, 2) # a1 <- BBBB (hi) - GET_OPA(a3) # a3 <- AA - sll t0, a1, 16 - or a0, a0, t0 # a0 <- BBBBbbbb - GET_VREG(a1, a3) # a1 <- vAA - EAS1(a0, rPC, a0) # a0 <- PC + BBBBbbbb*2 - JAL($func) # a0 <- code-unit branch offset - move rINST, v0 - addu a1, rINST, rINST # a1 <- byte offset - FETCH_ADVANCE_INST_RB(a1) # update rPC, load rINST - bgtz a1, 1f - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue -1: - GET_INST_OPCODE(t0) # extract opcode from rINST - GOTO_OPCODE(t0) # jump to next instruction -#endif - -%break - -.L${opcode}_finish: - GET_INST_OPCODE(t0) # extract opcode from rINST - GOTO_OPCODE(t0) # jump to next instruction + b MterpCommonTakenBranchNoFlags diff --git a/runtime/interpreter/mterp/mips/zcmp.S b/runtime/interpreter/mterp/mips/zcmp.S index 1fa13851c7..8d3a198891 100644 --- a/runtime/interpreter/mterp/mips/zcmp.S +++ b/runtime/interpreter/mterp/mips/zcmp.S @@ -1,32 +1,16 @@ /* - * Generic one-operand compare-and-branch operation. Provide a "revcmp" - * fragment that specifies the *reverse* comparison to perform, e.g. - * for "if-le" you would use "gt". + * Generic one-operand compare-and-branch operation. Provide a "condition" + * fragment that specifies the comparison to perform. * * for: if-eqz, if-nez, if-ltz, if-gez, if-gtz, if-lez */ /* if-cmp vAA, +BBBB */ GET_OPA(a0) # a0 <- AA - GET_VREG(a2, a0) # a2 <- vAA + GET_VREG(a0, a0) # a0 <- vAA FETCH_S(rINST, 1) # rINST <- branch offset, in code units - b${revcmp} a2, zero, 1f # branch to 1 if comparison failed - b 2f -1: - li rINST, 2 # rINST- BYTE branch dist for not-taken -2: -#if MTERP_PROFILE_BRANCHES - EXPORT_PC() - move a0, rSELF - addu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - JAL(MterpProfileBranch) # (self, shadow_frame, offset) - bnez v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - addu a1, rINST, rINST # convert to bytes - FETCH_ADVANCE_INST_RB(a1) # update rPC, load rINST - bgez a1, 3f - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue -3: + b${condition} a0, zero, MterpCommonTakenBranchNoFlags + li t0, JIT_CHECK_OSR # possible OSR re-entry? + beq rPROFILE, t0, .L_check_not_taken_osr + FETCH_ADVANCE_INST(2) # advance rPC, load rINST GET_INST_OPCODE(t0) # extract opcode from rINST GOTO_OPCODE(t0) # jump to next instruction diff --git a/runtime/interpreter/mterp/mips64/bincmp.S b/runtime/interpreter/mterp/mips64/bincmp.S index aa5e74b3de..07b12100fd 100644 --- a/runtime/interpreter/mterp/mips64/bincmp.S +++ b/runtime/interpreter/mterp/mips64/bincmp.S @@ -12,21 +12,9 @@ lh rINST, 2(rPC) # rINST <- offset (sign-extended CCCC) GET_VREG a0, a2 # a0 <- vA GET_VREG a1, a3 # a1 <- vB - b${condition}c a0, a1, 1f - li rINST, 2 # offset if branch not taken -1: -#if MTERP_PROFILE_BRANCHES - EXPORT_PC - move a0, rSELF - daddu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - jal MterpProfileBranch # (self, shadow_frame, offset) - bnezc v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - dlsa rPC, rINST, rPC, 1 # rPC <- rPC + offset * 2 - lw ra, THREAD_FLAGS_OFFSET(rSELF) # Preload flags for MterpCheckSuspendAndContinue - move a0, rINST # a0 <- offset - FETCH_INST # load rINST - bltz a0, MterpCheckSuspendAndContinue # suspend check if backwards branch + b${condition}c a0, a1, MterpCommonTakenBranchNoFlags + li v0, JIT_CHECK_OSR # possible OSR re-entry? + beqc rPROFILE, v0, .L_check_not_taken_osr + FETCH_ADVANCE_INST 2 # advance rPC, load rINST GET_INST_OPCODE v0 # extract opcode from rINST GOTO_OPCODE v0 # jump to next instruction diff --git a/runtime/interpreter/mterp/mips64/entry.S b/runtime/interpreter/mterp/mips64/entry.S index ae6c26b706..cc48d45298 100644 --- a/runtime/interpreter/mterp/mips64/entry.S +++ b/runtime/interpreter/mterp/mips64/entry.S @@ -57,6 +57,8 @@ ExecuteMterpImpl: .cfi_rel_offset 20, STACK_OFFSET_S4 sd s5, STACK_OFFSET_S5(sp) .cfi_rel_offset 21, STACK_OFFSET_S5 + sd s6, STACK_OFFSET_S6(sp) + .cfi_rel_offset 22, STACK_OFFSET_S6 /* Remember the return register */ sd a3, SHADOWFRAME_RESULT_REGISTER_OFFSET(a2) @@ -77,6 +79,12 @@ ExecuteMterpImpl: /* Starting ibase */ REFRESH_IBASE + /* Set up for backwards branches & osr profiling */ + ld a0, OFF_FP_METHOD(rFP) + daddu a1, rFP, OFF_FP_SHADOWFRAME + jal MterpSetUpHotnessCountdown + move rPROFILE, v0 # Starting hotness countdown to rPROFILE + /* start executing the instruction at rPC */ FETCH_INST GET_INST_OPCODE v0 diff --git a/runtime/interpreter/mterp/mips64/footer.S b/runtime/interpreter/mterp/mips64/footer.S index 14d5fe01f5..9994169950 100644 --- a/runtime/interpreter/mterp/mips64/footer.S +++ b/runtime/interpreter/mterp/mips64/footer.S @@ -71,23 +71,110 @@ MterpException: /* NOTE: no fallthrough */ /* - * Check for suspend check request. Assumes rINST already loaded, rPC advanced and - * still needs to get the opcode and branch to it, and flags are in ra. + * Common handling for branches with support for Jit profiling. + * On entry: + * rINST <= signed offset + * rPROFILE <= signed hotness countdown (expanded to 64 bits) + * + * We have quite a few different cases for branch profiling, OSR detection and + * suspend check support here. + * + * Taken backward branches: + * If profiling active, do hotness countdown and report if we hit zero. + * If in osr check mode, see if our target is a compiled loop header entry and do OSR if so. + * Is there a pending suspend request? If so, suspend. + * + * Taken forward branches and not-taken backward branches: + * If in osr check mode, see if our target is a compiled loop header entry and do OSR if so. + * + * Our most common case is expected to be a taken backward branch with active jit profiling, + * but no full OSR check and no pending suspend request. + * Next most common case is not-taken branch with no full OSR check. + * + */ +MterpCommonTakenBranchNoFlags: + bgtzc rINST, .L_forward_branch # don't add forward branches to hotness +/* + * We need to subtract 1 from positive values and we should not see 0 here, + * so we may use the result of the comparison with -1. */ - .extern MterpSuspendCheck -MterpCheckSuspendAndContinue: + li v0, JIT_CHECK_OSR + beqc rPROFILE, v0, .L_osr_check + bltc rPROFILE, v0, .L_resume_backward_branch + dsubu rPROFILE, 1 + beqzc rPROFILE, .L_add_batch # counted down to zero - report +.L_resume_backward_branch: + lw ra, THREAD_FLAGS_OFFSET(rSELF) REFRESH_IBASE - and ra, ra, (THREAD_SUSPEND_REQUEST | THREAD_CHECKPOINT_REQUEST) - bnez ra, check1 - GET_INST_OPCODE v0 # extract opcode from rINST - GOTO_OPCODE v0 # jump to next instruction -check1: + daddu a2, rINST, rINST # a2<- byte offset + FETCH_ADVANCE_INST_RB a2 # update rPC, load rINST + and ra, (THREAD_SUSPEND_REQUEST | THREAD_CHECKPOINT_REQUEST) + bnezc ra, .L_suspend_request_pending + GET_INST_OPCODE v0 # extract opcode from rINST + GOTO_OPCODE v0 # jump to next instruction + +.L_suspend_request_pending: EXPORT_PC move a0, rSELF - jal MterpSuspendCheck # (self) - bnezc v0, MterpFallback # Something in the environment changed, switch interpreters - GET_INST_OPCODE v0 # extract opcode from rINST - GOTO_OPCODE v0 # jump to next instruction + jal MterpSuspendCheck # (self) + bnezc v0, MterpFallback + REFRESH_IBASE # might have changed during suspend + GET_INST_OPCODE v0 # extract opcode from rINST + GOTO_OPCODE v0 # jump to next instruction + +.L_no_count_backwards: + li v0, JIT_CHECK_OSR # check for possible OSR re-entry + bnec rPROFILE, v0, .L_resume_backward_branch +.L_osr_check: + move a0, rSELF + daddu a1, rFP, OFF_FP_SHADOWFRAME + move a2, rINST + EXPORT_PC + jal MterpMaybeDoOnStackReplacement # (self, shadow_frame, offset) + bnezc v0, MterpOnStackReplacement + b .L_resume_backward_branch + +.L_forward_branch: + li v0, JIT_CHECK_OSR # check for possible OSR re-entry + beqc rPROFILE, v0, .L_check_osr_forward +.L_resume_forward_branch: + daddu a2, rINST, rINST # a2<- byte offset + FETCH_ADVANCE_INST_RB a2 # update rPC, load rINST + GET_INST_OPCODE v0 # extract opcode from rINST + GOTO_OPCODE v0 # jump to next instruction + +.L_check_osr_forward: + move a0, rSELF + daddu a1, rFP, OFF_FP_SHADOWFRAME + move a2, rINST + EXPORT_PC + jal MterpMaybeDoOnStackReplacement # (self, shadow_frame, offset) + bnezc v0, MterpOnStackReplacement + b .L_resume_forward_branch + +.L_add_batch: + daddu a1, rFP, OFF_FP_SHADOWFRAME + sh rPROFILE, SHADOWFRAME_HOTNESS_COUNTDOWN_OFFSET(a1) + ld a0, OFF_FP_METHOD(rFP) + move a2, rSELF + jal MterpAddHotnessBatch # (method, shadow_frame, self) + move rPROFILE, v0 # restore new hotness countdown to rPROFILE + b .L_no_count_backwards + +/* + * Entered from the conditional branch handlers when OSR check request active on + * not-taken path. All Dalvik not-taken conditional branch offsets are 2. + */ +.L_check_not_taken_osr: + move a0, rSELF + daddu a1, rFP, OFF_FP_SHADOWFRAME + li a2, 2 + EXPORT_PC + jal MterpMaybeDoOnStackReplacement # (self, shadow_frame, offset) + bnezc v0, MterpOnStackReplacement + FETCH_ADVANCE_INST 2 + GET_INST_OPCODE v0 # extract opcode from rINST + GOTO_OPCODE v0 # jump to next instruction /* * On-stack replacement has happened, and now we've returned from the compiled method. @@ -143,6 +230,28 @@ MterpReturn: check2: li v0, 1 # signal return to caller. MterpDone: +/* + * At this point, we expect rPROFILE to be non-zero. If negative, hotness is disabled or we're + * checking for OSR. If greater than zero, we might have unreported hotness to register + * (the difference between the ending rPROFILE and the cached hotness counter). rPROFILE + * should only reach zero immediately after a hotness decrement, and is then reset to either + * a negative special state or the new non-zero countdown value. + */ + blez rPROFILE, .L_pop_and_return # if > 0, we may have some counts to report. + +MterpProfileActive: + move rINST, v0 # stash return value + /* Report cached hotness counts */ + ld a0, OFF_FP_METHOD(rFP) + daddu a1, rFP, OFF_FP_SHADOWFRAME + move a2, rSELF + sh rPROFILE, SHADOWFRAME_HOTNESS_COUNTDOWN_OFFSET(a1) + jal MterpAddHotnessBatch # (method, shadow_frame, self) + move v0, rINST # restore return value + +.L_pop_and_return: + ld s6, STACK_OFFSET_S6(sp) + .cfi_restore 22 ld s5, STACK_OFFSET_S5(sp) .cfi_restore 21 ld s4, STACK_OFFSET_S4(sp) @@ -169,4 +278,5 @@ MterpDone: .cfi_adjust_cfa_offset -STACK_SIZE .cfi_endproc + .set reorder .size ExecuteMterpImpl, .-ExecuteMterpImpl diff --git a/runtime/interpreter/mterp/mips64/header.S b/runtime/interpreter/mterp/mips64/header.S index dd0fbe0057..b67df20a1f 100644 --- a/runtime/interpreter/mterp/mips64/header.S +++ b/runtime/interpreter/mterp/mips64/header.S @@ -51,16 +51,18 @@ The following registers have fixed assignments: s3 rINST first 16-bit code unit of current instruction s4 rIBASE interpreted instruction base pointer, used for computed goto s5 rREFS base of object references in shadow frame (ideally, we'll get rid of this later). + s6 rPROFILE jit profile hotness countdown */ /* During bringup, we'll use the shadow frame model instead of rFP */ /* single-purpose registers, given names for clarity */ -#define rPC s0 -#define rFP s1 -#define rSELF s2 -#define rINST s3 -#define rIBASE s4 -#define rREFS s5 +#define rPC s0 +#define rFP s1 +#define rSELF s2 +#define rINST s3 +#define rIBASE s4 +#define rREFS s5 +#define rPROFILE s6 /* * This is a #include, not a %include, because we want the C pre-processor @@ -80,7 +82,7 @@ The following registers have fixed assignments: #define OFF_FP_RESULT_REGISTER OFF_FP(SHADOWFRAME_RESULT_REGISTER_OFFSET) #define OFF_FP_DEX_PC_PTR OFF_FP(SHADOWFRAME_DEX_PC_PTR_OFFSET) #define OFF_FP_CODE_ITEM OFF_FP(SHADOWFRAME_CODE_ITEM_OFFSET) -#define OFF_FP_SHADOWFRAME (-SHADOWFRAME_VREGS_OFFSET) +#define OFF_FP_SHADOWFRAME OFF_FP(0) #define MTERP_PROFILE_BRANCHES 1 #define MTERP_LOGGING 0 @@ -121,6 +123,17 @@ The following registers have fixed assignments: .endm /* + * Fetch the next instruction from an offset specified by _reg and advance xPC. + * xPC to point to the next instruction. "_reg" must specify the distance + * in bytes, *not* 16-bit code units, and may be a signed value. Must not set flags. + * + */ +.macro FETCH_ADVANCE_INST_RB reg + daddu rPC, rPC, \reg + FETCH_INST +.endm + +/* * Fetch the next instruction from the specified offset. Advances rPC * to point to the next instruction. * @@ -267,7 +280,8 @@ The following registers have fixed assignments: #define STACK_OFFSET_S3 40 #define STACK_OFFSET_S4 48 #define STACK_OFFSET_S5 56 -#define STACK_SIZE 64 +#define STACK_OFFSET_S6 64 +#define STACK_SIZE 80 /* needs 16 byte alignment */ /* Constants for float/double_to_int/long conversions */ #define INT_MIN 0x80000000 diff --git a/runtime/interpreter/mterp/mips64/op_goto.S b/runtime/interpreter/mterp/mips64/op_goto.S index 7c7d0ecf5a..68fc83d0ca 100644 --- a/runtime/interpreter/mterp/mips64/op_goto.S +++ b/runtime/interpreter/mterp/mips64/op_goto.S @@ -5,21 +5,6 @@ * double to get a byte offset. */ /* goto +AA */ - .extern MterpProfileBranch srl rINST, rINST, 8 seb rINST, rINST # rINST <- offset (sign-extended AA) -#if MTERP_PROFILE_BRANCHES - EXPORT_PC - move a0, rSELF - daddu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - jal MterpProfileBranch # (self, shadow_frame, offset) - bnezc v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - dlsa rPC, rINST, rPC, 1 # rPC <- rPC + offset * 2 - lw ra, THREAD_FLAGS_OFFSET(rSELF) # Preload flags for MterpCheckSuspendAndContinue - move a0, rINST # a0 <- offset - FETCH_INST # load rINST - bltz a0, MterpCheckSuspendAndContinue # suspend check if backwards branch - GET_INST_OPCODE v0 # extract opcode from rINST - GOTO_OPCODE v0 # jump to next instruction + b MterpCommonTakenBranchNoFlags diff --git a/runtime/interpreter/mterp/mips64/op_goto_16.S b/runtime/interpreter/mterp/mips64/op_goto_16.S index 566e3a78f0..ae56066352 100644 --- a/runtime/interpreter/mterp/mips64/op_goto_16.S +++ b/runtime/interpreter/mterp/mips64/op_goto_16.S @@ -5,20 +5,5 @@ * double to get a byte offset. */ /* goto/16 +AAAA */ - .extern MterpProfileBranch lh rINST, 2(rPC) # rINST <- offset (sign-extended AAAA) -#if MTERP_PROFILE_BRANCHES - EXPORT_PC - move a0, rSELF - daddu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - jal MterpProfileBranch # (self, shadow_frame, offset) - bnezc v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - dlsa rPC, rINST, rPC, 1 # rPC <- rPC + offset * 2 - lw ra, THREAD_FLAGS_OFFSET(rSELF) # Preload flags for MterpCheckSuspendAndContinue - move a0, rINST # a0 <- offset - FETCH_INST # load rINST - bltz a0, MterpCheckSuspendAndContinue # suspend check if backwards branch - GET_INST_OPCODE v0 # extract opcode from rINST - GOTO_OPCODE v0 # jump to next instruction + b MterpCommonTakenBranchNoFlags diff --git a/runtime/interpreter/mterp/mips64/op_goto_32.S b/runtime/interpreter/mterp/mips64/op_goto_32.S index b260083ae8..498b6d60ae 100644 --- a/runtime/interpreter/mterp/mips64/op_goto_32.S +++ b/runtime/interpreter/mterp/mips64/op_goto_32.S @@ -8,22 +8,7 @@ * our "backward branch" test must be "<=0" instead of "<0". */ /* goto/32 +AAAAAAAA */ - .extern MterpProfileBranch lh rINST, 2(rPC) # rINST <- aaaa (low) lh a1, 4(rPC) # a1 <- AAAA (high) ins rINST, a1, 16, 16 # rINST <- offset (sign-extended AAAAaaaa) -#if MTERP_PROFILE_BRANCHES - EXPORT_PC - move a0, rSELF - daddu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - jal MterpProfileBranch # (self, shadow_frame, offset) - bnezc v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - dlsa rPC, rINST, rPC, 1 # rPC <- rPC + offset * 2 - lw ra, THREAD_FLAGS_OFFSET(rSELF) # Preload flags for MterpCheckSuspendAndContinue - move a0, rINST # a0 <- offset - FETCH_INST # load rINST - blez a0, MterpCheckSuspendAndContinue # suspend check if backwards branch - GET_INST_OPCODE v0 # extract opcode from rINST - GOTO_OPCODE v0 # jump to next instruction + b MterpCommonTakenBranchNoFlags diff --git a/runtime/interpreter/mterp/mips64/op_packed_switch.S b/runtime/interpreter/mterp/mips64/op_packed_switch.S index 2c6eb2f3ca..27ce580642 100644 --- a/runtime/interpreter/mterp/mips64/op_packed_switch.S +++ b/runtime/interpreter/mterp/mips64/op_packed_switch.S @@ -19,18 +19,4 @@ dlsa a0, a0, rPC, 1 # a0 <- PC + BBBBbbbb*2 jal $func # v0 <- code-unit branch offset move rINST, v0 -#if MTERP_PROFILE_BRANCHES - EXPORT_PC - move a0, rSELF - daddu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - jal MterpProfileBranch # (self, shadow_frame, offset) - bnezc v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - dlsa rPC, rINST, rPC, 1 # rPC <- rPC + offset * 2 - lw ra, THREAD_FLAGS_OFFSET(rSELF) # Preload flags for MterpCheckSuspendAndContinue - move a0, rINST # a0 <- offset - FETCH_INST # load rINST - blez a0, MterpCheckSuspendAndContinue # suspend check if backwards branch - GET_INST_OPCODE v0 # extract opcode from rINST - GOTO_OPCODE v0 # jump to next instruction + b MterpCommonTakenBranchNoFlags diff --git a/runtime/interpreter/mterp/mips64/zcmp.S b/runtime/interpreter/mterp/mips64/zcmp.S index 0e0477fadf..75db49edd4 100644 --- a/runtime/interpreter/mterp/mips64/zcmp.S +++ b/runtime/interpreter/mterp/mips64/zcmp.S @@ -6,25 +6,12 @@ * For: if-eqz, if-nez, if-ltz, if-gez, if-gtz, if-lez */ /* if-cmp vAA, +BBBB */ - .extern MterpProfileBranch srl a2, rINST, 8 # a2 <- AA lh rINST, 2(rPC) # rINST <- offset (sign-extended BBBB) GET_VREG a0, a2 # a0 <- vAA - b${condition}zc a0, 1f - li rINST, 2 # offset if branch not taken -1: -#if MTERP_PROFILE_BRANCHES - EXPORT_PC - move a0, rSELF - daddu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - jal MterpProfileBranch # (self, shadow_frame, offset) - bnezc v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - dlsa rPC, rINST, rPC, 1 # rPC <- rPC + offset * 2 - lw ra, THREAD_FLAGS_OFFSET(rSELF) # Preload flags for MterpCheckSuspendAndContinue - move a0, rINST # a0 <- offset - FETCH_INST # load rINST - bltz a0, MterpCheckSuspendAndContinue # suspend check if backwards branch + b${condition}zc a0, MterpCommonTakenBranchNoFlags + li v0, JIT_CHECK_OSR # possible OSR re-entry? + beqc rPROFILE, v0, .L_check_not_taken_osr + FETCH_ADVANCE_INST 2 # advance rPC, load rINST GET_INST_OPCODE v0 # extract opcode from rINST GOTO_OPCODE v0 # jump to next instruction diff --git a/runtime/interpreter/mterp/mterp.cc b/runtime/interpreter/mterp/mterp.cc index 32c45fca71..bd1af04608 100644 --- a/runtime/interpreter/mterp/mterp.cc +++ b/runtime/interpreter/mterp/mterp.cc @@ -20,8 +20,6 @@ #include "interpreter/interpreter_common.h" #include "entrypoints/entrypoint_utils-inl.h" #include "mterp.h" -#include "jit/jit.h" -#include "jit/jit_instrumentation.h" #include "debugger.h" namespace art { @@ -652,10 +650,9 @@ extern "C" int MterpSetUpHotnessCountdown(ArtMethod* method, ShadowFrame* shadow int32_t countdown_value = jit::kJitHotnessDisabled; jit::Jit* jit = Runtime::Current()->GetJit(); if (jit != nullptr) { - jit::JitInstrumentationCache* cache = jit->GetInstrumentationCache(); - int32_t warm_threshold = cache->WarmMethodThreshold(); - int32_t hot_threshold = cache->HotMethodThreshold(); - int32_t osr_threshold = cache->OSRMethodThreshold(); + int32_t warm_threshold = jit->WarmMethodThreshold(); + int32_t hot_threshold = jit->HotMethodThreshold(); + int32_t osr_threshold = jit->OSRMethodThreshold(); if (hotness_count < warm_threshold) { countdown_value = warm_threshold - hotness_count; } else if (hotness_count < hot_threshold) { @@ -666,7 +663,7 @@ extern "C" int MterpSetUpHotnessCountdown(ArtMethod* method, ShadowFrame* shadow countdown_value = jit::kJitCheckForOSR; } if (jit::Jit::ShouldUsePriorityThreadWeight()) { - int32_t priority_thread_weight = cache->PriorityThreadWeight(); + int32_t priority_thread_weight = jit->PriorityThreadWeight(); countdown_value = std::min(countdown_value, countdown_value / priority_thread_weight); } } @@ -692,12 +689,12 @@ extern "C" int16_t MterpAddHotnessBatch(ArtMethod* method, jit::Jit* jit = Runtime::Current()->GetJit(); if (jit != nullptr) { int16_t count = shadow_frame->GetCachedHotnessCountdown() - shadow_frame->GetHotnessCountdown(); - jit->GetInstrumentationCache()->AddSamples(self, method, count); + jit->AddSamples(self, method, count, /*with_backedges*/ true); } return MterpSetUpHotnessCountdown(method, shadow_frame); } -// TUNING: Unused by arm/arm64/x86. Remove when x86_64/mips/mips64 mterps support batch updates. +// TUNING: Unused by arm/arm64/x86/x86_64. Remove when mips/mips64 mterps support batch updates. extern "C" bool MterpProfileBranch(Thread* self, ShadowFrame* shadow_frame, int32_t offset) SHARED_REQUIRES(Locks::mutator_lock_) { ArtMethod* method = shadow_frame->GetMethod(); @@ -705,7 +702,7 @@ extern "C" bool MterpProfileBranch(Thread* self, ShadowFrame* shadow_frame, int uint32_t dex_pc = shadow_frame->GetDexPC(); jit::Jit* jit = Runtime::Current()->GetJit(); if ((jit != nullptr) && (offset <= 0)) { - jit->GetInstrumentationCache()->AddSamples(self, method, 1); + jit->AddSamples(self, method, 1, /*with_backedges*/ true); } int16_t countdown_value = MterpSetUpHotnessCountdown(method, shadow_frame); if (countdown_value == jit::kJitCheckForOSR) { @@ -725,7 +722,7 @@ extern "C" bool MterpMaybeDoOnStackReplacement(Thread* self, jit::Jit* jit = Runtime::Current()->GetJit(); if (offset <= 0) { // Keep updating hotness in case a compilation request was dropped. Eventually it will retry. - jit->GetInstrumentationCache()->AddSamples(self, method, 1); + jit->AddSamples(self, method, 1, /*with_backedges*/ true); } // Assumes caller has already determined that an OSR check is appropriate. return jit::Jit::MaybeDoOnStackReplacement(self, method, dex_pc, offset, result); diff --git a/runtime/interpreter/mterp/out/mterp_mips.S b/runtime/interpreter/mterp/out/mterp_mips.S index b134129a5a..daa6f2a0eb 100644 --- a/runtime/interpreter/mterp/out/mterp_mips.S +++ b/runtime/interpreter/mterp/out/mterp_mips.S @@ -58,7 +58,11 @@ s2 rSELF self (Thread) pointer s3 rIBASE interpreted instruction base pointer, used for computed goto s4 rINST first 16-bit code unit of current instruction + s5 rOBJ object pointer s6 rREFS base of object references in shadow frame (ideally, we'll get rid of this later). + s7 rTEMP used as temp storage that can survive a function call + s8 rPROFILE branch profiling countdown + */ /* single-purpose registers, given names for clarity */ @@ -70,6 +74,7 @@ #define rOBJ s5 #define rREFS s6 #define rTEMP s7 +#define rPROFILE s8 #define rARG0 a0 #define rARG1 a1 @@ -167,7 +172,7 @@ #define OFF_FP_RESULT_REGISTER OFF_FP(SHADOWFRAME_RESULT_REGISTER_OFFSET) #define OFF_FP_DEX_PC_PTR OFF_FP(SHADOWFRAME_DEX_PC_PTR_OFFSET) #define OFF_FP_CODE_ITEM OFF_FP(SHADOWFRAME_CODE_ITEM_OFFSET) -#define OFF_FP_SHADOWFRAME (-SHADOWFRAME_VREGS_OFFSET) +#define OFF_FP_SHADOWFRAME OFF_FP(0) #define MTERP_PROFILE_BRANCHES 1 #define MTERP_LOGGING 0 @@ -490,6 +495,9 @@ STACK_LOAD(ra, 124); \ DELETE_STACK(STACK_SIZE) +#define REFRESH_IBASE() \ + lw rIBASE, THREAD_CURRENT_IBASE_OFFSET(rSELF) + /* File: mips/entry.S */ /* * Copyright (C) 2016 The Android Open Source Project @@ -553,6 +561,12 @@ ExecuteMterpImpl: /* Starting ibase */ lw rIBASE, THREAD_CURRENT_IBASE_OFFSET(rSELF) + /* Set up for backwards branches & osr profiling */ + lw a0, OFF_FP_METHOD(rFP) + addu a1, rFP, OFF_FP_SHADOWFRAME + JAL(MterpSetUpHotnessCountdown) # (method, shadow_frame) + move rPROFILE, v0 # Starting hotness countdown to rPROFILE + /* start executing the instruction at rPC */ FETCH_INST() # load rINST from rPC GET_INST_OPCODE(t0) # extract opcode from rINST @@ -1284,37 +1298,9 @@ artMterpAsmInstructionStart = .L_op_nop * double to get a byte offset. */ /* goto +AA */ -#if MTERP_PROFILE_BRANCHES - sll a0, rINST, 16 # a0 <- AAxx0000 - sra rINST, a0, 24 # rINST <- ssssssAA (sign-extended) - EXPORT_PC() - move a0, rSELF - addu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - JAL(MterpProfileBranch) # (self, shadow_frame, offset) - bnez v0, MterpOnStackReplacement # Note: offset must be in rINST - addu a2, rINST, rINST # a2 <- byte offset - FETCH_ADVANCE_INST_RB(a2) # update rPC, load rINST - /* If backwards branch refresh rIBASE */ - bgez a2, 1f - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue -1: - GET_INST_OPCODE(t0) # extract opcode from rINST - GOTO_OPCODE(t0) # jump to next instruction -#else sll a0, rINST, 16 # a0 <- AAxx0000 sra rINST, a0, 24 # rINST <- ssssssAA (sign-extended) - addu a2, rINST, rINST # a2 <- byte offset - FETCH_ADVANCE_INST_RB(a2) # update rPC, load rINST - /* If backwards branch refresh rIBASE */ - bgez a1, 1f - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue -1: - GET_INST_OPCODE(t0) # extract opcode from rINST - GOTO_OPCODE(t0) # jump to next instruction -#endif + b MterpCommonTakenBranchNoFlags /* ------------------------------ */ .balign 128 @@ -1327,33 +1313,8 @@ artMterpAsmInstructionStart = .L_op_nop * double to get a byte offset. */ /* goto/16 +AAAA */ -#if MTERP_PROFILE_BRANCHES FETCH_S(rINST, 1) # rINST <- ssssAAAA (sign-extended) - EXPORT_PC() - move a0, rSELF - addu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - JAL(MterpProfileBranch) # (self, shadow_frame, offset) - bnez v0, MterpOnStackReplacement # Note: offset must be in rINST - addu a1, rINST, rINST # a1 <- byte offset, flags set - FETCH_ADVANCE_INST_RB(a1) # update rPC, load rINST - bgez a1, 1f - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue -1: - GET_INST_OPCODE(t0) # extract opcode from rINST - GOTO_OPCODE(t0) # jump to next instruction -#else - FETCH_S(rINST, 1) # rINST <- ssssAAAA (sign-extended) - addu a1, rINST, rINST # a1 <- byte offset, flags set - FETCH_ADVANCE_INST_RB(a1) # update rPC, load rINST - bgez a1, 1f - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue -1: - GET_INST_OPCODE(t0) # extract opcode from rINST - GOTO_OPCODE(t0) # jump to next instruction -#endif + b MterpCommonTakenBranchNoFlags /* ------------------------------ */ .balign 128 @@ -1369,39 +1330,11 @@ artMterpAsmInstructionStart = .L_op_nop * our "backward branch" test must be "<=0" instead of "<0". */ /* goto/32 +AAAAAAAA */ -#if MTERP_PROFILE_BRANCHES FETCH(a0, 1) # a0 <- aaaa (lo) FETCH(a1, 2) # a1 <- AAAA (hi) sll a1, a1, 16 or rINST, a0, a1 # rINST <- AAAAaaaa - EXPORT_PC() - move a0, rSELF - addu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - JAL(MterpProfileBranch) # (self, shadow_frame, offset) - bnez v0, MterpOnStackReplacement # Note: offset must be in rINST - addu a1, rINST, rINST # a1 <- byte offset - FETCH_ADVANCE_INST_RB(a1) # update rPC, load rINST - bgtz a1, 1f - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue -1: - GET_INST_OPCODE(t0) # extract opcode from rINST - GOTO_OPCODE(t0) # jump to next instruction -#else - FETCH(a0, 1) # a0 <- aaaa (lo) - FETCH(a1, 2) # a1 <- AAAA (hi) - sll a1, a1, 16 - or rINST, a0, a1 # rINST <- AAAAaaaa - addu a1, rINST, rINST # a1 <- byte offset - FETCH_ADVANCE_INST_RB(a1) # update rPC, load rINST - bgtz a1, 1f - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue -1: - GET_INST_OPCODE(t0) # extract opcode from rINST - GOTO_OPCODE(t0) # jump to next instruction -#endif + b MterpCommonTakenBranchNoFlags /* ------------------------------ */ .balign 128 @@ -1417,7 +1350,6 @@ artMterpAsmInstructionStart = .L_op_nop * for: packed-switch, sparse-switch */ /* op vAA, +BBBB */ -#if MTERP_PROFILE_BRANCHES FETCH(a0, 1) # a0 <- bbbb (lo) FETCH(a1, 2) # a1 <- BBBB (hi) GET_OPA(a3) # a3 <- AA @@ -1427,37 +1359,7 @@ artMterpAsmInstructionStart = .L_op_nop EAS1(a0, rPC, a0) # a0 <- PC + BBBBbbbb*2 JAL(MterpDoPackedSwitch) # a0 <- code-unit branch offset move rINST, v0 - EXPORT_PC() - move a0, rSELF - addu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - JAL(MterpProfileBranch) # (self, shadow_frame, offset) - bnez v0, MterpOnStackReplacement # Note: offset must be in rINST - addu a1, rINST, rINST # a1 <- byte offset - FETCH_ADVANCE_INST_RB(a1) # update rPC, load rINST - bgtz a1, .Lop_packed_switch_finish - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue -#else - FETCH(a0, 1) # a0 <- bbbb (lo) - FETCH(a1, 2) # a1 <- BBBB (hi) - GET_OPA(a3) # a3 <- AA - sll t0, a1, 16 - or a0, a0, t0 # a0 <- BBBBbbbb - GET_VREG(a1, a3) # a1 <- vAA - EAS1(a0, rPC, a0) # a0 <- PC + BBBBbbbb*2 - JAL(MterpDoPackedSwitch) # a0 <- code-unit branch offset - move rINST, v0 - addu a1, rINST, rINST # a1 <- byte offset - FETCH_ADVANCE_INST_RB(a1) # update rPC, load rINST - bgtz a1, 1f - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue -1: - GET_INST_OPCODE(t0) # extract opcode from rINST - GOTO_OPCODE(t0) # jump to next instruction -#endif - + b MterpCommonTakenBranchNoFlags /* ------------------------------ */ .balign 128 @@ -1474,7 +1376,6 @@ artMterpAsmInstructionStart = .L_op_nop * for: packed-switch, sparse-switch */ /* op vAA, +BBBB */ -#if MTERP_PROFILE_BRANCHES FETCH(a0, 1) # a0 <- bbbb (lo) FETCH(a1, 2) # a1 <- BBBB (hi) GET_OPA(a3) # a3 <- AA @@ -1484,37 +1385,7 @@ artMterpAsmInstructionStart = .L_op_nop EAS1(a0, rPC, a0) # a0 <- PC + BBBBbbbb*2 JAL(MterpDoSparseSwitch) # a0 <- code-unit branch offset move rINST, v0 - EXPORT_PC() - move a0, rSELF - addu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - JAL(MterpProfileBranch) # (self, shadow_frame, offset) - bnez v0, MterpOnStackReplacement # Note: offset must be in rINST - addu a1, rINST, rINST # a1 <- byte offset - FETCH_ADVANCE_INST_RB(a1) # update rPC, load rINST - bgtz a1, .Lop_sparse_switch_finish - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue -#else - FETCH(a0, 1) # a0 <- bbbb (lo) - FETCH(a1, 2) # a1 <- BBBB (hi) - GET_OPA(a3) # a3 <- AA - sll t0, a1, 16 - or a0, a0, t0 # a0 <- BBBBbbbb - GET_VREG(a1, a3) # a1 <- vAA - EAS1(a0, rPC, a0) # a0 <- PC + BBBBbbbb*2 - JAL(MterpDoSparseSwitch) # a0 <- code-unit branch offset - move rINST, v0 - addu a1, rINST, rINST # a1 <- byte offset - FETCH_ADVANCE_INST_RB(a1) # update rPC, load rINST - bgtz a1, 1f - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue -1: - GET_INST_OPCODE(t0) # extract opcode from rINST - GOTO_OPCODE(t0) # jump to next instruction -#endif - + b MterpCommonTakenBranchNoFlags /* ------------------------------ */ @@ -1772,9 +1643,8 @@ artMterpAsmInstructionStart = .L_op_nop /* File: mips/op_if_eq.S */ /* File: mips/bincmp.S */ /* - * Generic two-operand compare-and-branch operation. Provide a "revcmp" - * fragment that specifies the *reverse* comparison to perform, e.g. - * for "if-le" you would use "gt". + * Generic two-operand compare-and-branch operation. Provide a "condition" + * fragment that specifies the comparison to perform. * * For: if-eq, if-ne, if-lt, if-ge, if-gt, if-le */ @@ -1782,27 +1652,14 @@ artMterpAsmInstructionStart = .L_op_nop GET_OPA4(a0) # a0 <- A+ GET_OPB(a1) # a1 <- B GET_VREG(a3, a1) # a3 <- vB - GET_VREG(a2, a0) # a2 <- vA - bne a2, a3, 1f # branch to 1 if comparison failed + GET_VREG(a0, a0) # a0 <- vA FETCH_S(rINST, 1) # rINST<- branch offset, in code units - b 2f -1: - li rINST, 2 # rINST- BYTE branch dist for not-taken -2: -#if MTERP_PROFILE_BRANCHES - EXPORT_PC() - move a0, rSELF - addu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - JAL(MterpProfileBranch) # (self, shadow_frame, offset) - bnez v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - addu a2, rINST, rINST # convert to bytes - FETCH_ADVANCE_INST_RB(a2) # update rPC, load rINST - bgez a2, .L_op_if_eq_finish - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue - + beq a0, a3, MterpCommonTakenBranchNoFlags # compare (vA, vB) + li t0, JIT_CHECK_OSR + beq rPROFILE, t0, .L_check_not_taken_osr + FETCH_ADVANCE_INST(2) # advance rPC, load rINST + GET_INST_OPCODE(t0) # extract opcode from rINST + GOTO_OPCODE(t0) # jump to next instruction /* ------------------------------ */ @@ -1811,9 +1668,8 @@ artMterpAsmInstructionStart = .L_op_nop /* File: mips/op_if_ne.S */ /* File: mips/bincmp.S */ /* - * Generic two-operand compare-and-branch operation. Provide a "revcmp" - * fragment that specifies the *reverse* comparison to perform, e.g. - * for "if-le" you would use "gt". + * Generic two-operand compare-and-branch operation. Provide a "condition" + * fragment that specifies the comparison to perform. * * For: if-eq, if-ne, if-lt, if-ge, if-gt, if-le */ @@ -1821,27 +1677,14 @@ artMterpAsmInstructionStart = .L_op_nop GET_OPA4(a0) # a0 <- A+ GET_OPB(a1) # a1 <- B GET_VREG(a3, a1) # a3 <- vB - GET_VREG(a2, a0) # a2 <- vA - beq a2, a3, 1f # branch to 1 if comparison failed + GET_VREG(a0, a0) # a0 <- vA FETCH_S(rINST, 1) # rINST<- branch offset, in code units - b 2f -1: - li rINST, 2 # rINST- BYTE branch dist for not-taken -2: -#if MTERP_PROFILE_BRANCHES - EXPORT_PC() - move a0, rSELF - addu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - JAL(MterpProfileBranch) # (self, shadow_frame, offset) - bnez v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - addu a2, rINST, rINST # convert to bytes - FETCH_ADVANCE_INST_RB(a2) # update rPC, load rINST - bgez a2, .L_op_if_ne_finish - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue - + bne a0, a3, MterpCommonTakenBranchNoFlags # compare (vA, vB) + li t0, JIT_CHECK_OSR + beq rPROFILE, t0, .L_check_not_taken_osr + FETCH_ADVANCE_INST(2) # advance rPC, load rINST + GET_INST_OPCODE(t0) # extract opcode from rINST + GOTO_OPCODE(t0) # jump to next instruction /* ------------------------------ */ @@ -1850,9 +1693,8 @@ artMterpAsmInstructionStart = .L_op_nop /* File: mips/op_if_lt.S */ /* File: mips/bincmp.S */ /* - * Generic two-operand compare-and-branch operation. Provide a "revcmp" - * fragment that specifies the *reverse* comparison to perform, e.g. - * for "if-le" you would use "gt". + * Generic two-operand compare-and-branch operation. Provide a "condition" + * fragment that specifies the comparison to perform. * * For: if-eq, if-ne, if-lt, if-ge, if-gt, if-le */ @@ -1860,27 +1702,14 @@ artMterpAsmInstructionStart = .L_op_nop GET_OPA4(a0) # a0 <- A+ GET_OPB(a1) # a1 <- B GET_VREG(a3, a1) # a3 <- vB - GET_VREG(a2, a0) # a2 <- vA - bge a2, a3, 1f # branch to 1 if comparison failed + GET_VREG(a0, a0) # a0 <- vA FETCH_S(rINST, 1) # rINST<- branch offset, in code units - b 2f -1: - li rINST, 2 # rINST- BYTE branch dist for not-taken -2: -#if MTERP_PROFILE_BRANCHES - EXPORT_PC() - move a0, rSELF - addu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - JAL(MterpProfileBranch) # (self, shadow_frame, offset) - bnez v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - addu a2, rINST, rINST # convert to bytes - FETCH_ADVANCE_INST_RB(a2) # update rPC, load rINST - bgez a2, .L_op_if_lt_finish - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue - + blt a0, a3, MterpCommonTakenBranchNoFlags # compare (vA, vB) + li t0, JIT_CHECK_OSR + beq rPROFILE, t0, .L_check_not_taken_osr + FETCH_ADVANCE_INST(2) # advance rPC, load rINST + GET_INST_OPCODE(t0) # extract opcode from rINST + GOTO_OPCODE(t0) # jump to next instruction /* ------------------------------ */ @@ -1889,9 +1718,8 @@ artMterpAsmInstructionStart = .L_op_nop /* File: mips/op_if_ge.S */ /* File: mips/bincmp.S */ /* - * Generic two-operand compare-and-branch operation. Provide a "revcmp" - * fragment that specifies the *reverse* comparison to perform, e.g. - * for "if-le" you would use "gt". + * Generic two-operand compare-and-branch operation. Provide a "condition" + * fragment that specifies the comparison to perform. * * For: if-eq, if-ne, if-lt, if-ge, if-gt, if-le */ @@ -1899,27 +1727,14 @@ artMterpAsmInstructionStart = .L_op_nop GET_OPA4(a0) # a0 <- A+ GET_OPB(a1) # a1 <- B GET_VREG(a3, a1) # a3 <- vB - GET_VREG(a2, a0) # a2 <- vA - blt a2, a3, 1f # branch to 1 if comparison failed + GET_VREG(a0, a0) # a0 <- vA FETCH_S(rINST, 1) # rINST<- branch offset, in code units - b 2f -1: - li rINST, 2 # rINST- BYTE branch dist for not-taken -2: -#if MTERP_PROFILE_BRANCHES - EXPORT_PC() - move a0, rSELF - addu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - JAL(MterpProfileBranch) # (self, shadow_frame, offset) - bnez v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - addu a2, rINST, rINST # convert to bytes - FETCH_ADVANCE_INST_RB(a2) # update rPC, load rINST - bgez a2, .L_op_if_ge_finish - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue - + bge a0, a3, MterpCommonTakenBranchNoFlags # compare (vA, vB) + li t0, JIT_CHECK_OSR + beq rPROFILE, t0, .L_check_not_taken_osr + FETCH_ADVANCE_INST(2) # advance rPC, load rINST + GET_INST_OPCODE(t0) # extract opcode from rINST + GOTO_OPCODE(t0) # jump to next instruction /* ------------------------------ */ @@ -1928,9 +1743,8 @@ artMterpAsmInstructionStart = .L_op_nop /* File: mips/op_if_gt.S */ /* File: mips/bincmp.S */ /* - * Generic two-operand compare-and-branch operation. Provide a "revcmp" - * fragment that specifies the *reverse* comparison to perform, e.g. - * for "if-le" you would use "gt". + * Generic two-operand compare-and-branch operation. Provide a "condition" + * fragment that specifies the comparison to perform. * * For: if-eq, if-ne, if-lt, if-ge, if-gt, if-le */ @@ -1938,27 +1752,14 @@ artMterpAsmInstructionStart = .L_op_nop GET_OPA4(a0) # a0 <- A+ GET_OPB(a1) # a1 <- B GET_VREG(a3, a1) # a3 <- vB - GET_VREG(a2, a0) # a2 <- vA - ble a2, a3, 1f # branch to 1 if comparison failed + GET_VREG(a0, a0) # a0 <- vA FETCH_S(rINST, 1) # rINST<- branch offset, in code units - b 2f -1: - li rINST, 2 # rINST- BYTE branch dist for not-taken -2: -#if MTERP_PROFILE_BRANCHES - EXPORT_PC() - move a0, rSELF - addu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - JAL(MterpProfileBranch) # (self, shadow_frame, offset) - bnez v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - addu a2, rINST, rINST # convert to bytes - FETCH_ADVANCE_INST_RB(a2) # update rPC, load rINST - bgez a2, .L_op_if_gt_finish - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue - + bgt a0, a3, MterpCommonTakenBranchNoFlags # compare (vA, vB) + li t0, JIT_CHECK_OSR + beq rPROFILE, t0, .L_check_not_taken_osr + FETCH_ADVANCE_INST(2) # advance rPC, load rINST + GET_INST_OPCODE(t0) # extract opcode from rINST + GOTO_OPCODE(t0) # jump to next instruction /* ------------------------------ */ @@ -1967,9 +1768,8 @@ artMterpAsmInstructionStart = .L_op_nop /* File: mips/op_if_le.S */ /* File: mips/bincmp.S */ /* - * Generic two-operand compare-and-branch operation. Provide a "revcmp" - * fragment that specifies the *reverse* comparison to perform, e.g. - * for "if-le" you would use "gt". + * Generic two-operand compare-and-branch operation. Provide a "condition" + * fragment that specifies the comparison to perform. * * For: if-eq, if-ne, if-lt, if-ge, if-gt, if-le */ @@ -1977,27 +1777,14 @@ artMterpAsmInstructionStart = .L_op_nop GET_OPA4(a0) # a0 <- A+ GET_OPB(a1) # a1 <- B GET_VREG(a3, a1) # a3 <- vB - GET_VREG(a2, a0) # a2 <- vA - bgt a2, a3, 1f # branch to 1 if comparison failed + GET_VREG(a0, a0) # a0 <- vA FETCH_S(rINST, 1) # rINST<- branch offset, in code units - b 2f -1: - li rINST, 2 # rINST- BYTE branch dist for not-taken -2: -#if MTERP_PROFILE_BRANCHES - EXPORT_PC() - move a0, rSELF - addu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - JAL(MterpProfileBranch) # (self, shadow_frame, offset) - bnez v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - addu a2, rINST, rINST # convert to bytes - FETCH_ADVANCE_INST_RB(a2) # update rPC, load rINST - bgez a2, .L_op_if_le_finish - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue - + ble a0, a3, MterpCommonTakenBranchNoFlags # compare (vA, vB) + li t0, JIT_CHECK_OSR + beq rPROFILE, t0, .L_check_not_taken_osr + FETCH_ADVANCE_INST(2) # advance rPC, load rINST + GET_INST_OPCODE(t0) # extract opcode from rINST + GOTO_OPCODE(t0) # jump to next instruction /* ------------------------------ */ @@ -2006,35 +1793,19 @@ artMterpAsmInstructionStart = .L_op_nop /* File: mips/op_if_eqz.S */ /* File: mips/zcmp.S */ /* - * Generic one-operand compare-and-branch operation. Provide a "revcmp" - * fragment that specifies the *reverse* comparison to perform, e.g. - * for "if-le" you would use "gt". + * Generic one-operand compare-and-branch operation. Provide a "condition" + * fragment that specifies the comparison to perform. * * for: if-eqz, if-nez, if-ltz, if-gez, if-gtz, if-lez */ /* if-cmp vAA, +BBBB */ GET_OPA(a0) # a0 <- AA - GET_VREG(a2, a0) # a2 <- vAA + GET_VREG(a0, a0) # a0 <- vAA FETCH_S(rINST, 1) # rINST <- branch offset, in code units - bne a2, zero, 1f # branch to 1 if comparison failed - b 2f -1: - li rINST, 2 # rINST- BYTE branch dist for not-taken -2: -#if MTERP_PROFILE_BRANCHES - EXPORT_PC() - move a0, rSELF - addu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - JAL(MterpProfileBranch) # (self, shadow_frame, offset) - bnez v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - addu a1, rINST, rINST # convert to bytes - FETCH_ADVANCE_INST_RB(a1) # update rPC, load rINST - bgez a1, 3f - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue -3: + beq a0, zero, MterpCommonTakenBranchNoFlags + li t0, JIT_CHECK_OSR # possible OSR re-entry? + beq rPROFILE, t0, .L_check_not_taken_osr + FETCH_ADVANCE_INST(2) # advance rPC, load rINST GET_INST_OPCODE(t0) # extract opcode from rINST GOTO_OPCODE(t0) # jump to next instruction @@ -2045,35 +1816,19 @@ artMterpAsmInstructionStart = .L_op_nop /* File: mips/op_if_nez.S */ /* File: mips/zcmp.S */ /* - * Generic one-operand compare-and-branch operation. Provide a "revcmp" - * fragment that specifies the *reverse* comparison to perform, e.g. - * for "if-le" you would use "gt". + * Generic one-operand compare-and-branch operation. Provide a "condition" + * fragment that specifies the comparison to perform. * * for: if-eqz, if-nez, if-ltz, if-gez, if-gtz, if-lez */ /* if-cmp vAA, +BBBB */ GET_OPA(a0) # a0 <- AA - GET_VREG(a2, a0) # a2 <- vAA + GET_VREG(a0, a0) # a0 <- vAA FETCH_S(rINST, 1) # rINST <- branch offset, in code units - beq a2, zero, 1f # branch to 1 if comparison failed - b 2f -1: - li rINST, 2 # rINST- BYTE branch dist for not-taken -2: -#if MTERP_PROFILE_BRANCHES - EXPORT_PC() - move a0, rSELF - addu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - JAL(MterpProfileBranch) # (self, shadow_frame, offset) - bnez v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - addu a1, rINST, rINST # convert to bytes - FETCH_ADVANCE_INST_RB(a1) # update rPC, load rINST - bgez a1, 3f - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue -3: + bne a0, zero, MterpCommonTakenBranchNoFlags + li t0, JIT_CHECK_OSR # possible OSR re-entry? + beq rPROFILE, t0, .L_check_not_taken_osr + FETCH_ADVANCE_INST(2) # advance rPC, load rINST GET_INST_OPCODE(t0) # extract opcode from rINST GOTO_OPCODE(t0) # jump to next instruction @@ -2084,35 +1839,19 @@ artMterpAsmInstructionStart = .L_op_nop /* File: mips/op_if_ltz.S */ /* File: mips/zcmp.S */ /* - * Generic one-operand compare-and-branch operation. Provide a "revcmp" - * fragment that specifies the *reverse* comparison to perform, e.g. - * for "if-le" you would use "gt". + * Generic one-operand compare-and-branch operation. Provide a "condition" + * fragment that specifies the comparison to perform. * * for: if-eqz, if-nez, if-ltz, if-gez, if-gtz, if-lez */ /* if-cmp vAA, +BBBB */ GET_OPA(a0) # a0 <- AA - GET_VREG(a2, a0) # a2 <- vAA + GET_VREG(a0, a0) # a0 <- vAA FETCH_S(rINST, 1) # rINST <- branch offset, in code units - bge a2, zero, 1f # branch to 1 if comparison failed - b 2f -1: - li rINST, 2 # rINST- BYTE branch dist for not-taken -2: -#if MTERP_PROFILE_BRANCHES - EXPORT_PC() - move a0, rSELF - addu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - JAL(MterpProfileBranch) # (self, shadow_frame, offset) - bnez v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - addu a1, rINST, rINST # convert to bytes - FETCH_ADVANCE_INST_RB(a1) # update rPC, load rINST - bgez a1, 3f - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue -3: + blt a0, zero, MterpCommonTakenBranchNoFlags + li t0, JIT_CHECK_OSR # possible OSR re-entry? + beq rPROFILE, t0, .L_check_not_taken_osr + FETCH_ADVANCE_INST(2) # advance rPC, load rINST GET_INST_OPCODE(t0) # extract opcode from rINST GOTO_OPCODE(t0) # jump to next instruction @@ -2123,35 +1862,19 @@ artMterpAsmInstructionStart = .L_op_nop /* File: mips/op_if_gez.S */ /* File: mips/zcmp.S */ /* - * Generic one-operand compare-and-branch operation. Provide a "revcmp" - * fragment that specifies the *reverse* comparison to perform, e.g. - * for "if-le" you would use "gt". + * Generic one-operand compare-and-branch operation. Provide a "condition" + * fragment that specifies the comparison to perform. * * for: if-eqz, if-nez, if-ltz, if-gez, if-gtz, if-lez */ /* if-cmp vAA, +BBBB */ GET_OPA(a0) # a0 <- AA - GET_VREG(a2, a0) # a2 <- vAA + GET_VREG(a0, a0) # a0 <- vAA FETCH_S(rINST, 1) # rINST <- branch offset, in code units - blt a2, zero, 1f # branch to 1 if comparison failed - b 2f -1: - li rINST, 2 # rINST- BYTE branch dist for not-taken -2: -#if MTERP_PROFILE_BRANCHES - EXPORT_PC() - move a0, rSELF - addu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - JAL(MterpProfileBranch) # (self, shadow_frame, offset) - bnez v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - addu a1, rINST, rINST # convert to bytes - FETCH_ADVANCE_INST_RB(a1) # update rPC, load rINST - bgez a1, 3f - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue -3: + bge a0, zero, MterpCommonTakenBranchNoFlags + li t0, JIT_CHECK_OSR # possible OSR re-entry? + beq rPROFILE, t0, .L_check_not_taken_osr + FETCH_ADVANCE_INST(2) # advance rPC, load rINST GET_INST_OPCODE(t0) # extract opcode from rINST GOTO_OPCODE(t0) # jump to next instruction @@ -2162,35 +1885,19 @@ artMterpAsmInstructionStart = .L_op_nop /* File: mips/op_if_gtz.S */ /* File: mips/zcmp.S */ /* - * Generic one-operand compare-and-branch operation. Provide a "revcmp" - * fragment that specifies the *reverse* comparison to perform, e.g. - * for "if-le" you would use "gt". + * Generic one-operand compare-and-branch operation. Provide a "condition" + * fragment that specifies the comparison to perform. * * for: if-eqz, if-nez, if-ltz, if-gez, if-gtz, if-lez */ /* if-cmp vAA, +BBBB */ GET_OPA(a0) # a0 <- AA - GET_VREG(a2, a0) # a2 <- vAA + GET_VREG(a0, a0) # a0 <- vAA FETCH_S(rINST, 1) # rINST <- branch offset, in code units - ble a2, zero, 1f # branch to 1 if comparison failed - b 2f -1: - li rINST, 2 # rINST- BYTE branch dist for not-taken -2: -#if MTERP_PROFILE_BRANCHES - EXPORT_PC() - move a0, rSELF - addu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - JAL(MterpProfileBranch) # (self, shadow_frame, offset) - bnez v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - addu a1, rINST, rINST # convert to bytes - FETCH_ADVANCE_INST_RB(a1) # update rPC, load rINST - bgez a1, 3f - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue -3: + bgt a0, zero, MterpCommonTakenBranchNoFlags + li t0, JIT_CHECK_OSR # possible OSR re-entry? + beq rPROFILE, t0, .L_check_not_taken_osr + FETCH_ADVANCE_INST(2) # advance rPC, load rINST GET_INST_OPCODE(t0) # extract opcode from rINST GOTO_OPCODE(t0) # jump to next instruction @@ -2201,35 +1908,19 @@ artMterpAsmInstructionStart = .L_op_nop /* File: mips/op_if_lez.S */ /* File: mips/zcmp.S */ /* - * Generic one-operand compare-and-branch operation. Provide a "revcmp" - * fragment that specifies the *reverse* comparison to perform, e.g. - * for "if-le" you would use "gt". + * Generic one-operand compare-and-branch operation. Provide a "condition" + * fragment that specifies the comparison to perform. * * for: if-eqz, if-nez, if-ltz, if-gez, if-gtz, if-lez */ /* if-cmp vAA, +BBBB */ GET_OPA(a0) # a0 <- AA - GET_VREG(a2, a0) # a2 <- vAA + GET_VREG(a0, a0) # a0 <- vAA FETCH_S(rINST, 1) # rINST <- branch offset, in code units - bgt a2, zero, 1f # branch to 1 if comparison failed - b 2f -1: - li rINST, 2 # rINST- BYTE branch dist for not-taken -2: -#if MTERP_PROFILE_BRANCHES - EXPORT_PC() - move a0, rSELF - addu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - JAL(MterpProfileBranch) # (self, shadow_frame, offset) - bnez v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - addu a1, rINST, rINST # convert to bytes - FETCH_ADVANCE_INST_RB(a1) # update rPC, load rINST - bgez a1, 3f - lw ra, THREAD_FLAGS_OFFSET(rSELF) - b MterpCheckSuspendAndContinue -3: + ble a0, zero, MterpCommonTakenBranchNoFlags + li t0, JIT_CHECK_OSR # possible OSR re-entry? + beq rPROFILE, t0, .L_check_not_taken_osr + FETCH_ADVANCE_INST(2) # advance rPC, load rINST GET_INST_OPCODE(t0) # extract opcode from rINST GOTO_OPCODE(t0) # jump to next instruction @@ -7983,18 +7674,6 @@ artMterpAsmInstructionEnd: .balign 4 artMterpAsmSisterStart: -/* continuation for op_packed_switch */ - -.Lop_packed_switch_finish: - GET_INST_OPCODE(t0) # extract opcode from rINST - GOTO_OPCODE(t0) # jump to next instruction - -/* continuation for op_sparse_switch */ - -.Lop_sparse_switch_finish: - GET_INST_OPCODE(t0) # extract opcode from rINST - GOTO_OPCODE(t0) # jump to next instruction - /* continuation for op_cmpl_float */ .Lop_cmpl_float_nan: @@ -8039,42 +7718,6 @@ artMterpAsmSisterStart: GET_INST_OPCODE(t0) # extract opcode from rINST SET_VREG_GOTO(rTEMP, rOBJ, t0) # vAA <- rTEMP -/* continuation for op_if_eq */ - -.L_op_if_eq_finish: - GET_INST_OPCODE(t0) # extract opcode from rINST - GOTO_OPCODE(t0) # jump to next instruction - -/* continuation for op_if_ne */ - -.L_op_if_ne_finish: - GET_INST_OPCODE(t0) # extract opcode from rINST - GOTO_OPCODE(t0) # jump to next instruction - -/* continuation for op_if_lt */ - -.L_op_if_lt_finish: - GET_INST_OPCODE(t0) # extract opcode from rINST - GOTO_OPCODE(t0) # jump to next instruction - -/* continuation for op_if_ge */ - -.L_op_if_ge_finish: - GET_INST_OPCODE(t0) # extract opcode from rINST - GOTO_OPCODE(t0) # jump to next instruction - -/* continuation for op_if_gt */ - -.L_op_if_gt_finish: - GET_INST_OPCODE(t0) # extract opcode from rINST - GOTO_OPCODE(t0) # jump to next instruction - -/* continuation for op_if_le */ - -.L_op_if_le_finish: - GET_INST_OPCODE(t0) # extract opcode from rINST - GOTO_OPCODE(t0) # jump to next instruction - /* continuation for op_float_to_int */ /* @@ -13089,20 +12732,110 @@ MterpException: /* NOTE: no fallthrough */ /* - * Check for suspend check request. Assumes rINST already loaded, rPC advanced and - * still needs to get the opcode and branch to it, and flags are in lr. + * Common handling for branches with support for Jit profiling. + * On entry: + * rINST <= signed offset + * rPROFILE <= signed hotness countdown (expanded to 32 bits) + * + * We have quite a few different cases for branch profiling, OSR detection and + * suspend check support here. + * + * Taken backward branches: + * If profiling active, do hotness countdown and report if we hit zero. + * If in osr check mode, see if our target is a compiled loop header entry and do OSR if so. + * Is there a pending suspend request? If so, suspend. + * + * Taken forward branches and not-taken backward branches: + * If in osr check mode, see if our target is a compiled loop header entry and do OSR if so. + * + * Our most common case is expected to be a taken backward branch with active jit profiling, + * but no full OSR check and no pending suspend request. + * Next most common case is not-taken branch with no full OSR check. */ -MterpCheckSuspendAndContinue: - lw rIBASE, THREAD_CURRENT_IBASE_OFFSET(rSELF) # refresh rIBASE +MterpCommonTakenBranchNoFlags: + bgtz rINST, .L_forward_branch # don't add forward branches to hotness +/* + * We need to subtract 1 from positive values and we should not see 0 here, + * so we may use the result of the comparison with -1. + */ +#if JIT_CHECK_OSR != -1 +# error "JIT_CHECK_OSR must be -1." +#endif + li t0, JIT_CHECK_OSR + beq rPROFILE, t0, .L_osr_check + blt rPROFILE, t0, .L_resume_backward_branch + subu rPROFILE, 1 + beqz rPROFILE, .L_add_batch # counted down to zero - report +.L_resume_backward_branch: + lw ra, THREAD_FLAGS_OFFSET(rSELF) + REFRESH_IBASE() + addu a2, rINST, rINST # a2<- byte offset + FETCH_ADVANCE_INST_RB(a2) # update rPC, load rINST and ra, (THREAD_SUSPEND_REQUEST | THREAD_CHECKPOINT_REQUEST) - bnez ra, 1f + bnez ra, .L_suspend_request_pending GET_INST_OPCODE(t0) # extract opcode from rINST GOTO_OPCODE(t0) # jump to next instruction -1: + +.L_suspend_request_pending: EXPORT_PC() move a0, rSELF JAL(MterpSuspendCheck) # (self) bnez v0, MterpFallback + REFRESH_IBASE() # might have changed during suspend + GET_INST_OPCODE(t0) # extract opcode from rINST + GOTO_OPCODE(t0) # jump to next instruction + +.L_no_count_backwards: + li t0, JIT_CHECK_OSR # check for possible OSR re-entry + bne rPROFILE, t0, .L_resume_backward_branch +.L_osr_check: + move a0, rSELF + addu a1, rFP, OFF_FP_SHADOWFRAME + move a2, rINST + EXPORT_PC() + JAL(MterpMaybeDoOnStackReplacement) # (self, shadow_frame, offset) + bnez v0, MterpOnStackReplacement + b .L_resume_backward_branch + +.L_forward_branch: + li t0, JIT_CHECK_OSR # check for possible OSR re-entry + beq rPROFILE, t0, .L_check_osr_forward +.L_resume_forward_branch: + add a2, rINST, rINST # a2<- byte offset + FETCH_ADVANCE_INST_RB(a2) # update rPC, load rINST + GET_INST_OPCODE(t0) # extract opcode from rINST + GOTO_OPCODE(t0) # jump to next instruction + +.L_check_osr_forward: + move a0, rSELF + addu a1, rFP, OFF_FP_SHADOWFRAME + move a2, rINST + EXPORT_PC() + JAL(MterpMaybeDoOnStackReplacement) # (self, shadow_frame, offset) + bnez v0, MterpOnStackReplacement + b .L_resume_forward_branch + +.L_add_batch: + addu a1, rFP, OFF_FP_SHADOWFRAME + sh rPROFILE, SHADOWFRAME_HOTNESS_COUNTDOWN_OFFSET(a1) + lw a0, OFF_FP_METHOD(rFP) + move a2, rSELF + JAL(MterpAddHotnessBatch) # (method, shadow_frame, self) + move rPROFILE, v0 # restore new hotness countdown to rPROFILE + b .L_no_count_backwards + +/* + * Entered from the conditional branch handlers when OSR check request active on + * not-taken path. All Dalvik not-taken conditional branch offsets are 2. + */ +.L_check_not_taken_osr: + move a0, rSELF + addu a1, rFP, OFF_FP_SHADOWFRAME + li a2, 2 + EXPORT_PC() + JAL(MterpMaybeDoOnStackReplacement) # (self, shadow_frame, offset) + bnez v0, MterpOnStackReplacement + FETCH_ADVANCE_INST(2) GET_INST_OPCODE(t0) # extract opcode from rINST GOTO_OPCODE(t0) # jump to next instruction @@ -13149,6 +12882,26 @@ MterpReturn: sw v1, 4(a2) li v0, 1 # signal return to caller. MterpDone: +/* + * At this point, we expect rPROFILE to be non-zero. If negative, hotness is disabled or we're + * checking for OSR. If greater than zero, we might have unreported hotness to register + * (the difference between the ending rPROFILE and the cached hotness counter). rPROFILE + * should only reach zero immediately after a hotness decrement, and is then reset to either + * a negative special state or the new non-zero countdown value. + */ + blez rPROFILE, .L_pop_and_return # if > 0, we may have some counts to report. + +MterpProfileActive: + move rINST, v0 # stash return value + /* Report cached hotness counts */ + lw a0, OFF_FP_METHOD(rFP) + addu a1, rFP, OFF_FP_SHADOWFRAME + move a2, rSELF + sh rPROFILE, SHADOWFRAME_HOTNESS_COUNTDOWN_OFFSET(a1) + JAL(MterpAddHotnessBatch) # (method, shadow_frame, self) + move v0, rINST # restore return value + +.L_pop_and_return: /* Restore from the stack and return. Frame size = STACK_SIZE */ STACK_LOAD_FULL() jalr zero, ra diff --git a/runtime/interpreter/mterp/out/mterp_mips64.S b/runtime/interpreter/mterp/out/mterp_mips64.S index a17252b2f8..29a12bfd31 100644 --- a/runtime/interpreter/mterp/out/mterp_mips64.S +++ b/runtime/interpreter/mterp/out/mterp_mips64.S @@ -58,16 +58,18 @@ The following registers have fixed assignments: s3 rINST first 16-bit code unit of current instruction s4 rIBASE interpreted instruction base pointer, used for computed goto s5 rREFS base of object references in shadow frame (ideally, we'll get rid of this later). + s6 rPROFILE jit profile hotness countdown */ /* During bringup, we'll use the shadow frame model instead of rFP */ /* single-purpose registers, given names for clarity */ -#define rPC s0 -#define rFP s1 -#define rSELF s2 -#define rINST s3 -#define rIBASE s4 -#define rREFS s5 +#define rPC s0 +#define rFP s1 +#define rSELF s2 +#define rINST s3 +#define rIBASE s4 +#define rREFS s5 +#define rPROFILE s6 /* * This is a #include, not a %include, because we want the C pre-processor @@ -87,7 +89,7 @@ The following registers have fixed assignments: #define OFF_FP_RESULT_REGISTER OFF_FP(SHADOWFRAME_RESULT_REGISTER_OFFSET) #define OFF_FP_DEX_PC_PTR OFF_FP(SHADOWFRAME_DEX_PC_PTR_OFFSET) #define OFF_FP_CODE_ITEM OFF_FP(SHADOWFRAME_CODE_ITEM_OFFSET) -#define OFF_FP_SHADOWFRAME (-SHADOWFRAME_VREGS_OFFSET) +#define OFF_FP_SHADOWFRAME OFF_FP(0) #define MTERP_PROFILE_BRANCHES 1 #define MTERP_LOGGING 0 @@ -128,6 +130,17 @@ The following registers have fixed assignments: .endm /* + * Fetch the next instruction from an offset specified by _reg and advance xPC. + * xPC to point to the next instruction. "_reg" must specify the distance + * in bytes, *not* 16-bit code units, and may be a signed value. Must not set flags. + * + */ +.macro FETCH_ADVANCE_INST_RB reg + daddu rPC, rPC, \reg + FETCH_INST +.endm + +/* * Fetch the next instruction from the specified offset. Advances rPC * to point to the next instruction. * @@ -274,7 +287,8 @@ The following registers have fixed assignments: #define STACK_OFFSET_S3 40 #define STACK_OFFSET_S4 48 #define STACK_OFFSET_S5 56 -#define STACK_SIZE 64 +#define STACK_OFFSET_S6 64 +#define STACK_SIZE 80 /* needs 16 byte alignment */ /* Constants for float/double_to_int/long conversions */ #define INT_MIN 0x80000000 @@ -344,6 +358,8 @@ ExecuteMterpImpl: .cfi_rel_offset 20, STACK_OFFSET_S4 sd s5, STACK_OFFSET_S5(sp) .cfi_rel_offset 21, STACK_OFFSET_S5 + sd s6, STACK_OFFSET_S6(sp) + .cfi_rel_offset 22, STACK_OFFSET_S6 /* Remember the return register */ sd a3, SHADOWFRAME_RESULT_REGISTER_OFFSET(a2) @@ -364,6 +380,12 @@ ExecuteMterpImpl: /* Starting ibase */ REFRESH_IBASE + /* Set up for backwards branches & osr profiling */ + ld a0, OFF_FP_METHOD(rFP) + daddu a1, rFP, OFF_FP_SHADOWFRAME + jal MterpSetUpHotnessCountdown + move rPROFILE, v0 # Starting hotness countdown to rPROFILE + /* start executing the instruction at rPC */ FETCH_INST GET_INST_OPCODE v0 @@ -1100,24 +1122,9 @@ artMterpAsmInstructionStart = .L_op_nop * double to get a byte offset. */ /* goto +AA */ - .extern MterpProfileBranch srl rINST, rINST, 8 seb rINST, rINST # rINST <- offset (sign-extended AA) -#if MTERP_PROFILE_BRANCHES - EXPORT_PC - move a0, rSELF - daddu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - jal MterpProfileBranch # (self, shadow_frame, offset) - bnezc v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - dlsa rPC, rINST, rPC, 1 # rPC <- rPC + offset * 2 - lw ra, THREAD_FLAGS_OFFSET(rSELF) # Preload flags for MterpCheckSuspendAndContinue - move a0, rINST # a0 <- offset - FETCH_INST # load rINST - bltz a0, MterpCheckSuspendAndContinue # suspend check if backwards branch - GET_INST_OPCODE v0 # extract opcode from rINST - GOTO_OPCODE v0 # jump to next instruction + b MterpCommonTakenBranchNoFlags /* ------------------------------ */ .balign 128 @@ -1130,23 +1137,8 @@ artMterpAsmInstructionStart = .L_op_nop * double to get a byte offset. */ /* goto/16 +AAAA */ - .extern MterpProfileBranch lh rINST, 2(rPC) # rINST <- offset (sign-extended AAAA) -#if MTERP_PROFILE_BRANCHES - EXPORT_PC - move a0, rSELF - daddu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - jal MterpProfileBranch # (self, shadow_frame, offset) - bnezc v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - dlsa rPC, rINST, rPC, 1 # rPC <- rPC + offset * 2 - lw ra, THREAD_FLAGS_OFFSET(rSELF) # Preload flags for MterpCheckSuspendAndContinue - move a0, rINST # a0 <- offset - FETCH_INST # load rINST - bltz a0, MterpCheckSuspendAndContinue # suspend check if backwards branch - GET_INST_OPCODE v0 # extract opcode from rINST - GOTO_OPCODE v0 # jump to next instruction + b MterpCommonTakenBranchNoFlags /* ------------------------------ */ .balign 128 @@ -1162,25 +1154,10 @@ artMterpAsmInstructionStart = .L_op_nop * our "backward branch" test must be "<=0" instead of "<0". */ /* goto/32 +AAAAAAAA */ - .extern MterpProfileBranch lh rINST, 2(rPC) # rINST <- aaaa (low) lh a1, 4(rPC) # a1 <- AAAA (high) ins rINST, a1, 16, 16 # rINST <- offset (sign-extended AAAAaaaa) -#if MTERP_PROFILE_BRANCHES - EXPORT_PC - move a0, rSELF - daddu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - jal MterpProfileBranch # (self, shadow_frame, offset) - bnezc v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - dlsa rPC, rINST, rPC, 1 # rPC <- rPC + offset * 2 - lw ra, THREAD_FLAGS_OFFSET(rSELF) # Preload flags for MterpCheckSuspendAndContinue - move a0, rINST # a0 <- offset - FETCH_INST # load rINST - blez a0, MterpCheckSuspendAndContinue # suspend check if backwards branch - GET_INST_OPCODE v0 # extract opcode from rINST - GOTO_OPCODE v0 # jump to next instruction + b MterpCommonTakenBranchNoFlags /* ------------------------------ */ .balign 128 @@ -1206,21 +1183,7 @@ artMterpAsmInstructionStart = .L_op_nop dlsa a0, a0, rPC, 1 # a0 <- PC + BBBBbbbb*2 jal MterpDoPackedSwitch # v0 <- code-unit branch offset move rINST, v0 -#if MTERP_PROFILE_BRANCHES - EXPORT_PC - move a0, rSELF - daddu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - jal MterpProfileBranch # (self, shadow_frame, offset) - bnezc v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - dlsa rPC, rINST, rPC, 1 # rPC <- rPC + offset * 2 - lw ra, THREAD_FLAGS_OFFSET(rSELF) # Preload flags for MterpCheckSuspendAndContinue - move a0, rINST # a0 <- offset - FETCH_INST # load rINST - blez a0, MterpCheckSuspendAndContinue # suspend check if backwards branch - GET_INST_OPCODE v0 # extract opcode from rINST - GOTO_OPCODE v0 # jump to next instruction + b MterpCommonTakenBranchNoFlags /* ------------------------------ */ .balign 128 @@ -1247,21 +1210,7 @@ artMterpAsmInstructionStart = .L_op_nop dlsa a0, a0, rPC, 1 # a0 <- PC + BBBBbbbb*2 jal MterpDoSparseSwitch # v0 <- code-unit branch offset move rINST, v0 -#if MTERP_PROFILE_BRANCHES - EXPORT_PC - move a0, rSELF - daddu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - jal MterpProfileBranch # (self, shadow_frame, offset) - bnezc v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - dlsa rPC, rINST, rPC, 1 # rPC <- rPC + offset * 2 - lw ra, THREAD_FLAGS_OFFSET(rSELF) # Preload flags for MterpCheckSuspendAndContinue - move a0, rINST # a0 <- offset - FETCH_INST # load rINST - blez a0, MterpCheckSuspendAndContinue # suspend check if backwards branch - GET_INST_OPCODE v0 # extract opcode from rINST - GOTO_OPCODE v0 # jump to next instruction + b MterpCommonTakenBranchNoFlags /* ------------------------------ */ @@ -1453,22 +1402,10 @@ artMterpAsmInstructionStart = .L_op_nop lh rINST, 2(rPC) # rINST <- offset (sign-extended CCCC) GET_VREG a0, a2 # a0 <- vA GET_VREG a1, a3 # a1 <- vB - beqc a0, a1, 1f - li rINST, 2 # offset if branch not taken -1: -#if MTERP_PROFILE_BRANCHES - EXPORT_PC - move a0, rSELF - daddu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - jal MterpProfileBranch # (self, shadow_frame, offset) - bnezc v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - dlsa rPC, rINST, rPC, 1 # rPC <- rPC + offset * 2 - lw ra, THREAD_FLAGS_OFFSET(rSELF) # Preload flags for MterpCheckSuspendAndContinue - move a0, rINST # a0 <- offset - FETCH_INST # load rINST - bltz a0, MterpCheckSuspendAndContinue # suspend check if backwards branch + beqc a0, a1, MterpCommonTakenBranchNoFlags + li v0, JIT_CHECK_OSR # possible OSR re-entry? + beqc rPROFILE, v0, .L_check_not_taken_osr + FETCH_ADVANCE_INST 2 # advance rPC, load rINST GET_INST_OPCODE v0 # extract opcode from rINST GOTO_OPCODE v0 # jump to next instruction @@ -1492,22 +1429,10 @@ artMterpAsmInstructionStart = .L_op_nop lh rINST, 2(rPC) # rINST <- offset (sign-extended CCCC) GET_VREG a0, a2 # a0 <- vA GET_VREG a1, a3 # a1 <- vB - bnec a0, a1, 1f - li rINST, 2 # offset if branch not taken -1: -#if MTERP_PROFILE_BRANCHES - EXPORT_PC - move a0, rSELF - daddu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - jal MterpProfileBranch # (self, shadow_frame, offset) - bnezc v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - dlsa rPC, rINST, rPC, 1 # rPC <- rPC + offset * 2 - lw ra, THREAD_FLAGS_OFFSET(rSELF) # Preload flags for MterpCheckSuspendAndContinue - move a0, rINST # a0 <- offset - FETCH_INST # load rINST - bltz a0, MterpCheckSuspendAndContinue # suspend check if backwards branch + bnec a0, a1, MterpCommonTakenBranchNoFlags + li v0, JIT_CHECK_OSR # possible OSR re-entry? + beqc rPROFILE, v0, .L_check_not_taken_osr + FETCH_ADVANCE_INST 2 # advance rPC, load rINST GET_INST_OPCODE v0 # extract opcode from rINST GOTO_OPCODE v0 # jump to next instruction @@ -1531,22 +1456,10 @@ artMterpAsmInstructionStart = .L_op_nop lh rINST, 2(rPC) # rINST <- offset (sign-extended CCCC) GET_VREG a0, a2 # a0 <- vA GET_VREG a1, a3 # a1 <- vB - bltc a0, a1, 1f - li rINST, 2 # offset if branch not taken -1: -#if MTERP_PROFILE_BRANCHES - EXPORT_PC - move a0, rSELF - daddu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - jal MterpProfileBranch # (self, shadow_frame, offset) - bnezc v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - dlsa rPC, rINST, rPC, 1 # rPC <- rPC + offset * 2 - lw ra, THREAD_FLAGS_OFFSET(rSELF) # Preload flags for MterpCheckSuspendAndContinue - move a0, rINST # a0 <- offset - FETCH_INST # load rINST - bltz a0, MterpCheckSuspendAndContinue # suspend check if backwards branch + bltc a0, a1, MterpCommonTakenBranchNoFlags + li v0, JIT_CHECK_OSR # possible OSR re-entry? + beqc rPROFILE, v0, .L_check_not_taken_osr + FETCH_ADVANCE_INST 2 # advance rPC, load rINST GET_INST_OPCODE v0 # extract opcode from rINST GOTO_OPCODE v0 # jump to next instruction @@ -1570,22 +1483,10 @@ artMterpAsmInstructionStart = .L_op_nop lh rINST, 2(rPC) # rINST <- offset (sign-extended CCCC) GET_VREG a0, a2 # a0 <- vA GET_VREG a1, a3 # a1 <- vB - bgec a0, a1, 1f - li rINST, 2 # offset if branch not taken -1: -#if MTERP_PROFILE_BRANCHES - EXPORT_PC - move a0, rSELF - daddu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - jal MterpProfileBranch # (self, shadow_frame, offset) - bnezc v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - dlsa rPC, rINST, rPC, 1 # rPC <- rPC + offset * 2 - lw ra, THREAD_FLAGS_OFFSET(rSELF) # Preload flags for MterpCheckSuspendAndContinue - move a0, rINST # a0 <- offset - FETCH_INST # load rINST - bltz a0, MterpCheckSuspendAndContinue # suspend check if backwards branch + bgec a0, a1, MterpCommonTakenBranchNoFlags + li v0, JIT_CHECK_OSR # possible OSR re-entry? + beqc rPROFILE, v0, .L_check_not_taken_osr + FETCH_ADVANCE_INST 2 # advance rPC, load rINST GET_INST_OPCODE v0 # extract opcode from rINST GOTO_OPCODE v0 # jump to next instruction @@ -1609,22 +1510,10 @@ artMterpAsmInstructionStart = .L_op_nop lh rINST, 2(rPC) # rINST <- offset (sign-extended CCCC) GET_VREG a0, a2 # a0 <- vA GET_VREG a1, a3 # a1 <- vB - bgtc a0, a1, 1f - li rINST, 2 # offset if branch not taken -1: -#if MTERP_PROFILE_BRANCHES - EXPORT_PC - move a0, rSELF - daddu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - jal MterpProfileBranch # (self, shadow_frame, offset) - bnezc v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - dlsa rPC, rINST, rPC, 1 # rPC <- rPC + offset * 2 - lw ra, THREAD_FLAGS_OFFSET(rSELF) # Preload flags for MterpCheckSuspendAndContinue - move a0, rINST # a0 <- offset - FETCH_INST # load rINST - bltz a0, MterpCheckSuspendAndContinue # suspend check if backwards branch + bgtc a0, a1, MterpCommonTakenBranchNoFlags + li v0, JIT_CHECK_OSR # possible OSR re-entry? + beqc rPROFILE, v0, .L_check_not_taken_osr + FETCH_ADVANCE_INST 2 # advance rPC, load rINST GET_INST_OPCODE v0 # extract opcode from rINST GOTO_OPCODE v0 # jump to next instruction @@ -1648,22 +1537,10 @@ artMterpAsmInstructionStart = .L_op_nop lh rINST, 2(rPC) # rINST <- offset (sign-extended CCCC) GET_VREG a0, a2 # a0 <- vA GET_VREG a1, a3 # a1 <- vB - blec a0, a1, 1f - li rINST, 2 # offset if branch not taken -1: -#if MTERP_PROFILE_BRANCHES - EXPORT_PC - move a0, rSELF - daddu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - jal MterpProfileBranch # (self, shadow_frame, offset) - bnezc v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - dlsa rPC, rINST, rPC, 1 # rPC <- rPC + offset * 2 - lw ra, THREAD_FLAGS_OFFSET(rSELF) # Preload flags for MterpCheckSuspendAndContinue - move a0, rINST # a0 <- offset - FETCH_INST # load rINST - bltz a0, MterpCheckSuspendAndContinue # suspend check if backwards branch + blec a0, a1, MterpCommonTakenBranchNoFlags + li v0, JIT_CHECK_OSR # possible OSR re-entry? + beqc rPROFILE, v0, .L_check_not_taken_osr + FETCH_ADVANCE_INST 2 # advance rPC, load rINST GET_INST_OPCODE v0 # extract opcode from rINST GOTO_OPCODE v0 # jump to next instruction @@ -1681,26 +1558,13 @@ artMterpAsmInstructionStart = .L_op_nop * For: if-eqz, if-nez, if-ltz, if-gez, if-gtz, if-lez */ /* if-cmp vAA, +BBBB */ - .extern MterpProfileBranch srl a2, rINST, 8 # a2 <- AA lh rINST, 2(rPC) # rINST <- offset (sign-extended BBBB) GET_VREG a0, a2 # a0 <- vAA - beqzc a0, 1f - li rINST, 2 # offset if branch not taken -1: -#if MTERP_PROFILE_BRANCHES - EXPORT_PC - move a0, rSELF - daddu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - jal MterpProfileBranch # (self, shadow_frame, offset) - bnezc v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - dlsa rPC, rINST, rPC, 1 # rPC <- rPC + offset * 2 - lw ra, THREAD_FLAGS_OFFSET(rSELF) # Preload flags for MterpCheckSuspendAndContinue - move a0, rINST # a0 <- offset - FETCH_INST # load rINST - bltz a0, MterpCheckSuspendAndContinue # suspend check if backwards branch + beqzc a0, MterpCommonTakenBranchNoFlags + li v0, JIT_CHECK_OSR # possible OSR re-entry? + beqc rPROFILE, v0, .L_check_not_taken_osr + FETCH_ADVANCE_INST 2 # advance rPC, load rINST GET_INST_OPCODE v0 # extract opcode from rINST GOTO_OPCODE v0 # jump to next instruction @@ -1718,26 +1582,13 @@ artMterpAsmInstructionStart = .L_op_nop * For: if-eqz, if-nez, if-ltz, if-gez, if-gtz, if-lez */ /* if-cmp vAA, +BBBB */ - .extern MterpProfileBranch srl a2, rINST, 8 # a2 <- AA lh rINST, 2(rPC) # rINST <- offset (sign-extended BBBB) GET_VREG a0, a2 # a0 <- vAA - bnezc a0, 1f - li rINST, 2 # offset if branch not taken -1: -#if MTERP_PROFILE_BRANCHES - EXPORT_PC - move a0, rSELF - daddu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - jal MterpProfileBranch # (self, shadow_frame, offset) - bnezc v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - dlsa rPC, rINST, rPC, 1 # rPC <- rPC + offset * 2 - lw ra, THREAD_FLAGS_OFFSET(rSELF) # Preload flags for MterpCheckSuspendAndContinue - move a0, rINST # a0 <- offset - FETCH_INST # load rINST - bltz a0, MterpCheckSuspendAndContinue # suspend check if backwards branch + bnezc a0, MterpCommonTakenBranchNoFlags + li v0, JIT_CHECK_OSR # possible OSR re-entry? + beqc rPROFILE, v0, .L_check_not_taken_osr + FETCH_ADVANCE_INST 2 # advance rPC, load rINST GET_INST_OPCODE v0 # extract opcode from rINST GOTO_OPCODE v0 # jump to next instruction @@ -1755,26 +1606,13 @@ artMterpAsmInstructionStart = .L_op_nop * For: if-eqz, if-nez, if-ltz, if-gez, if-gtz, if-lez */ /* if-cmp vAA, +BBBB */ - .extern MterpProfileBranch srl a2, rINST, 8 # a2 <- AA lh rINST, 2(rPC) # rINST <- offset (sign-extended BBBB) GET_VREG a0, a2 # a0 <- vAA - bltzc a0, 1f - li rINST, 2 # offset if branch not taken -1: -#if MTERP_PROFILE_BRANCHES - EXPORT_PC - move a0, rSELF - daddu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - jal MterpProfileBranch # (self, shadow_frame, offset) - bnezc v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - dlsa rPC, rINST, rPC, 1 # rPC <- rPC + offset * 2 - lw ra, THREAD_FLAGS_OFFSET(rSELF) # Preload flags for MterpCheckSuspendAndContinue - move a0, rINST # a0 <- offset - FETCH_INST # load rINST - bltz a0, MterpCheckSuspendAndContinue # suspend check if backwards branch + bltzc a0, MterpCommonTakenBranchNoFlags + li v0, JIT_CHECK_OSR # possible OSR re-entry? + beqc rPROFILE, v0, .L_check_not_taken_osr + FETCH_ADVANCE_INST 2 # advance rPC, load rINST GET_INST_OPCODE v0 # extract opcode from rINST GOTO_OPCODE v0 # jump to next instruction @@ -1792,26 +1630,13 @@ artMterpAsmInstructionStart = .L_op_nop * For: if-eqz, if-nez, if-ltz, if-gez, if-gtz, if-lez */ /* if-cmp vAA, +BBBB */ - .extern MterpProfileBranch srl a2, rINST, 8 # a2 <- AA lh rINST, 2(rPC) # rINST <- offset (sign-extended BBBB) GET_VREG a0, a2 # a0 <- vAA - bgezc a0, 1f - li rINST, 2 # offset if branch not taken -1: -#if MTERP_PROFILE_BRANCHES - EXPORT_PC - move a0, rSELF - daddu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - jal MterpProfileBranch # (self, shadow_frame, offset) - bnezc v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - dlsa rPC, rINST, rPC, 1 # rPC <- rPC + offset * 2 - lw ra, THREAD_FLAGS_OFFSET(rSELF) # Preload flags for MterpCheckSuspendAndContinue - move a0, rINST # a0 <- offset - FETCH_INST # load rINST - bltz a0, MterpCheckSuspendAndContinue # suspend check if backwards branch + bgezc a0, MterpCommonTakenBranchNoFlags + li v0, JIT_CHECK_OSR # possible OSR re-entry? + beqc rPROFILE, v0, .L_check_not_taken_osr + FETCH_ADVANCE_INST 2 # advance rPC, load rINST GET_INST_OPCODE v0 # extract opcode from rINST GOTO_OPCODE v0 # jump to next instruction @@ -1829,26 +1654,13 @@ artMterpAsmInstructionStart = .L_op_nop * For: if-eqz, if-nez, if-ltz, if-gez, if-gtz, if-lez */ /* if-cmp vAA, +BBBB */ - .extern MterpProfileBranch srl a2, rINST, 8 # a2 <- AA lh rINST, 2(rPC) # rINST <- offset (sign-extended BBBB) GET_VREG a0, a2 # a0 <- vAA - bgtzc a0, 1f - li rINST, 2 # offset if branch not taken -1: -#if MTERP_PROFILE_BRANCHES - EXPORT_PC - move a0, rSELF - daddu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - jal MterpProfileBranch # (self, shadow_frame, offset) - bnezc v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - dlsa rPC, rINST, rPC, 1 # rPC <- rPC + offset * 2 - lw ra, THREAD_FLAGS_OFFSET(rSELF) # Preload flags for MterpCheckSuspendAndContinue - move a0, rINST # a0 <- offset - FETCH_INST # load rINST - bltz a0, MterpCheckSuspendAndContinue # suspend check if backwards branch + bgtzc a0, MterpCommonTakenBranchNoFlags + li v0, JIT_CHECK_OSR # possible OSR re-entry? + beqc rPROFILE, v0, .L_check_not_taken_osr + FETCH_ADVANCE_INST 2 # advance rPC, load rINST GET_INST_OPCODE v0 # extract opcode from rINST GOTO_OPCODE v0 # jump to next instruction @@ -1866,26 +1678,13 @@ artMterpAsmInstructionStart = .L_op_nop * For: if-eqz, if-nez, if-ltz, if-gez, if-gtz, if-lez */ /* if-cmp vAA, +BBBB */ - .extern MterpProfileBranch srl a2, rINST, 8 # a2 <- AA lh rINST, 2(rPC) # rINST <- offset (sign-extended BBBB) GET_VREG a0, a2 # a0 <- vAA - blezc a0, 1f - li rINST, 2 # offset if branch not taken -1: -#if MTERP_PROFILE_BRANCHES - EXPORT_PC - move a0, rSELF - daddu a1, rFP, OFF_FP_SHADOWFRAME - move a2, rINST - jal MterpProfileBranch # (self, shadow_frame, offset) - bnezc v0, MterpOnStackReplacement # Note: offset must be in rINST -#endif - dlsa rPC, rINST, rPC, 1 # rPC <- rPC + offset * 2 - lw ra, THREAD_FLAGS_OFFSET(rSELF) # Preload flags for MterpCheckSuspendAndContinue - move a0, rINST # a0 <- offset - FETCH_INST # load rINST - bltz a0, MterpCheckSuspendAndContinue # suspend check if backwards branch + blezc a0, MterpCommonTakenBranchNoFlags + li v0, JIT_CHECK_OSR # possible OSR re-entry? + beqc rPROFILE, v0, .L_check_not_taken_osr + FETCH_ADVANCE_INST 2 # advance rPC, load rINST GET_INST_OPCODE v0 # extract opcode from rINST GOTO_OPCODE v0 # jump to next instruction @@ -12323,23 +12122,110 @@ MterpException: /* NOTE: no fallthrough */ /* - * Check for suspend check request. Assumes rINST already loaded, rPC advanced and - * still needs to get the opcode and branch to it, and flags are in ra. + * Common handling for branches with support for Jit profiling. + * On entry: + * rINST <= signed offset + * rPROFILE <= signed hotness countdown (expanded to 64 bits) + * + * We have quite a few different cases for branch profiling, OSR detection and + * suspend check support here. + * + * Taken backward branches: + * If profiling active, do hotness countdown and report if we hit zero. + * If in osr check mode, see if our target is a compiled loop header entry and do OSR if so. + * Is there a pending suspend request? If so, suspend. + * + * Taken forward branches and not-taken backward branches: + * If in osr check mode, see if our target is a compiled loop header entry and do OSR if so. + * + * Our most common case is expected to be a taken backward branch with active jit profiling, + * but no full OSR check and no pending suspend request. + * Next most common case is not-taken branch with no full OSR check. + * */ - .extern MterpSuspendCheck -MterpCheckSuspendAndContinue: +MterpCommonTakenBranchNoFlags: + bgtzc rINST, .L_forward_branch # don't add forward branches to hotness +/* + * We need to subtract 1 from positive values and we should not see 0 here, + * so we may use the result of the comparison with -1. + */ + li v0, JIT_CHECK_OSR + beqc rPROFILE, v0, .L_osr_check + bltc rPROFILE, v0, .L_resume_backward_branch + dsubu rPROFILE, 1 + beqzc rPROFILE, .L_add_batch # counted down to zero - report +.L_resume_backward_branch: + lw ra, THREAD_FLAGS_OFFSET(rSELF) REFRESH_IBASE - and ra, ra, (THREAD_SUSPEND_REQUEST | THREAD_CHECKPOINT_REQUEST) - bnez ra, check1 - GET_INST_OPCODE v0 # extract opcode from rINST - GOTO_OPCODE v0 # jump to next instruction -check1: + daddu a2, rINST, rINST # a2<- byte offset + FETCH_ADVANCE_INST_RB a2 # update rPC, load rINST + and ra, (THREAD_SUSPEND_REQUEST | THREAD_CHECKPOINT_REQUEST) + bnezc ra, .L_suspend_request_pending + GET_INST_OPCODE v0 # extract opcode from rINST + GOTO_OPCODE v0 # jump to next instruction + +.L_suspend_request_pending: EXPORT_PC move a0, rSELF - jal MterpSuspendCheck # (self) - bnezc v0, MterpFallback # Something in the environment changed, switch interpreters - GET_INST_OPCODE v0 # extract opcode from rINST - GOTO_OPCODE v0 # jump to next instruction + jal MterpSuspendCheck # (self) + bnezc v0, MterpFallback + REFRESH_IBASE # might have changed during suspend + GET_INST_OPCODE v0 # extract opcode from rINST + GOTO_OPCODE v0 # jump to next instruction + +.L_no_count_backwards: + li v0, JIT_CHECK_OSR # check for possible OSR re-entry + bnec rPROFILE, v0, .L_resume_backward_branch +.L_osr_check: + move a0, rSELF + daddu a1, rFP, OFF_FP_SHADOWFRAME + move a2, rINST + EXPORT_PC + jal MterpMaybeDoOnStackReplacement # (self, shadow_frame, offset) + bnezc v0, MterpOnStackReplacement + b .L_resume_backward_branch + +.L_forward_branch: + li v0, JIT_CHECK_OSR # check for possible OSR re-entry + beqc rPROFILE, v0, .L_check_osr_forward +.L_resume_forward_branch: + daddu a2, rINST, rINST # a2<- byte offset + FETCH_ADVANCE_INST_RB a2 # update rPC, load rINST + GET_INST_OPCODE v0 # extract opcode from rINST + GOTO_OPCODE v0 # jump to next instruction + +.L_check_osr_forward: + move a0, rSELF + daddu a1, rFP, OFF_FP_SHADOWFRAME + move a2, rINST + EXPORT_PC + jal MterpMaybeDoOnStackReplacement # (self, shadow_frame, offset) + bnezc v0, MterpOnStackReplacement + b .L_resume_forward_branch + +.L_add_batch: + daddu a1, rFP, OFF_FP_SHADOWFRAME + sh rPROFILE, SHADOWFRAME_HOTNESS_COUNTDOWN_OFFSET(a1) + ld a0, OFF_FP_METHOD(rFP) + move a2, rSELF + jal MterpAddHotnessBatch # (method, shadow_frame, self) + move rPROFILE, v0 # restore new hotness countdown to rPROFILE + b .L_no_count_backwards + +/* + * Entered from the conditional branch handlers when OSR check request active on + * not-taken path. All Dalvik not-taken conditional branch offsets are 2. + */ +.L_check_not_taken_osr: + move a0, rSELF + daddu a1, rFP, OFF_FP_SHADOWFRAME + li a2, 2 + EXPORT_PC + jal MterpMaybeDoOnStackReplacement # (self, shadow_frame, offset) + bnezc v0, MterpOnStackReplacement + FETCH_ADVANCE_INST 2 + GET_INST_OPCODE v0 # extract opcode from rINST + GOTO_OPCODE v0 # jump to next instruction /* * On-stack replacement has happened, and now we've returned from the compiled method. @@ -12395,6 +12281,28 @@ MterpReturn: check2: li v0, 1 # signal return to caller. MterpDone: +/* + * At this point, we expect rPROFILE to be non-zero. If negative, hotness is disabled or we're + * checking for OSR. If greater than zero, we might have unreported hotness to register + * (the difference between the ending rPROFILE and the cached hotness counter). rPROFILE + * should only reach zero immediately after a hotness decrement, and is then reset to either + * a negative special state or the new non-zero countdown value. + */ + blez rPROFILE, .L_pop_and_return # if > 0, we may have some counts to report. + +MterpProfileActive: + move rINST, v0 # stash return value + /* Report cached hotness counts */ + ld a0, OFF_FP_METHOD(rFP) + daddu a1, rFP, OFF_FP_SHADOWFRAME + move a2, rSELF + sh rPROFILE, SHADOWFRAME_HOTNESS_COUNTDOWN_OFFSET(a1) + jal MterpAddHotnessBatch # (method, shadow_frame, self) + move v0, rINST # restore return value + +.L_pop_and_return: + ld s6, STACK_OFFSET_S6(sp) + .cfi_restore 22 ld s5, STACK_OFFSET_S5(sp) .cfi_restore 21 ld s4, STACK_OFFSET_S4(sp) @@ -12421,5 +12329,6 @@ MterpDone: .cfi_adjust_cfa_offset -STACK_SIZE .cfi_endproc + .set reorder .size ExecuteMterpImpl, .-ExecuteMterpImpl diff --git a/runtime/interpreter/mterp/out/mterp_x86.S b/runtime/interpreter/mterp/out/mterp_x86.S index 685b9b69d7..f78e1bc416 100644 --- a/runtime/interpreter/mterp/out/mterp_x86.S +++ b/runtime/interpreter/mterp/out/mterp_x86.S @@ -12985,11 +12985,12 @@ MterpCommonTakenBranch: * not-taken path. All Dalvik not-taken conditional branch offsets are 2. */ .L_check_not_taken_osr: + EXPORT_PC movl rSELF, %eax movl %eax, OUT_ARG0(%esp) leal OFF_FP_SHADOWFRAME(rFP), %ecx movl %ecx, OUT_ARG1(%esp) - movl rINST, OUT_ARG3(%esp) + movl $2, OUT_ARG2(%esp) call SYMBOL(MterpMaybeDoOnStackReplacement) # (self, shadow_frame, offset) testb %al, %al REFRESH_IBASE diff --git a/runtime/interpreter/mterp/out/mterp_x86_64.S b/runtime/interpreter/mterp/out/mterp_x86_64.S index a1360e0934..031cec8233 100644 --- a/runtime/interpreter/mterp/out/mterp_x86_64.S +++ b/runtime/interpreter/mterp/out/mterp_x86_64.S @@ -67,7 +67,7 @@ Mterp and x86_64 notes: Some key interpreter variables will be assigned to registers. nick reg purpose - rSELF rbp pointer to ThreadSelf. + rPROFILE rbp countdown register for jit profiling rPC r12 interpreted program counter, used for fetching instructions rFP r13 interpreted frame pointer, used for accessing locals and args rINSTw bx first 16-bit code of current instruction @@ -120,6 +120,21 @@ unspecified registers or condition codes. .cfi_restore \_reg .endm +/* + * Instead of holding a pointer to the shadow frame, we keep rFP at the base of the vregs. So, + * to access other shadow frame fields, we need to use a backwards offset. Define those here. + */ +#define OFF_FP(a) (a - SHADOWFRAME_VREGS_OFFSET) +#define OFF_FP_NUMBER_OF_VREGS OFF_FP(SHADOWFRAME_NUMBER_OF_VREGS_OFFSET) +#define OFF_FP_DEX_PC OFF_FP(SHADOWFRAME_DEX_PC_OFFSET) +#define OFF_FP_LINK OFF_FP(SHADOWFRAME_LINK_OFFSET) +#define OFF_FP_METHOD OFF_FP(SHADOWFRAME_METHOD_OFFSET) +#define OFF_FP_RESULT_REGISTER OFF_FP(SHADOWFRAME_RESULT_REGISTER_OFFSET) +#define OFF_FP_DEX_PC_PTR OFF_FP(SHADOWFRAME_DEX_PC_PTR_OFFSET) +#define OFF_FP_CODE_ITEM OFF_FP(SHADOWFRAME_CODE_ITEM_OFFSET) +#define OFF_FP_COUNTDOWN_OFFSET OFF_FP(SHADOWFRAME_HOTNESS_COUNTDOWN_OFFSET) +#define OFF_FP_SHADOWFRAME (-SHADOWFRAME_VREGS_OFFSET) + /* Frame size must be 16-byte aligned. * Remember about 8 bytes for return address + 6 * 8 for spills. */ @@ -130,6 +145,8 @@ unspecified registers or condition codes. #define IN_ARG2 %rdx #define IN_ARG1 %rsi #define IN_ARG0 %rdi +/* Spill offsets relative to %esp */ +#define SELF_SPILL (FRAME_SIZE - 8) /* Out Args */ #define OUT_ARG3 %rcx #define OUT_ARG2 %rdx @@ -144,7 +161,7 @@ unspecified registers or condition codes. /* During bringup, we'll use the shadow frame model instead of rFP */ /* single-purpose registers, given names for clarity */ -#define rSELF %rbp +#define rSELF SELF_SPILL(%rsp) #define rPC %r12 #define rFP %r13 #define rINST %ebx @@ -154,40 +171,11 @@ unspecified registers or condition codes. #define rINSTbl %bl #define rIBASE %r14 #define rREFS %r15 +#define rPROFILE %ebp -/* - * Instead of holding a pointer to the shadow frame, we keep rFP at the base of the vregs. So, - * to access other shadow frame fields, we need to use a backwards offset. Define those here. - */ -#define OFF_FP(a) (a - SHADOWFRAME_VREGS_OFFSET) -#define OFF_FP_NUMBER_OF_VREGS OFF_FP(SHADOWFRAME_NUMBER_OF_VREGS_OFFSET) -#define OFF_FP_DEX_PC OFF_FP(SHADOWFRAME_DEX_PC_OFFSET) -#define OFF_FP_LINK OFF_FP(SHADOWFRAME_LINK_OFFSET) -#define OFF_FP_METHOD OFF_FP(SHADOWFRAME_METHOD_OFFSET) -#define OFF_FP_RESULT_REGISTER OFF_FP(SHADOWFRAME_RESULT_REGISTER_OFFSET) -#define OFF_FP_DEX_PC_PTR OFF_FP(SHADOWFRAME_DEX_PC_PTR_OFFSET) -#define OFF_FP_CODE_ITEM OFF_FP(SHADOWFRAME_CODE_ITEM_OFFSET) -#define OFF_FP_SHADOWFRAME (-SHADOWFRAME_VREGS_OFFSET) - -#define MTERP_PROFILE_BRANCHES 1 #define MTERP_LOGGING 0 /* - * Profile branch. rINST should contain the offset. %eax is scratch. - */ -.macro MTERP_PROFILE_BRANCH -#ifdef MTERP_PROFILE_BRANCHES - EXPORT_PC - movq rSELF, OUT_ARG0 - leaq OFF_FP_SHADOWFRAME(rFP), OUT_ARG1 - movl rINST, OUT_32_ARG2 - call SYMBOL(MterpProfileBranch) - testb %al, %al - jnz MterpOnStackReplacement -#endif -.endm - -/* * "export" the PC to dex_pc field in the shadow frame, f/b/o future exception objects. Must * be done *before* something throws. * @@ -211,7 +199,8 @@ unspecified registers or condition codes. * */ .macro REFRESH_IBASE - movq THREAD_CURRENT_IBASE_OFFSET(rSELF), rIBASE + movq rSELF, rIBASE + movq THREAD_CURRENT_IBASE_OFFSET(rIBASE), rIBASE .endm /* @@ -377,6 +366,12 @@ SYMBOL(ExecuteMterpImpl): movq IN_ARG0, rSELF REFRESH_IBASE + /* Set up for backwards branches & osr profiling */ + movq OFF_FP_METHOD(rFP), OUT_ARG0 + leaq OFF_FP_SHADOWFRAME(rFP), OUT_ARG1 + call SYMBOL(MterpSetUpHotnessCountdown) + movswl %ax, rPROFILE + /* start executing the instruction at rPC */ FETCH_INST GOTO_NEXT @@ -579,9 +574,10 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop .L_op_move_exception: /* 0x0d */ /* File: x86_64/op_move_exception.S */ /* move-exception vAA */ - movl THREAD_EXCEPTION_OFFSET(rSELF), %eax + movq rSELF, %rcx + movl THREAD_EXCEPTION_OFFSET(%rcx), %eax SET_VREG_OBJECT %eax, rINSTq # fp[AA] <- exception object - movl $0, THREAD_EXCEPTION_OFFSET(rSELF) + movl $0, THREAD_EXCEPTION_OFFSET(%rcx) ADVANCE_PC_FETCH_AND_GOTO_NEXT 1 /* ------------------------------ */ @@ -590,9 +586,9 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop /* File: x86_64/op_return_void.S */ .extern MterpThreadFenceForConstructor call SYMBOL(MterpThreadFenceForConstructor) - testl $(THREAD_SUSPEND_REQUEST | THREAD_CHECKPOINT_REQUEST), THREAD_FLAGS_OFFSET(rSELF) - jz 1f movq rSELF, OUT_ARG0 + testl $(THREAD_SUSPEND_REQUEST | THREAD_CHECKPOINT_REQUEST), THREAD_FLAGS_OFFSET(OUT_ARG0) + jz 1f call SYMBOL(MterpSuspendCheck) 1: xorq %rax, %rax @@ -610,9 +606,9 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop /* op vAA */ .extern MterpThreadFenceForConstructor call SYMBOL(MterpThreadFenceForConstructor) - testl $(THREAD_SUSPEND_REQUEST | THREAD_CHECKPOINT_REQUEST), THREAD_FLAGS_OFFSET(rSELF) - jz 1f movq rSELF, OUT_ARG0 + testl $(THREAD_SUSPEND_REQUEST | THREAD_CHECKPOINT_REQUEST), THREAD_FLAGS_OFFSET(OUT_ARG0) + jz 1f call SYMBOL(MterpSuspendCheck) 1: GET_VREG %eax, rINSTq # eax <- vAA @@ -628,9 +624,9 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop /* return-wide vAA */ .extern MterpThreadFenceForConstructor call SYMBOL(MterpThreadFenceForConstructor) - testl $(THREAD_SUSPEND_REQUEST | THREAD_CHECKPOINT_REQUEST), THREAD_FLAGS_OFFSET(rSELF) - jz 1f movq rSELF, OUT_ARG0 + testl $(THREAD_SUSPEND_REQUEST | THREAD_CHECKPOINT_REQUEST), THREAD_FLAGS_OFFSET(OUT_ARG0) + jz 1f call SYMBOL(MterpSuspendCheck) 1: GET_WIDE_VREG %rax, rINSTq # eax <- v[AA] @@ -649,9 +645,9 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop /* op vAA */ .extern MterpThreadFenceForConstructor call SYMBOL(MterpThreadFenceForConstructor) - testl $(THREAD_SUSPEND_REQUEST | THREAD_CHECKPOINT_REQUEST), THREAD_FLAGS_OFFSET(rSELF) - jz 1f movq rSELF, OUT_ARG0 + testl $(THREAD_SUSPEND_REQUEST | THREAD_CHECKPOINT_REQUEST), THREAD_FLAGS_OFFSET(OUT_ARG0) + jz 1f call SYMBOL(MterpSuspendCheck) 1: GET_VREG %eax, rINSTq # eax <- vAA @@ -854,7 +850,8 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop movq rSELF, OUT_ARG3 call SYMBOL(MterpInstanceOf) # (index, &obj, method, self) movsbl %al, %eax - cmpq $0, THREAD_EXCEPTION_OFFSET(rSELF) + movq rSELF, %rcx + cmpq $0, THREAD_EXCEPTION_OFFSET(%rcx) jnz MterpException andb $0xf, rINSTbl # rINSTbl <- A SET_VREG %eax, rINSTq @@ -988,7 +985,8 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop GET_VREG %eax, rINSTq # eax<- vAA (exception object) testb %al, %al jz common_errNullObject - movq %rax, THREAD_EXCEPTION_OFFSET(rSELF) + movq rSELF, %rcx + movq %rax, THREAD_EXCEPTION_OFFSET(%rcx) jmp MterpException /* ------------------------------ */ @@ -1003,12 +1001,8 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop */ /* goto +AA */ movsbq rINSTbl, rINSTq # rINSTq <- ssssssAA - MTERP_PROFILE_BRANCH - addq rINSTq, rINSTq # rINSTq <- AA * 2 - leaq (rPC, rINSTq), rPC - FETCH_INST - jle MterpCheckSuspendAndContinue # AA * 2 <= 0 => suspend check - GOTO_NEXT + testq rINSTq, rINSTq + jmp MterpCommonTakenBranch /* ------------------------------ */ .balign 128 @@ -1022,12 +1016,8 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop */ /* goto/16 +AAAA */ movswq 2(rPC), rINSTq # rINSTq <- ssssAAAA - MTERP_PROFILE_BRANCH - addq rINSTq, rINSTq # rINSTq <- AA * 2 - leaq (rPC, rINSTq), rPC - FETCH_INST - jle MterpCheckSuspendAndContinue # AA * 2 <= 0 => suspend check - GOTO_NEXT + testq rINSTq, rINSTq + jmp MterpCommonTakenBranch /* ------------------------------ */ .balign 128 @@ -1044,12 +1034,8 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop */ /* goto/32 +AAAAAAAA */ movslq 2(rPC), rINSTq # rINSTq <- AAAAAAAA - MTERP_PROFILE_BRANCH - addq rINSTq, rINSTq # rINSTq <- AA * 2 - leaq (rPC, rINSTq), rPC - FETCH_INST - jle MterpCheckSuspendAndContinue # AA * 2 <= 0 => suspend check - GOTO_NEXT + testq rINSTq, rINSTq + jmp MterpCommonTakenBranch /* ------------------------------ */ .balign 128 @@ -1069,13 +1055,9 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop leaq (rPC,OUT_ARG0,2), OUT_ARG0 # rcx <- PC + BBBBbbbb*2 GET_VREG OUT_32_ARG1, rINSTq # eax <- vAA call SYMBOL(MterpDoPackedSwitch) + testl %eax, %eax movslq %eax, rINSTq - MTERP_PROFILE_BRANCH - addq rINSTq, rINSTq - leaq (rPC, rINSTq), rPC - FETCH_INST - jle MterpCheckSuspendAndContinue - GOTO_NEXT + jmp MterpCommonTakenBranch /* ------------------------------ */ .balign 128 @@ -1096,13 +1078,9 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop leaq (rPC,OUT_ARG0,2), OUT_ARG0 # rcx <- PC + BBBBbbbb*2 GET_VREG OUT_32_ARG1, rINSTq # eax <- vAA call SYMBOL(MterpDoSparseSwitch) + testl %eax, %eax movslq %eax, rINSTq - MTERP_PROFILE_BRANCH - addq rINSTq, rINSTq - leaq (rPC, rINSTq), rPC - FETCH_INST - jle MterpCheckSuspendAndContinue - GOTO_NEXT + jmp MterpCommonTakenBranch /* ------------------------------ */ @@ -1309,16 +1287,14 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop andb $0xf, %cl # rcx <- A GET_VREG %eax, %rcx # eax <- vA cmpl VREG_ADDRESS(rINSTq), %eax # compare (vA, vB) - movl $2, rINST # assume not taken jne 1f movswq 2(rPC), rINSTq # Get signed branch offset + testq rINSTq, rINSTq + jmp MterpCommonTakenBranch 1: - MTERP_PROFILE_BRANCH - addq rINSTq, rINSTq # rax <- AA * 2 - leaq (rPC, rINSTq), rPC - FETCH_INST - jle MterpCheckSuspendAndContinue # AA * 2 <= 0 => suspend check - GOTO_NEXT + cmpl $JIT_CHECK_OSR, rPROFILE + je .L_check_not_taken_osr + ADVANCE_PC_FETCH_AND_GOTO_NEXT 2 /* ------------------------------ */ @@ -1339,16 +1315,14 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop andb $0xf, %cl # rcx <- A GET_VREG %eax, %rcx # eax <- vA cmpl VREG_ADDRESS(rINSTq), %eax # compare (vA, vB) - movl $2, rINST # assume not taken je 1f movswq 2(rPC), rINSTq # Get signed branch offset + testq rINSTq, rINSTq + jmp MterpCommonTakenBranch 1: - MTERP_PROFILE_BRANCH - addq rINSTq, rINSTq # rax <- AA * 2 - leaq (rPC, rINSTq), rPC - FETCH_INST - jle MterpCheckSuspendAndContinue # AA * 2 <= 0 => suspend check - GOTO_NEXT + cmpl $JIT_CHECK_OSR, rPROFILE + je .L_check_not_taken_osr + ADVANCE_PC_FETCH_AND_GOTO_NEXT 2 /* ------------------------------ */ @@ -1369,16 +1343,14 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop andb $0xf, %cl # rcx <- A GET_VREG %eax, %rcx # eax <- vA cmpl VREG_ADDRESS(rINSTq), %eax # compare (vA, vB) - movl $2, rINST # assume not taken jge 1f movswq 2(rPC), rINSTq # Get signed branch offset + testq rINSTq, rINSTq + jmp MterpCommonTakenBranch 1: - MTERP_PROFILE_BRANCH - addq rINSTq, rINSTq # rax <- AA * 2 - leaq (rPC, rINSTq), rPC - FETCH_INST - jle MterpCheckSuspendAndContinue # AA * 2 <= 0 => suspend check - GOTO_NEXT + cmpl $JIT_CHECK_OSR, rPROFILE + je .L_check_not_taken_osr + ADVANCE_PC_FETCH_AND_GOTO_NEXT 2 /* ------------------------------ */ @@ -1399,16 +1371,14 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop andb $0xf, %cl # rcx <- A GET_VREG %eax, %rcx # eax <- vA cmpl VREG_ADDRESS(rINSTq), %eax # compare (vA, vB) - movl $2, rINST # assume not taken jl 1f movswq 2(rPC), rINSTq # Get signed branch offset + testq rINSTq, rINSTq + jmp MterpCommonTakenBranch 1: - MTERP_PROFILE_BRANCH - addq rINSTq, rINSTq # rax <- AA * 2 - leaq (rPC, rINSTq), rPC - FETCH_INST - jle MterpCheckSuspendAndContinue # AA * 2 <= 0 => suspend check - GOTO_NEXT + cmpl $JIT_CHECK_OSR, rPROFILE + je .L_check_not_taken_osr + ADVANCE_PC_FETCH_AND_GOTO_NEXT 2 /* ------------------------------ */ @@ -1429,16 +1399,14 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop andb $0xf, %cl # rcx <- A GET_VREG %eax, %rcx # eax <- vA cmpl VREG_ADDRESS(rINSTq), %eax # compare (vA, vB) - movl $2, rINST # assume not taken jle 1f movswq 2(rPC), rINSTq # Get signed branch offset + testq rINSTq, rINSTq + jmp MterpCommonTakenBranch 1: - MTERP_PROFILE_BRANCH - addq rINSTq, rINSTq # rax <- AA * 2 - leaq (rPC, rINSTq), rPC - FETCH_INST - jle MterpCheckSuspendAndContinue # AA * 2 <= 0 => suspend check - GOTO_NEXT + cmpl $JIT_CHECK_OSR, rPROFILE + je .L_check_not_taken_osr + ADVANCE_PC_FETCH_AND_GOTO_NEXT 2 /* ------------------------------ */ @@ -1459,16 +1427,14 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop andb $0xf, %cl # rcx <- A GET_VREG %eax, %rcx # eax <- vA cmpl VREG_ADDRESS(rINSTq), %eax # compare (vA, vB) - movl $2, rINST # assume not taken jg 1f movswq 2(rPC), rINSTq # Get signed branch offset + testq rINSTq, rINSTq + jmp MterpCommonTakenBranch 1: - MTERP_PROFILE_BRANCH - addq rINSTq, rINSTq # rax <- AA * 2 - leaq (rPC, rINSTq), rPC - FETCH_INST - jle MterpCheckSuspendAndContinue # AA * 2 <= 0 => suspend check - GOTO_NEXT + cmpl $JIT_CHECK_OSR, rPROFILE + je .L_check_not_taken_osr + ADVANCE_PC_FETCH_AND_GOTO_NEXT 2 /* ------------------------------ */ @@ -1485,16 +1451,14 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop */ /* if-cmp vAA, +BBBB */ cmpl $0, VREG_ADDRESS(rINSTq) # compare (vA, 0) - movl $2, rINST # assume branch not taken jne 1f movswq 2(rPC), rINSTq # fetch signed displacement + testq rINSTq, rINSTq + jmp MterpCommonTakenBranch 1: - MTERP_PROFILE_BRANCH - addq rINSTq, rINSTq # rINSTq <- AA * 2 - leaq (rPC, rINSTq), rPC - FETCH_INST - jle MterpCheckSuspendAndContinue # AA * 2 <= 0 => suspend check - GOTO_NEXT + cmpl $JIT_CHECK_OSR, rPROFILE + je .L_check_not_taken_osr + ADVANCE_PC_FETCH_AND_GOTO_NEXT 2 /* ------------------------------ */ @@ -1511,16 +1475,14 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop */ /* if-cmp vAA, +BBBB */ cmpl $0, VREG_ADDRESS(rINSTq) # compare (vA, 0) - movl $2, rINST # assume branch not taken je 1f movswq 2(rPC), rINSTq # fetch signed displacement + testq rINSTq, rINSTq + jmp MterpCommonTakenBranch 1: - MTERP_PROFILE_BRANCH - addq rINSTq, rINSTq # rINSTq <- AA * 2 - leaq (rPC, rINSTq), rPC - FETCH_INST - jle MterpCheckSuspendAndContinue # AA * 2 <= 0 => suspend check - GOTO_NEXT + cmpl $JIT_CHECK_OSR, rPROFILE + je .L_check_not_taken_osr + ADVANCE_PC_FETCH_AND_GOTO_NEXT 2 /* ------------------------------ */ @@ -1537,16 +1499,14 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop */ /* if-cmp vAA, +BBBB */ cmpl $0, VREG_ADDRESS(rINSTq) # compare (vA, 0) - movl $2, rINST # assume branch not taken jge 1f movswq 2(rPC), rINSTq # fetch signed displacement + testq rINSTq, rINSTq + jmp MterpCommonTakenBranch 1: - MTERP_PROFILE_BRANCH - addq rINSTq, rINSTq # rINSTq <- AA * 2 - leaq (rPC, rINSTq), rPC - FETCH_INST - jle MterpCheckSuspendAndContinue # AA * 2 <= 0 => suspend check - GOTO_NEXT + cmpl $JIT_CHECK_OSR, rPROFILE + je .L_check_not_taken_osr + ADVANCE_PC_FETCH_AND_GOTO_NEXT 2 /* ------------------------------ */ @@ -1563,16 +1523,14 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop */ /* if-cmp vAA, +BBBB */ cmpl $0, VREG_ADDRESS(rINSTq) # compare (vA, 0) - movl $2, rINST # assume branch not taken jl 1f movswq 2(rPC), rINSTq # fetch signed displacement + testq rINSTq, rINSTq + jmp MterpCommonTakenBranch 1: - MTERP_PROFILE_BRANCH - addq rINSTq, rINSTq # rINSTq <- AA * 2 - leaq (rPC, rINSTq), rPC - FETCH_INST - jle MterpCheckSuspendAndContinue # AA * 2 <= 0 => suspend check - GOTO_NEXT + cmpl $JIT_CHECK_OSR, rPROFILE + je .L_check_not_taken_osr + ADVANCE_PC_FETCH_AND_GOTO_NEXT 2 /* ------------------------------ */ @@ -1589,16 +1547,14 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop */ /* if-cmp vAA, +BBBB */ cmpl $0, VREG_ADDRESS(rINSTq) # compare (vA, 0) - movl $2, rINST # assume branch not taken jle 1f movswq 2(rPC), rINSTq # fetch signed displacement + testq rINSTq, rINSTq + jmp MterpCommonTakenBranch 1: - MTERP_PROFILE_BRANCH - addq rINSTq, rINSTq # rINSTq <- AA * 2 - leaq (rPC, rINSTq), rPC - FETCH_INST - jle MterpCheckSuspendAndContinue # AA * 2 <= 0 => suspend check - GOTO_NEXT + cmpl $JIT_CHECK_OSR, rPROFILE + je .L_check_not_taken_osr + ADVANCE_PC_FETCH_AND_GOTO_NEXT 2 /* ------------------------------ */ @@ -1615,16 +1571,14 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop */ /* if-cmp vAA, +BBBB */ cmpl $0, VREG_ADDRESS(rINSTq) # compare (vA, 0) - movl $2, rINST # assume branch not taken jg 1f movswq 2(rPC), rINSTq # fetch signed displacement + testq rINSTq, rINSTq + jmp MterpCommonTakenBranch 1: - MTERP_PROFILE_BRANCH - addq rINSTq, rINSTq # rINSTq <- AA * 2 - leaq (rPC, rINSTq), rPC - FETCH_INST - jle MterpCheckSuspendAndContinue # AA * 2 <= 0 => suspend check - GOTO_NEXT + cmpl $JIT_CHECK_OSR, rPROFILE + je .L_check_not_taken_osr + ADVANCE_PC_FETCH_AND_GOTO_NEXT 2 /* ------------------------------ */ @@ -1767,7 +1721,8 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop GET_VREG OUT_32_ARG1, %rcx # ecx <- vCC (requested index) EXPORT_PC call SYMBOL(artAGetObjectFromMterp) # (array, index) - cmpq $0, THREAD_EXCEPTION_OFFSET(rSELF) + movq rSELF, %rcx + cmpq $0, THREAD_EXCEPTION_OFFSET(%rcx) jnz MterpException SET_VREG_OBJECT %eax, rINSTq ADVANCE_PC_FETCH_AND_GOTO_NEXT 2 @@ -2099,7 +2054,8 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop movq OFF_FP_METHOD(rFP), OUT_ARG2 # referrer movq rSELF, OUT_ARG3 call SYMBOL(artGet32InstanceFromCode) - cmpq $0, THREAD_EXCEPTION_OFFSET(rSELF) + movq rSELF, %rcx + cmpq $0, THREAD_EXCEPTION_OFFSET(%rcx) jnz MterpException # bail out andb $0xf, rINSTbl # rINST <- A .if 0 @@ -2131,7 +2087,8 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop movq OFF_FP_METHOD(rFP), OUT_ARG2 # referrer movq rSELF, OUT_ARG3 call SYMBOL(artGet64InstanceFromCode) - cmpq $0, THREAD_EXCEPTION_OFFSET(rSELF) + movq rSELF, %rcx + cmpq $0, THREAD_EXCEPTION_OFFSET(%rcx) jnz MterpException # bail out andb $0xf, rINSTbl # rINST <- A .if 0 @@ -2164,7 +2121,8 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop movq OFF_FP_METHOD(rFP), OUT_ARG2 # referrer movq rSELF, OUT_ARG3 call SYMBOL(artGetObjInstanceFromCode) - cmpq $0, THREAD_EXCEPTION_OFFSET(rSELF) + movq rSELF, %rcx + cmpq $0, THREAD_EXCEPTION_OFFSET(%rcx) jnz MterpException # bail out andb $0xf, rINSTbl # rINST <- A .if 1 @@ -2197,7 +2155,8 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop movq OFF_FP_METHOD(rFP), OUT_ARG2 # referrer movq rSELF, OUT_ARG3 call SYMBOL(artGetBooleanInstanceFromCode) - cmpq $0, THREAD_EXCEPTION_OFFSET(rSELF) + movq rSELF, %rcx + cmpq $0, THREAD_EXCEPTION_OFFSET(%rcx) jnz MterpException # bail out andb $0xf, rINSTbl # rINST <- A .if 0 @@ -2230,7 +2189,8 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop movq OFF_FP_METHOD(rFP), OUT_ARG2 # referrer movq rSELF, OUT_ARG3 call SYMBOL(artGetByteInstanceFromCode) - cmpq $0, THREAD_EXCEPTION_OFFSET(rSELF) + movq rSELF, %rcx + cmpq $0, THREAD_EXCEPTION_OFFSET(%rcx) jnz MterpException # bail out andb $0xf, rINSTbl # rINST <- A .if 0 @@ -2263,7 +2223,8 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop movq OFF_FP_METHOD(rFP), OUT_ARG2 # referrer movq rSELF, OUT_ARG3 call SYMBOL(artGetCharInstanceFromCode) - cmpq $0, THREAD_EXCEPTION_OFFSET(rSELF) + movq rSELF, %rcx + cmpq $0, THREAD_EXCEPTION_OFFSET(%rcx) jnz MterpException # bail out andb $0xf, rINSTbl # rINST <- A .if 0 @@ -2296,7 +2257,8 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop movq OFF_FP_METHOD(rFP), OUT_ARG2 # referrer movq rSELF, OUT_ARG3 call SYMBOL(artGetShortInstanceFromCode) - cmpq $0, THREAD_EXCEPTION_OFFSET(rSELF) + movq rSELF, %rcx + cmpq $0, THREAD_EXCEPTION_OFFSET(%rcx) jnz MterpException # bail out andb $0xf, rINSTbl # rINST <- A .if 0 @@ -2489,7 +2451,8 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop movq OFF_FP_METHOD(rFP), OUT_ARG1 # referrer movq rSELF, OUT_ARG2 # self call SYMBOL(artGet32StaticFromCode) - cmpl $0, THREAD_EXCEPTION_OFFSET(rSELF) + movq rSELF, %rcx + cmpl $0, THREAD_EXCEPTION_OFFSET(%rcx) jnz MterpException .if 0 SET_VREG_OBJECT %eax, rINSTq # fp[A] <- value @@ -2519,7 +2482,8 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop movq OFF_FP_METHOD(rFP), OUT_ARG1 # referrer movq rSELF, OUT_ARG2 # self call SYMBOL(artGet64StaticFromCode) - cmpl $0, THREAD_EXCEPTION_OFFSET(rSELF) + movq rSELF, %rcx + cmpl $0, THREAD_EXCEPTION_OFFSET(%rcx) jnz MterpException .if 0 SET_VREG_OBJECT %eax, rINSTq # fp[A] <- value @@ -2550,7 +2514,8 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop movq OFF_FP_METHOD(rFP), OUT_ARG1 # referrer movq rSELF, OUT_ARG2 # self call SYMBOL(artGetObjStaticFromCode) - cmpl $0, THREAD_EXCEPTION_OFFSET(rSELF) + movq rSELF, %rcx + cmpl $0, THREAD_EXCEPTION_OFFSET(%rcx) jnz MterpException .if 1 SET_VREG_OBJECT %eax, rINSTq # fp[A] <- value @@ -2581,7 +2546,8 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop movq OFF_FP_METHOD(rFP), OUT_ARG1 # referrer movq rSELF, OUT_ARG2 # self call SYMBOL(artGetBooleanStaticFromCode) - cmpl $0, THREAD_EXCEPTION_OFFSET(rSELF) + movq rSELF, %rcx + cmpl $0, THREAD_EXCEPTION_OFFSET(%rcx) jnz MterpException .if 0 SET_VREG_OBJECT %eax, rINSTq # fp[A] <- value @@ -2612,7 +2578,8 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop movq OFF_FP_METHOD(rFP), OUT_ARG1 # referrer movq rSELF, OUT_ARG2 # self call SYMBOL(artGetByteStaticFromCode) - cmpl $0, THREAD_EXCEPTION_OFFSET(rSELF) + movq rSELF, %rcx + cmpl $0, THREAD_EXCEPTION_OFFSET(%rcx) jnz MterpException .if 0 SET_VREG_OBJECT %eax, rINSTq # fp[A] <- value @@ -2643,7 +2610,8 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop movq OFF_FP_METHOD(rFP), OUT_ARG1 # referrer movq rSELF, OUT_ARG2 # self call SYMBOL(artGetCharStaticFromCode) - cmpl $0, THREAD_EXCEPTION_OFFSET(rSELF) + movq rSELF, %rcx + cmpl $0, THREAD_EXCEPTION_OFFSET(%rcx) jnz MterpException .if 0 SET_VREG_OBJECT %eax, rINSTq # fp[A] <- value @@ -2674,7 +2642,8 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop movq OFF_FP_METHOD(rFP), OUT_ARG1 # referrer movq rSELF, OUT_ARG2 # self call SYMBOL(artGetShortStaticFromCode) - cmpl $0, THREAD_EXCEPTION_OFFSET(rSELF) + movq rSELF, %rcx + cmpl $0, THREAD_EXCEPTION_OFFSET(%rcx) jnz MterpException .if 0 SET_VREG_OBJECT %eax, rINSTq # fp[A] <- value @@ -3002,9 +2971,9 @@ SYMBOL(artMterpAsmInstructionStart) = .L_op_nop .balign 128 .L_op_return_void_no_barrier: /* 0x73 */ /* File: x86_64/op_return_void_no_barrier.S */ - testl $(THREAD_SUSPEND_REQUEST | THREAD_CHECKPOINT_REQUEST), THREAD_FLAGS_OFFSET(rSELF) - jz 1f movq rSELF, OUT_ARG0 + testl $(THREAD_SUSPEND_REQUEST | THREAD_CHECKPOINT_REQUEST), THREAD_FLAGS_OFFSET(OUT_ARG0) + jz 1f call SYMBOL(MterpSuspendCheck) 1: xorq %rax, %rax @@ -5712,7 +5681,8 @@ movswl %ax, %eax movzwl 2(rPC), OUT_32_ARG1 # eax <- field byte offset EXPORT_PC callq SYMBOL(artIGetObjectFromMterp) # (obj, offset) - cmpq $0, THREAD_EXCEPTION_OFFSET(rSELF) + movq rSELF, %rcx + cmpq $0, THREAD_EXCEPTION_OFFSET(%rcx) jnz MterpException # bail out andb $0xf, rINSTbl # rINST <- A SET_VREG_OBJECT %eax, rINSTq # fp[A] <- value @@ -11849,7 +11819,7 @@ MterpSuspendFallback: #if MTERP_LOGGING movq rSELF, OUT_ARG0 leaq OFF_FP_SHADOWFRAME(rFP), OUT_ARG1 - movl THREAD_FLAGS_OFFSET(rSELF), OUT_32_ARG2 + movl THREAD_FLAGS_OFFSET(OUT_ARG0), OUT_32_ARG2 call SYMBOL(MterpLogSuspendFallback) #endif jmp MterpCommonFallback @@ -11860,7 +11830,8 @@ MterpSuspendFallback: * interpreter. */ MterpPossibleException: - cmpq $0, THREAD_EXCEPTION_OFFSET(rSELF) + movq rSELF, %rcx + cmpq $0, THREAD_EXCEPTION_OFFSET(%rcx) jz MterpFallback /* intentional fallthrough - handle pending exception. */ @@ -11891,19 +11862,114 @@ MterpException: /* NOTE: no fallthrough */ /* - * Check for suspend check request. Assumes rINST already loaded, rPC advanced and - * still needs to get the opcode and branch to it, and flags are in lr. + * Common handling for branches with support for Jit profiling. + * On entry: + * rINST <= signed offset + * rPROFILE <= signed hotness countdown (expanded to 32 bits) + * condition bits <= set to establish sign of offset (use "NoFlags" entry if not) + * + * We have quite a few different cases for branch profiling, OSR detection and + * suspend check support here. + * + * Taken backward branches: + * If profiling active, do hotness countdown and report if we hit zero. + * If in osr check mode, see if our target is a compiled loop header entry and do OSR if so. + * Is there a pending suspend request? If so, suspend. + * + * Taken forward branches and not-taken backward branches: + * If in osr check mode, see if our target is a compiled loop header entry and do OSR if so. + * + * Our most common case is expected to be a taken backward branch with active jit profiling, + * but no full OSR check and no pending suspend request. + * Next most common case is not-taken branch with no full OSR check. + * + */ +MterpCommonTakenBranch: + jg .L_forward_branch # don't add forward branches to hotness +/* + * We need to subtract 1 from positive values and we should not see 0 here, + * so we may use the result of the comparison with -1. */ -MterpCheckSuspendAndContinue: - REFRESH_IBASE - testl $(THREAD_SUSPEND_REQUEST | THREAD_CHECKPOINT_REQUEST), THREAD_FLAGS_OFFSET(rSELF) - jz 1f +#if JIT_CHECK_OSR != -1 +# error "JIT_CHECK_OSR must be -1." +#endif + cmpl $JIT_CHECK_OSR, rPROFILE + je .L_osr_check + decl rPROFILE + je .L_add_batch # counted down to zero - report +.L_resume_backward_branch: + movq rSELF, %rax + testl $(THREAD_SUSPEND_REQUEST | THREAD_CHECKPOINT_REQUEST), THREAD_FLAGS_OFFSET(%rax) + REFRESH_IBASE + leaq (rPC, rINSTq, 2), rPC + FETCH_INST + jnz .L_suspend_request_pending + GOTO_NEXT + +.L_suspend_request_pending: EXPORT_PC movq rSELF, OUT_ARG0 - call SYMBOL(MterpSuspendCheck) -1: + call SYMBOL(MterpSuspendCheck) # (self) + testb %al, %al + jnz MterpFallback + REFRESH_IBASE # might have changed during suspend + GOTO_NEXT + +.L_no_count_backwards: + cmpl $JIT_CHECK_OSR, rPROFILE # possible OSR re-entry? + jne .L_resume_backward_branch +.L_osr_check: + EXPORT_PC + movq rSELF, OUT_ARG0 + leaq OFF_FP_SHADOWFRAME(rFP), OUT_ARG1 + movq rINSTq, OUT_ARG2 + call SYMBOL(MterpMaybeDoOnStackReplacement) # (self, shadow_frame, offset) + testb %al, %al + jz .L_resume_backward_branch + jmp MterpOnStackReplacement + +.L_forward_branch: + cmpl $JIT_CHECK_OSR, rPROFILE # possible OSR re-entry? + je .L_check_osr_forward +.L_resume_forward_branch: + leaq (rPC, rINSTq, 2), rPC + FETCH_INST GOTO_NEXT +.L_check_osr_forward: + EXPORT_PC + movq rSELF, OUT_ARG0 + leaq OFF_FP_SHADOWFRAME(rFP), OUT_ARG1 + movq rINSTq, OUT_ARG2 + call SYMBOL(MterpMaybeDoOnStackReplacement) # (self, shadow_frame, offset) + testb %al, %al + jz .L_resume_forward_branch + jmp MterpOnStackReplacement + +.L_add_batch: + movl rPROFILE, %eax + movq OFF_FP_METHOD(rFP), OUT_ARG0 + leaq OFF_FP_SHADOWFRAME(rFP), OUT_ARG1 + movw %ax, OFF_FP_COUNTDOWN_OFFSET(rFP) + movq rSELF, OUT_ARG2 + call SYMBOL(MterpAddHotnessBatch) # (method, shadow_frame, self) + movswl %ax, rPROFILE + jmp .L_no_count_backwards + +/* + * Entered from the conditional branch handlers when OSR check request active on + * not-taken path. All Dalvik not-taken conditional branch offsets are 2. + */ +.L_check_not_taken_osr: + EXPORT_PC + movq rSELF, OUT_ARG0 + leaq OFF_FP_SHADOWFRAME(rFP), OUT_ARG1 + movl $2, OUT_32_ARG2 + call SYMBOL(MterpMaybeDoOnStackReplacement) # (self, shadow_frame, offset) + testb %al, %al + jnz MterpOnStackReplacement + ADVANCE_PC_FETCH_AND_GOTO_NEXT 2 + /* * On-stack replacement has happened, and now we've returned from the compiled method. */ @@ -11943,7 +12009,28 @@ MterpReturn: movq %rax, (%rdx) movl $1, %eax MterpDone: +/* + * At this point, we expect rPROFILE to be non-zero. If negative, hotness is disabled or we're + * checking for OSR. If greater than zero, we might have unreported hotness to register + * (the difference between the ending rPROFILE and the cached hotness counter). rPROFILE + * should only reach zero immediately after a hotness decrement, and is then reset to either + * a negative special state or the new non-zero countdown value. + */ + testl rPROFILE, rPROFILE + jle MRestoreFrame # if > 0, we may have some counts to report. + + movl %eax, rINST # stash return value + /* Report cached hotness counts */ + movl rPROFILE, %eax + movq OFF_FP_METHOD(rFP), OUT_ARG0 + leaq OFF_FP_SHADOWFRAME(rFP), OUT_ARG1 + movw %ax, OFF_FP_COUNTDOWN_OFFSET(rFP) + movq rSELF, OUT_ARG2 + call SYMBOL(MterpAddHotnessBatch) # (method, shadow_frame, self) + movl rINST, %eax # restore return value + /* pop up frame */ +MRestoreFrame: addq $FRAME_SIZE, %rsp .cfi_adjust_cfa_offset -FRAME_SIZE diff --git a/runtime/interpreter/mterp/x86/footer.S b/runtime/interpreter/mterp/x86/footer.S index df10ff03e1..e8c8ca8d79 100644 --- a/runtime/interpreter/mterp/x86/footer.S +++ b/runtime/interpreter/mterp/x86/footer.S @@ -234,11 +234,12 @@ MterpCommonTakenBranch: * not-taken path. All Dalvik not-taken conditional branch offsets are 2. */ .L_check_not_taken_osr: + EXPORT_PC movl rSELF, %eax movl %eax, OUT_ARG0(%esp) leal OFF_FP_SHADOWFRAME(rFP), %ecx movl %ecx, OUT_ARG1(%esp) - movl rINST, OUT_ARG3(%esp) + movl $$2, OUT_ARG2(%esp) call SYMBOL(MterpMaybeDoOnStackReplacement) # (self, shadow_frame, offset) testb %al, %al REFRESH_IBASE diff --git a/runtime/interpreter/mterp/x86_64/bincmp.S b/runtime/interpreter/mterp/x86_64/bincmp.S index a16050b371..6601483ebe 100644 --- a/runtime/interpreter/mterp/x86_64/bincmp.S +++ b/runtime/interpreter/mterp/x86_64/bincmp.S @@ -11,13 +11,11 @@ andb $$0xf, %cl # rcx <- A GET_VREG %eax, %rcx # eax <- vA cmpl VREG_ADDRESS(rINSTq), %eax # compare (vA, vB) - movl $$2, rINST # assume not taken j${revcmp} 1f movswq 2(rPC), rINSTq # Get signed branch offset + testq rINSTq, rINSTq + jmp MterpCommonTakenBranch 1: - MTERP_PROFILE_BRANCH - addq rINSTq, rINSTq # rax <- AA * 2 - leaq (rPC, rINSTq), rPC - FETCH_INST - jle MterpCheckSuspendAndContinue # AA * 2 <= 0 => suspend check - GOTO_NEXT + cmpl $$JIT_CHECK_OSR, rPROFILE + je .L_check_not_taken_osr + ADVANCE_PC_FETCH_AND_GOTO_NEXT 2 diff --git a/runtime/interpreter/mterp/x86_64/entry.S b/runtime/interpreter/mterp/x86_64/entry.S index 69b2371dea..d992956769 100644 --- a/runtime/interpreter/mterp/x86_64/entry.S +++ b/runtime/interpreter/mterp/x86_64/entry.S @@ -65,6 +65,12 @@ SYMBOL(ExecuteMterpImpl): movq IN_ARG0, rSELF REFRESH_IBASE + /* Set up for backwards branches & osr profiling */ + movq OFF_FP_METHOD(rFP), OUT_ARG0 + leaq OFF_FP_SHADOWFRAME(rFP), OUT_ARG1 + call SYMBOL(MterpSetUpHotnessCountdown) + movswl %ax, rPROFILE + /* start executing the instruction at rPC */ FETCH_INST GOTO_NEXT diff --git a/runtime/interpreter/mterp/x86_64/footer.S b/runtime/interpreter/mterp/x86_64/footer.S index 573256b781..f78f163576 100644 --- a/runtime/interpreter/mterp/x86_64/footer.S +++ b/runtime/interpreter/mterp/x86_64/footer.S @@ -71,7 +71,7 @@ MterpSuspendFallback: #if MTERP_LOGGING movq rSELF, OUT_ARG0 leaq OFF_FP_SHADOWFRAME(rFP), OUT_ARG1 - movl THREAD_FLAGS_OFFSET(rSELF), OUT_32_ARG2 + movl THREAD_FLAGS_OFFSET(OUT_ARG0), OUT_32_ARG2 call SYMBOL(MterpLogSuspendFallback) #endif jmp MterpCommonFallback @@ -82,7 +82,8 @@ MterpSuspendFallback: * interpreter. */ MterpPossibleException: - cmpq $$0, THREAD_EXCEPTION_OFFSET(rSELF) + movq rSELF, %rcx + cmpq $$0, THREAD_EXCEPTION_OFFSET(%rcx) jz MterpFallback /* intentional fallthrough - handle pending exception. */ @@ -113,19 +114,114 @@ MterpException: /* NOTE: no fallthrough */ /* - * Check for suspend check request. Assumes rINST already loaded, rPC advanced and - * still needs to get the opcode and branch to it, and flags are in lr. + * Common handling for branches with support for Jit profiling. + * On entry: + * rINST <= signed offset + * rPROFILE <= signed hotness countdown (expanded to 32 bits) + * condition bits <= set to establish sign of offset (use "NoFlags" entry if not) + * + * We have quite a few different cases for branch profiling, OSR detection and + * suspend check support here. + * + * Taken backward branches: + * If profiling active, do hotness countdown and report if we hit zero. + * If in osr check mode, see if our target is a compiled loop header entry and do OSR if so. + * Is there a pending suspend request? If so, suspend. + * + * Taken forward branches and not-taken backward branches: + * If in osr check mode, see if our target is a compiled loop header entry and do OSR if so. + * + * Our most common case is expected to be a taken backward branch with active jit profiling, + * but no full OSR check and no pending suspend request. + * Next most common case is not-taken branch with no full OSR check. + * */ -MterpCheckSuspendAndContinue: +MterpCommonTakenBranch: + jg .L_forward_branch # don't add forward branches to hotness +/* + * We need to subtract 1 from positive values and we should not see 0 here, + * so we may use the result of the comparison with -1. + */ +#if JIT_CHECK_OSR != -1 +# error "JIT_CHECK_OSR must be -1." +#endif + cmpl $$JIT_CHECK_OSR, rPROFILE + je .L_osr_check + decl rPROFILE + je .L_add_batch # counted down to zero - report +.L_resume_backward_branch: + movq rSELF, %rax + testl $$(THREAD_SUSPEND_REQUEST | THREAD_CHECKPOINT_REQUEST), THREAD_FLAGS_OFFSET(%rax) REFRESH_IBASE - testl $$(THREAD_SUSPEND_REQUEST | THREAD_CHECKPOINT_REQUEST), THREAD_FLAGS_OFFSET(rSELF) - jz 1f + leaq (rPC, rINSTq, 2), rPC + FETCH_INST + jnz .L_suspend_request_pending + GOTO_NEXT + +.L_suspend_request_pending: EXPORT_PC movq rSELF, OUT_ARG0 - call SYMBOL(MterpSuspendCheck) -1: + call SYMBOL(MterpSuspendCheck) # (self) + testb %al, %al + jnz MterpFallback + REFRESH_IBASE # might have changed during suspend GOTO_NEXT +.L_no_count_backwards: + cmpl $$JIT_CHECK_OSR, rPROFILE # possible OSR re-entry? + jne .L_resume_backward_branch +.L_osr_check: + EXPORT_PC + movq rSELF, OUT_ARG0 + leaq OFF_FP_SHADOWFRAME(rFP), OUT_ARG1 + movq rINSTq, OUT_ARG2 + call SYMBOL(MterpMaybeDoOnStackReplacement) # (self, shadow_frame, offset) + testb %al, %al + jz .L_resume_backward_branch + jmp MterpOnStackReplacement + +.L_forward_branch: + cmpl $$JIT_CHECK_OSR, rPROFILE # possible OSR re-entry? + je .L_check_osr_forward +.L_resume_forward_branch: + leaq (rPC, rINSTq, 2), rPC + FETCH_INST + GOTO_NEXT + +.L_check_osr_forward: + EXPORT_PC + movq rSELF, OUT_ARG0 + leaq OFF_FP_SHADOWFRAME(rFP), OUT_ARG1 + movq rINSTq, OUT_ARG2 + call SYMBOL(MterpMaybeDoOnStackReplacement) # (self, shadow_frame, offset) + testb %al, %al + jz .L_resume_forward_branch + jmp MterpOnStackReplacement + +.L_add_batch: + movl rPROFILE, %eax + movq OFF_FP_METHOD(rFP), OUT_ARG0 + leaq OFF_FP_SHADOWFRAME(rFP), OUT_ARG1 + movw %ax, OFF_FP_COUNTDOWN_OFFSET(rFP) + movq rSELF, OUT_ARG2 + call SYMBOL(MterpAddHotnessBatch) # (method, shadow_frame, self) + movswl %ax, rPROFILE + jmp .L_no_count_backwards + +/* + * Entered from the conditional branch handlers when OSR check request active on + * not-taken path. All Dalvik not-taken conditional branch offsets are 2. + */ +.L_check_not_taken_osr: + EXPORT_PC + movq rSELF, OUT_ARG0 + leaq OFF_FP_SHADOWFRAME(rFP), OUT_ARG1 + movl $$2, OUT_32_ARG2 + call SYMBOL(MterpMaybeDoOnStackReplacement) # (self, shadow_frame, offset) + testb %al, %al + jnz MterpOnStackReplacement + ADVANCE_PC_FETCH_AND_GOTO_NEXT 2 + /* * On-stack replacement has happened, and now we've returned from the compiled method. */ @@ -165,7 +261,28 @@ MterpReturn: movq %rax, (%rdx) movl $$1, %eax MterpDone: +/* + * At this point, we expect rPROFILE to be non-zero. If negative, hotness is disabled or we're + * checking for OSR. If greater than zero, we might have unreported hotness to register + * (the difference between the ending rPROFILE and the cached hotness counter). rPROFILE + * should only reach zero immediately after a hotness decrement, and is then reset to either + * a negative special state or the new non-zero countdown value. + */ + testl rPROFILE, rPROFILE + jle MRestoreFrame # if > 0, we may have some counts to report. + + movl %eax, rINST # stash return value + /* Report cached hotness counts */ + movl rPROFILE, %eax + movq OFF_FP_METHOD(rFP), OUT_ARG0 + leaq OFF_FP_SHADOWFRAME(rFP), OUT_ARG1 + movw %ax, OFF_FP_COUNTDOWN_OFFSET(rFP) + movq rSELF, OUT_ARG2 + call SYMBOL(MterpAddHotnessBatch) # (method, shadow_frame, self) + movl rINST, %eax # restore return value + /* pop up frame */ +MRestoreFrame: addq $$FRAME_SIZE, %rsp .cfi_adjust_cfa_offset -FRAME_SIZE diff --git a/runtime/interpreter/mterp/x86_64/header.S b/runtime/interpreter/mterp/x86_64/header.S index eb84ea1eb5..7699fc4dd7 100644 --- a/runtime/interpreter/mterp/x86_64/header.S +++ b/runtime/interpreter/mterp/x86_64/header.S @@ -60,7 +60,7 @@ Mterp and x86_64 notes: Some key interpreter variables will be assigned to registers. nick reg purpose - rSELF rbp pointer to ThreadSelf. + rPROFILE rbp countdown register for jit profiling rPC r12 interpreted program counter, used for fetching instructions rFP r13 interpreted frame pointer, used for accessing locals and args rINSTw bx first 16-bit code of current instruction @@ -113,6 +113,21 @@ unspecified registers or condition codes. .cfi_restore \_reg .endm +/* + * Instead of holding a pointer to the shadow frame, we keep rFP at the base of the vregs. So, + * to access other shadow frame fields, we need to use a backwards offset. Define those here. + */ +#define OFF_FP(a) (a - SHADOWFRAME_VREGS_OFFSET) +#define OFF_FP_NUMBER_OF_VREGS OFF_FP(SHADOWFRAME_NUMBER_OF_VREGS_OFFSET) +#define OFF_FP_DEX_PC OFF_FP(SHADOWFRAME_DEX_PC_OFFSET) +#define OFF_FP_LINK OFF_FP(SHADOWFRAME_LINK_OFFSET) +#define OFF_FP_METHOD OFF_FP(SHADOWFRAME_METHOD_OFFSET) +#define OFF_FP_RESULT_REGISTER OFF_FP(SHADOWFRAME_RESULT_REGISTER_OFFSET) +#define OFF_FP_DEX_PC_PTR OFF_FP(SHADOWFRAME_DEX_PC_PTR_OFFSET) +#define OFF_FP_CODE_ITEM OFF_FP(SHADOWFRAME_CODE_ITEM_OFFSET) +#define OFF_FP_COUNTDOWN_OFFSET OFF_FP(SHADOWFRAME_HOTNESS_COUNTDOWN_OFFSET) +#define OFF_FP_SHADOWFRAME (-SHADOWFRAME_VREGS_OFFSET) + /* Frame size must be 16-byte aligned. * Remember about 8 bytes for return address + 6 * 8 for spills. */ @@ -123,6 +138,8 @@ unspecified registers or condition codes. #define IN_ARG2 %rdx #define IN_ARG1 %rsi #define IN_ARG0 %rdi +/* Spill offsets relative to %esp */ +#define SELF_SPILL (FRAME_SIZE - 8) /* Out Args */ #define OUT_ARG3 %rcx #define OUT_ARG2 %rdx @@ -137,7 +154,7 @@ unspecified registers or condition codes. /* During bringup, we'll use the shadow frame model instead of rFP */ /* single-purpose registers, given names for clarity */ -#define rSELF %rbp +#define rSELF SELF_SPILL(%rsp) #define rPC %r12 #define rFP %r13 #define rINST %ebx @@ -147,40 +164,11 @@ unspecified registers or condition codes. #define rINSTbl %bl #define rIBASE %r14 #define rREFS %r15 +#define rPROFILE %ebp -/* - * Instead of holding a pointer to the shadow frame, we keep rFP at the base of the vregs. So, - * to access other shadow frame fields, we need to use a backwards offset. Define those here. - */ -#define OFF_FP(a) (a - SHADOWFRAME_VREGS_OFFSET) -#define OFF_FP_NUMBER_OF_VREGS OFF_FP(SHADOWFRAME_NUMBER_OF_VREGS_OFFSET) -#define OFF_FP_DEX_PC OFF_FP(SHADOWFRAME_DEX_PC_OFFSET) -#define OFF_FP_LINK OFF_FP(SHADOWFRAME_LINK_OFFSET) -#define OFF_FP_METHOD OFF_FP(SHADOWFRAME_METHOD_OFFSET) -#define OFF_FP_RESULT_REGISTER OFF_FP(SHADOWFRAME_RESULT_REGISTER_OFFSET) -#define OFF_FP_DEX_PC_PTR OFF_FP(SHADOWFRAME_DEX_PC_PTR_OFFSET) -#define OFF_FP_CODE_ITEM OFF_FP(SHADOWFRAME_CODE_ITEM_OFFSET) -#define OFF_FP_SHADOWFRAME (-SHADOWFRAME_VREGS_OFFSET) - -#define MTERP_PROFILE_BRANCHES 1 #define MTERP_LOGGING 0 /* - * Profile branch. rINST should contain the offset. %eax is scratch. - */ -.macro MTERP_PROFILE_BRANCH -#ifdef MTERP_PROFILE_BRANCHES - EXPORT_PC - movq rSELF, OUT_ARG0 - leaq OFF_FP_SHADOWFRAME(rFP), OUT_ARG1 - movl rINST, OUT_32_ARG2 - call SYMBOL(MterpProfileBranch) - testb %al, %al - jnz MterpOnStackReplacement -#endif -.endm - -/* * "export" the PC to dex_pc field in the shadow frame, f/b/o future exception objects. Must * be done *before* something throws. * @@ -204,7 +192,8 @@ unspecified registers or condition codes. * */ .macro REFRESH_IBASE - movq THREAD_CURRENT_IBASE_OFFSET(rSELF), rIBASE + movq rSELF, rIBASE + movq THREAD_CURRENT_IBASE_OFFSET(rIBASE), rIBASE .endm /* diff --git a/runtime/interpreter/mterp/x86_64/op_aget_object.S b/runtime/interpreter/mterp/x86_64/op_aget_object.S index 8baedeab5e..5f77a97748 100644 --- a/runtime/interpreter/mterp/x86_64/op_aget_object.S +++ b/runtime/interpreter/mterp/x86_64/op_aget_object.S @@ -10,7 +10,8 @@ GET_VREG OUT_32_ARG1, %rcx # ecx <- vCC (requested index) EXPORT_PC call SYMBOL(artAGetObjectFromMterp) # (array, index) - cmpq $$0, THREAD_EXCEPTION_OFFSET(rSELF) + movq rSELF, %rcx + cmpq $$0, THREAD_EXCEPTION_OFFSET(%rcx) jnz MterpException SET_VREG_OBJECT %eax, rINSTq ADVANCE_PC_FETCH_AND_GOTO_NEXT 2 diff --git a/runtime/interpreter/mterp/x86_64/op_goto.S b/runtime/interpreter/mterp/x86_64/op_goto.S index c4fc97644f..9749901f5a 100644 --- a/runtime/interpreter/mterp/x86_64/op_goto.S +++ b/runtime/interpreter/mterp/x86_64/op_goto.S @@ -6,9 +6,5 @@ */ /* goto +AA */ movsbq rINSTbl, rINSTq # rINSTq <- ssssssAA - MTERP_PROFILE_BRANCH - addq rINSTq, rINSTq # rINSTq <- AA * 2 - leaq (rPC, rINSTq), rPC - FETCH_INST - jle MterpCheckSuspendAndContinue # AA * 2 <= 0 => suspend check - GOTO_NEXT + testq rINSTq, rINSTq + jmp MterpCommonTakenBranch diff --git a/runtime/interpreter/mterp/x86_64/op_goto_16.S b/runtime/interpreter/mterp/x86_64/op_goto_16.S index 8cb9a5c50f..77688e05e4 100644 --- a/runtime/interpreter/mterp/x86_64/op_goto_16.S +++ b/runtime/interpreter/mterp/x86_64/op_goto_16.S @@ -6,9 +6,5 @@ */ /* goto/16 +AAAA */ movswq 2(rPC), rINSTq # rINSTq <- ssssAAAA - MTERP_PROFILE_BRANCH - addq rINSTq, rINSTq # rINSTq <- AA * 2 - leaq (rPC, rINSTq), rPC - FETCH_INST - jle MterpCheckSuspendAndContinue # AA * 2 <= 0 => suspend check - GOTO_NEXT + testq rINSTq, rINSTq + jmp MterpCommonTakenBranch diff --git a/runtime/interpreter/mterp/x86_64/op_goto_32.S b/runtime/interpreter/mterp/x86_64/op_goto_32.S index 4ecdacd3e6..29d777b5a6 100644 --- a/runtime/interpreter/mterp/x86_64/op_goto_32.S +++ b/runtime/interpreter/mterp/x86_64/op_goto_32.S @@ -9,9 +9,5 @@ */ /* goto/32 +AAAAAAAA */ movslq 2(rPC), rINSTq # rINSTq <- AAAAAAAA - MTERP_PROFILE_BRANCH - addq rINSTq, rINSTq # rINSTq <- AA * 2 - leaq (rPC, rINSTq), rPC - FETCH_INST - jle MterpCheckSuspendAndContinue # AA * 2 <= 0 => suspend check - GOTO_NEXT + testq rINSTq, rINSTq + jmp MterpCommonTakenBranch diff --git a/runtime/interpreter/mterp/x86_64/op_iget.S b/runtime/interpreter/mterp/x86_64/op_iget.S index a0d0fafba1..df43efe6a4 100644 --- a/runtime/interpreter/mterp/x86_64/op_iget.S +++ b/runtime/interpreter/mterp/x86_64/op_iget.S @@ -12,7 +12,8 @@ movq OFF_FP_METHOD(rFP), OUT_ARG2 # referrer movq rSELF, OUT_ARG3 call SYMBOL($helper) - cmpq $$0, THREAD_EXCEPTION_OFFSET(rSELF) + movq rSELF, %rcx + cmpq $$0, THREAD_EXCEPTION_OFFSET(%rcx) jnz MterpException # bail out andb $$0xf, rINSTbl # rINST <- A .if $is_object diff --git a/runtime/interpreter/mterp/x86_64/op_iget_object_quick.S b/runtime/interpreter/mterp/x86_64/op_iget_object_quick.S index 964d20ad74..176c9544ef 100644 --- a/runtime/interpreter/mterp/x86_64/op_iget_object_quick.S +++ b/runtime/interpreter/mterp/x86_64/op_iget_object_quick.S @@ -7,7 +7,8 @@ movzwl 2(rPC), OUT_32_ARG1 # eax <- field byte offset EXPORT_PC callq SYMBOL(artIGetObjectFromMterp) # (obj, offset) - cmpq $$0, THREAD_EXCEPTION_OFFSET(rSELF) + movq rSELF, %rcx + cmpq $$0, THREAD_EXCEPTION_OFFSET(%rcx) jnz MterpException # bail out andb $$0xf, rINSTbl # rINST <- A SET_VREG_OBJECT %eax, rINSTq # fp[A] <- value diff --git a/runtime/interpreter/mterp/x86_64/op_instance_of.S b/runtime/interpreter/mterp/x86_64/op_instance_of.S index 6be37f9166..4819833658 100644 --- a/runtime/interpreter/mterp/x86_64/op_instance_of.S +++ b/runtime/interpreter/mterp/x86_64/op_instance_of.S @@ -14,7 +14,8 @@ movq rSELF, OUT_ARG3 call SYMBOL(MterpInstanceOf) # (index, &obj, method, self) movsbl %al, %eax - cmpq $$0, THREAD_EXCEPTION_OFFSET(rSELF) + movq rSELF, %rcx + cmpq $$0, THREAD_EXCEPTION_OFFSET(%rcx) jnz MterpException andb $$0xf, rINSTbl # rINSTbl <- A SET_VREG %eax, rINSTq diff --git a/runtime/interpreter/mterp/x86_64/op_move_exception.S b/runtime/interpreter/mterp/x86_64/op_move_exception.S index d0a14fdc8d..33db878236 100644 --- a/runtime/interpreter/mterp/x86_64/op_move_exception.S +++ b/runtime/interpreter/mterp/x86_64/op_move_exception.S @@ -1,5 +1,6 @@ /* move-exception vAA */ - movl THREAD_EXCEPTION_OFFSET(rSELF), %eax + movq rSELF, %rcx + movl THREAD_EXCEPTION_OFFSET(%rcx), %eax SET_VREG_OBJECT %eax, rINSTq # fp[AA] <- exception object - movl $$0, THREAD_EXCEPTION_OFFSET(rSELF) + movl $$0, THREAD_EXCEPTION_OFFSET(%rcx) ADVANCE_PC_FETCH_AND_GOTO_NEXT 1 diff --git a/runtime/interpreter/mterp/x86_64/op_packed_switch.S b/runtime/interpreter/mterp/x86_64/op_packed_switch.S index cb0acb7a72..fdf5a50f9c 100644 --- a/runtime/interpreter/mterp/x86_64/op_packed_switch.S +++ b/runtime/interpreter/mterp/x86_64/op_packed_switch.S @@ -13,10 +13,6 @@ leaq (rPC,OUT_ARG0,2), OUT_ARG0 # rcx <- PC + BBBBbbbb*2 GET_VREG OUT_32_ARG1, rINSTq # eax <- vAA call SYMBOL($func) + testl %eax, %eax movslq %eax, rINSTq - MTERP_PROFILE_BRANCH - addq rINSTq, rINSTq - leaq (rPC, rINSTq), rPC - FETCH_INST - jle MterpCheckSuspendAndContinue - GOTO_NEXT + jmp MterpCommonTakenBranch diff --git a/runtime/interpreter/mterp/x86_64/op_return.S b/runtime/interpreter/mterp/x86_64/op_return.S index 14f4f8a446..07e0e5357c 100644 --- a/runtime/interpreter/mterp/x86_64/op_return.S +++ b/runtime/interpreter/mterp/x86_64/op_return.S @@ -6,9 +6,9 @@ /* op vAA */ .extern MterpThreadFenceForConstructor call SYMBOL(MterpThreadFenceForConstructor) - testl $$(THREAD_SUSPEND_REQUEST | THREAD_CHECKPOINT_REQUEST), THREAD_FLAGS_OFFSET(rSELF) - jz 1f movq rSELF, OUT_ARG0 + testl $$(THREAD_SUSPEND_REQUEST | THREAD_CHECKPOINT_REQUEST), THREAD_FLAGS_OFFSET(OUT_ARG0) + jz 1f call SYMBOL(MterpSuspendCheck) 1: GET_VREG %eax, rINSTq # eax <- vAA diff --git a/runtime/interpreter/mterp/x86_64/op_return_void.S b/runtime/interpreter/mterp/x86_64/op_return_void.S index 46a5753c87..6a12df318b 100644 --- a/runtime/interpreter/mterp/x86_64/op_return_void.S +++ b/runtime/interpreter/mterp/x86_64/op_return_void.S @@ -1,8 +1,8 @@ .extern MterpThreadFenceForConstructor call SYMBOL(MterpThreadFenceForConstructor) - testl $$(THREAD_SUSPEND_REQUEST | THREAD_CHECKPOINT_REQUEST), THREAD_FLAGS_OFFSET(rSELF) - jz 1f movq rSELF, OUT_ARG0 + testl $$(THREAD_SUSPEND_REQUEST | THREAD_CHECKPOINT_REQUEST), THREAD_FLAGS_OFFSET(OUT_ARG0) + jz 1f call SYMBOL(MterpSuspendCheck) 1: xorq %rax, %rax diff --git a/runtime/interpreter/mterp/x86_64/op_return_void_no_barrier.S b/runtime/interpreter/mterp/x86_64/op_return_void_no_barrier.S index 92e3506d1d..822b2e85e6 100644 --- a/runtime/interpreter/mterp/x86_64/op_return_void_no_barrier.S +++ b/runtime/interpreter/mterp/x86_64/op_return_void_no_barrier.S @@ -1,6 +1,6 @@ - testl $$(THREAD_SUSPEND_REQUEST | THREAD_CHECKPOINT_REQUEST), THREAD_FLAGS_OFFSET(rSELF) - jz 1f movq rSELF, OUT_ARG0 + testl $$(THREAD_SUSPEND_REQUEST | THREAD_CHECKPOINT_REQUEST), THREAD_FLAGS_OFFSET(OUT_ARG0) + jz 1f call SYMBOL(MterpSuspendCheck) 1: xorq %rax, %rax diff --git a/runtime/interpreter/mterp/x86_64/op_return_wide.S b/runtime/interpreter/mterp/x86_64/op_return_wide.S index f2d6e04cab..288eb96f8c 100644 --- a/runtime/interpreter/mterp/x86_64/op_return_wide.S +++ b/runtime/interpreter/mterp/x86_64/op_return_wide.S @@ -4,9 +4,9 @@ /* return-wide vAA */ .extern MterpThreadFenceForConstructor call SYMBOL(MterpThreadFenceForConstructor) - testl $$(THREAD_SUSPEND_REQUEST | THREAD_CHECKPOINT_REQUEST), THREAD_FLAGS_OFFSET(rSELF) - jz 1f movq rSELF, OUT_ARG0 + testl $$(THREAD_SUSPEND_REQUEST | THREAD_CHECKPOINT_REQUEST), THREAD_FLAGS_OFFSET(OUT_ARG0) + jz 1f call SYMBOL(MterpSuspendCheck) 1: GET_WIDE_VREG %rax, rINSTq # eax <- v[AA] diff --git a/runtime/interpreter/mterp/x86_64/op_sget.S b/runtime/interpreter/mterp/x86_64/op_sget.S index 38d9a5e6c8..d39e6c4396 100644 --- a/runtime/interpreter/mterp/x86_64/op_sget.S +++ b/runtime/interpreter/mterp/x86_64/op_sget.S @@ -11,7 +11,8 @@ movq OFF_FP_METHOD(rFP), OUT_ARG1 # referrer movq rSELF, OUT_ARG2 # self call SYMBOL($helper) - cmpl $$0, THREAD_EXCEPTION_OFFSET(rSELF) + movq rSELF, %rcx + cmpl $$0, THREAD_EXCEPTION_OFFSET(%rcx) jnz MterpException .if $is_object SET_VREG_OBJECT %eax, rINSTq # fp[A] <- value diff --git a/runtime/interpreter/mterp/x86_64/op_throw.S b/runtime/interpreter/mterp/x86_64/op_throw.S index 22ed990645..8095c25b08 100644 --- a/runtime/interpreter/mterp/x86_64/op_throw.S +++ b/runtime/interpreter/mterp/x86_64/op_throw.S @@ -6,5 +6,6 @@ GET_VREG %eax, rINSTq # eax<- vAA (exception object) testb %al, %al jz common_errNullObject - movq %rax, THREAD_EXCEPTION_OFFSET(rSELF) + movq rSELF, %rcx + movq %rax, THREAD_EXCEPTION_OFFSET(%rcx) jmp MterpException diff --git a/runtime/interpreter/mterp/x86_64/zcmp.S b/runtime/interpreter/mterp/x86_64/zcmp.S index 0051407cad..fb8ae6af6e 100644 --- a/runtime/interpreter/mterp/x86_64/zcmp.S +++ b/runtime/interpreter/mterp/x86_64/zcmp.S @@ -7,13 +7,11 @@ */ /* if-cmp vAA, +BBBB */ cmpl $$0, VREG_ADDRESS(rINSTq) # compare (vA, 0) - movl $$2, rINST # assume branch not taken j${revcmp} 1f movswq 2(rPC), rINSTq # fetch signed displacement + testq rINSTq, rINSTq + jmp MterpCommonTakenBranch 1: - MTERP_PROFILE_BRANCH - addq rINSTq, rINSTq # rINSTq <- AA * 2 - leaq (rPC, rINSTq), rPC - FETCH_INST - jle MterpCheckSuspendAndContinue # AA * 2 <= 0 => suspend check - GOTO_NEXT + cmpl $$JIT_CHECK_OSR, rPROFILE + je .L_check_not_taken_osr + ADVANCE_PC_FETCH_AND_GOTO_NEXT 2 diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc index 02e05c5429..1f473e404c 100644 --- a/runtime/interpreter/unstarted_runtime.cc +++ b/runtime/interpreter/unstarted_runtime.cc @@ -16,11 +16,13 @@ #include "unstarted_runtime.h" +#include <ctype.h> #include <errno.h> #include <stdlib.h> #include <cmath> #include <limits> +#include <locale> #include <unordered_map> #include "ScopedLocalRef.h" @@ -42,6 +44,7 @@ #include "mirror/object_array-inl.h" #include "mirror/string-inl.h" #include "nth_caller_visitor.h" +#include "reflection.h" #include "thread.h" #include "transaction.h" #include "well_known_classes.h" @@ -70,6 +73,43 @@ static void AbortTransactionOrFail(Thread* self, const char* fmt, ...) { } } +// Restricted support for character upper case / lower case. Only support ASCII, where +// it's easy. Abort the transaction otherwise. +static void CharacterLowerUpper(Thread* self, + ShadowFrame* shadow_frame, + JValue* result, + size_t arg_offset, + bool to_lower_case) SHARED_REQUIRES(Locks::mutator_lock_) { + uint32_t int_value = static_cast<uint32_t>(shadow_frame->GetVReg(arg_offset)); + + // Only ASCII (7-bit). + if (!isascii(int_value)) { + AbortTransactionOrFail(self, + "Only support ASCII characters for toLowerCase/toUpperCase: %u", + int_value); + return; + } + + std::locale c_locale("C"); + char char_value = static_cast<char>(int_value); + + if (to_lower_case) { + result->SetI(std::tolower(char_value, c_locale)); + } else { + result->SetI(std::toupper(char_value, c_locale)); + } +} + +void UnstartedRuntime::UnstartedCharacterToLowerCase( + Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) { + CharacterLowerUpper(self, shadow_frame, result, arg_offset, true); +} + +void UnstartedRuntime::UnstartedCharacterToUpperCase( + Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) { + CharacterLowerUpper(self, shadow_frame, result, arg_offset, false); +} + // Helper function to deal with class loading in an unstarted runtime. static void UnstartedRuntimeFindClass(Thread* self, Handle<mirror::String> className, Handle<mirror::ClassLoader> class_loader, JValue* result, @@ -313,6 +353,171 @@ void UnstartedRuntime::UnstartedClassGetEnclosingClass( result->SetL(klass->GetDexFile().GetEnclosingClass(klass)); } +void UnstartedRuntime::UnstartedClassGetInnerClassFlags( + Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) { + StackHandleScope<1> hs(self); + Handle<mirror::Class> klass(hs.NewHandle( + reinterpret_cast<mirror::Class*>(shadow_frame->GetVRegReference(arg_offset)))); + const int32_t default_value = shadow_frame->GetVReg(arg_offset + 1); + result->SetI(mirror::Class::GetInnerClassFlags(klass, default_value)); +} + +static std::unique_ptr<MemMap> FindAndExtractEntry(const std::string& jar_file, + const char* entry_name, + size_t* size, + std::string* error_msg) { + CHECK(size != nullptr); + + std::unique_ptr<ZipArchive> zip_archive(ZipArchive::Open(jar_file.c_str(), error_msg)); + if (zip_archive == nullptr) { + return nullptr;; + } + std::unique_ptr<ZipEntry> zip_entry(zip_archive->Find(entry_name, error_msg)); + if (zip_entry == nullptr) { + return nullptr; + } + std::unique_ptr<MemMap> tmp_map( + zip_entry->ExtractToMemMap(jar_file.c_str(), entry_name, error_msg)); + if (tmp_map == nullptr) { + return nullptr; + } + + // OK, from here everything seems fine. + *size = zip_entry->GetUncompressedLength(); + return tmp_map; +} + +static void GetResourceAsStream(Thread* self, + ShadowFrame* shadow_frame, + JValue* result, + size_t arg_offset) SHARED_REQUIRES(Locks::mutator_lock_) { + mirror::Object* resource_obj = shadow_frame->GetVRegReference(arg_offset + 1); + if (resource_obj == nullptr) { + AbortTransactionOrFail(self, "null name for getResourceAsStream"); + return; + } + CHECK(resource_obj->IsString()); + mirror::String* resource_name = resource_obj->AsString(); + + std::string resource_name_str = resource_name->ToModifiedUtf8(); + if (resource_name_str.empty() || resource_name_str == "/") { + AbortTransactionOrFail(self, + "Unsupported name %s for getResourceAsStream", + resource_name_str.c_str()); + return; + } + const char* resource_cstr = resource_name_str.c_str(); + if (resource_cstr[0] == '/') { + resource_cstr++; + } + + Runtime* runtime = Runtime::Current(); + + std::vector<std::string> split; + Split(runtime->GetBootClassPathString(), ':', &split); + if (split.empty()) { + AbortTransactionOrFail(self, + "Boot classpath not set or split error:: %s", + runtime->GetBootClassPathString().c_str()); + return; + } + + std::unique_ptr<MemMap> mem_map; + size_t map_size; + std::string last_error_msg; // Only store the last message (we could concatenate). + + for (const std::string& jar_file : split) { + mem_map = FindAndExtractEntry(jar_file, resource_cstr, &map_size, &last_error_msg); + if (mem_map != nullptr) { + break; + } + } + + if (mem_map == nullptr) { + // Didn't find it. There's a good chance this will be the same at runtime, but still + // conservatively abort the transaction here. + AbortTransactionOrFail(self, + "Could not find resource %s. Last error was %s.", + resource_name_str.c_str(), + last_error_msg.c_str()); + return; + } + + StackHandleScope<3> hs(self); + + // Create byte array for content. + Handle<mirror::ByteArray> h_array(hs.NewHandle(mirror::ByteArray::Alloc(self, map_size))); + if (h_array.Get() == nullptr) { + AbortTransactionOrFail(self, "Could not find/create byte array class"); + return; + } + // Copy in content. + memcpy(h_array->GetData(), mem_map->Begin(), map_size); + // Be proactive releasing memory. + mem_map.release(); + + // Create a ByteArrayInputStream. + Handle<mirror::Class> h_class(hs.NewHandle( + runtime->GetClassLinker()->FindClass(self, + "Ljava/io/ByteArrayInputStream;", + ScopedNullHandle<mirror::ClassLoader>()))); + if (h_class.Get() == nullptr) { + AbortTransactionOrFail(self, "Could not find ByteArrayInputStream class"); + return; + } + if (!runtime->GetClassLinker()->EnsureInitialized(self, h_class, true, true)) { + AbortTransactionOrFail(self, "Could not initialize ByteArrayInputStream class"); + return; + } + + Handle<mirror::Object> h_obj(hs.NewHandle(h_class->AllocObject(self))); + if (h_obj.Get() == nullptr) { + AbortTransactionOrFail(self, "Could not allocate ByteArrayInputStream object"); + return; + } + + auto* cl = Runtime::Current()->GetClassLinker(); + ArtMethod* constructor = h_class->FindDeclaredDirectMethod( + "<init>", "([B)V", cl->GetImagePointerSize()); + if (constructor == nullptr) { + AbortTransactionOrFail(self, "Could not find ByteArrayInputStream constructor"); + return; + } + + uint32_t args[1]; + args[0] = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(h_array.Get())); + EnterInterpreterFromInvoke(self, constructor, h_obj.Get(), args, nullptr); + + if (self->IsExceptionPending()) { + AbortTransactionOrFail(self, "Could not run ByteArrayInputStream constructor"); + return; + } + + result->SetL(h_obj.Get()); +} + +void UnstartedRuntime::UnstartedClassLoaderGetResourceAsStream( + Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) { + { + mirror::Object* this_obj = shadow_frame->GetVRegReference(arg_offset); + CHECK(this_obj != nullptr); + CHECK(this_obj->IsClassLoader()); + + StackHandleScope<1> hs(self); + Handle<mirror::Class> this_classloader_class(hs.NewHandle(this_obj->GetClass())); + + if (self->DecodeJObject(WellKnownClasses::java_lang_BootClassLoader) != + this_classloader_class.Get()) { + AbortTransactionOrFail(self, + "Unsupported classloader type %s for getResourceAsStream", + PrettyClass(this_classloader_class.Get()).c_str()); + return; + } + } + + GetResourceAsStream(self, shadow_frame, result, arg_offset); +} + void UnstartedRuntime::UnstartedVmClassLoaderFindLoadedClass( Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) { mirror::String* class_name = shadow_frame->GetVRegReference(arg_offset + 1)->AsString(); @@ -484,6 +689,101 @@ void UnstartedRuntime::UnstartedSystemGetSecurityManager( result->SetL(nullptr); } +static constexpr const char* kAndroidHardcodedSystemPropertiesFieldName = "STATIC_PROPERTIES"; + +static void GetSystemProperty(Thread* self, + ShadowFrame* shadow_frame, + JValue* result, + size_t arg_offset, + bool is_default_version) + SHARED_REQUIRES(Locks::mutator_lock_) { + StackHandleScope<4> hs(self); + Handle<mirror::String> h_key( + hs.NewHandle(reinterpret_cast<mirror::String*>(shadow_frame->GetVRegReference(arg_offset)))); + if (h_key.Get() == nullptr) { + AbortTransactionOrFail(self, "getProperty key was null"); + return; + } + + // This is overall inefficient, but reflecting the values here is not great, either. So + // for simplicity, and with the assumption that the number of getProperty calls is not + // too great, just iterate each time. + + // Get the storage class. + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + Handle<mirror::Class> h_props_class(hs.NewHandle( + class_linker->FindClass(self, + "Ljava/lang/AndroidHardcodedSystemProperties;", + ScopedNullHandle<mirror::ClassLoader>()))); + if (h_props_class.Get() == nullptr) { + AbortTransactionOrFail(self, "Could not find AndroidHardcodedSystemProperties"); + return; + } + if (!class_linker->EnsureInitialized(self, h_props_class, true, true)) { + AbortTransactionOrFail(self, "Could not initialize AndroidHardcodedSystemProperties"); + return; + } + + // Get the storage array. + ArtField* static_properties = + h_props_class->FindDeclaredStaticField(kAndroidHardcodedSystemPropertiesFieldName, + "[[Ljava/lang/String;"); + if (static_properties == nullptr) { + AbortTransactionOrFail(self, + "Could not find %s field", + kAndroidHardcodedSystemPropertiesFieldName); + return; + } + Handle<mirror::ObjectArray<mirror::ObjectArray<mirror::String>>> h_2string_array( + hs.NewHandle(reinterpret_cast<mirror::ObjectArray<mirror::ObjectArray<mirror::String>>*>( + static_properties->GetObject(h_props_class.Get())))); + if (h_2string_array.Get() == nullptr) { + AbortTransactionOrFail(self, "Field %s is null", kAndroidHardcodedSystemPropertiesFieldName); + return; + } + + // Iterate over it. + const int32_t prop_count = h_2string_array->GetLength(); + // Use the third handle as mutable. + MutableHandle<mirror::ObjectArray<mirror::String>> h_string_array( + hs.NewHandle<mirror::ObjectArray<mirror::String>>(nullptr)); + for (int32_t i = 0; i < prop_count; ++i) { + h_string_array.Assign(h_2string_array->Get(i)); + if (h_string_array.Get() == nullptr || + h_string_array->GetLength() != 2 || + h_string_array->Get(0) == nullptr) { + AbortTransactionOrFail(self, + "Unexpected content of %s", + kAndroidHardcodedSystemPropertiesFieldName); + return; + } + if (h_key->Equals(h_string_array->Get(0))) { + // Found a value. + if (h_string_array->Get(1) == nullptr && is_default_version) { + // Null is being delegated to the default map, and then resolved to the given default value. + // As there's no default map, return the given value. + result->SetL(shadow_frame->GetVRegReference(arg_offset + 1)); + } else { + result->SetL(h_string_array->Get(1)); + } + return; + } + } + + // Key is not supported. + AbortTransactionOrFail(self, "getProperty key %s not supported", h_key->ToModifiedUtf8().c_str()); +} + +void UnstartedRuntime::UnstartedSystemGetProperty( + Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) { + GetSystemProperty(self, shadow_frame, result, arg_offset, false); +} + +void UnstartedRuntime::UnstartedSystemGetPropertyWithDefault( + Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) { + GetSystemProperty(self, shadow_frame, result, arg_offset, true); +} + void UnstartedRuntime::UnstartedThreadLocalGet( Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset ATTRIBUTE_UNUSED) { std::string caller(PrettyMethod(shadow_frame->GetLink()->GetMethod())); @@ -534,6 +834,22 @@ void UnstartedRuntime::UnstartedMathFloor( result->SetD(floor(shadow_frame->GetVRegDouble(arg_offset))); } +void UnstartedRuntime::UnstartedMathSin( + Thread* self ATTRIBUTE_UNUSED, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) { + result->SetD(sin(shadow_frame->GetVRegDouble(arg_offset))); +} + +void UnstartedRuntime::UnstartedMathCos( + Thread* self ATTRIBUTE_UNUSED, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) { + result->SetD(cos(shadow_frame->GetVRegDouble(arg_offset))); +} + +void UnstartedRuntime::UnstartedMathPow( + Thread* self ATTRIBUTE_UNUSED, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) { + result->SetD(pow(shadow_frame->GetVRegDouble(arg_offset), + shadow_frame->GetVRegDouble(arg_offset + 2))); +} + void UnstartedRuntime::UnstartedObjectHashCode( Thread* self ATTRIBUTE_UNUSED, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) { mirror::Object* obj = shadow_frame->GetVRegReference(arg_offset); @@ -710,112 +1026,6 @@ void UnstartedRuntime::UnstartedMemoryPeekByteArray( UnstartedMemoryPeekArray(Primitive::kPrimByte, self, shadow_frame, arg_offset); } -// This allows reading security.properties in an unstarted runtime and initialize Security. -void UnstartedRuntime::UnstartedSecurityGetSecurityPropertiesReader( - Thread* self, ShadowFrame* shadow_frame ATTRIBUTE_UNUSED, JValue* result, - size_t arg_offset ATTRIBUTE_UNUSED) { - Runtime* runtime = Runtime::Current(); - - std::vector<std::string> split; - Split(runtime->GetBootClassPathString(), ':', &split); - if (split.empty()) { - AbortTransactionOrFail(self, - "Boot classpath not set or split error:: %s", - runtime->GetBootClassPathString().c_str()); - return; - } - const std::string& source = split[0]; - - mirror::String* string_data; - - // Use a block to enclose the I/O and MemMap code so buffers are released early. - { - std::string error_msg; - std::unique_ptr<ZipArchive> zip_archive(ZipArchive::Open(source.c_str(), &error_msg)); - if (zip_archive.get() == nullptr) { - AbortTransactionOrFail(self, - "Could not open zip file %s: %s", - source.c_str(), - error_msg.c_str()); - return; - } - std::unique_ptr<ZipEntry> zip_entry(zip_archive->Find("java/security/security.properties", - &error_msg)); - if (zip_entry.get() == nullptr) { - AbortTransactionOrFail(self, - "Could not find security.properties file in %s: %s", - source.c_str(), - error_msg.c_str()); - return; - } - std::unique_ptr<MemMap> map(zip_entry->ExtractToMemMap(source.c_str(), - "java/security/security.properties", - &error_msg)); - if (map.get() == nullptr) { - AbortTransactionOrFail(self, - "Could not unzip security.properties file in %s: %s", - source.c_str(), - error_msg.c_str()); - return; - } - - uint32_t length = zip_entry->GetUncompressedLength(); - std::unique_ptr<char[]> tmp(new char[length + 1]); - memcpy(tmp.get(), map->Begin(), length); - tmp.get()[length] = 0; // null terminator - - string_data = mirror::String::AllocFromModifiedUtf8(self, tmp.get()); - } - - if (string_data == nullptr) { - AbortTransactionOrFail(self, "Could not create string from file content of %s", source.c_str()); - return; - } - - // Create a StringReader. - StackHandleScope<3> hs(self); - Handle<mirror::String> h_string(hs.NewHandle(string_data)); - - Handle<mirror::Class> h_class(hs.NewHandle( - runtime->GetClassLinker()->FindClass(self, - "Ljava/io/StringReader;", - ScopedNullHandle<mirror::ClassLoader>()))); - if (h_class.Get() == nullptr) { - AbortTransactionOrFail(self, "Could not find StringReader class"); - return; - } - - if (!runtime->GetClassLinker()->EnsureInitialized(self, h_class, true, true)) { - AbortTransactionOrFail(self, "Could not initialize StringReader class"); - return; - } - - Handle<mirror::Object> h_obj(hs.NewHandle(h_class->AllocObject(self))); - if (h_obj.Get() == nullptr) { - AbortTransactionOrFail(self, "Could not allocate StringReader object"); - return; - } - - auto* cl = Runtime::Current()->GetClassLinker(); - ArtMethod* constructor = h_class->FindDeclaredDirectMethod( - "<init>", "(Ljava/lang/String;)V", cl->GetImagePointerSize()); - if (constructor == nullptr) { - AbortTransactionOrFail(self, "Could not find StringReader constructor"); - return; - } - - uint32_t args[1]; - args[0] = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(h_string.Get())); - EnterInterpreterFromInvoke(self, constructor, h_obj.Get(), args, nullptr); - - if (self->IsExceptionPending()) { - AbortTransactionOrFail(self, "Could not run StringReader constructor"); - return; - } - - result->SetL(h_obj.Get()); -} - // This allows reading the new style of String objects during compilation. void UnstartedRuntime::UnstartedStringGetCharsNoCheck( Thread* self, ShadowFrame* shadow_frame, JValue* result ATTRIBUTE_UNUSED, size_t arg_offset) { @@ -1173,6 +1383,36 @@ void UnstartedRuntime::UnstartedLongParseLong( result->SetJ(l); } +void UnstartedRuntime::UnstartedMethodInvoke( + Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) + SHARED_REQUIRES(Locks::mutator_lock_) { + JNIEnvExt* env = self->GetJniEnv(); + ScopedObjectAccessUnchecked soa(self); + + mirror::Object* java_method_obj = shadow_frame->GetVRegReference(arg_offset); + ScopedLocalRef<jobject> java_method(env, + java_method_obj == nullptr ? nullptr :env->AddLocalReference<jobject>(java_method_obj)); + + mirror::Object* java_receiver_obj = shadow_frame->GetVRegReference(arg_offset + 1); + ScopedLocalRef<jobject> java_receiver(env, + java_receiver_obj == nullptr ? nullptr : env->AddLocalReference<jobject>(java_receiver_obj)); + + mirror::Object* java_args_obj = shadow_frame->GetVRegReference(arg_offset + 2); + ScopedLocalRef<jobject> java_args(env, + java_args_obj == nullptr ? nullptr : env->AddLocalReference<jobject>(java_args_obj)); + + ScopedLocalRef<jobject> result_jobj(env, + InvokeMethod(soa, java_method.get(), java_receiver.get(), java_args.get())); + + result->SetL(self->DecodeJObject(result_jobj.get())); + + // Conservatively flag all exceptions as transaction aborts. This way we don't need to unwrap + // InvocationTargetExceptions. + if (self->IsExceptionPending()) { + AbortTransactionOrFail(self, "Failed Method.invoke"); + } +} + void UnstartedRuntime::UnstartedJNIVMRuntimeNewUnpaddedArray( Thread* self, ArtMethod* method ATTRIBUTE_UNUSED, mirror::Object* receiver ATTRIBUTE_UNUSED, @@ -1456,7 +1696,13 @@ void UnstartedRuntime::Invoke(Thread* self, const DexFile::CodeItem* code_item, if (iter != invoke_handlers_.end()) { // Clear out the result in case it's not zeroed out. result->SetL(0); + + // Push the shadow frame. This is so the failing method can be seen in abort dumps. + self->PushShadowFrame(shadow_frame); + (*iter->second)(self, shadow_frame, result, arg_offset); + + self->PopShadowFrame(); } else { // Not special, continue with regular interpreter execution. ArtInterpreterToInterpreterBridge(self, code_item, shadow_frame, result); diff --git a/runtime/interpreter/unstarted_runtime_list.h b/runtime/interpreter/unstarted_runtime_list.h index 3312701135..b8553b5771 100644 --- a/runtime/interpreter/unstarted_runtime_list.h +++ b/runtime/interpreter/unstarted_runtime_list.h @@ -19,6 +19,8 @@ // Methods that intercept available libcore implementations. #define UNSTARTED_RUNTIME_DIRECT_LIST(V) \ + V(CharacterToLowerCase, "int java.lang.Character.toLowerCase(int)") \ + V(CharacterToUpperCase, "int java.lang.Character.toUpperCase(int)") \ V(ClassForName, "java.lang.Class java.lang.Class.forName(java.lang.String)") \ V(ClassForNameLong, "java.lang.Class java.lang.Class.forName(java.lang.String, boolean, java.lang.ClassLoader)") \ V(ClassClassForName, "java.lang.Class java.lang.Class.classForName(java.lang.String, boolean, java.lang.ClassLoader)") \ @@ -27,6 +29,8 @@ V(ClassGetDeclaredMethod, "java.lang.reflect.Method java.lang.Class.getDeclaredMethodInternal(java.lang.String, java.lang.Class[])") \ V(ClassGetDeclaredConstructor, "java.lang.reflect.Constructor java.lang.Class.getDeclaredConstructorInternal(java.lang.Class[])") \ V(ClassGetEnclosingClass, "java.lang.Class java.lang.Class.getEnclosingClass()") \ + V(ClassGetInnerClassFlags, "int java.lang.Class.getInnerClassFlags(int)") \ + V(ClassLoaderGetResourceAsStream, "java.io.InputStream java.lang.ClassLoader.getResourceAsStream(java.lang.String)") \ V(VmClassLoaderFindLoadedClass, "java.lang.Class java.lang.VMClassLoader.findLoadedClass(java.lang.ClassLoader, java.lang.String)") \ V(VoidLookupType, "java.lang.Class java.lang.Void.lookupType()") \ V(SystemArraycopy, "void java.lang.System.arraycopy(java.lang.Object, int, java.lang.Object, int, int)") \ @@ -34,9 +38,14 @@ V(SystemArraycopyChar, "void java.lang.System.arraycopy(char[], int, char[], int, int)") \ V(SystemArraycopyInt, "void java.lang.System.arraycopy(int[], int, int[], int, int)") \ V(SystemGetSecurityManager, "java.lang.SecurityManager java.lang.System.getSecurityManager()") \ + V(SystemGetProperty, "java.lang.String java.lang.System.getProperty(java.lang.String)") \ + V(SystemGetPropertyWithDefault, "java.lang.String java.lang.System.getProperty(java.lang.String, java.lang.String)") \ V(ThreadLocalGet, "java.lang.Object java.lang.ThreadLocal.get()") \ V(MathCeil, "double java.lang.Math.ceil(double)") \ V(MathFloor, "double java.lang.Math.floor(double)") \ + V(MathSin, "double java.lang.Math.sin(double)") \ + V(MathCos, "double java.lang.Math.cos(double)") \ + V(MathPow, "double java.lang.Math.pow(double, double)") \ V(ObjectHashCode, "int java.lang.Object.hashCode()") \ V(DoubleDoubleToRawLongBits, "long java.lang.Double.doubleToRawLongBits(double)") \ V(DexCacheGetDexNative, "com.android.dex.Dex java.lang.DexCache.getDexNative()") \ @@ -45,9 +54,9 @@ V(MemoryPeekInt, "int libcore.io.Memory.peekIntNative(long)") \ V(MemoryPeekLong, "long libcore.io.Memory.peekLongNative(long)") \ V(MemoryPeekByteArray, "void libcore.io.Memory.peekByteArray(long, byte[], int, int)") \ + V(MethodInvoke, "java.lang.Object java.lang.reflect.Method.invoke(java.lang.Object, java.lang.Object[])") \ V(ReferenceGetReferent, "java.lang.Object java.lang.ref.Reference.getReferent()") \ V(RuntimeAvailableProcessors, "int java.lang.Runtime.availableProcessors()") \ - V(SecurityGetSecurityPropertiesReader, "java.io.Reader java.security.Security.getSecurityPropertiesReader()") \ V(StringGetCharsNoCheck, "void java.lang.String.getCharsNoCheck(int, int, char[], int)") \ V(StringCharAt, "char java.lang.String.charAt(int)") \ V(StringSetCharAt, "void java.lang.String.setCharAt(int, char)") \ diff --git a/runtime/interpreter/unstarted_runtime_test.cc b/runtime/interpreter/unstarted_runtime_test.cc index 1b5b665ed2..814b0018f7 100644 --- a/runtime/interpreter/unstarted_runtime_test.cc +++ b/runtime/interpreter/unstarted_runtime_test.cc @@ -17,8 +17,10 @@ #include "unstarted_runtime.h" #include <limits> +#include <locale> #include "base/casts.h" +#include "base/memory_tool.h" #include "class_linker.h" #include "common_runtime_test.h" #include "dex_instruction.h" @@ -30,6 +32,7 @@ #include "runtime.h" #include "scoped_thread_state_change.h" #include "thread.h" +#include "transaction.h" namespace art { namespace interpreter { @@ -182,6 +185,16 @@ class UnstartedRuntimeTest : public CommonRuntimeTest { EXPECT_EQ(expect_int64t, result_int64t) << result.GetD() << " vs " << test_pairs[i][1]; } } + + // Prepare for aborts. Aborts assume that the exception class is already resolved, as the + // loading code doesn't work under transactions. + void PrepareForAborts() SHARED_REQUIRES(Locks::mutator_lock_) { + mirror::Object* result = Runtime::Current()->GetClassLinker()->FindClass( + Thread::Current(), + Transaction::kAbortExceptionSignature, + ScopedNullHandle<mirror::ClassLoader>()); + CHECK(result != nullptr); + } }; TEST_F(UnstartedRuntimeTest, MemoryPeekByte) { @@ -689,5 +702,171 @@ TEST_F(UnstartedRuntimeTest, Floor) { ShadowFrame::DeleteDeoptimizedFrame(tmp); } +TEST_F(UnstartedRuntimeTest, ToLowerUpper) { + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + + ShadowFrame* tmp = ShadowFrame::CreateDeoptimizedFrame(10, nullptr, nullptr, 0); + + std::locale c_locale("C"); + + // Check ASCII. + for (uint32_t i = 0; i < 128; ++i) { + bool c_upper = std::isupper(static_cast<char>(i), c_locale); + bool c_lower = std::islower(static_cast<char>(i), c_locale); + EXPECT_FALSE(c_upper && c_lower) << i; + + // Check toLowerCase. + { + JValue result; + tmp->SetVReg(0, static_cast<int32_t>(i)); + UnstartedCharacterToLowerCase(self, tmp, &result, 0); + ASSERT_FALSE(self->IsExceptionPending()); + uint32_t lower_result = static_cast<uint32_t>(result.GetI()); + if (c_lower) { + EXPECT_EQ(i, lower_result); + } else if (c_upper) { + EXPECT_EQ(static_cast<uint32_t>(std::tolower(static_cast<char>(i), c_locale)), + lower_result); + } else { + EXPECT_EQ(i, lower_result); + } + } + + // Check toUpperCase. + { + JValue result2; + tmp->SetVReg(0, static_cast<int32_t>(i)); + UnstartedCharacterToUpperCase(self, tmp, &result2, 0); + ASSERT_FALSE(self->IsExceptionPending()); + uint32_t upper_result = static_cast<uint32_t>(result2.GetI()); + if (c_upper) { + EXPECT_EQ(i, upper_result); + } else if (c_lower) { + EXPECT_EQ(static_cast<uint32_t>(std::toupper(static_cast<char>(i), c_locale)), + upper_result); + } else { + EXPECT_EQ(i, upper_result); + } + } + } + + // Check abort for other things. Can't test all. + + PrepareForAborts(); + + for (uint32_t i = 128; i < 256; ++i) { + { + JValue result; + tmp->SetVReg(0, static_cast<int32_t>(i)); + Transaction transaction; + Runtime::Current()->EnterTransactionMode(&transaction); + UnstartedCharacterToLowerCase(self, tmp, &result, 0); + Runtime::Current()->ExitTransactionMode(); + ASSERT_TRUE(self->IsExceptionPending()); + ASSERT_TRUE(transaction.IsAborted()); + } + { + JValue result; + tmp->SetVReg(0, static_cast<int32_t>(i)); + Transaction transaction; + Runtime::Current()->EnterTransactionMode(&transaction); + UnstartedCharacterToUpperCase(self, tmp, &result, 0); + Runtime::Current()->ExitTransactionMode(); + ASSERT_TRUE(self->IsExceptionPending()); + ASSERT_TRUE(transaction.IsAborted()); + } + } + for (uint64_t i = 256; i <= std::numeric_limits<uint32_t>::max(); i <<= 1) { + { + JValue result; + tmp->SetVReg(0, static_cast<int32_t>(i)); + Transaction transaction; + Runtime::Current()->EnterTransactionMode(&transaction); + UnstartedCharacterToLowerCase(self, tmp, &result, 0); + Runtime::Current()->ExitTransactionMode(); + ASSERT_TRUE(self->IsExceptionPending()); + ASSERT_TRUE(transaction.IsAborted()); + } + { + JValue result; + tmp->SetVReg(0, static_cast<int32_t>(i)); + Transaction transaction; + Runtime::Current()->EnterTransactionMode(&transaction); + UnstartedCharacterToUpperCase(self, tmp, &result, 0); + Runtime::Current()->ExitTransactionMode(); + ASSERT_TRUE(self->IsExceptionPending()); + ASSERT_TRUE(transaction.IsAborted()); + } + } + + ShadowFrame::DeleteDeoptimizedFrame(tmp); +} + +TEST_F(UnstartedRuntimeTest, Sin) { + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + + ShadowFrame* tmp = ShadowFrame::CreateDeoptimizedFrame(10, nullptr, nullptr, 0); + + // Test an important value, PI/6. That's the one we see in practice. + constexpr uint64_t lvalue = UINT64_C(0x3fe0c152382d7365); + tmp->SetVRegLong(0, static_cast<int64_t>(lvalue)); + + JValue result; + UnstartedMathSin(self, tmp, &result, 0); + + const uint64_t lresult = static_cast<uint64_t>(result.GetJ()); + EXPECT_EQ(UINT64_C(0x3fdfffffffffffff), lresult); + + ShadowFrame::DeleteDeoptimizedFrame(tmp); +} + +TEST_F(UnstartedRuntimeTest, Cos) { + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + + ShadowFrame* tmp = ShadowFrame::CreateDeoptimizedFrame(10, nullptr, nullptr, 0); + + // Test an important value, PI/6. That's the one we see in practice. + constexpr uint64_t lvalue = UINT64_C(0x3fe0c152382d7365); + tmp->SetVRegLong(0, static_cast<int64_t>(lvalue)); + + JValue result; + UnstartedMathCos(self, tmp, &result, 0); + + const uint64_t lresult = static_cast<uint64_t>(result.GetJ()); + EXPECT_EQ(UINT64_C(0x3febb67ae8584cab), lresult); + + ShadowFrame::DeleteDeoptimizedFrame(tmp); +} + +TEST_F(UnstartedRuntimeTest, Pow) { + // Valgrind seems to get this wrong, actually. Disable for valgrind. + if (RUNNING_ON_MEMORY_TOOL != 0 && kMemoryToolIsValgrind) { + return; + } + + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + + ShadowFrame* tmp = ShadowFrame::CreateDeoptimizedFrame(10, nullptr, nullptr, 0); + + // Test an important pair. + constexpr uint64_t lvalue1 = UINT64_C(0x4079000000000000); + constexpr uint64_t lvalue2 = UINT64_C(0xbfe6db6dc0000000); + + tmp->SetVRegLong(0, static_cast<int64_t>(lvalue1)); + tmp->SetVRegLong(2, static_cast<int64_t>(lvalue2)); + + JValue result; + UnstartedMathPow(self, tmp, &result, 0); + + const uint64_t lresult = static_cast<uint64_t>(result.GetJ()); + EXPECT_EQ(UINT64_C(0x3f8c5c51326aa7ee), lresult); + + ShadowFrame::DeleteDeoptimizedFrame(tmp); +} + } // namespace interpreter } // namespace art diff --git a/runtime/java_vm_ext.cc b/runtime/java_vm_ext.cc index a41fd45041..d983a9fa19 100644 --- a/runtime/java_vm_ext.cc +++ b/runtime/java_vm_ext.cc @@ -318,6 +318,7 @@ class JII { } JavaVMExt* raw_vm = reinterpret_cast<JavaVMExt*>(vm); delete raw_vm->GetRuntime(); + android::ResetNativeLoader(); return JNI_OK; } @@ -942,6 +943,11 @@ extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) { if (!Runtime::Create(options, ignore_unrecognized)) { return JNI_ERR; } + + // Initialize native loader. This step makes sure we have + // everything set up before we start using JNI. + android::InitializeNativeLoader(); + Runtime* runtime = Runtime::Current(); bool started = runtime->Start(); if (!started) { @@ -950,6 +956,7 @@ extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) { LOG(WARNING) << "CreateJavaVM failed"; return JNI_ERR; } + *p_env = Thread::Current()->GetJniEnv(); *p_vm = runtime->GetJavaVM(); return JNI_OK; diff --git a/runtime/jdwp/jdwp_adb.cc b/runtime/jdwp/jdwp_adb.cc index 51952c4923..e9d6d079c1 100644 --- a/runtime/jdwp/jdwp_adb.cc +++ b/runtime/jdwp/jdwp_adb.cc @@ -24,7 +24,7 @@ #include "base/stringprintf.h" #include "jdwp/jdwp_priv.h" -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID #include "cutils/sockets.h" #endif @@ -224,7 +224,7 @@ bool JdwpAdbState::Accept() { */ int ret = connect(control_sock_, &control_addr_.controlAddrPlain, control_addr_len_); if (!ret) { -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID if (!socket_peer_is_trusted(control_sock_)) { if (shutdown(control_sock_, SHUT_RDWR)) { PLOG(ERROR) << "trouble shutting down socket"; diff --git a/runtime/jdwp/jdwp_main.cc b/runtime/jdwp/jdwp_main.cc index 668d5dc490..dbf04fe7a1 100644 --- a/runtime/jdwp/jdwp_main.cc +++ b/runtime/jdwp/jdwp_main.cc @@ -251,7 +251,7 @@ JdwpState* JdwpState::Create(const JdwpOptions* options) { case kJdwpTransportSocket: InitSocketTransport(state.get(), options); break; -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID case kJdwpTransportAndroidAdb: InitAdbTransport(state.get(), options); break; diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc index 3344346f06..ae5a0f6777 100644 --- a/runtime/jit/jit.cc +++ b/runtime/jit/jit.cc @@ -23,7 +23,6 @@ #include "entrypoints/runtime_asm_entrypoints.h" #include "interpreter/interpreter.h" #include "jit_code_cache.h" -#include "jit_instrumentation.h" #include "oat_file_manager.h" #include "oat_quick_method_header.h" #include "offline_profiling_info.h" @@ -31,12 +30,15 @@ #include "runtime.h" #include "runtime_options.h" #include "stack_map.h" +#include "thread_list.h" #include "utils.h" namespace art { namespace jit { static constexpr bool kEnableOnStackReplacement = true; +// At what priority to schedule jit threads. 9 is the lowest foreground priority on device. +static constexpr int kJitPoolThreadPthreadPriority = 9; // JIT compiler void* Jit::jit_library_handle_= nullptr; @@ -49,7 +51,7 @@ bool Jit::generate_debug_info_ = false; JitOptions* JitOptions::CreateFromRuntimeArguments(const RuntimeArgumentMap& options) { auto* jit_options = new JitOptions; - jit_options->use_jit_ = options.GetOrDefault(RuntimeArgumentMap::UseJIT); + jit_options->use_jit_compilation_ = options.GetOrDefault(RuntimeArgumentMap::UseJitCompilation); jit_options->code_cache_initial_capacity_ = options.GetOrDefault(RuntimeArgumentMap::JITCodeCacheInitialCapacity); @@ -95,18 +97,31 @@ JitOptions* JitOptions::CreateFromRuntimeArguments(const RuntimeArgumentMap& opt LOG(FATAL) << "Priority thread weight cannot be 0."; } } else { - jit_options->priority_thread_weight_ = - std::max(jit_options->compile_threshold_ / 2000, static_cast<size_t>(1)); + jit_options->priority_thread_weight_ = std::max( + jit_options->warmup_threshold_ / Jit::kDefaultPriorityThreadWeightRatio, + static_cast<size_t>(1)); + } + + if (options.Exists(RuntimeArgumentMap::JITInvokeTransitionWeight)) { + jit_options->invoke_transition_weight_ = + *options.Get(RuntimeArgumentMap::JITInvokeTransitionWeight); + if (jit_options->invoke_transition_weight_ > jit_options->warmup_threshold_) { + LOG(FATAL) << "Invoke transition weight is above the warmup threshold."; + } else if (jit_options->invoke_transition_weight_ == 0) { + LOG(FATAL) << "Invoke transition weight cannot be 0."; + } + } else { + jit_options->invoke_transition_weight_ = std::max( + jit_options->warmup_threshold_ / Jit::kDefaultInvokeTransitionWeightRatio, + static_cast<size_t>(1));; } return jit_options; } bool Jit::ShouldUsePriorityThreadWeight() { - // TODO(calin): verify that IsSensitiveThread covers only the cases we are interested on. - // In particular if apps can set StrictMode policies for any of their threads, case in which - // we need to find another way to track sensitive threads. - return Runtime::Current()->InJankPerceptibleProcessState() && Thread::IsSensitiveThread(); + return Runtime::Current()->InJankPerceptibleProcessState() + && Thread::Current()->IsJitSensitiveThread(); } void Jit::DumpInfo(std::ostream& os) { @@ -116,6 +131,11 @@ void Jit::DumpInfo(std::ostream& os) { memory_use_.PrintMemoryUse(os); } +void Jit::DumpForSigQuit(std::ostream& os) { + DumpInfo(os); + ProfileSaver::DumpInstanceInfo(os); +} + void Jit::AddTimingLogger(const TimingLogger& logger) { cumulative_timings_.AddLogger(logger); } @@ -124,9 +144,11 @@ Jit::Jit() : dump_info_on_shutdown_(false), cumulative_timings_("JIT timings"), memory_use_("Memory used for compilation", 16), lock_("JIT memory use lock"), + use_jit_compilation_(true), save_profiling_info_(false) {} Jit* Jit::Create(JitOptions* options, std::string* error_msg) { + DCHECK(options->UseJitCompilation() || options->GetSaveProfilingInfo()); std::unique_ptr<Jit> jit(new Jit); jit->dump_info_on_shutdown_ = options->DumpJitInfoOnShutdown(); if (jit_compiler_handle_ == nullptr && !LoadCompiler(error_msg)) { @@ -140,12 +162,25 @@ Jit* Jit::Create(JitOptions* options, std::string* error_msg) { if (jit->GetCodeCache() == nullptr) { return nullptr; } + jit->use_jit_compilation_ = options->UseJitCompilation(); jit->save_profiling_info_ = options->GetSaveProfilingInfo(); VLOG(jit) << "JIT created with initial_capacity=" << PrettySize(options->GetCodeCacheInitialCapacity()) << ", max_capacity=" << PrettySize(options->GetCodeCacheMaxCapacity()) << ", compile_threshold=" << options->GetCompileThreshold() << ", save_profiling_info=" << options->GetSaveProfilingInfo(); + + + jit->hot_method_threshold_ = options->GetCompileThreshold(); + jit->warm_method_threshold_ = options->GetWarmupThreshold(); + jit->osr_method_threshold_ = options->GetOsrThreshold(); + jit->priority_thread_weight_ = options->GetPriorityThreadWeight(); + jit->invoke_transition_weight_ = options->GetInvokeTransitionWeight(); + + jit->CreateThreadPool(); + + // Notify native debugger about the classes already loaded before the creation of the jit. + jit->DumpTypeInfoForLoadedTypes(Runtime::Current()->GetClassLinker()); return jit.release(); } @@ -206,6 +241,7 @@ bool Jit::LoadCompiler(std::string* error_msg) { } bool Jit::CompileMethod(ArtMethod* method, Thread* self, bool osr) { + DCHECK(Runtime::Current()->UseJitCompilation()); DCHECK(!method->IsRuntimeMethod()); // Don't compile the method if it has breakpoints. @@ -227,19 +263,46 @@ bool Jit::CompileMethod(ArtMethod* method, Thread* self, bool osr) { if (!code_cache_->NotifyCompilationOf(method_to_compile, self, osr)) { return false; } + + VLOG(jit) << "Compiling method " + << PrettyMethod(method_to_compile) + << " osr=" << std::boolalpha << osr; bool success = jit_compile_method_(jit_compiler_handle_, method_to_compile, self, osr); code_cache_->DoneCompiling(method_to_compile, self, osr); + if (!success) { + VLOG(jit) << "Failed to compile method " + << PrettyMethod(method_to_compile) + << " osr=" << std::boolalpha << osr; + } return success; } void Jit::CreateThreadPool() { - CHECK(instrumentation_cache_.get() != nullptr); - instrumentation_cache_->CreateThreadPool(); + // There is a DCHECK in the 'AddSamples' method to ensure the tread pool + // is not null when we instrument. + thread_pool_.reset(new ThreadPool("Jit thread pool", 1)); + thread_pool_->SetPthreadPriority(kJitPoolThreadPthreadPriority); + thread_pool_->StartWorkers(Thread::Current()); } void Jit::DeleteThreadPool() { - if (instrumentation_cache_.get() != nullptr) { - instrumentation_cache_->DeleteThreadPool(Thread::Current()); + Thread* self = Thread::Current(); + DCHECK(Runtime::Current()->IsShuttingDown(self)); + if (thread_pool_ != nullptr) { + ThreadPool* cache = nullptr; + { + ScopedSuspendAll ssa(__FUNCTION__); + // Clear thread_pool_ field while the threads are suspended. + // A mutator in the 'AddSamples' method will check against it. + cache = thread_pool_.release(); + } + cache->StopWorkers(self); + cache->RemoveAllTasks(self); + // We could just suspend all threads, but we know those threads + // will finish in a short period, so it's not worth adding a suspend logic + // here. Besides, this is only done for shutdown. + cache->Wait(self, false, false); + delete cache; } } @@ -254,15 +317,12 @@ void Jit::StartProfileSaver(const std::string& filename, void Jit::StopProfileSaver() { if (save_profiling_info_ && ProfileSaver::IsStarted()) { - ProfileSaver::Stop(); + ProfileSaver::Stop(dump_info_on_shutdown_); } } bool Jit::JitAtFirstUse() { - if (instrumentation_cache_ != nullptr) { - return instrumentation_cache_->HotMethodThreshold() == 0; - } - return false; + return HotMethodThreshold() == 0; } bool Jit::CanInvokeCompiledCode(ArtMethod* method) { @@ -285,20 +345,13 @@ Jit::~Jit() { } } -void Jit::CreateInstrumentationCache(size_t compile_threshold, - size_t warmup_threshold, - size_t osr_threshold, - uint16_t priority_thread_weight) { - instrumentation_cache_.reset( - new jit::JitInstrumentationCache(compile_threshold, - warmup_threshold, - osr_threshold, - priority_thread_weight)); -} - void Jit::NewTypeLoadedIfUsingJit(mirror::Class* type) { + if (!Runtime::Current()->UseJitCompilation()) { + // No need to notify if we only use the JIT to save profiles. + return; + } jit::Jit* jit = Runtime::Current()->GetJit(); - if (jit != nullptr && jit->generate_debug_info_) { + if (jit->generate_debug_info_) { DCHECK(jit->jit_types_loaded_ != nullptr); jit->jit_types_loaded_(jit->jit_compiler_handle_, &type, 1); } @@ -391,6 +444,13 @@ bool Jit::MaybeDoOnStackReplacement(Thread* thread, return false; } + // Before allowing the jump, make sure the debugger is not active to avoid jumping from + // interpreter to OSR while e.g. single stepping. Note that we could selectively disable + // OSR when single stepping, but that's currently hard to know at this point. + if (Dbg::IsDebuggerActive()) { + return false; + } + // We found a stack map, now fill the frame with dex register values from the interpreter's // shadow frame. DexRegisterMap vreg_map = @@ -480,5 +540,165 @@ void Jit::AddMemoryUsage(ArtMethod* method, size_t bytes) { memory_use_.AddValue(bytes); } +class JitCompileTask FINAL : public Task { + public: + enum TaskKind { + kAllocateProfile, + kCompile, + kCompileOsr + }; + + JitCompileTask(ArtMethod* method, TaskKind kind) : method_(method), kind_(kind) { + ScopedObjectAccess soa(Thread::Current()); + // Add a global ref to the class to prevent class unloading until compilation is done. + klass_ = soa.Vm()->AddGlobalRef(soa.Self(), method_->GetDeclaringClass()); + CHECK(klass_ != nullptr); + } + + ~JitCompileTask() { + ScopedObjectAccess soa(Thread::Current()); + soa.Vm()->DeleteGlobalRef(soa.Self(), klass_); + } + + void Run(Thread* self) OVERRIDE { + ScopedObjectAccess soa(self); + if (kind_ == kCompile) { + Runtime::Current()->GetJit()->CompileMethod(method_, self, /* osr */ false); + } else if (kind_ == kCompileOsr) { + Runtime::Current()->GetJit()->CompileMethod(method_, self, /* osr */ true); + } else { + DCHECK(kind_ == kAllocateProfile); + if (ProfilingInfo::Create(self, method_, /* retry_allocation */ true)) { + VLOG(jit) << "Start profiling " << PrettyMethod(method_); + } + } + ProfileSaver::NotifyJitActivity(); + } + + void Finalize() OVERRIDE { + delete this; + } + + private: + ArtMethod* const method_; + const TaskKind kind_; + jobject klass_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(JitCompileTask); +}; + +void Jit::AddSamples(Thread* self, ArtMethod* method, uint16_t count, bool with_backedges) { + if (thread_pool_ == nullptr) { + // Should only see this when shutting down. + DCHECK(Runtime::Current()->IsShuttingDown(self)); + return; + } + + if (method->IsClassInitializer() || method->IsNative() || !method->IsCompilable()) { + // We do not want to compile such methods. + return; + } + DCHECK(thread_pool_ != nullptr); + DCHECK_GT(warm_method_threshold_, 0); + DCHECK_GT(hot_method_threshold_, warm_method_threshold_); + DCHECK_GT(osr_method_threshold_, hot_method_threshold_); + DCHECK_GE(priority_thread_weight_, 1); + DCHECK_LE(priority_thread_weight_, hot_method_threshold_); + + int32_t starting_count = method->GetCounter(); + if (Jit::ShouldUsePriorityThreadWeight()) { + count *= priority_thread_weight_; + } + int32_t new_count = starting_count + count; // int32 here to avoid wrap-around; + if (starting_count < warm_method_threshold_) { + if ((new_count >= warm_method_threshold_) && + (method->GetProfilingInfo(sizeof(void*)) == nullptr)) { + bool success = ProfilingInfo::Create(self, method, /* retry_allocation */ false); + if (success) { + VLOG(jit) << "Start profiling " << PrettyMethod(method); + } + + if (thread_pool_ == nullptr) { + // Calling ProfilingInfo::Create might put us in a suspended state, which could + // lead to the thread pool being deleted when we are shutting down. + DCHECK(Runtime::Current()->IsShuttingDown(self)); + return; + } + + if (!success) { + // We failed allocating. Instead of doing the collection on the Java thread, we push + // an allocation to a compiler thread, that will do the collection. + thread_pool_->AddTask(self, new JitCompileTask(method, JitCompileTask::kAllocateProfile)); + } + } + // Avoid jumping more than one state at a time. + new_count = std::min(new_count, hot_method_threshold_ - 1); + } else if (use_jit_compilation_) { + if (starting_count < hot_method_threshold_) { + if ((new_count >= hot_method_threshold_) && + !code_cache_->ContainsPc(method->GetEntryPointFromQuickCompiledCode())) { + DCHECK(thread_pool_ != nullptr); + thread_pool_->AddTask(self, new JitCompileTask(method, JitCompileTask::kCompile)); + } + // Avoid jumping more than one state at a time. + new_count = std::min(new_count, osr_method_threshold_ - 1); + } else if (starting_count < osr_method_threshold_) { + if (!with_backedges) { + // If the samples don't contain any back edge, we don't increment the hotness. + return; + } + if ((new_count >= osr_method_threshold_) && !code_cache_->IsOsrCompiled(method)) { + DCHECK(thread_pool_ != nullptr); + thread_pool_->AddTask(self, new JitCompileTask(method, JitCompileTask::kCompileOsr)); + } + } + } + // Update hotness counter + method->SetCounter(new_count); +} + +void Jit::MethodEntered(Thread* thread, ArtMethod* method) { + Runtime* runtime = Runtime::Current(); + if (UNLIKELY(runtime->UseJitCompilation() && runtime->GetJit()->JitAtFirstUse())) { + // The compiler requires a ProfilingInfo object. + ProfilingInfo::Create(thread, method, /* retry_allocation */ true); + JitCompileTask compile_task(method, JitCompileTask::kCompile); + compile_task.Run(thread); + return; + } + + ProfilingInfo* profiling_info = method->GetProfilingInfo(sizeof(void*)); + // Update the entrypoint if the ProfilingInfo has one. The interpreter will call it + // instead of interpreting the method. + if ((profiling_info != nullptr) && (profiling_info->GetSavedEntryPoint() != nullptr)) { + Runtime::Current()->GetInstrumentation()->UpdateMethodsCode( + method, profiling_info->GetSavedEntryPoint()); + } else { + AddSamples(thread, method, 1, /* with_backedges */false); + } +} + +void Jit::InvokeVirtualOrInterface(Thread* thread, + mirror::Object* this_object, + ArtMethod* caller, + uint32_t dex_pc, + ArtMethod* callee ATTRIBUTE_UNUSED) { + ScopedAssertNoThreadSuspension ants(thread, __FUNCTION__); + DCHECK(this_object != nullptr); + ProfilingInfo* info = caller->GetProfilingInfo(sizeof(void*)); + if (info != nullptr) { + // Since the instrumentation is marked from the declaring class we need to mark the card so + // that mod-union tables and card rescanning know about the update. + Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(caller->GetDeclaringClass()); + info->AddInvokeInfo(dex_pc, this_object->GetClass()); + } +} + +void Jit::WaitForCompilationToFinish(Thread* self) { + if (thread_pool_ != nullptr) { + thread_pool_->Wait(self, false, false); + } +} + } // namespace jit } // namespace art diff --git a/runtime/jit/jit.h b/runtime/jit/jit.h index e2123666f9..f3a6240e80 100644 --- a/runtime/jit/jit.h +++ b/runtime/jit/jit.h @@ -34,29 +34,32 @@ struct RuntimeArgumentMap; namespace jit { class JitCodeCache; -class JitInstrumentationCache; class JitOptions; +static constexpr int16_t kJitCheckForOSR = -1; +static constexpr int16_t kJitHotnessDisabled = -2; + class Jit { public: static constexpr bool kStressMode = kIsDebugBuild; static constexpr size_t kDefaultCompileThreshold = kStressMode ? 2 : 10000; + static constexpr size_t kDefaultPriorityThreadWeightRatio = 1000; + static constexpr size_t kDefaultInvokeTransitionWeightRatio = 500; virtual ~Jit(); static Jit* Create(JitOptions* options, std::string* error_msg); bool CompileMethod(ArtMethod* method, Thread* self, bool osr) SHARED_REQUIRES(Locks::mutator_lock_); - void CreateInstrumentationCache(size_t compile_threshold, - size_t warmup_threshold, - size_t osr_threshold, - uint16_t priority_thread_weight); void CreateThreadPool(); + const JitCodeCache* GetCodeCache() const { return code_cache_.get(); } + JitCodeCache* GetCodeCache() { return code_cache_.get(); } + void DeleteThreadPool(); // Dump interesting info: #methods compiled, code vs data size, compile / verify cumulative // loggers. @@ -68,8 +71,56 @@ class Jit { REQUIRES(!lock_) SHARED_REQUIRES(Locks::mutator_lock_); - JitInstrumentationCache* GetInstrumentationCache() const { - return instrumentation_cache_.get(); + size_t OSRMethodThreshold() const { + return osr_method_threshold_; + } + + size_t HotMethodThreshold() const { + return hot_method_threshold_; + } + + size_t WarmMethodThreshold() const { + return warm_method_threshold_; + } + + uint16_t PriorityThreadWeight() const { + return priority_thread_weight_; + } + + // Returns false if we only need to save profile information and not compile methods. + bool UseJitCompilation() const { + return use_jit_compilation_; + } + + bool SaveProfilingInfo() const { + return save_profiling_info_; + } + + // Wait until there is no more pending compilation tasks. + void WaitForCompilationToFinish(Thread* self); + + // Profiling methods. + void MethodEntered(Thread* thread, ArtMethod* method) + SHARED_REQUIRES(Locks::mutator_lock_); + + void AddSamples(Thread* self, ArtMethod* method, uint16_t samples, bool with_backedges) + SHARED_REQUIRES(Locks::mutator_lock_); + + void InvokeVirtualOrInterface(Thread* thread, + mirror::Object* this_object, + ArtMethod* caller, + uint32_t dex_pc, + ArtMethod* callee) + SHARED_REQUIRES(Locks::mutator_lock_); + + void NotifyInterpreterToCompiledCodeTransition(Thread* self, ArtMethod* caller) + SHARED_REQUIRES(Locks::mutator_lock_) { + AddSamples(self, caller, invoke_transition_weight_, false); + } + + void NotifyCompiledCodeToInterpreterTransition(Thread* self, ArtMethod* callee) + SHARED_REQUIRES(Locks::mutator_lock_) { + AddSamples(self, callee, invoke_transition_weight_, false); } // Starts the profile saver if the config options allow profile recording. @@ -85,9 +136,7 @@ class Jit { const std::string& app_dir); void StopProfileSaver(); - void DumpForSigQuit(std::ostream& os) REQUIRES(!lock_) { - DumpInfo(os); - } + void DumpForSigQuit(std::ostream& os) REQUIRES(!lock_); static void NewTypeLoadedIfUsingJit(mirror::Class* type) SHARED_REQUIRES(Locks::mutator_lock_); @@ -137,11 +186,17 @@ class Jit { Histogram<uint64_t> memory_use_ GUARDED_BY(lock_); Mutex lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; - std::unique_ptr<jit::JitInstrumentationCache> instrumentation_cache_; std::unique_ptr<jit::JitCodeCache> code_cache_; + bool use_jit_compilation_; bool save_profiling_info_; static bool generate_debug_info_; + uint16_t hot_method_threshold_; + uint16_t warm_method_threshold_; + uint16_t osr_method_threshold_; + uint16_t priority_thread_weight_; + uint16_t invoke_transition_weight_; + std::unique_ptr<ThreadPool> thread_pool_; DISALLOW_COPY_AND_ASSIGN(Jit); }; @@ -161,6 +216,9 @@ class JitOptions { uint16_t GetPriorityThreadWeight() const { return priority_thread_weight_; } + size_t GetInvokeTransitionWeight() const { + return invoke_transition_weight_; + } size_t GetCodeCacheInitialCapacity() const { return code_cache_initial_capacity_; } @@ -173,33 +231,34 @@ class JitOptions { bool GetSaveProfilingInfo() const { return save_profiling_info_; } - bool UseJIT() const { - return use_jit_; + bool UseJitCompilation() const { + return use_jit_compilation_; } - void SetUseJIT(bool b) { - use_jit_ = b; + void SetUseJitCompilation(bool b) { + use_jit_compilation_ = b; } void SetSaveProfilingInfo(bool b) { save_profiling_info_ = b; } void SetJitAtFirstUse() { - use_jit_ = true; + use_jit_compilation_ = true; compile_threshold_ = 0; } private: - bool use_jit_; + bool use_jit_compilation_; size_t code_cache_initial_capacity_; size_t code_cache_max_capacity_; size_t compile_threshold_; size_t warmup_threshold_; size_t osr_threshold_; uint16_t priority_thread_weight_; + size_t invoke_transition_weight_; bool dump_info_on_shutdown_; bool save_profiling_info_; JitOptions() - : use_jit_(false), + : use_jit_compilation_(false), code_cache_initial_capacity_(0), code_cache_max_capacity_(0), compile_threshold_(0), diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc index 820ae6acab..6b6f5a5c15 100644 --- a/runtime/jit/jit_code_cache.cc +++ b/runtime/jit/jit_code_cache.cc @@ -354,8 +354,7 @@ uint8_t* JitCodeCache::CommitCodeInternal(Thread* self, if (osr) { number_of_osr_compilations_++; osr_code_map_.Put(method, code_ptr); - } else if (!Runtime::Current()->GetInstrumentation()->AreExitStubsInstalled()) { - // TODO(ngeoffray): Clean up instrumentation and code cache interactions. + } else { Runtime::Current()->GetInstrumentation()->UpdateMethodsCode( method, method_header->GetEntryPoint()); } @@ -366,7 +365,7 @@ uint8_t* JitCodeCache::CommitCodeInternal(Thread* self, } last_update_time_ns_.StoreRelease(NanoTime()); VLOG(jit) - << "JIT added (osr = " << std::boolalpha << osr << std::noboolalpha << ") " + << "JIT added (osr=" << std::boolalpha << osr << std::noboolalpha << ") " << PrettyMethod(method) << "@" << method << " ccache_size=" << PrettySize(CodeCacheSizeLocked()) << ": " << " dcache_size=" << PrettySize(DataCacheSizeLocked()) << ": " @@ -634,10 +633,7 @@ void JitCodeCache::GarbageCollectCache(Thread* self) { bool next_collection_will_be_full = ShouldDoFullCollection(); // Start polling the liveness of compiled code to prepare for the next full collection. - // We avoid doing this if exit stubs are installed to not mess with the instrumentation. - // TODO(ngeoffray): Clean up instrumentation and code cache interactions. - if (!Runtime::Current()->GetInstrumentation()->AreExitStubsInstalled() && - next_collection_will_be_full) { + if (next_collection_will_be_full) { // Save the entry point of methods we have compiled, and update the entry // point of those methods to the interpreter. If the method is invoked, the // interpreter will update its entry point to the compiled code and call it. @@ -645,7 +641,8 @@ void JitCodeCache::GarbageCollectCache(Thread* self) { const void* entry_point = info->GetMethod()->GetEntryPointFromQuickCompiledCode(); if (ContainsPc(entry_point)) { info->SetSavedEntryPoint(entry_point); - info->GetMethod()->SetEntryPointFromQuickCompiledCode(GetQuickToInterpreterBridge()); + Runtime::Current()->GetInstrumentation()->UpdateMethodsCode( + info->GetMethod(), GetQuickToInterpreterBridge()); } } @@ -890,13 +887,15 @@ void* JitCodeCache::MoreCore(const void* mspace, intptr_t increment) NO_THREAD_S } } -void JitCodeCache::GetCompiledArtMethods(const std::set<std::string>& dex_base_locations, - std::vector<ArtMethod*>& methods) { +void JitCodeCache::GetProfiledMethods(const std::set<std::string>& dex_base_locations, + std::vector<MethodReference>& methods) { ScopedTrace trace(__FUNCTION__); MutexLock mu(Thread::Current(), lock_); - for (auto it : method_code_map_) { - if (ContainsElement(dex_base_locations, it.second->GetDexFile()->GetBaseLocation())) { - methods.push_back(it.second); + for (const ProfilingInfo* info : profiling_infos_) { + ArtMethod* method = info->GetMethod(); + const DexFile* dex_file = method->GetDexFile(); + if (ContainsElement(dex_base_locations, dex_file->GetBaseLocation())) { + methods.emplace_back(dex_file, method->GetDexMethodIndex()); } } } @@ -905,15 +904,18 @@ uint64_t JitCodeCache::GetLastUpdateTimeNs() const { return last_update_time_ns_.LoadAcquire(); } +bool JitCodeCache::IsOsrCompiled(ArtMethod* method) { + MutexLock mu(Thread::Current(), lock_); + return osr_code_map_.find(method) != osr_code_map_.end(); +} + bool JitCodeCache::NotifyCompilationOf(ArtMethod* method, Thread* self, bool osr) { if (!osr && ContainsPc(method->GetEntryPointFromQuickCompiledCode())) { - VLOG(jit) << PrettyMethod(method) << " is already compiled"; return false; } MutexLock mu(self, lock_); if (osr && (osr_code_map_.find(method) != osr_code_map_.end())) { - VLOG(jit) << PrettyMethod(method) << " is already osr compiled"; return false; } @@ -928,7 +930,6 @@ bool JitCodeCache::NotifyCompilationOf(ArtMethod* method, Thread* self, bool osr } if (info->IsMethodBeingCompiled(osr)) { - VLOG(jit) << PrettyMethod(method) << " is already being compiled"; return false; } diff --git a/runtime/jit/jit_code_cache.h b/runtime/jit/jit_code_cache.h index 9f18c700d4..4df6762517 100644 --- a/runtime/jit/jit_code_cache.h +++ b/runtime/jit/jit_code_cache.h @@ -26,6 +26,7 @@ #include "gc/accounting/bitmap.h" #include "gc_root.h" #include "jni.h" +#include "method_reference.h" #include "oat_file.h" #include "object_callbacks.h" #include "safe_map.h" @@ -165,9 +166,9 @@ class JitCodeCache { void* MoreCore(const void* mspace, intptr_t increment); - // Adds to `methods` all the compiled ArtMethods which are part of any of the given dex locations. - void GetCompiledArtMethods(const std::set<std::string>& dex_base_locations, - std::vector<ArtMethod*>& methods) + // Adds to `methods` all profiled methods which are part of any of the given dex locations. + void GetProfiledMethods(const std::set<std::string>& dex_base_locations, + std::vector<MethodReference>& methods) REQUIRES(!lock_) SHARED_REQUIRES(Locks::mutator_lock_); @@ -186,6 +187,8 @@ class JitCodeCache { void Dump(std::ostream& os) REQUIRES(!lock_); + bool IsOsrCompiled(ArtMethod* method) REQUIRES(!lock_); + private: // Take ownership of maps. JitCodeCache(MemMap* code_map, diff --git a/runtime/jit/jit_instrumentation.cc b/runtime/jit/jit_instrumentation.cc deleted file mode 100644 index b2c0c20237..0000000000 --- a/runtime/jit/jit_instrumentation.cc +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright 2014 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 "jit_instrumentation.h" - -#include "art_method-inl.h" -#include "jit.h" -#include "jit_code_cache.h" -#include "scoped_thread_state_change.h" -#include "thread_list.h" - -namespace art { -namespace jit { - -// At what priority to schedule jit threads. 9 is the lowest foreground priority on device. -static constexpr int kJitPoolThreadPthreadPriority = 9; - -class JitCompileTask FINAL : public Task { - public: - enum TaskKind { - kAllocateProfile, - kCompile, - kCompileOsr - }; - - JitCompileTask(ArtMethod* method, TaskKind kind) : method_(method), kind_(kind) { - ScopedObjectAccess soa(Thread::Current()); - // Add a global ref to the class to prevent class unloading until compilation is done. - klass_ = soa.Vm()->AddGlobalRef(soa.Self(), method_->GetDeclaringClass()); - CHECK(klass_ != nullptr); - } - - ~JitCompileTask() { - ScopedObjectAccess soa(Thread::Current()); - soa.Vm()->DeleteGlobalRef(soa.Self(), klass_); - } - - void Run(Thread* self) OVERRIDE { - ScopedObjectAccess soa(self); - if (kind_ == kCompile) { - VLOG(jit) << "JitCompileTask compiling method " << PrettyMethod(method_); - if (!Runtime::Current()->GetJit()->CompileMethod(method_, self, /* osr */ false)) { - VLOG(jit) << "Failed to compile method " << PrettyMethod(method_); - } - } else if (kind_ == kCompileOsr) { - VLOG(jit) << "JitCompileTask compiling method osr " << PrettyMethod(method_); - if (!Runtime::Current()->GetJit()->CompileMethod(method_, self, /* osr */ true)) { - VLOG(jit) << "Failed to compile method osr " << PrettyMethod(method_); - } - } else { - DCHECK(kind_ == kAllocateProfile); - if (ProfilingInfo::Create(self, method_, /* retry_allocation */ true)) { - VLOG(jit) << "Start profiling " << PrettyMethod(method_); - } - } - } - - void Finalize() OVERRIDE { - delete this; - } - - private: - ArtMethod* const method_; - const TaskKind kind_; - jobject klass_; - - DISALLOW_IMPLICIT_CONSTRUCTORS(JitCompileTask); -}; - -JitInstrumentationCache::JitInstrumentationCache(uint16_t hot_method_threshold, - uint16_t warm_method_threshold, - uint16_t osr_method_threshold, - uint16_t priority_thread_weight) - : hot_method_threshold_(hot_method_threshold), - warm_method_threshold_(warm_method_threshold), - osr_method_threshold_(osr_method_threshold), - priority_thread_weight_(priority_thread_weight), - listener_(this) { -} - -void JitInstrumentationCache::CreateThreadPool() { - // Create the thread pool before setting the instrumentation, so that - // when the threads stopped being suspended, they can use it directly. - // There is a DCHECK in the 'AddSamples' method to ensure the tread pool - // is not null when we instrument. - thread_pool_.reset(new ThreadPool("Jit thread pool", 1)); - thread_pool_->SetPthreadPriority(kJitPoolThreadPthreadPriority); - thread_pool_->StartWorkers(Thread::Current()); - { - // Add Jit interpreter instrumentation, tells the interpreter when - // to notify the jit to compile something. - ScopedSuspendAll ssa(__FUNCTION__); - Runtime::Current()->GetInstrumentation()->AddListener( - &listener_, JitInstrumentationListener::kJitEvents); - } -} - -void JitInstrumentationCache::DeleteThreadPool(Thread* self) { - DCHECK(Runtime::Current()->IsShuttingDown(self)); - if (thread_pool_ != nullptr) { - // First remove the listener, to avoid having mutators enter - // 'AddSamples'. - ThreadPool* cache = nullptr; - { - ScopedSuspendAll ssa(__FUNCTION__); - Runtime::Current()->GetInstrumentation()->RemoveListener( - &listener_, JitInstrumentationListener::kJitEvents); - // Clear thread_pool_ field while the threads are suspended. - // A mutator in the 'AddSamples' method will check against it. - cache = thread_pool_.release(); - } - cache->StopWorkers(self); - cache->RemoveAllTasks(self); - // We could just suspend all threads, but we know those threads - // will finish in a short period, so it's not worth adding a suspend logic - // here. Besides, this is only done for shutdown. - cache->Wait(self, false, false); - delete cache; - } -} - -void JitInstrumentationCache::AddSamples(Thread* self, ArtMethod* method, uint16_t count) { - // Since we don't have on-stack replacement, some methods can remain in the interpreter longer - // than we want resulting in samples even after the method is compiled. Also, if the - // jit is no longer interested in hotness samples because we're shutting down, just return. - if (method->IsClassInitializer() || method->IsNative() || (thread_pool_ == nullptr)) { - if (thread_pool_ == nullptr) { - // Should only see this when shutting down. - DCHECK(Runtime::Current()->IsShuttingDown(self)); - } - return; - } - DCHECK(thread_pool_ != nullptr); - DCHECK_GT(warm_method_threshold_, 0); - DCHECK_GT(hot_method_threshold_, warm_method_threshold_); - DCHECK_GT(osr_method_threshold_, hot_method_threshold_); - DCHECK_GE(priority_thread_weight_, 1); - DCHECK_LE(priority_thread_weight_, hot_method_threshold_); - - int32_t starting_count = method->GetCounter(); - if (Jit::ShouldUsePriorityThreadWeight()) { - count *= priority_thread_weight_; - } - int32_t new_count = starting_count + count; // int32 here to avoid wrap-around; - if (starting_count < warm_method_threshold_) { - if (new_count >= warm_method_threshold_) { - bool success = ProfilingInfo::Create(self, method, /* retry_allocation */ false); - if (success) { - VLOG(jit) << "Start profiling " << PrettyMethod(method); - } - - if (thread_pool_ == nullptr) { - // Calling ProfilingInfo::Create might put us in a suspended state, which could - // lead to the thread pool being deleted when we are shutting down. - DCHECK(Runtime::Current()->IsShuttingDown(self)); - return; - } - - if (!success) { - // We failed allocating. Instead of doing the collection on the Java thread, we push - // an allocation to a compiler thread, that will do the collection. - thread_pool_->AddTask(self, new JitCompileTask(method, JitCompileTask::kAllocateProfile)); - } - } - // Avoid jumping more than one state at a time. - new_count = std::min(new_count, hot_method_threshold_ - 1); - } else if (starting_count < hot_method_threshold_) { - if (new_count >= hot_method_threshold_) { - DCHECK(thread_pool_ != nullptr); - thread_pool_->AddTask(self, new JitCompileTask(method, JitCompileTask::kCompile)); - } - // Avoid jumping more than one state at a time. - new_count = std::min(new_count, osr_method_threshold_ - 1); - } else if (starting_count < osr_method_threshold_) { - if (new_count >= osr_method_threshold_) { - DCHECK(thread_pool_ != nullptr); - thread_pool_->AddTask(self, new JitCompileTask(method, JitCompileTask::kCompileOsr)); - } - } - // Update hotness counter - method->SetCounter(new_count); -} - -JitInstrumentationListener::JitInstrumentationListener(JitInstrumentationCache* cache) - : instrumentation_cache_(cache) { - CHECK(instrumentation_cache_ != nullptr); -} - -void JitInstrumentationListener::MethodEntered(Thread* thread, - mirror::Object* /*this_object*/, - ArtMethod* method, - uint32_t /*dex_pc*/) { - if (UNLIKELY(Runtime::Current()->GetJit()->JitAtFirstUse())) { - // The compiler requires a ProfilingInfo object. - ProfilingInfo::Create(thread, method, /* retry_allocation */ true); - JitCompileTask compile_task(method, JitCompileTask::kCompile); - compile_task.Run(thread); - return; - } - - ProfilingInfo* profiling_info = method->GetProfilingInfo(sizeof(void*)); - // Update the entrypoint if the ProfilingInfo has one. The interpreter will call it - // instead of interpreting the method. - // We avoid doing this if exit stubs are installed to not mess with the instrumentation. - // TODO(ngeoffray): Clean up instrumentation and code cache interactions. - if ((profiling_info != nullptr) && - (profiling_info->GetSavedEntryPoint() != nullptr) && - !Runtime::Current()->GetInstrumentation()->AreExitStubsInstalled()) { - method->SetEntryPointFromQuickCompiledCode(profiling_info->GetSavedEntryPoint()); - } else { - instrumentation_cache_->AddSamples(thread, method, 1); - } -} - -void JitInstrumentationListener::Branch(Thread* thread, - ArtMethod* method, - uint32_t dex_pc ATTRIBUTE_UNUSED, - int32_t dex_pc_offset) { - if (dex_pc_offset < 0) { - // Increment method hotness if it is a backward branch. - instrumentation_cache_->AddSamples(thread, method, 1); - } -} - -void JitInstrumentationListener::InvokeVirtualOrInterface(Thread* thread, - mirror::Object* this_object, - ArtMethod* caller, - uint32_t dex_pc, - ArtMethod* callee ATTRIBUTE_UNUSED) { - // We make sure we cannot be suspended, as the profiling info can be concurrently deleted. - instrumentation_cache_->AddSamples(thread, caller, 1); - DCHECK(this_object != nullptr); - ProfilingInfo* info = caller->GetProfilingInfo(sizeof(void*)); - if (info != nullptr) { - // Since the instrumentation is marked from the declaring class we need to mark the card so - // that mod-union tables and card rescanning know about the update. - Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(caller->GetDeclaringClass()); - info->AddInvokeInfo(dex_pc, this_object->GetClass()); - } -} - -void JitInstrumentationCache::WaitForCompilationToFinish(Thread* self) { - if (thread_pool_ != nullptr) { - thread_pool_->Wait(self, false, false); - } -} - -} // namespace jit -} // namespace art diff --git a/runtime/jit/jit_instrumentation.h b/runtime/jit/jit_instrumentation.h deleted file mode 100644 index d0545f8e24..0000000000 --- a/runtime/jit/jit_instrumentation.h +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright 2014 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. - */ - -#ifndef ART_RUNTIME_JIT_JIT_INSTRUMENTATION_H_ -#define ART_RUNTIME_JIT_JIT_INSTRUMENTATION_H_ - -#include <unordered_map> - -#include "instrumentation.h" - -#include "atomic.h" -#include "base/macros.h" -#include "base/mutex.h" -#include "gc_root.h" -#include "jni.h" -#include "object_callbacks.h" -#include "thread_pool.h" - -namespace art { -namespace mirror { - class Object; - class Throwable; -} // namespace mirror -class ArtField; -class ArtMethod; -union JValue; -class Thread; - -namespace jit { -static constexpr int16_t kJitCheckForOSR = -1; -static constexpr int16_t kJitHotnessDisabled = -2; - -class JitInstrumentationCache; - -class JitInstrumentationListener : public instrumentation::InstrumentationListener { - public: - explicit JitInstrumentationListener(JitInstrumentationCache* cache); - - void MethodEntered(Thread* thread, mirror::Object* /*this_object*/, - ArtMethod* method, uint32_t /*dex_pc*/) - OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_); - - void MethodExited(Thread* /*thread*/, mirror::Object* /*this_object*/, - ArtMethod* /*method*/, uint32_t /*dex_pc*/, - const JValue& /*return_value*/) - OVERRIDE { } - void MethodUnwind(Thread* /*thread*/, mirror::Object* /*this_object*/, - ArtMethod* /*method*/, uint32_t /*dex_pc*/) OVERRIDE { } - void FieldRead(Thread* /*thread*/, mirror::Object* /*this_object*/, - ArtMethod* /*method*/, uint32_t /*dex_pc*/, - ArtField* /*field*/) OVERRIDE { } - void FieldWritten(Thread* /*thread*/, mirror::Object* /*this_object*/, - ArtMethod* /*method*/, uint32_t /*dex_pc*/, - ArtField* /*field*/, const JValue& /*field_value*/) - OVERRIDE { } - void ExceptionCaught(Thread* /*thread*/, - mirror::Throwable* /*exception_object*/) OVERRIDE { } - - void DexPcMoved(Thread* /*self*/, mirror::Object* /*this_object*/, - ArtMethod* /*method*/, uint32_t /*new_dex_pc*/) OVERRIDE { } - - void Branch(Thread* thread, ArtMethod* method, uint32_t dex_pc, int32_t dex_pc_offset) - OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_); - - void InvokeVirtualOrInterface(Thread* thread, - mirror::Object* this_object, - ArtMethod* caller, - uint32_t dex_pc, - ArtMethod* callee) - OVERRIDE - REQUIRES(Roles::uninterruptible_) - SHARED_REQUIRES(Locks::mutator_lock_); - - static constexpr uint32_t kJitEvents = - instrumentation::Instrumentation::kMethodEntered | - instrumentation::Instrumentation::kInvokeVirtualOrInterface; - - private: - JitInstrumentationCache* const instrumentation_cache_; - - DISALLOW_IMPLICIT_CONSTRUCTORS(JitInstrumentationListener); -}; - -// Keeps track of which methods are hot. -class JitInstrumentationCache { - public: - JitInstrumentationCache(uint16_t hot_method_threshold, - uint16_t warm_method_threshold, - uint16_t osr_method_threshold, - uint16_t priority_thread_weight); - void AddSamples(Thread* self, ArtMethod* method, uint16_t samples) - SHARED_REQUIRES(Locks::mutator_lock_); - void CreateThreadPool(); - void DeleteThreadPool(Thread* self); - - size_t OSRMethodThreshold() const { - return osr_method_threshold_; - } - - size_t HotMethodThreshold() const { - return hot_method_threshold_; - } - - size_t WarmMethodThreshold() const { - return warm_method_threshold_; - } - - size_t PriorityThreadWeight() const { - return priority_thread_weight_; - } - - // Wait until there is no more pending compilation tasks. - void WaitForCompilationToFinish(Thread* self); - - private: - uint16_t hot_method_threshold_; - uint16_t warm_method_threshold_; - uint16_t osr_method_threshold_; - uint16_t priority_thread_weight_; - JitInstrumentationListener listener_; - std::unique_ptr<ThreadPool> thread_pool_; - - DISALLOW_IMPLICIT_CONSTRUCTORS(JitInstrumentationCache); -}; - -} // namespace jit -} // namespace art - -#endif // ART_RUNTIME_JIT_JIT_INSTRUMENTATION_H_ diff --git a/runtime/jit/offline_profiling_info.cc b/runtime/jit/offline_profiling_info.cc index f181ca3467..c99d3636a1 100644 --- a/runtime/jit/offline_profiling_info.cc +++ b/runtime/jit/offline_profiling_info.cc @@ -16,7 +16,8 @@ #include "offline_profiling_info.h" -#include <fstream> +#include "errno.h" +#include <limits.h> #include <vector> #include <sys/file.h> #include <sys/stat.h> @@ -34,6 +35,11 @@ namespace art { +const uint8_t ProfileCompilationInfo::kProfileMagic[] = { 'p', 'r', 'o', '\0' }; +const uint8_t ProfileCompilationInfo::kProfileVersion[] = { '0', '0', '1', '\0' }; + +static constexpr uint16_t kMaxDexFileKeyLength = PATH_MAX; + // Transform the actual dex location into relative paths. // Note: this is OK because we don't store profiles of different apps into the same file. // Apps with split apks don't cause trouble because each split has a different name and will not @@ -49,15 +55,27 @@ std::string ProfileCompilationInfo::GetProfileDexFileKey(const std::string& dex_ } } -bool ProfileCompilationInfo::SaveProfilingInfo( - const std::string& filename, - const std::vector<ArtMethod*>& methods, +bool ProfileCompilationInfo::AddMethodsAndClasses( + const std::vector<MethodReference>& methods, const std::set<DexCacheResolvedClasses>& resolved_classes) { - if (methods.empty() && resolved_classes.empty()) { - VLOG(profiler) << "No info to save to " << filename; - return true; + for (const MethodReference& method : methods) { + if (!AddMethodIndex(GetProfileDexFileKey(method.dex_file->GetLocation()), + method.dex_file->GetLocationChecksum(), + method.dex_method_index)) { + return false; + } } + for (const DexCacheResolvedClasses& dex_cache : resolved_classes) { + if (!AddResolvedClasses(dex_cache)) { + return false; + } + } + return true; +} +bool ProfileCompilationInfo::MergeAndSave(const std::string& filename, + uint64_t* bytes_written, + bool force) { ScopedTrace trace(__PRETTY_FUNCTION__); ScopedFlock flock; std::string error; @@ -68,26 +86,37 @@ bool ProfileCompilationInfo::SaveProfilingInfo( int fd = flock.GetFile()->Fd(); - ProfileCompilationInfo info; - if (!info.Load(fd)) { - LOG(WARNING) << "Could not load previous profile data from file " << filename; - return false; - } - { - ScopedObjectAccess soa(Thread::Current()); - for (ArtMethod* method : methods) { - const DexFile* dex_file = method->GetDexFile(); - if (!info.AddMethodIndex(GetProfileDexFileKey(dex_file->GetLocation()), - dex_file->GetLocationChecksum(), - method->GetDexMethodIndex())) { + // Load the file but keep a copy around to be able to infer if the content has changed. + ProfileCompilationInfo fileInfo; + ProfileLoadSatus status = fileInfo.LoadInternal(fd, &error); + if (status == kProfileLoadSuccess) { + // Merge the content of file into the current object. + if (MergeWith(fileInfo)) { + // If after the merge we have the same data as what is the file there's no point + // in actually doing the write. The file will be exactly the same as before. + if (Equals(fileInfo)) { + if (bytes_written != nullptr) { + *bytes_written = 0; + } + return true; + } + } else { + LOG(WARNING) << "Could not merge previous profile data from file " << filename; + if (!force) { return false; } } - for (const DexCacheResolvedClasses& dex_cache : resolved_classes) { - info.AddResolvedClasses(dex_cache); - } + } else if (force && + ((status == kProfileLoadVersionMismatch) || (status == kProfileLoadBadData))) { + // Log a warning but don't return false. We will clear the profile anyway. + LOG(WARNING) << "Clearing bad or obsolete profile data from file " + << filename << ": " << error; + } else { + LOG(WARNING) << "Could not load profile data from file " << filename << ": " << error; + return false; } + // We need to clear the data because we don't support appending to the profiles yet. if (!flock.GetFile()->ClearContent()) { PLOG(WARNING) << "Could not clear profile file: " << filename; return false; @@ -95,95 +124,118 @@ bool ProfileCompilationInfo::SaveProfilingInfo( // This doesn't need locking because we are trying to lock the file for exclusive // access and fail immediately if we can't. - bool result = info.Save(fd); + bool result = Save(fd); if (result) { VLOG(profiler) << "Successfully saved profile info to " << filename << " Size: " << GetFileSizeBytes(filename); + if (bytes_written != nullptr) { + *bytes_written = GetFileSizeBytes(filename); + } } else { VLOG(profiler) << "Failed to save profile info to " << filename; } return result; } -static bool WriteToFile(int fd, const std::ostringstream& os) { - std::string data(os.str()); - const char *p = data.c_str(); - size_t length = data.length(); - do { - int n = TEMP_FAILURE_RETRY(write(fd, p, length)); - if (n < 0) { - PLOG(WARNING) << "Failed to write to descriptor: " << fd; +// Returns true if all the bytes were successfully written to the file descriptor. +static bool WriteBuffer(int fd, const uint8_t* buffer, size_t byte_count) { + while (byte_count > 0) { + int bytes_written = TEMP_FAILURE_RETRY(write(fd, buffer, byte_count)); + if (bytes_written == -1) { return false; } - p += n; - length -= n; - } while (length > 0); + byte_count -= bytes_written; // Reduce the number of remaining bytes. + buffer += bytes_written; // Move the buffer forward. + } return true; } -static constexpr const char kFieldSeparator = ','; -static constexpr const char kLineSeparator = '\n'; -static constexpr const char* kClassesMarker = "classes"; +// Add the string bytes to the buffer. +static void AddStringToBuffer(std::vector<uint8_t>* buffer, const std::string& value) { + buffer->insert(buffer->end(), value.begin(), value.end()); +} + +// Insert each byte, from low to high into the buffer. +template <typename T> +static void AddUintToBuffer(std::vector<uint8_t>* buffer, T value) { + for (size_t i = 0; i < sizeof(T); i++) { + buffer->push_back((value >> (i * kBitsPerByte)) & 0xff); + } +} + +static constexpr size_t kLineHeaderSize = + 3 * sizeof(uint16_t) + // method_set.size + class_set.size + dex_location.size + sizeof(uint32_t); // checksum /** * Serialization format: - * dex_location1,dex_location_checksum1,method_id11,method_id12...,classes,class_id1,class_id2... - * dex_location2,dex_location_checksum2,method_id21,method_id22...,classes,class_id1,class_id2... - * e.g. - * app.apk,131232145,11,23,454,54,classes,1,2,4,1234 - * app.apk:classes5.dex,218490184,39,13,49,1 + * magic,version,number_of_lines + * dex_location1,number_of_methods1,number_of_classes1,dex_location_checksum1, \ + * method_id11,method_id12...,class_id1,class_id2... + * dex_location2,number_of_methods2,number_of_classes2,dex_location_checksum2, \ + * method_id21,method_id22...,,class_id1,class_id2... + * ..... **/ bool ProfileCompilationInfo::Save(int fd) { ScopedTrace trace(__PRETTY_FUNCTION__); DCHECK_GE(fd, 0); - // TODO(calin): Profile this and see how much memory it takes. If too much, - // write to file directly. - std::ostringstream os; + + // Cache at most 5KB before writing. + static constexpr size_t kMaxSizeToKeepBeforeWriting = 5 * KB; + // Use a vector wrapper to avoid keeping track of offsets when we add elements. + std::vector<uint8_t> buffer; + WriteBuffer(fd, kProfileMagic, sizeof(kProfileMagic)); + WriteBuffer(fd, kProfileVersion, sizeof(kProfileVersion)); + AddUintToBuffer(&buffer, static_cast<uint16_t>(info_.size())); + for (const auto& it : info_) { + if (buffer.size() > kMaxSizeToKeepBeforeWriting) { + if (!WriteBuffer(fd, buffer.data(), buffer.size())) { + return false; + } + buffer.clear(); + } const std::string& dex_location = it.first; const DexFileData& dex_data = it.second; if (dex_data.method_set.empty() && dex_data.class_set.empty()) { continue; } - os << dex_location << kFieldSeparator << dex_data.checksum; - for (auto method_it : dex_data.method_set) { - os << kFieldSeparator << method_it; - } - if (!dex_data.class_set.empty()) { - os << kFieldSeparator << kClassesMarker; - for (auto class_id : dex_data.class_set) { - os << kFieldSeparator << class_id; - } + if (dex_location.size() >= kMaxDexFileKeyLength) { + LOG(WARNING) << "DexFileKey exceeds allocated limit"; + return false; } - os << kLineSeparator; - } - return WriteToFile(fd, os); -} + // Make sure that the buffer has enough capacity to avoid repeated resizings + // while we add data. + size_t required_capacity = buffer.size() + + kLineHeaderSize + + dex_location.size() + + sizeof(uint16_t) * (dex_data.class_set.size() + dex_data.method_set.size()); -// TODO(calin): This a duplicate of Utils::Split fixing the case where the first character -// is the separator. Merge the fix into Utils::Split once verified that it doesn't break its users. -static void SplitString(const std::string& s, char separator, std::vector<std::string>* result) { - const char* p = s.data(); - const char* end = p + s.size(); - // Check if the first character is the separator. - if (p != end && *p ==separator) { - result->push_back(""); - ++p; - } - // Process the rest of the characters. - while (p != end) { - if (*p == separator) { - ++p; - } else { - const char* start = p; - while (++p != end && *p != separator) { - // Skip to the next occurrence of the separator. - } - result->push_back(std::string(start, p - start)); + buffer.reserve(required_capacity); + + DCHECK_LE(dex_location.size(), std::numeric_limits<uint16_t>::max()); + DCHECK_LE(dex_data.method_set.size(), std::numeric_limits<uint16_t>::max()); + DCHECK_LE(dex_data.class_set.size(), std::numeric_limits<uint16_t>::max()); + AddUintToBuffer(&buffer, static_cast<uint16_t>(dex_location.size())); + AddUintToBuffer(&buffer, static_cast<uint16_t>(dex_data.method_set.size())); + AddUintToBuffer(&buffer, static_cast<uint16_t>(dex_data.class_set.size())); + AddUintToBuffer(&buffer, dex_data.checksum); // uint32_t + + AddStringToBuffer(&buffer, dex_location); + + for (auto method_it : dex_data.method_set) { + AddUintToBuffer(&buffer, method_it); + } + for (auto class_id : dex_data.class_set) { + AddUintToBuffer(&buffer, class_id); } + DCHECK_EQ(required_capacity, buffer.size()) + << "Failed to add the expected number of bytes in the buffer"; } + + return WriteBuffer(fd, buffer.data(), buffer.size()); } ProfileCompilationInfo::DexFileData* ProfileCompilationInfo::GetOrAddDexFileData( @@ -233,120 +285,260 @@ bool ProfileCompilationInfo::AddClassIndex(const std::string& dex_location, return true; } -bool ProfileCompilationInfo::ProcessLine(const std::string& line) { - std::vector<std::string> parts; - SplitString(line, kFieldSeparator, &parts); - if (parts.size() < 3) { - LOG(WARNING) << "Invalid line: " << line; - return false; +bool ProfileCompilationInfo::ProcessLine(SafeBuffer& line_buffer, + uint16_t method_set_size, + uint16_t class_set_size, + uint32_t checksum, + const std::string& dex_location) { + for (uint16_t i = 0; i < method_set_size; i++) { + uint16_t method_idx = line_buffer.ReadUintAndAdvance<uint16_t>(); + if (!AddMethodIndex(dex_location, checksum, method_idx)) { + return false; + } } - const std::string& dex_location = parts[0]; - uint32_t checksum; - if (!ParseInt(parts[1].c_str(), &checksum)) { + for (uint16_t i = 0; i < class_set_size; i++) { + uint16_t class_def_idx = line_buffer.ReadUintAndAdvance<uint16_t>(); + if (!AddClassIndex(dex_location, checksum, class_def_idx)) { + return false; + } + } + return true; +} + +// Tests for EOF by trying to read 1 byte from the descriptor. +// Returns: +// 0 if the descriptor is at the EOF, +// -1 if there was an IO error +// 1 if the descriptor has more content to read +static int testEOF(int fd) { + uint8_t buffer[1]; + return TEMP_FAILURE_RETRY(read(fd, buffer, 1)); +} + +// Reads an uint value previously written with AddUintToBuffer. +template <typename T> +T ProfileCompilationInfo::SafeBuffer::ReadUintAndAdvance() { + static_assert(std::is_unsigned<T>::value, "Type is not unsigned"); + CHECK_LE(ptr_current_ + sizeof(T), ptr_end_); + T value = 0; + for (size_t i = 0; i < sizeof(T); i++) { + value += ptr_current_[i] << (i * kBitsPerByte); + } + ptr_current_ += sizeof(T); + return value; +} + +bool ProfileCompilationInfo::SafeBuffer::CompareAndAdvance(const uint8_t* data, size_t data_size) { + if (ptr_current_ + data_size > ptr_end_) { return false; } + if (memcmp(ptr_current_, data, data_size) == 0) { + ptr_current_ += data_size; + return true; + } + return false; +} - for (size_t i = 2; i < parts.size(); i++) { - if (parts[i] == kClassesMarker) { - ++i; - // All of the remaining idx are class def indexes. - for (++i; i < parts.size(); ++i) { - uint32_t class_def_idx; - if (!ParseInt(parts[i].c_str(), &class_def_idx)) { - LOG(WARNING) << "Cannot parse class_def_idx " << parts[i]; - return false; - } else if (class_def_idx >= std::numeric_limits<uint16_t>::max()) { - LOG(WARNING) << "Class def idx " << class_def_idx << " is larger than uint16_t max"; - return false; - } - if (!AddClassIndex(dex_location, checksum, class_def_idx)) { - return false; - } - } - break; - } - uint32_t method_idx; - if (!ParseInt(parts[i].c_str(), &method_idx)) { - LOG(WARNING) << "Cannot parse method_idx " << parts[i]; - return false; - } - if (!AddMethodIndex(dex_location, checksum, method_idx)) { - return false; +ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::SafeBuffer::FillFromFd( + int fd, + const std::string& source, + /*out*/std::string* error) { + size_t byte_count = ptr_end_ - ptr_current_; + uint8_t* buffer = ptr_current_; + while (byte_count > 0) { + int bytes_read = TEMP_FAILURE_RETRY(read(fd, buffer, byte_count)); + if (bytes_read == 0) { + *error += "Profile EOF reached prematurely for " + source; + return kProfileLoadBadData; + } else if (bytes_read < 0) { + *error += "Profile IO error for " + source + strerror(errno); + return kProfileLoadIOError; } + byte_count -= bytes_read; + buffer += bytes_read; } - return true; + return kProfileLoadSuccess; } -// Parses the buffer (of length n) starting from start_from and identify new lines -// based on kLineSeparator marker. -// Returns the first position after kLineSeparator in the buffer (starting from start_from), -// or -1 if the marker doesn't appear. -// The processed characters are appended to the given line. -static int GetLineFromBuffer(char* buffer, int n, int start_from, std::string& line) { - if (start_from >= n) { - return -1; - } - int new_line_pos = -1; - for (int i = start_from; i < n; i++) { - if (buffer[i] == kLineSeparator) { - new_line_pos = i; - break; +ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::ReadProfileHeader( + int fd, + /*out*/uint16_t* number_of_lines, + /*out*/std::string* error) { + // Read magic and version + const size_t kMagicVersionSize = + sizeof(kProfileMagic) + + sizeof(kProfileVersion) + + sizeof(uint16_t); // number of lines + + SafeBuffer safe_buffer(kMagicVersionSize); + + ProfileLoadSatus status = safe_buffer.FillFromFd(fd, "ReadProfileHeader", error); + if (status != kProfileLoadSuccess) { + return status; + } + + if (!safe_buffer.CompareAndAdvance(kProfileMagic, sizeof(kProfileMagic))) { + *error = "Profile missing magic"; + return kProfileLoadVersionMismatch; + } + if (!safe_buffer.CompareAndAdvance(kProfileVersion, sizeof(kProfileVersion))) { + *error = "Profile version mismatch"; + return kProfileLoadVersionMismatch; + } + *number_of_lines = safe_buffer.ReadUintAndAdvance<uint16_t>(); + return kProfileLoadSuccess; +} + +ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::ReadProfileLineHeader( + int fd, + /*out*/ProfileLineHeader* line_header, + /*out*/std::string* error) { + SafeBuffer header_buffer(kLineHeaderSize); + ProfileLoadSatus status = header_buffer.FillFromFd(fd, "ReadProfileHeader", error); + if (status != kProfileLoadSuccess) { + return status; + } + + uint16_t dex_location_size = header_buffer.ReadUintAndAdvance<uint16_t>(); + line_header->method_set_size = header_buffer.ReadUintAndAdvance<uint16_t>(); + line_header->class_set_size = header_buffer.ReadUintAndAdvance<uint16_t>(); + line_header->checksum = header_buffer.ReadUintAndAdvance<uint32_t>(); + + if (dex_location_size == 0 || dex_location_size > kMaxDexFileKeyLength) { + *error = "DexFileKey has an invalid size: " + + std::to_string(static_cast<uint32_t>(dex_location_size)); + return kProfileLoadBadData; + } + + SafeBuffer location_buffer(dex_location_size); + status = location_buffer.FillFromFd(fd, "ReadProfileHeaderDexLocation", error); + if (status != kProfileLoadSuccess) { + return status; + } + line_header->dex_location.assign( + reinterpret_cast<char*>(location_buffer.Get()), dex_location_size); + return kProfileLoadSuccess; +} + +ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::ReadProfileLine( + int fd, + const ProfileLineHeader& line_header, + /*out*/std::string* error) { + // Make sure that we don't try to read everything in memory (in case the profile if full). + // Split readings in chunks of at most 10kb. + static constexpr uint16_t kMaxNumberOfEntriesToRead = 5120; + uint16_t methods_left_to_read = line_header.method_set_size; + uint16_t classes_left_to_read = line_header.class_set_size; + + while ((methods_left_to_read > 0) || (classes_left_to_read > 0)) { + uint16_t methods_to_read = std::min(kMaxNumberOfEntriesToRead, methods_left_to_read); + uint16_t max_classes_to_read = kMaxNumberOfEntriesToRead - methods_to_read; + uint16_t classes_to_read = std::min(max_classes_to_read, classes_left_to_read); + + size_t line_size = sizeof(uint16_t) * (methods_to_read + classes_to_read); + SafeBuffer line_buffer(line_size); + + ProfileLoadSatus status = line_buffer.FillFromFd(fd, "ReadProfileLine", error); + if (status != kProfileLoadSuccess) { + return status; } + if (!ProcessLine(line_buffer, + methods_to_read, + classes_to_read, + line_header.checksum, + line_header.dex_location)) { + *error = "Error when reading profile file line"; + return kProfileLoadBadData; + } + methods_left_to_read -= methods_to_read; + classes_left_to_read -= classes_to_read; } - int append_limit = new_line_pos == -1 ? n : new_line_pos; - line.append(buffer + start_from, append_limit - start_from); - // Jump over kLineSeparator and return the position of the next character. - return new_line_pos == -1 ? new_line_pos : new_line_pos + 1; + return kProfileLoadSuccess; } bool ProfileCompilationInfo::Load(int fd) { + std::string error; + ProfileLoadSatus status = LoadInternal(fd, &error); + + if (status == kProfileLoadSuccess) { + return true; + } else { + PLOG(WARNING) << "Error when reading profile " << error; + return false; + } +} + +ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::LoadInternal( + int fd, std::string* error) { ScopedTrace trace(__PRETTY_FUNCTION__); DCHECK_GE(fd, 0); - std::string current_line; - const int kBufferSize = 1024; - char buffer[kBufferSize]; + struct stat stat_buffer; + if (fstat(fd, &stat_buffer) != 0) { + return kProfileLoadIOError; + } + // We allow empty profile files. + // Profiles may be created by ActivityManager or installd before we manage to + // process them in the runtime or profman. + if (stat_buffer.st_size == 0) { + return kProfileLoadSuccess; + } + // Read profile header: magic + version + number_of_lines. + uint16_t number_of_lines; + ProfileLoadSatus status = ReadProfileHeader(fd, &number_of_lines, error); + if (status != kProfileLoadSuccess) { + return status; + } - while (true) { - int n = TEMP_FAILURE_RETRY(read(fd, buffer, kBufferSize)); - if (n < 0) { - PLOG(WARNING) << "Error when reading profile file"; - return false; - } else if (n == 0) { - break; + while (number_of_lines > 0) { + ProfileLineHeader line_header; + // First, read the line header to get the amount of data we need to read. + status = ReadProfileLineHeader(fd, &line_header, error); + if (status != kProfileLoadSuccess) { + return status; } - // Detect the new lines from the buffer. If we manage to complete a line, - // process it. Otherwise append to the current line. - int current_start_pos = 0; - while (current_start_pos < n) { - current_start_pos = GetLineFromBuffer(buffer, n, current_start_pos, current_line); - if (current_start_pos == -1) { - break; - } - if (!ProcessLine(current_line)) { - return false; - } - // Reset the current line (we just processed it). - current_line.clear(); + + // Now read the actual profile line. + status = ReadProfileLine(fd, line_header, error); + if (status != kProfileLoadSuccess) { + return status; } + number_of_lines--; + } + + // Check that we read everything and that profiles don't contain junk data. + int result = testEOF(fd); + if (result == 0) { + return kProfileLoadSuccess; + } else if (result < 0) { + return kProfileLoadIOError; + } else { + *error = "Unexpected content in the profile file"; + return kProfileLoadBadData; } - return true; } -bool ProfileCompilationInfo::Load(const ProfileCompilationInfo& other) { +bool ProfileCompilationInfo::MergeWith(const ProfileCompilationInfo& other) { + // First verify that all checksums match. This will avoid adding garbage to + // the current profile info. + // Note that the number of elements should be very small, so this should not + // be a performance issue. + for (const auto& other_it : other.info_) { + auto info_it = info_.find(other_it.first); + if ((info_it != info_.end()) && (info_it->second.checksum != other_it.second.checksum)) { + LOG(WARNING) << "Checksum mismatch for dex " << other_it.first; + return false; + } + } + // All checksums match. Import the data. for (const auto& other_it : other.info_) { const std::string& other_dex_location = other_it.first; const DexFileData& other_dex_data = other_it.second; - auto info_it = info_.find(other_dex_location); if (info_it == info_.end()) { info_it = info_.Put(other_dex_location, DexFileData(other_dex_data.checksum)); } - if (info_it->second.checksum != other_dex_data.checksum) { - LOG(WARNING) << "Checksum mismatch for dex " << other_dex_location; - return false; - } info_it->second.method_set.insert(other_dex_data.method_set.begin(), other_dex_data.method_set.end()); info_it->second.class_set.insert(other_dex_data.class_set.begin(), @@ -387,6 +579,14 @@ uint32_t ProfileCompilationInfo::GetNumberOfMethods() const { return total; } +uint32_t ProfileCompilationInfo::GetNumberOfResolvedClasses() const { + uint32_t total = 0; + for (const auto& it : info_) { + total += it.second.class_set.size(); + } + return total; +} + std::string ProfileCompilationInfo::DumpInfo(const std::vector<const DexFile*>* dex_files, bool print_full_dex_location) const { std::ostringstream os; @@ -408,19 +608,29 @@ std::string ProfileCompilationInfo::DumpInfo(const std::vector<const DexFile*>* std::string multidex_suffix = DexFile::GetMultiDexSuffix(location); os << (multidex_suffix.empty() ? kFirstDexFileKeySubstitute : multidex_suffix); } - for (const auto method_it : dex_data.method_set) { - if (dex_files != nullptr) { - const DexFile* dex_file = nullptr; - for (size_t i = 0; i < dex_files->size(); i++) { - if (location == (*dex_files)[i]->GetLocation()) { - dex_file = (*dex_files)[i]; - } - } - if (dex_file != nullptr) { - os << "\n " << PrettyMethod(method_it, *dex_file, true); + const DexFile* dex_file = nullptr; + if (dex_files != nullptr) { + for (size_t i = 0; i < dex_files->size(); i++) { + if (location == (*dex_files)[i]->GetLocation()) { + dex_file = (*dex_files)[i]; } } - os << "\n " << method_it; + } + os << "\n\tmethods: "; + for (const auto method_it : dex_data.method_set) { + if (dex_file != nullptr) { + os << "\n\t\t" << PrettyMethod(method_it, *dex_file, true); + } else { + os << method_it << ","; + } + } + os << "\n\tclasses: "; + for (const auto class_it : dex_data.class_set) { + if (dex_file != nullptr) { + os << "\n\t\t" << PrettyType(class_it, *dex_file); + } else { + os << class_it << ","; + } } } return os.str(); @@ -435,11 +645,18 @@ std::set<DexCacheResolvedClasses> ProfileCompilationInfo::GetResolvedClasses() c for (auto&& pair : info_) { const std::string& profile_key = pair.first; const DexFileData& data = pair.second; - DexCacheResolvedClasses classes(profile_key, data.checksum); + // TODO: Is it OK to use the same location for both base and dex location here? + DexCacheResolvedClasses classes(profile_key, profile_key, data.checksum); classes.AddClasses(data.class_set.begin(), data.class_set.end()); ret.insert(classes); } return ret; } +void ProfileCompilationInfo::ClearResolvedClasses() { + for (auto& pair : info_) { + pair.second.class_set.clear(); + } +} + } // namespace art diff --git a/runtime/jit/offline_profiling_info.h b/runtime/jit/offline_profiling_info.h index df03244779..5a07da79a1 100644 --- a/runtime/jit/offline_profiling_info.h +++ b/runtime/jit/offline_profiling_info.h @@ -28,9 +28,6 @@ namespace art { -class ArtMethod; -class DexCacheProfileData; - // TODO: rename file. /** * Profile information in a format suitable to be queried by the compiler and @@ -41,21 +38,29 @@ class DexCacheProfileData; */ class ProfileCompilationInfo { public: - // Saves profile information about the given methods in the given file. - // Note that the saving proceeds only if the file can be locked for exclusive access. - // If not (the locking is not blocking), the function does not save and returns false. - static bool SaveProfilingInfo(const std::string& filename, - const std::vector<ArtMethod*>& methods, - const std::set<DexCacheResolvedClasses>& resolved_classes); + static const uint8_t kProfileMagic[]; + static const uint8_t kProfileVersion[]; + // Add the given methods and classes to the current profile object. + bool AddMethodsAndClasses(const std::vector<MethodReference>& methods, + const std::set<DexCacheResolvedClasses>& resolved_classes); // Loads profile information from the given file descriptor. bool Load(int fd); - // Loads the data from another ProfileCompilationInfo object. - bool Load(const ProfileCompilationInfo& info); + // Merge the data from another ProfileCompilationInfo into the current object. + bool MergeWith(const ProfileCompilationInfo& info); // Saves the profile data to the given file descriptor. bool Save(int fd); + // Loads and merges profile information from the given file into the current + // object and tries to save it back to disk. + // If `force` is true then the save will go through even if the given file + // has bad data or its version does not match. In this cases the profile content + // is ignored. + bool MergeAndSave(const std::string& filename, uint64_t* bytes_written, bool force); + // Returns the number of methods that were profiled. uint32_t GetNumberOfMethods() const; + // Returns the number of resolved classes that were profiled. + uint32_t GetNumberOfResolvedClasses() const; // Returns true if the method reference is present in the profiling info. bool ContainsMethod(const MethodReference& method_ref) const; @@ -70,8 +75,8 @@ class ProfileCompilationInfo { std::string DumpInfo(const std::vector<const DexFile*>* dex_files, bool print_full_dex_location = true) const; - // For testing purposes. bool Equals(const ProfileCompilationInfo& other); + static std::string GetProfileDexFileKey(const std::string& dex_location); // Returns the class descriptors for all of the classes in the profiles' class sets. @@ -79,7 +84,17 @@ class ProfileCompilationInfo { // profile info stuff to generate a map back to the dex location. std::set<DexCacheResolvedClasses> GetResolvedClasses() const; + // Clears the resolved classes from the current object. + void ClearResolvedClasses(); + private: + enum ProfileLoadSatus { + kProfileLoadIOError, + kProfileLoadVersionMismatch, + kProfileLoadBadData, + kProfileLoadSuccess + }; + struct DexFileData { explicit DexFileData(uint32_t location_checksum) : checksum(location_checksum) {} uint32_t checksum; @@ -96,9 +111,65 @@ class ProfileCompilationInfo { DexFileData* GetOrAddDexFileData(const std::string& dex_location, uint32_t checksum); bool AddMethodIndex(const std::string& dex_location, uint32_t checksum, uint16_t method_idx); bool AddClassIndex(const std::string& dex_location, uint32_t checksum, uint16_t class_idx); - bool AddResolvedClasses(const DexCacheResolvedClasses& classes) - SHARED_REQUIRES(Locks::mutator_lock_); - bool ProcessLine(const std::string& line); + bool AddResolvedClasses(const DexCacheResolvedClasses& classes); + + // Parsing functionality. + + struct ProfileLineHeader { + std::string dex_location; + uint16_t method_set_size; + uint16_t class_set_size; + uint32_t checksum; + }; + + // A helper structure to make sure we don't read past our buffers in the loops. + struct SafeBuffer { + public: + explicit SafeBuffer(size_t size) : storage_(new uint8_t[size]) { + ptr_current_ = storage_.get(); + ptr_end_ = ptr_current_ + size; + } + + // Reads the content of the descriptor at the current position. + ProfileLoadSatus FillFromFd(int fd, + const std::string& source, + /*out*/std::string* error); + + // Reads an uint value (high bits to low bits) and advances the current pointer + // with the number of bits read. + template <typename T> T ReadUintAndAdvance(); + + // Compares the given data with the content current pointer. If the contents are + // equal it advances the current pointer by data_size. + bool CompareAndAdvance(const uint8_t* data, size_t data_size); + + // Get the underlying raw buffer. + uint8_t* Get() { return storage_.get(); } + + private: + std::unique_ptr<uint8_t> storage_; + uint8_t* ptr_current_; + uint8_t* ptr_end_; + }; + + ProfileLoadSatus LoadInternal(int fd, std::string* error); + + ProfileLoadSatus ReadProfileHeader(int fd, + /*out*/uint16_t* number_of_lines, + /*out*/std::string* error); + + ProfileLoadSatus ReadProfileLineHeader(int fd, + /*out*/ProfileLineHeader* line_header, + /*out*/std::string* error); + ProfileLoadSatus ReadProfileLine(int fd, + const ProfileLineHeader& line_header, + /*out*/std::string* error); + + bool ProcessLine(SafeBuffer& line_buffer, + uint16_t method_set_size, + uint16_t class_set_size, + uint32_t checksum, + const std::string& dex_location); friend class ProfileCompilationInfoTest; friend class CompilerDriverProfileTest; diff --git a/runtime/jit/profile_compilation_info_test.cc b/runtime/jit/profile_compilation_info_test.cc index fdd8c6e600..c8f4d94c74 100644 --- a/runtime/jit/profile_compilation_info_test.cc +++ b/runtime/jit/profile_compilation_info_test.cc @@ -21,6 +21,7 @@ #include "class_linker-inl.h" #include "common_runtime_test.h" #include "dex_file.h" +#include "method_reference.h" #include "mirror/class-inl.h" #include "mirror/class_loader.h" #include "handle_scope-inl.h" @@ -49,16 +50,44 @@ class ProfileCompilationInfoTest : public CommonRuntimeTest { return methods; } - bool AddData(const std::string& dex_location, - uint32_t checksum, - uint16_t method_index, - ProfileCompilationInfo* info) { + bool AddMethod(const std::string& dex_location, + uint32_t checksum, + uint16_t method_index, + ProfileCompilationInfo* info) { return info->AddMethodIndex(dex_location, checksum, method_index); } + bool AddClass(const std::string& dex_location, + uint32_t checksum, + uint16_t class_index, + ProfileCompilationInfo* info) { + return info->AddMethodIndex(dex_location, checksum, class_index); + } + uint32_t GetFd(const ScratchFile& file) { return static_cast<uint32_t>(file.GetFd()); } + + bool SaveProfilingInfo( + const std::string& filename, + const std::vector<ArtMethod*>& methods, + const std::set<DexCacheResolvedClasses>& resolved_classes) { + ProfileCompilationInfo info; + std::vector<MethodReference> method_refs; + ScopedObjectAccess soa(Thread::Current()); + for (ArtMethod* method : methods) { + method_refs.emplace_back(method->GetDexFile(), method->GetDexMethodIndex()); + } + if (!info.AddMethodsAndClasses(method_refs, resolved_classes)) { + return false; + } + return info.MergeAndSave(filename, nullptr, false); + } + + // Cannot sizeof the actual arrays so hardcode the values here. + // They should not change anyway. + static constexpr int kProfileMagicSize = 4; + static constexpr int kProfileVersionSize = 4; }; TEST_F(ProfileCompilationInfoTest, SaveArtMethods) { @@ -75,9 +104,7 @@ TEST_F(ProfileCompilationInfoTest, SaveArtMethods) { // Save virtual methods from Main. std::set<DexCacheResolvedClasses> resolved_classes; std::vector<ArtMethod*> main_methods = GetVirtualMethods(class_loader, "LMain;"); - ASSERT_TRUE(ProfileCompilationInfo::SaveProfilingInfo(profile.GetFilename(), - main_methods, - resolved_classes)); + ASSERT_TRUE(SaveProfilingInfo(profile.GetFilename(), main_methods, resolved_classes)); // Check that what we saved is in the profile. ProfileCompilationInfo info1; @@ -92,9 +119,7 @@ TEST_F(ProfileCompilationInfoTest, SaveArtMethods) { // Save virtual methods from Second. std::vector<ArtMethod*> second_methods = GetVirtualMethods(class_loader, "LSecond;"); - ASSERT_TRUE(ProfileCompilationInfo::SaveProfilingInfo(profile.GetFilename(), - second_methods, - resolved_classes)); + ASSERT_TRUE(SaveProfilingInfo(profile.GetFilename(), second_methods, resolved_classes)); // Check that what we saved is in the profile (methods form Main and Second). ProfileCompilationInfo info2; @@ -118,8 +143,8 @@ TEST_F(ProfileCompilationInfoTest, SaveFd) { ProfileCompilationInfo saved_info; // Save a few methods. for (uint16_t i = 0; i < 10; i++) { - ASSERT_TRUE(AddData("dex_location1", /* checksum */ 1, /* method_idx */ i, &saved_info)); - ASSERT_TRUE(AddData("dex_location2", /* checksum */ 2, /* method_idx */ i, &saved_info)); + ASSERT_TRUE(AddMethod("dex_location1", /* checksum */ 1, /* method_idx */ i, &saved_info)); + ASSERT_TRUE(AddMethod("dex_location2", /* checksum */ 2, /* method_idx */ i, &saved_info)); } ASSERT_TRUE(saved_info.Save(GetFd(profile))); ASSERT_EQ(0, profile.GetFile()->Flush()); @@ -132,9 +157,9 @@ TEST_F(ProfileCompilationInfoTest, SaveFd) { // Save more methods. for (uint16_t i = 0; i < 100; i++) { - ASSERT_TRUE(AddData("dex_location1", /* checksum */ 1, /* method_idx */ i, &saved_info)); - ASSERT_TRUE(AddData("dex_location2", /* checksum */ 2, /* method_idx */ i, &saved_info)); - ASSERT_TRUE(AddData("dex_location3", /* checksum */ 3, /* method_idx */ i, &saved_info)); + ASSERT_TRUE(AddMethod("dex_location1", /* checksum */ 1, /* method_idx */ i, &saved_info)); + ASSERT_TRUE(AddMethod("dex_location2", /* checksum */ 2, /* method_idx */ i, &saved_info)); + ASSERT_TRUE(AddMethod("dex_location3", /* checksum */ 3, /* method_idx */ i, &saved_info)); } ASSERT_TRUE(profile.GetFile()->ResetOffset()); ASSERT_TRUE(saved_info.Save(GetFd(profile))); @@ -147,25 +172,156 @@ TEST_F(ProfileCompilationInfoTest, SaveFd) { ASSERT_TRUE(loaded_info2.Equals(saved_info)); } -TEST_F(ProfileCompilationInfoTest, AddDataFail) { +TEST_F(ProfileCompilationInfoTest, AddMethodsAndClassesFail) { ScratchFile profile; ProfileCompilationInfo info; - ASSERT_TRUE(AddData("dex_location", /* checksum */ 1, /* method_idx */ 1, &info)); + ASSERT_TRUE(AddMethod("dex_location", /* checksum */ 1, /* method_idx */ 1, &info)); // Trying to add info for an existing file but with a different checksum. - ASSERT_FALSE(AddData("dex_location", /* checksum */ 2, /* method_idx */ 2, &info)); + ASSERT_FALSE(AddMethod("dex_location", /* checksum */ 2, /* method_idx */ 2, &info)); } -TEST_F(ProfileCompilationInfoTest, LoadFail) { +TEST_F(ProfileCompilationInfoTest, MergeFail) { ScratchFile profile; ProfileCompilationInfo info1; - ASSERT_TRUE(AddData("dex_location", /* checksum */ 1, /* method_idx */ 1, &info1)); + ASSERT_TRUE(AddMethod("dex_location", /* checksum */ 1, /* method_idx */ 1, &info1)); // Use the same file, change the checksum. ProfileCompilationInfo info2; - ASSERT_TRUE(AddData("dex_location", /* checksum */ 2, /* method_idx */ 2, &info2)); + ASSERT_TRUE(AddMethod("dex_location", /* checksum */ 2, /* method_idx */ 2, &info2)); + + ASSERT_FALSE(info1.MergeWith(info2)); +} + +TEST_F(ProfileCompilationInfoTest, SaveMaxMethods) { + ScratchFile profile; + + ProfileCompilationInfo saved_info; + // Save the maximum number of methods + for (uint16_t i = 0; i < std::numeric_limits<uint16_t>::max(); i++) { + ASSERT_TRUE(AddMethod("dex_location1", /* checksum */ 1, /* method_idx */ i, &saved_info)); + ASSERT_TRUE(AddMethod("dex_location2", /* checksum */ 2, /* method_idx */ i, &saved_info)); + } + // Save the maximum number of classes + for (uint16_t i = 0; i < std::numeric_limits<uint16_t>::max(); i++) { + ASSERT_TRUE(AddClass("dex_location1", /* checksum */ 1, /* class_idx */ i, &saved_info)); + ASSERT_TRUE(AddClass("dex_location2", /* checksum */ 2, /* class_idx */ i, &saved_info)); + } + + ASSERT_TRUE(saved_info.Save(GetFd(profile))); + ASSERT_EQ(0, profile.GetFile()->Flush()); + + // Check that we get back what we saved. + ProfileCompilationInfo loaded_info; + ASSERT_TRUE(profile.GetFile()->ResetOffset()); + ASSERT_TRUE(loaded_info.Load(GetFd(profile))); + ASSERT_TRUE(loaded_info.Equals(saved_info)); +} + +TEST_F(ProfileCompilationInfoTest, SaveEmpty) { + ScratchFile profile; + + ProfileCompilationInfo saved_info; + ASSERT_TRUE(saved_info.Save(GetFd(profile))); + ASSERT_EQ(0, profile.GetFile()->Flush()); + + // Check that we get back what we saved. + ProfileCompilationInfo loaded_info; + ASSERT_TRUE(profile.GetFile()->ResetOffset()); + ASSERT_TRUE(loaded_info.Load(GetFd(profile))); + ASSERT_TRUE(loaded_info.Equals(saved_info)); +} + +TEST_F(ProfileCompilationInfoTest, LoadEmpty) { + ScratchFile profile; + + ProfileCompilationInfo empyt_info; + + ProfileCompilationInfo loaded_info; + ASSERT_TRUE(profile.GetFile()->ResetOffset()); + ASSERT_TRUE(loaded_info.Load(GetFd(profile))); + ASSERT_TRUE(loaded_info.Equals(empyt_info)); +} + +TEST_F(ProfileCompilationInfoTest, BadMagic) { + ScratchFile profile; + uint8_t buffer[] = { 1, 2, 3, 4 }; + ASSERT_TRUE(profile.GetFile()->WriteFully(buffer, sizeof(buffer))); + ProfileCompilationInfo loaded_info; + ASSERT_TRUE(profile.GetFile()->ResetOffset()); + ASSERT_FALSE(loaded_info.Load(GetFd(profile))); +} + +TEST_F(ProfileCompilationInfoTest, BadVersion) { + ScratchFile profile; + + ASSERT_TRUE(profile.GetFile()->WriteFully( + ProfileCompilationInfo::kProfileMagic, kProfileMagicSize)); + uint8_t version[] = { 'v', 'e', 'r', 's', 'i', 'o', 'n' }; + ASSERT_TRUE(profile.GetFile()->WriteFully(version, sizeof(version))); + ASSERT_EQ(0, profile.GetFile()->Flush()); + + ProfileCompilationInfo loaded_info; + ASSERT_TRUE(profile.GetFile()->ResetOffset()); + ASSERT_FALSE(loaded_info.Load(GetFd(profile))); +} - ASSERT_FALSE(info1.Load(info2)); +TEST_F(ProfileCompilationInfoTest, Incomplete) { + ScratchFile profile; + ASSERT_TRUE(profile.GetFile()->WriteFully( + ProfileCompilationInfo::kProfileMagic, kProfileMagicSize)); + ASSERT_TRUE(profile.GetFile()->WriteFully( + ProfileCompilationInfo::kProfileVersion, kProfileVersionSize)); + // Write that we have at least one line. + uint8_t line_number[] = { 0, 1 }; + ASSERT_TRUE(profile.GetFile()->WriteFully(line_number, sizeof(line_number))); + ASSERT_EQ(0, profile.GetFile()->Flush()); + + ProfileCompilationInfo loaded_info; + ASSERT_TRUE(profile.GetFile()->ResetOffset()); + ASSERT_FALSE(loaded_info.Load(GetFd(profile))); +} + +TEST_F(ProfileCompilationInfoTest, TooLongDexLocation) { + ScratchFile profile; + ASSERT_TRUE(profile.GetFile()->WriteFully( + ProfileCompilationInfo::kProfileMagic, kProfileMagicSize)); + ASSERT_TRUE(profile.GetFile()->WriteFully( + ProfileCompilationInfo::kProfileVersion, kProfileVersionSize)); + // Write that we have at least one line. + uint8_t line_number[] = { 0, 1 }; + ASSERT_TRUE(profile.GetFile()->WriteFully(line_number, sizeof(line_number))); + + // dex_location_size, methods_size, classes_size, checksum. + // Dex location size is too big and should be rejected. + uint8_t line[] = { 255, 255, 0, 1, 0, 1, 0, 0, 0, 0 }; + ASSERT_TRUE(profile.GetFile()->WriteFully(line, sizeof(line))); + ASSERT_EQ(0, profile.GetFile()->Flush()); + + ProfileCompilationInfo loaded_info; + ASSERT_TRUE(profile.GetFile()->ResetOffset()); + ASSERT_FALSE(loaded_info.Load(GetFd(profile))); +} + +TEST_F(ProfileCompilationInfoTest, UnexpectedContent) { + ScratchFile profile; + + ProfileCompilationInfo saved_info; + // Save the maximum number of methods + for (uint16_t i = 0; i < 10; i++) { + ASSERT_TRUE(AddMethod("dex_location1", /* checksum */ 1, /* method_idx */ i, &saved_info)); + } + ASSERT_TRUE(saved_info.Save(GetFd(profile))); + + uint8_t random_data[] = { 1, 2, 3}; + ASSERT_TRUE(profile.GetFile()->WriteFully(random_data, sizeof(random_data))); + + ASSERT_EQ(0, profile.GetFile()->Flush()); + + // Check that we fail because of unexpected data at the end of the file. + ProfileCompilationInfo loaded_info; + ASSERT_TRUE(profile.GetFile()->ResetOffset()); + ASSERT_FALSE(loaded_info.Load(GetFd(profile))); } } // namespace art diff --git a/runtime/jit/profile_saver.cc b/runtime/jit/profile_saver.cc index 6fe17dbe15..9822f6e851 100644 --- a/runtime/jit/profile_saver.cc +++ b/runtime/jit/profile_saver.cc @@ -22,25 +22,28 @@ #include "art_method-inl.h" #include "base/systrace.h" -#include "scoped_thread_state_change.h" +#include "base/time_utils.h" +#include "compiler_filter.h" #include "oat_file_manager.h" +#include "scoped_thread_state_change.h" -namespace art { -// An arbitrary value to throttle save requests. Set to 2s for now. -static constexpr const uint64_t kMilisecondsToNano = 1000000; -static constexpr const uint64_t kMinimumTimeBetweenCodeCacheUpdatesNs = 2000 * kMilisecondsToNano; +namespace art { // TODO: read the constants from ProfileOptions, // Add a random delay each time we go to sleep so that we don't hammer the CPU // with all profile savers running at the same time. -static constexpr const uint64_t kRandomDelayMaxMs = 20 * 1000; // 20 seconds -static constexpr const uint64_t kMaxBackoffMs = 5 * 60 * 1000; // 5 minutes -static constexpr const uint64_t kSavePeriodMs = 10 * 1000; // 10 seconds -static constexpr const uint64_t kInitialDelayMs = 2 * 1000; // 2 seconds -static constexpr const double kBackoffCoef = 1.5; +static constexpr const uint64_t kMinSavePeriodNs = MsToNs(20 * 1000); // 20 seconds +static constexpr const uint64_t kSaveResolvedClassesDelayMs = 2 * 1000; // 2 seconds +// Minimum number of JIT samples during launch to include a method into the profile. +static constexpr const size_t kStartupMethodSamples = 1; + +static constexpr const uint32_t kMinimumNumberOfMethodsToSave = 10; +static constexpr const uint32_t kMinimumNumberOfClassesToSave = 10; +static constexpr const uint32_t kMinimumNumberOfNotificationBeforeWake = + kMinimumNumberOfMethodsToSave; +static constexpr const uint32_t kMaximumNumberOfNotificationBeforeWake = 50; -static constexpr const uint32_t kMinimumNrOrMethodsToSave = 10; ProfileSaver* ProfileSaver::instance_ = nullptr; pthread_t ProfileSaver::profiler_pthread_ = 0U; @@ -52,13 +55,25 @@ ProfileSaver::ProfileSaver(const std::string& output_filename, const std::string& app_data_dir) : jit_code_cache_(jit_code_cache), foreign_dex_profile_path_(foreign_dex_profile_path), - code_cache_last_update_time_ns_(0), shutting_down_(false), - first_profile_(true), + last_save_number_of_methods_(0), + last_save_number_of_classes_(0), + last_time_ns_saver_woke_up_(0), + jit_activity_notifications_(0), wait_lock_("ProfileSaver wait lock"), - period_condition_("ProfileSaver period condition", wait_lock_) { - AddTrackedLocations(output_filename, code_paths); - app_data_dir_ = ""; + period_condition_("ProfileSaver period condition", wait_lock_), + total_bytes_written_(0), + total_number_of_writes_(0), + total_number_of_code_cache_queries_(0), + total_number_of_skipped_writes_(0), + total_number_of_failed_writes_(0), + total_ms_of_sleep_(0), + total_ns_of_work_(0), + total_number_of_foreign_dex_marks_(0), + max_number_of_profile_entries_cached_(0), + total_number_of_hot_spikes_(0), + total_number_of_wake_ups_(0) { + AddTrackedLocations(output_filename, app_data_dir, code_paths); if (!app_data_dir.empty()) { // The application directory is used to determine which dex files are owned by app. // Since it could be a symlink (e.g. /data/data instead of /data/user/0), and we @@ -66,116 +81,296 @@ ProfileSaver::ProfileSaver(const std::string& output_filename, // store it's canonical form to be sure we use the same base when comparing. UniqueCPtr<const char[]> app_data_dir_real_path(realpath(app_data_dir.c_str(), nullptr)); if (app_data_dir_real_path != nullptr) { - app_data_dir_.assign(app_data_dir_real_path.get()); + app_data_dirs_.emplace(app_data_dir_real_path.get()); } else { - LOG(WARNING) << "Failed to get the real path for app dir: " << app_data_dir_ + LOG(WARNING) << "Failed to get the real path for app dir: " << app_data_dir << ". The app dir will not be used to determine which dex files belong to the app"; } } } void ProfileSaver::Run() { - srand(MicroTime() * getpid()); Thread* self = Thread::Current(); - uint64_t save_period_ms = kSavePeriodMs; - VLOG(profiler) << "Save profiling information every " << save_period_ms << " ms"; + // Fetch the resolved classes for the app images after sleeping for + // kSaveResolvedClassesDelayMs. + // TODO(calin) This only considers the case of the primary profile file. + // Anything that gets loaded in the same VM will not have their resolved + // classes save (unless they started before the initial saving was done). + { + MutexLock mu(self, wait_lock_); + constexpr uint64_t kSleepTime = kSaveResolvedClassesDelayMs; + const uint64_t end_time = NanoTime() + MsToNs(kSleepTime); + while (true) { + const uint64_t current_time = NanoTime(); + if (current_time >= end_time) { + break; + } + period_condition_.TimedWait(self, NsToMs(end_time - current_time), 0); + } + total_ms_of_sleep_ += kSaveResolvedClassesDelayMs; + } + FetchAndCacheResolvedClassesAndMethods(); - bool first_iteration = true; + // Loop for the profiled methods. while (!ShuttingDown(self)) { - uint64_t sleep_time_ms; - if (first_iteration) { - // Sleep less long for the first iteration since we want to record loaded classes shortly - // after app launch. - sleep_time_ms = kInitialDelayMs; - } else { - const uint64_t random_sleep_delay_ms = rand() % kRandomDelayMaxMs; - sleep_time_ms = save_period_ms + random_sleep_delay_ms; - } + uint64_t sleep_start = NanoTime(); { - MutexLock mu(self, wait_lock_); - period_condition_.TimedWait(self, sleep_time_ms, 0); + uint64_t sleep_time = 0; + { + MutexLock mu(self, wait_lock_); + period_condition_.Wait(self); + sleep_time = NanoTime() - sleep_start; + } + // Check if the thread was woken up for shutdown. + if (ShuttingDown(self)) { + break; + } + total_number_of_wake_ups_++; + // We might have been woken up by a huge number of notifications to guarantee saving. + // If we didn't meet the minimum saving period go back to sleep (only if missed by + // a reasonable margin). + while (kMinSavePeriodNs * 0.9 > sleep_time) { + { + MutexLock mu(self, wait_lock_); + period_condition_.TimedWait(self, NsToMs(kMinSavePeriodNs - sleep_time), 0); + sleep_time = NanoTime() - sleep_start; + } + // Check if the thread was woken up for shutdown. + if (ShuttingDown(self)) { + break; + } + total_number_of_wake_ups_++; + } } + total_ms_of_sleep_ += NsToMs(NanoTime() - sleep_start); if (ShuttingDown(self)) { break; } - if (!ProcessProfilingInfo() && save_period_ms < kMaxBackoffMs) { - // If we don't need to save now it is less likely that we will need to do - // so in the future. Increase the time between saves according to the - // kBackoffCoef, but make it no larger than kMaxBackoffMs. - save_period_ms = static_cast<uint64_t>(kBackoffCoef * save_period_ms); - } else { - // Reset the period to the initial value as it's highly likely to JIT again. - save_period_ms = kSavePeriodMs; + uint16_t new_methods = 0; + uint64_t start_work = NanoTime(); + bool profile_saved_to_disk = ProcessProfilingInfo(&new_methods); + // Update the notification counter based on result. Note that there might be contention on this + // but we don't care about to be 100% precise. + if (!profile_saved_to_disk) { + // If we didn't save to disk it may be because we didn't have enough new methods. + // Set the jit activity notifications to new_methods so we can wake up earlier if needed. + jit_activity_notifications_ = new_methods; + } + total_ns_of_work_ += NanoTime() - start_work; + } +} + +void ProfileSaver::NotifyJitActivity() { + MutexLock mu(Thread::Current(), *Locks::profiler_lock_); + if (instance_ == nullptr || instance_->shutting_down_) { + return; + } + instance_->NotifyJitActivityInternal(); +} + +void ProfileSaver::WakeUpSaver() { + jit_activity_notifications_ = 0; + last_time_ns_saver_woke_up_ = NanoTime(); + period_condition_.Signal(Thread::Current()); +} + +void ProfileSaver::NotifyJitActivityInternal() { + // Unlikely to overflow but if it happens, + // we would have waken up the saver long before that. + jit_activity_notifications_++; + // Note that we are not as precise as we could be here but we don't want to wake the saver + // every time we see a hot method. + if (jit_activity_notifications_ > kMinimumNumberOfNotificationBeforeWake) { + MutexLock wait_mutex(Thread::Current(), wait_lock_); + if ((NanoTime() - last_time_ns_saver_woke_up_) > kMinSavePeriodNs) { + WakeUpSaver(); } - first_iteration = false; + } else if (jit_activity_notifications_ > kMaximumNumberOfNotificationBeforeWake) { + // Make sure to wake up the saver if we see a spike in the number of notifications. + // This is a precaution to avoid "loosing" a big number of methods in case + // this is a spike with no jit after. + total_number_of_hot_spikes_++; + MutexLock wait_mutex(Thread::Current(), wait_lock_); + WakeUpSaver(); + } +} + +ProfileCompilationInfo* ProfileSaver::GetCachedProfiledInfo(const std::string& filename) { + auto info_it = profile_cache_.find(filename); + if (info_it == profile_cache_.end()) { + info_it = profile_cache_.Put(filename, ProfileCompilationInfo()); } + return &info_it->second; } -bool ProfileSaver::ProcessProfilingInfo() { +// Get resolved methods that have a profile info or more than kStartupMethodSamples samples. +// Excludes native methods and classes in the boot image. +class GetMethodsVisitor : public ClassVisitor { + public: + explicit GetMethodsVisitor(std::vector<MethodReference>* methods) : methods_(methods) {} + + virtual bool operator()(mirror::Class* klass) SHARED_REQUIRES(Locks::mutator_lock_) { + if (Runtime::Current()->GetHeap()->ObjectIsInBootImageSpace(klass)) { + return true; + } + for (ArtMethod& method : klass->GetMethods(sizeof(void*))) { + if (!method.IsNative()) { + if (method.GetCounter() >= kStartupMethodSamples || + method.GetProfilingInfo(sizeof(void*)) != nullptr) { + // Have samples, add to profile. + const DexFile* dex_file = method.GetInterfaceMethodIfProxy(sizeof(void*))->GetDexFile(); + methods_->push_back(MethodReference(dex_file, method.GetDexMethodIndex())); + } + } + } + return true; + } + + private: + std::vector<MethodReference>* const methods_; +}; + +void ProfileSaver::FetchAndCacheResolvedClassesAndMethods() { ScopedTrace trace(__PRETTY_FUNCTION__); - uint64_t last_update_time_ns = jit_code_cache_->GetLastUpdateTimeNs(); - if (!first_profile_ && last_update_time_ns - code_cache_last_update_time_ns_ - < kMinimumTimeBetweenCodeCacheUpdatesNs) { - VLOG(profiler) << "Not enough time has passed since the last code cache update." - << "Last update: " << last_update_time_ns - << " Last save: " << code_cache_last_update_time_ns_; - return false; + ClassLinker* const class_linker = Runtime::Current()->GetClassLinker(); + std::set<DexCacheResolvedClasses> resolved_classes = + class_linker->GetResolvedClasses(/*ignore boot classes*/ true); + + std::vector<MethodReference> methods; + { + ScopedTrace trace2("Get hot methods"); + GetMethodsVisitor visitor(&methods); + ScopedObjectAccess soa(Thread::Current()); + class_linker->VisitClasses(&visitor); + VLOG(profiler) << "Methods with samples greater than " + << kStartupMethodSamples << " = " << methods.size(); + } + MutexLock mu(Thread::Current(), *Locks::profiler_lock_); + uint64_t total_number_of_profile_entries_cached = 0; + + for (const auto& it : tracked_dex_base_locations_) { + std::set<DexCacheResolvedClasses> resolved_classes_for_location; + const std::string& filename = it.first; + const std::set<std::string>& locations = it.second; + std::vector<MethodReference> methods_for_location; + for (const MethodReference& ref : methods) { + if (locations.find(ref.dex_file->GetBaseLocation()) != locations.end()) { + methods_for_location.push_back(ref); + } + } + for (const DexCacheResolvedClasses& classes : resolved_classes) { + if (locations.find(classes.GetBaseLocation()) != locations.end()) { + VLOG(profiler) << "Added " << classes.GetClasses().size() << " classes for location " + << classes.GetBaseLocation() << " (" << classes.GetDexLocation() << ")"; + resolved_classes_for_location.insert(classes); + } else { + VLOG(profiler) << "Location not found " << classes.GetBaseLocation() + << " (" << classes.GetDexLocation() << ")"; + } + } + ProfileCompilationInfo* info = GetCachedProfiledInfo(filename); + info->AddMethodsAndClasses(methods_for_location, resolved_classes_for_location); + total_number_of_profile_entries_cached += resolved_classes_for_location.size(); } + max_number_of_profile_entries_cached_ = std::max( + max_number_of_profile_entries_cached_, + total_number_of_profile_entries_cached); +} - uint64_t start = NanoTime(); - code_cache_last_update_time_ns_ = last_update_time_ns; +bool ProfileSaver::ProcessProfilingInfo(uint16_t* new_methods) { + ScopedTrace trace(__PRETTY_FUNCTION__); SafeMap<std::string, std::set<std::string>> tracked_locations; { // Make a copy so that we don't hold the lock while doing I/O. MutexLock mu(Thread::Current(), *Locks::profiler_lock_); tracked_locations = tracked_dex_base_locations_; } + + bool profile_file_saved = false; + uint64_t total_number_of_profile_entries_cached = 0; + *new_methods = 0; + for (const auto& it : tracked_locations) { if (ShuttingDown(Thread::Current())) { return true; } const std::string& filename = it.first; const std::set<std::string>& locations = it.second; - std::vector<ArtMethod*> methods; + std::vector<MethodReference> methods; { ScopedObjectAccess soa(Thread::Current()); - jit_code_cache_->GetCompiledArtMethods(locations, methods); - } - // Always save for the first one for loaded classes profile. - if (methods.size() < kMinimumNrOrMethodsToSave && !first_profile_) { - VLOG(profiler) << "Not enough information to save to: " << filename - <<" Nr of methods: " << methods.size(); - return false; + jit_code_cache_->GetProfiledMethods(locations, methods); + total_number_of_code_cache_queries_++; } - std::set<DexCacheResolvedClasses> resolved_classes; - if (first_profile_) { - ClassLinker* const class_linker = Runtime::Current()->GetClassLinker(); - resolved_classes = class_linker->GetResolvedClasses(/*ignore boot classes*/true); + ProfileCompilationInfo* cached_info = GetCachedProfiledInfo(filename); + cached_info->AddMethodsAndClasses(methods, std::set<DexCacheResolvedClasses>()); + int64_t delta_number_of_methods = + cached_info->GetNumberOfMethods() - + static_cast<int64_t>(last_save_number_of_methods_); + int64_t delta_number_of_classes = + cached_info->GetNumberOfResolvedClasses() - + static_cast<int64_t>(last_save_number_of_classes_); + + if (delta_number_of_methods < kMinimumNumberOfMethodsToSave && + delta_number_of_classes < kMinimumNumberOfClassesToSave) { + VLOG(profiler) << "Not enough information to save to: " << filename + << " Nr of methods: " << delta_number_of_methods + << " Nr of classes: " << delta_number_of_classes; + total_number_of_skipped_writes_++; + continue; } - - if (!ProfileCompilationInfo::SaveProfilingInfo(filename, methods, resolved_classes)) { + *new_methods = std::max(static_cast<uint16_t>(delta_number_of_methods), *new_methods); + uint64_t bytes_written; + // Force the save. In case the profile data is corrupted or the the profile + // has the wrong version this will "fix" the file to the correct format. + if (cached_info->MergeAndSave(filename, &bytes_written, /*force*/ true)) { + last_save_number_of_methods_ = cached_info->GetNumberOfMethods(); + last_save_number_of_classes_ = cached_info->GetNumberOfResolvedClasses(); + // Clear resolved classes. No need to store them around as + // they don't change after the first write. + cached_info->ClearResolvedClasses(); + if (bytes_written > 0) { + total_number_of_writes_++; + total_bytes_written_ += bytes_written; + profile_file_saved = true; + } else { + // At this point we could still have avoided the write. + // We load and merge the data from the file lazily at its first ever + // save attempt. So, whatever we are trying to save could already be + // in the file. + total_number_of_skipped_writes_++; + } + } else { LOG(WARNING) << "Could not save profiling info to " << filename; - return false; + total_number_of_failed_writes_++; } - - VLOG(profiler) << "Profile process time: " << PrettyDuration(NanoTime() - start); + total_number_of_profile_entries_cached += + cached_info->GetNumberOfMethods() + + cached_info->GetNumberOfResolvedClasses(); } - first_profile_ = false; - return true; + max_number_of_profile_entries_cached_ = std::max( + max_number_of_profile_entries_cached_, + total_number_of_profile_entries_cached); + return profile_file_saved; } void* ProfileSaver::RunProfileSaverThread(void* arg) { Runtime* runtime = Runtime::Current(); - ProfileSaver* profile_saver = reinterpret_cast<ProfileSaver*>(arg); - CHECK(runtime->AttachCurrentThread("Profile Saver", - /*as_daemon*/true, - runtime->GetSystemThreadGroup(), - /*create_peer*/true)); + bool attached = runtime->AttachCurrentThread("Profile Saver", + /*as_daemon*/true, + runtime->GetSystemThreadGroup(), + /*create_peer*/true); + if (!attached) { + CHECK(runtime->IsShuttingDown(Thread::Current())); + return nullptr; + } + + ProfileSaver* profile_saver = reinterpret_cast<ProfileSaver*>(arg); profile_saver->Run(); runtime->DetachCurrentThread(); @@ -183,15 +378,47 @@ void* ProfileSaver::RunProfileSaverThread(void* arg) { return nullptr; } +static bool ShouldProfileLocation(const std::string& location) { + OatFileManager& oat_manager = Runtime::Current()->GetOatFileManager(); + const OatFile* oat_file = oat_manager.FindOpenedOatFileFromDexLocation(location); + if (oat_file == nullptr) { + // This can happen if we fallback to run code directly from the APK. + // Profile it with the hope that the background dexopt will get us back into + // a good state. + VLOG(profiler) << "Asked to profile a location without an oat file:" << location; + return true; + } + CompilerFilter::Filter filter = oat_file->GetCompilerFilter(); + if ((filter == CompilerFilter::kSpeed) || (filter == CompilerFilter::kEverything)) { + VLOG(profiler) + << "Skip profiling oat file because it's already speed|everything compiled: " + << location << " oat location: " << oat_file->GetLocation(); + return false; + } + return true; +} + void ProfileSaver::Start(const std::string& output_filename, jit::JitCodeCache* jit_code_cache, const std::vector<std::string>& code_paths, const std::string& foreign_dex_profile_path, const std::string& app_data_dir) { - DCHECK(Runtime::Current()->UseJit()); + DCHECK(Runtime::Current()->SaveProfileInfo()); DCHECK(!output_filename.empty()); DCHECK(jit_code_cache != nullptr); + std::vector<std::string> code_paths_to_profile; + + for (const std::string& location : code_paths) { + if (ShouldProfileLocation(location)) { + code_paths_to_profile.push_back(location); + } + } + if (code_paths_to_profile.empty()) { + VLOG(profiler) << "No code paths should be profiled."; + return; + } + MutexLock mu(Thread::Current(), *Locks::profiler_lock_); if (instance_ != nullptr) { // If we already have an instance, make sure it uses the same jit_code_cache. @@ -199,16 +426,16 @@ void ProfileSaver::Start(const std::string& output_filename, // apps which share the same runtime). DCHECK_EQ(instance_->jit_code_cache_, jit_code_cache); // Add the code_paths to the tracked locations. - instance_->AddTrackedLocations(output_filename, code_paths); + instance_->AddTrackedLocations(output_filename, app_data_dir, code_paths_to_profile); return; } VLOG(profiler) << "Starting profile saver using output file: " << output_filename - << ". Tracking: " << Join(code_paths, ':'); + << ". Tracking: " << Join(code_paths_to_profile, ':'); instance_ = new ProfileSaver(output_filename, jit_code_cache, - code_paths, + code_paths_to_profile, foreign_dex_profile_path, app_data_dir); @@ -219,7 +446,7 @@ void ProfileSaver::Start(const std::string& output_filename, "Profile saver thread"); } -void ProfileSaver::Stop() { +void ProfileSaver::Stop(bool dump_info) { ProfileSaver* profile_saver = nullptr; pthread_t profiler_pthread = 0U; @@ -237,6 +464,9 @@ void ProfileSaver::Stop() { return; } instance_->shutting_down_ = true; + if (dump_info) { + instance_->DumpInfo(LOG(INFO)); + } } { @@ -267,49 +497,62 @@ bool ProfileSaver::IsStarted() { } void ProfileSaver::AddTrackedLocations(const std::string& output_filename, + const std::string& app_data_dir, const std::vector<std::string>& code_paths) { auto it = tracked_dex_base_locations_.find(output_filename); if (it == tracked_dex_base_locations_.end()) { tracked_dex_base_locations_.Put(output_filename, std::set<std::string>(code_paths.begin(), code_paths.end())); + app_data_dirs_.insert(app_data_dir); } else { it->second.insert(code_paths.begin(), code_paths.end()); } } void ProfileSaver::NotifyDexUse(const std::string& dex_location) { + if (!ShouldProfileLocation(dex_location)) { + return; + } std::set<std::string> app_code_paths; std::string foreign_dex_profile_path; - std::string app_data_dir; + std::set<std::string> app_data_dirs; { MutexLock mu(Thread::Current(), *Locks::profiler_lock_); - DCHECK(instance_ != nullptr); + if (instance_ == nullptr) { + return; + } // Make a copy so that we don't hold the lock while doing I/O. for (const auto& it : instance_->tracked_dex_base_locations_) { app_code_paths.insert(it.second.begin(), it.second.end()); } foreign_dex_profile_path = instance_->foreign_dex_profile_path_; - app_data_dir = instance_->app_data_dir_; + app_data_dirs.insert(instance_->app_data_dirs_.begin(), instance_->app_data_dirs_.end()); } - MaybeRecordDexUseInternal(dex_location, - app_code_paths, - foreign_dex_profile_path, - app_data_dir); + bool mark_created = MaybeRecordDexUseInternal(dex_location, + app_code_paths, + foreign_dex_profile_path, + app_data_dirs); + if (mark_created) { + MutexLock mu(Thread::Current(), *Locks::profiler_lock_); + if (instance_ != nullptr) { + instance_->total_number_of_foreign_dex_marks_++; + } + } } -void ProfileSaver::MaybeRecordDexUseInternal( +bool ProfileSaver::MaybeRecordDexUseInternal( const std::string& dex_location, const std::set<std::string>& app_code_paths, const std::string& foreign_dex_profile_path, - const std::string& app_data_dir) { + const std::set<std::string>& app_data_dirs) { if (dex_location.empty()) { LOG(WARNING) << "Asked to record foreign dex use with an empty dex location."; - return; + return false; } if (foreign_dex_profile_path.empty()) { LOG(WARNING) << "Asked to record foreign dex use without a valid profile path "; - return; + return false; } UniqueCPtr<const char[]> dex_location_real_path(realpath(dex_location.c_str(), nullptr)); @@ -320,14 +563,14 @@ void ProfileSaver::MaybeRecordDexUseInternal( ? dex_location.c_str() : dex_location_real_path.get()); - if (dex_location_real_path_str.compare(0, app_data_dir.length(), app_data_dir) == 0) { + if (app_data_dirs.find(dex_location_real_path_str) != app_data_dirs.end()) { // The dex location is under the application folder. Nothing to record. - return; + return false; } if (app_code_paths.find(dex_location) != app_code_paths.end()) { // The dex location belongs to the application code paths. Nothing to record. - return; + return false; } // Do another round of checks with the real paths. // Note that we could cache all the real locations in the saver (since it's an expensive @@ -344,7 +587,7 @@ void ProfileSaver::MaybeRecordDexUseInternal( : real_app_code_location.get()); if (real_app_code_location_str == dex_location_real_path_str) { // The dex location belongs to the application code paths. Nothing to record. - return; + return false; } } @@ -355,19 +598,78 @@ void ProfileSaver::MaybeRecordDexUseInternal( // frameworks/base/services/core/java/com/android/server/pm/PackageDexOptimizer.java) std::replace(dex_location_real_path_str.begin(), dex_location_real_path_str.end(), '/', '@'); std::string flag_path = foreign_dex_profile_path + "/" + dex_location_real_path_str; - // No need to give any sort of access to flag_path. The system has enough permissions - // to test for its existence. - int fd = TEMP_FAILURE_RETRY(open(flag_path.c_str(), O_CREAT | O_EXCL, 0)); + // We use O_RDONLY as the access mode because we must supply some access + // mode, and there is no access mode that means 'create but do not read' the + // file. We will not not actually read from the file. + int fd = TEMP_FAILURE_RETRY(open(flag_path.c_str(), + O_CREAT | O_RDONLY | O_EXCL | O_CLOEXEC | O_NOFOLLOW, 0)); if (fd != -1) { if (close(fd) != 0) { PLOG(WARNING) << "Could not close file after flagging foreign dex use " << flag_path; } + return true; } else { - if (errno != EEXIST) { - // Another app could have already created the file. + if (errno != EEXIST && errno != EACCES) { + // Another app could have already created the file, and selinux may not + // allow the read access to the file implied by the call to open. PLOG(WARNING) << "Could not create foreign dex use mark " << flag_path; + return false; + } + return true; + } +} + +void ProfileSaver::DumpInstanceInfo(std::ostream& os) { + MutexLock mu(Thread::Current(), *Locks::profiler_lock_); + if (instance_ != nullptr) { + instance_->DumpInfo(os); + } +} + +void ProfileSaver::DumpInfo(std::ostream& os) { + os << "ProfileSaver total_bytes_written=" << total_bytes_written_ << '\n' + << "ProfileSaver total_number_of_writes=" << total_number_of_writes_ << '\n' + << "ProfileSaver total_number_of_code_cache_queries=" + << total_number_of_code_cache_queries_ << '\n' + << "ProfileSaver total_number_of_skipped_writes=" << total_number_of_skipped_writes_ << '\n' + << "ProfileSaver total_number_of_failed_writes=" << total_number_of_failed_writes_ << '\n' + << "ProfileSaver total_ms_of_sleep=" << total_ms_of_sleep_ << '\n' + << "ProfileSaver total_ms_of_work=" << NsToMs(total_ns_of_work_) << '\n' + << "ProfileSaver total_number_of_foreign_dex_marks=" + << total_number_of_foreign_dex_marks_ << '\n' + << "ProfileSaver max_number_profile_entries_cached=" + << max_number_of_profile_entries_cached_ << '\n' + << "ProfileSaver total_number_of_hot_spikes=" << total_number_of_hot_spikes_ << '\n' + << "ProfileSaver total_number_of_wake_ups=" << total_number_of_wake_ups_ << '\n'; +} + + +void ProfileSaver::ForceProcessProfiles() { + ProfileSaver* saver = nullptr; + { + MutexLock mu(Thread::Current(), *Locks::profiler_lock_); + saver = instance_; + } + // TODO(calin): this is not actually thread safe as the instance_ may have been deleted, + // but we only use this in testing when we now this won't happen. + // Refactor the way we handle the instance so that we don't end up in this situation. + if (saver != nullptr) { + uint16_t new_methods; + saver->ProcessProfilingInfo(&new_methods); + } +} + +bool ProfileSaver::HasSeenMethod(const std::string& profile, + const DexFile* dex_file, + uint16_t method_idx) { + MutexLock mu(Thread::Current(), *Locks::profiler_lock_); + if (instance_ != nullptr) { + ProfileCompilationInfo* info = instance_->GetCachedProfiledInfo(profile); + if (info != nullptr) { + return info->ContainsMethod(MethodReference(dex_file, method_idx)); } } + return false; } } // namespace art diff --git a/runtime/jit/profile_saver.h b/runtime/jit/profile_saver.h index e7eab95f3d..9c6d0fa1cf 100644 --- a/runtime/jit/profile_saver.h +++ b/runtime/jit/profile_saver.h @@ -37,7 +37,7 @@ class ProfileSaver { // Stops the profile saver thread. // NO_THREAD_SAFETY_ANALYSIS for static function calling into member function with excludes lock. - static void Stop() + static void Stop(bool dump_info_) REQUIRES(!Locks::profiler_lock_, !wait_lock_) NO_THREAD_SAFETY_ANALYSIS; @@ -46,6 +46,20 @@ class ProfileSaver { static void NotifyDexUse(const std::string& dex_location); + // If the profile saver is running, dumps statistics to the `os`. Otherwise it does nothing. + static void DumpInstanceInfo(std::ostream& os); + + // NO_THREAD_SAFETY_ANALYSIS for static function calling into member function with excludes lock. + static void NotifyJitActivity() + REQUIRES(!Locks::profiler_lock_, !wait_lock_) + NO_THREAD_SAFETY_ANALYSIS; + + // Just for testing purpose. + static void ForceProcessProfiles(); + static bool HasSeenMethod(const std::string& profile, + const DexFile* dex_file, + uint16_t method_idx); + private: ProfileSaver(const std::string& output_filename, jit::JitCodeCache* jit_code_cache, @@ -62,19 +76,36 @@ class ProfileSaver { void Run() REQUIRES(!Locks::profiler_lock_, !wait_lock_); // Processes the existing profiling info from the jit code cache and returns // true if it needed to be saved to disk. - bool ProcessProfilingInfo(); + bool ProcessProfilingInfo(uint16_t* new_methods) + REQUIRES(!Locks::profiler_lock_) + REQUIRES(!Locks::mutator_lock_); + + void NotifyJitActivityInternal() REQUIRES(!wait_lock_); + void WakeUpSaver() REQUIRES(wait_lock_); + // Returns true if the saver is shutting down (ProfileSaver::Stop() has been called). bool ShuttingDown(Thread* self) REQUIRES(!Locks::profiler_lock_); void AddTrackedLocations(const std::string& output_filename, + const std::string& app_data_dir, const std::vector<std::string>& code_paths) REQUIRES(Locks::profiler_lock_); - static void MaybeRecordDexUseInternal( + // Retrieves the cached profile compilation info for the given profile file. + // If no entry exists, a new empty one will be created, added to the cache and + // then returned. + ProfileCompilationInfo* GetCachedProfiledInfo(const std::string& filename); + // Fetches the current resolved classes and methods from the ClassLinker and stores them in the + // profile_cache_ for later save. + void FetchAndCacheResolvedClassesAndMethods(); + + static bool MaybeRecordDexUseInternal( const std::string& dex_location, const std::set<std::string>& tracked_locations, const std::string& foreign_dex_profile_path, - const std::string& app_data_dir); + const std::set<std::string>& app_data_dirs); + + void DumpInfo(std::ostream& os); // The only instance of the saver. static ProfileSaver* instance_ GUARDED_BY(Locks::profiler_lock_); @@ -82,18 +113,48 @@ class ProfileSaver { static pthread_t profiler_pthread_ GUARDED_BY(Locks::profiler_lock_); jit::JitCodeCache* jit_code_cache_; + + // Collection of code paths that the profiles tracks. + // It maps profile locations to code paths (dex base locations). SafeMap<std::string, std::set<std::string>> tracked_dex_base_locations_ GUARDED_BY(Locks::profiler_lock_); + // The directory were the we should store the code paths. std::string foreign_dex_profile_path_; - std::string app_data_dir_; - uint64_t code_cache_last_update_time_ns_; + + // A list of application directories, used to infer if a loaded dex belongs + // to the application or not. Multiple application data directories are possible when + // different apps share the same runtime. + std::set<std::string> app_data_dirs_ GUARDED_BY(Locks::profiler_lock_); + bool shutting_down_ GUARDED_BY(Locks::profiler_lock_); - bool first_profile_ = true; + uint32_t last_save_number_of_methods_; + uint32_t last_save_number_of_classes_; + uint64_t last_time_ns_saver_woke_up_ GUARDED_BY(wait_lock_); + uint32_t jit_activity_notifications_; + + // A local cache for the profile information. Maps each tracked file to its + // profile information. The size of this cache is usually very small and tops + // to just a few hundreds entries in the ProfileCompilationInfo objects. + // It helps avoiding unnecessary writes to disk. + SafeMap<std::string, ProfileCompilationInfo> profile_cache_; // Save period condition support. Mutex wait_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; ConditionVariable period_condition_ GUARDED_BY(wait_lock_); + uint64_t total_bytes_written_; + uint64_t total_number_of_writes_; + uint64_t total_number_of_code_cache_queries_; + uint64_t total_number_of_skipped_writes_; + uint64_t total_number_of_failed_writes_; + uint64_t total_ms_of_sleep_; + uint64_t total_ns_of_work_; + uint64_t total_number_of_foreign_dex_marks_; + // TODO(calin): replace with an actual size. + uint64_t max_number_of_profile_entries_cached_; + uint64_t total_number_of_hot_spikes_; + uint64_t total_number_of_wake_ups_; + DISALLOW_COPY_AND_ASSIGN(ProfileSaver); }; diff --git a/runtime/jit/profiling_info.h b/runtime/jit/profiling_info.h index 3a71bbaec1..d04d2de756 100644 --- a/runtime/jit/profiling_info.h +++ b/runtime/jit/profiling_info.h @@ -170,6 +170,7 @@ class ProfilingInfo { : number_of_inline_caches_(entries.size()), method_(method), is_method_being_compiled_(false), + is_osr_method_being_compiled_(false), current_inline_uses_(0), saved_entry_point_(nullptr) { memset(&cache_, 0, number_of_inline_caches_ * sizeof(InlineCache)); diff --git a/runtime/jni_internal.cc b/runtime/jni_internal.cc index 7bd85ec040..8cdf96de8f 100644 --- a/runtime/jni_internal.cc +++ b/runtime/jni_internal.cc @@ -301,13 +301,13 @@ static JavaVMExt* JavaVmExtFromEnv(JNIEnv* env) { CHECK_NON_NULL_ARGUMENT_FN_NAME(__FUNCTION__, value, return_val) #define CHECK_NON_NULL_ARGUMENT_FN_NAME(name, value, return_val) \ - if (UNLIKELY(value == nullptr)) { \ + if (UNLIKELY((value) == nullptr)) { \ JavaVmExtFromEnv(env)->JniAbortF(name, #value " == null"); \ return return_val; \ } #define CHECK_NON_NULL_MEMCPY_ARGUMENT(length, value) \ - if (UNLIKELY(length != 0 && value == nullptr)) { \ + if (UNLIKELY((length) != 0 && (value) == nullptr)) { \ JavaVmExtFromEnv(env)->JniAbortF(__FUNCTION__, #value " == null"); \ return; \ } diff --git a/runtime/jni_internal_test.cc b/runtime/jni_internal_test.cc index c718466eae..04ba8dfc64 100644 --- a/runtime/jni_internal_test.cc +++ b/runtime/jni_internal_test.cc @@ -2286,16 +2286,16 @@ TEST_F(JniInternalTest, IndirectReferenceTableOffsets) { // Test the offset computation of JNIEnvExt offsets. b/26071368. TEST_F(JniInternalTest, JNIEnvExtOffsets) { EXPECT_EQ(OFFSETOF_MEMBER(JNIEnvExt, local_ref_cookie), - JNIEnvExt::LocalRefCookieOffset(sizeof(void*)).Int32Value()); + JNIEnvExt::LocalRefCookieOffset(sizeof(void*)).Uint32Value()); - EXPECT_EQ(OFFSETOF_MEMBER(JNIEnvExt, self), JNIEnvExt::SelfOffset(sizeof(void*)).Int32Value()); + EXPECT_EQ(OFFSETOF_MEMBER(JNIEnvExt, self), JNIEnvExt::SelfOffset(sizeof(void*)).Uint32Value()); // segment_state_ is private in the IndirectReferenceTable. So this test isn't as good as we'd // hope it to be. - int32_t segment_state_now = + uint32_t segment_state_now = OFFSETOF_MEMBER(JNIEnvExt, locals) + - IndirectReferenceTable::SegmentStateOffset(sizeof(void*)).Int32Value(); - int32_t segment_state_computed = JNIEnvExt::SegmentStateOffset(sizeof(void*)).Int32Value(); + IndirectReferenceTable::SegmentStateOffset(sizeof(void*)).Uint32Value(); + uint32_t segment_state_computed = JNIEnvExt::SegmentStateOffset(sizeof(void*)).Uint32Value(); EXPECT_EQ(segment_state_now, segment_state_computed); } diff --git a/runtime/lambda/shorty_field_type.h b/runtime/lambda/shorty_field_type.h index 46ddaa9ab3..c314fd2ac3 100644 --- a/runtime/lambda/shorty_field_type.h +++ b/runtime/lambda/shorty_field_type.h @@ -391,7 +391,7 @@ struct ShortyFieldTypeTraits { private: #define IS_VALID_TYPE_SPECIALIZATION(type, name) \ - static inline constexpr bool Is ## name ## TypeImpl(type* const = 0) { \ + static inline constexpr bool Is ## name ## TypeImpl(type* const = 0) { /*NOLINT*/ \ return true; \ } \ \ diff --git a/runtime/mem_map.cc b/runtime/mem_map.cc index 5d89c21803..771f8ed290 100644 --- a/runtime/mem_map.cc +++ b/runtime/mem_map.cc @@ -302,8 +302,9 @@ MemMap* MemMap::MapAnonymous(const char* name, if (use_ashmem) { if (!kIsTargetBuild) { - // When not on Android ashmem is faked using files in /tmp. Ensure that such files won't - // fail due to ulimit restrictions. If they will then use a regular mmap. + // When not on Android (either host or assuming a linux target) ashmem is faked using + // files in /tmp. Ensure that such files won't fail due to ulimit restrictions. If they + // will then use a regular mmap. struct rlimit rlimit_fsize; CHECK_EQ(getrlimit(RLIMIT_FSIZE, &rlimit_fsize), 0); use_ashmem = (rlimit_fsize.rlim_cur == RLIM_INFINITY) || diff --git a/runtime/mem_map.h b/runtime/mem_map.h index 3eaf576845..597f0d46e1 100644 --- a/runtime/mem_map.h +++ b/runtime/mem_map.h @@ -68,7 +68,7 @@ class MemMap { bool low_4gb, bool reuse, std::string* error_msg, - bool use_ashmem = true); + bool use_ashmem = !kIsTargetLinux); // Create placeholder for a region allocated by direct call to mmap. // This is useful when we do not have control over the code calling mmap, @@ -172,7 +172,7 @@ class MemMap { const char* tail_name, int tail_prot, std::string* error_msg, - bool use_ashmem = true); + bool use_ashmem = !kIsTargetLinux); static bool CheckNoGaps(MemMap* begin_map, MemMap* end_map) REQUIRES(!Locks::mem_maps_lock_); diff --git a/runtime/mirror/class-inl.h b/runtime/mirror/class-inl.h index dfb728f00e..fcdfc88495 100644 --- a/runtime/mirror/class-inl.h +++ b/runtime/mirror/class-inl.h @@ -419,8 +419,6 @@ inline bool Class::ResolvedMethodAccessTest(Class* access_to, ArtMethod* method, } return false; } - DCHECK_EQ(this->CanAccessMember(access_to, method->GetAccessFlags()), - this->CanAccessMember(dex_access_to, method->GetAccessFlags())); } if (LIKELY(this->CanAccessMember(access_to, method->GetAccessFlags()))) { return true; diff --git a/runtime/mirror/class.cc b/runtime/mirror/class.cc index 42f003dc84..b4a23badba 100644 --- a/runtime/mirror/class.cc +++ b/runtime/mirror/class.cc @@ -1165,5 +1165,16 @@ template mirror::Constructor* Class::GetDeclaredConstructorInternal<true>( mirror::Class* klass, mirror::ObjectArray<mirror::Class>* args); +int32_t Class::GetInnerClassFlags(Handle<Class> h_this, int32_t default_value) { + if (h_this->IsProxyClass() || h_this->GetDexCache() == nullptr) { + return default_value; + } + uint32_t flags; + if (!h_this->GetDexFile().GetInnerClassFlags(h_this, &flags)) { + return default_value; + } + return flags; +} + } // namespace mirror } // namespace art diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h index 57c3590a2e..8c20fa680f 100644 --- a/runtime/mirror/class.h +++ b/runtime/mirror/class.h @@ -673,7 +673,7 @@ class MANAGED Class FINAL : public Object { // `This` and `klass` must be classes. Class* GetCommonSuperClass(Handle<Class> klass) SHARED_REQUIRES(Locks::mutator_lock_); - void SetSuperClass(Class *new_super_class) SHARED_REQUIRES(Locks::mutator_lock_) { + void SetSuperClass(Class* new_super_class) SHARED_REQUIRES(Locks::mutator_lock_) { // Super class is assigned once, except during class linker initialization. Class* old_super_class = GetFieldObject<Class>(OFFSET_OF_OBJECT_MEMBER(Class, super_class_)); DCHECK(old_super_class == nullptr || old_super_class == new_super_class); @@ -1223,6 +1223,9 @@ class MANAGED Class FINAL : public Object { Thread* self, Handle<mirror::ObjectArray<mirror::Class>> args, size_t pointer_size) SHARED_REQUIRES(Locks::mutator_lock_); + static int32_t GetInnerClassFlags(Handle<Class> h_this, int32_t default_value) + SHARED_REQUIRES(Locks::mutator_lock_); + // Used to initialize a class in the allocation code path to ensure it is guarded by a StoreStore // fence. class InitializeClassVisitor { diff --git a/runtime/mirror/dex_cache-inl.h b/runtime/mirror/dex_cache-inl.h index 2da3d8479c..2894b68f03 100644 --- a/runtime/mirror/dex_cache-inl.h +++ b/runtime/mirror/dex_cache-inl.h @@ -148,9 +148,7 @@ inline void DexCache::FixupStrings(GcRoot<mirror::String>* dest, const Visitor& for (size_t i = 0, count = NumStrings(); i < count; ++i) { mirror::String* source = src[i].Read<kReadBarrierOption>(); mirror::String* new_source = visitor(source); - if (source != new_source) { - dest[i] = GcRoot<mirror::String>(new_source); - } + dest[i] = GcRoot<mirror::String>(new_source); } } @@ -160,9 +158,7 @@ inline void DexCache::FixupResolvedTypes(GcRoot<mirror::Class>* dest, const Visi for (size_t i = 0, count = NumResolvedTypes(); i < count; ++i) { mirror::Class* source = src[i].Read<kReadBarrierOption>(); mirror::Class* new_source = visitor(source); - if (source != new_source) { - dest[i] = GcRoot<mirror::Class>(new_source); - } + dest[i] = GcRoot<mirror::Class>(new_source); } } diff --git a/runtime/mirror/string-inl.h b/runtime/mirror/string-inl.h index cdf468c809..628554225c 100644 --- a/runtime/mirror/string-inl.h +++ b/runtime/mirror/string-inl.h @@ -33,8 +33,8 @@ namespace art { namespace mirror { inline uint32_t String::ClassSize(size_t pointer_size) { - uint32_t vtable_entries = Object::kVTableLength + 53; - return Class::ComputeClassSize(true, vtable_entries, 0, 2, 0, 1, 2, pointer_size); + uint32_t vtable_entries = Object::kVTableLength + 56; + return Class::ComputeClassSize(true, vtable_entries, 0, 0, 0, 1, 2, pointer_size); } // Sets string count in the allocation code path to ensure it is guarded by a CAS. diff --git a/runtime/modifiers.h b/runtime/modifiers.h index c31b22ee89..fd7a125bc3 100644 --- a/runtime/modifiers.h +++ b/runtime/modifiers.h @@ -60,7 +60,13 @@ static constexpr uint32_t kAccDefault = 0x00400000; // method (ru // This is set by the class linker during LinkInterfaceMethods. Prior to that point we do not know // if any particular method needs to be a default conflict. Used to figure out at runtime if // invoking this method will throw an exception. -static constexpr uint32_t kAccDefaultConflict = 0x00800000; // method (runtime) +static constexpr uint32_t kAccDefaultConflict = 0x00800000; // method (runtime) + +// Set by the verifier for a method we do not want the compiler to compile. +static constexpr uint32_t kAccCompileDontBother = 0x01000000; // method (runtime) + +// Set by the verifier for a method that could not be verified to follow structured locking. +static constexpr uint32_t kAccMustCountLocks = 0x02000000; // method (runtime) // Special runtime-only flags. // Interface and all its super-interfaces with default methods have been recursively initialized. diff --git a/runtime/monitor.cc b/runtime/monitor.cc index 6290cb2ff7..71c866f3d6 100644 --- a/runtime/monitor.cc +++ b/runtime/monitor.cc @@ -215,6 +215,105 @@ void Monitor::SetObject(mirror::Object* object) { obj_ = GcRoot<mirror::Object>(object); } +// Note: Adapted from CurrentMethodVisitor in thread.cc. We must not resolve here. + +struct NthCallerWithDexPcVisitor FINAL : public StackVisitor { + explicit NthCallerWithDexPcVisitor(Thread* thread, size_t frame) + SHARED_REQUIRES(Locks::mutator_lock_) + : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFramesNoResolve), + method_(nullptr), + dex_pc_(0), + current_frame_number_(0), + wanted_frame_number_(frame) {} + bool VisitFrame() OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_) { + ArtMethod* m = GetMethod(); + if (m == nullptr || m->IsRuntimeMethod()) { + // Runtime method, upcall, or resolution issue. Skip. + return true; + } + + // Is this the requested frame? + if (current_frame_number_ == wanted_frame_number_) { + method_ = m; + dex_pc_ = GetDexPc(false /* abort_on_error*/); + return false; + } + + // Look for more. + current_frame_number_++; + return true; + } + + ArtMethod* method_; + uint32_t dex_pc_; + + private: + size_t current_frame_number_; + const size_t wanted_frame_number_; +}; + +// This function is inlined and just helps to not have the VLOG and ATRACE check at all the +// potential tracing points. +void Monitor::AtraceMonitorLock(Thread* self, mirror::Object* obj, bool is_wait) { + if (UNLIKELY(VLOG_IS_ON(systrace_lock_logging) && ATRACE_ENABLED())) { + AtraceMonitorLockImpl(self, obj, is_wait); + } +} + +void Monitor::AtraceMonitorLockImpl(Thread* self, mirror::Object* obj, bool is_wait) { + // Wait() requires a deeper call stack to be useful. Otherwise you'll see "Waiting at + // Object.java". Assume that we'll wait a nontrivial amount, so it's OK to do a longer + // stack walk than if !is_wait. + NthCallerWithDexPcVisitor visitor(self, is_wait ? 1U : 0U); + visitor.WalkStack(false); + const char* prefix = is_wait ? "Waiting on " : "Locking "; + + const char* filename; + int32_t line_number; + TranslateLocation(visitor.method_, visitor.dex_pc_, &filename, &line_number); + + // It would be nice to have a stable "ID" for the object here. However, the only stable thing + // would be the identity hashcode. But we cannot use IdentityHashcode here: For one, there are + // times when it is unsafe to make that call (see stack dumping for an explanation). More + // importantly, we would have to give up on thin-locking when adding systrace locks, as the + // identity hashcode is stored in the lockword normally (so can't be used with thin-locks). + // + // Because of thin-locks we also cannot use the monitor id (as there is no monitor). Monitor ids + // also do not have to be stable, as the monitor may be deflated. + std::string tmp = StringPrintf("%s %d at %s:%d", + prefix, + (obj == nullptr ? -1 : static_cast<int32_t>(reinterpret_cast<uintptr_t>(obj))), + (filename != nullptr ? filename : "null"), + line_number); + ATRACE_BEGIN(tmp.c_str()); +} + +void Monitor::AtraceMonitorUnlock() { + if (UNLIKELY(VLOG_IS_ON(systrace_lock_logging))) { + ATRACE_END(); + } +} + +std::string Monitor::PrettyContentionInfo(const std::string& owner_name, + pid_t owner_tid, + ArtMethod* owners_method, + uint32_t owners_dex_pc, + size_t num_waiters) { + const char* owners_filename; + int32_t owners_line_number = 0; + if (owners_method != nullptr) { + TranslateLocation(owners_method, owners_dex_pc, &owners_filename, &owners_line_number); + } + std::ostringstream oss; + oss << "monitor contention with owner " << owner_name << " (" << owner_tid << ")"; + if (owners_method != nullptr) { + oss << " at " << PrettyMethod(owners_method); + oss << "(" << owners_filename << ":" << owners_line_number << ")"; + } + oss << " waiters=" << num_waiters; + return oss.str(); +} + void Monitor::Lock(Thread* self) { MutexLock mu(self, monitor_lock_); while (true) { @@ -226,10 +325,10 @@ void Monitor::Lock(Thread* self) { if (lock_profiling_threshold_ != 0) { locking_method_ = self->GetCurrentMethod(&locking_dex_pc_); } - return; + break; } else if (owner_ == self) { // Recursive. lock_count_++; - return; + break; } // Contended. const bool log_contention = (lock_profiling_threshold_ != 0); @@ -242,36 +341,86 @@ void Monitor::Lock(Thread* self) { monitor_lock_.Unlock(self); // Let go of locks in order. self->SetMonitorEnterObject(GetObject()); { + uint32_t original_owner_thread_id = 0u; ScopedThreadStateChange tsc(self, kBlocked); // Change to blocked and give up mutator_lock_. - // Reacquire monitor_lock_ without mutator_lock_ for Wait. - MutexLock mu2(self, monitor_lock_); - if (owner_ != nullptr) { // Did the owner_ give the lock up? - if (ATRACE_ENABLED()) { - std::string name; - owner_->GetThreadName(name); - ATRACE_BEGIN(("Contended on monitor with owner " + name).c_str()); + { + // Reacquire monitor_lock_ without mutator_lock_ for Wait. + MutexLock mu2(self, monitor_lock_); + if (owner_ != nullptr) { // Did the owner_ give the lock up? + original_owner_thread_id = owner_->GetThreadId(); + if (ATRACE_ENABLED()) { + std::ostringstream oss; + std::string name; + owner_->GetThreadName(name); + oss << PrettyContentionInfo(name, + owner_->GetTid(), + owners_method, + owners_dex_pc, + num_waiters); + // Add info for contending thread. + uint32_t pc; + ArtMethod* m = self->GetCurrentMethod(&pc); + const char* filename; + int32_t line_number; + TranslateLocation(m, pc, &filename, &line_number); + oss << " blocking from " + << PrettyMethod(m) << "(" << (filename != nullptr ? filename : "null") << ":" + << line_number << ")"; + ATRACE_BEGIN(oss.str().c_str()); + } + monitor_contenders_.Wait(self); // Still contended so wait. } - monitor_contenders_.Wait(self); // Still contended so wait. + } + if (original_owner_thread_id != 0u) { // Woken from contention. if (log_contention) { - uint64_t wait_ms = MilliTime() - wait_start_ms; - uint32_t sample_percent; - if (wait_ms >= lock_profiling_threshold_) { - sample_percent = 100; - } else { - sample_percent = 100 * wait_ms / lock_profiling_threshold_; + uint32_t original_owner_tid = 0; + std::string original_owner_name; + { + MutexLock mu2(Thread::Current(), *Locks::thread_list_lock_); + // Re-find the owner in case the thread got killed. + Thread* original_owner = Runtime::Current()->GetThreadList()->FindThreadByThreadId( + original_owner_thread_id); + // Do not do any work that requires the mutator lock. + if (original_owner != nullptr) { + original_owner_tid = original_owner->GetTid(); + original_owner->GetThreadName(original_owner_name); + } } - if (sample_percent != 0 && (static_cast<uint32_t>(rand() % 100) < sample_percent)) { - const char* owners_filename; - int32_t owners_line_number; - TranslateLocation(owners_method, owners_dex_pc, &owners_filename, &owners_line_number); - if (wait_ms > kLongWaitMs && owners_method != nullptr) { - LOG(WARNING) << "Long monitor contention event with owner method=" - << PrettyMethod(owners_method) << " from " << owners_filename << ":" - << owners_line_number << " waiters=" << num_waiters << " for " - << PrettyDuration(MsToNs(wait_ms)); + + if (original_owner_tid != 0u) { + uint64_t wait_ms = MilliTime() - wait_start_ms; + uint32_t sample_percent; + if (wait_ms >= lock_profiling_threshold_) { + sample_percent = 100; + } else { + sample_percent = 100 * wait_ms / lock_profiling_threshold_; + } + if (sample_percent != 0 && (static_cast<uint32_t>(rand() % 100) < sample_percent)) { + if (wait_ms > kLongWaitMs && owners_method != nullptr) { + uint32_t pc; + ArtMethod* m = self->GetCurrentMethod(&pc); + // TODO: We should maybe check that original_owner is still a live thread. + LOG(WARNING) << "Long " + << PrettyContentionInfo(original_owner_name, + original_owner_tid, + owners_method, + owners_dex_pc, + num_waiters) + << " in " << PrettyMethod(m) << " for " << PrettyDuration(MsToNs(wait_ms)); + } + const char* owners_filename; + int32_t owners_line_number; + TranslateLocation(owners_method, + owners_dex_pc, + &owners_filename, + &owners_line_number); + LogContentionEvent(self, + wait_ms, + sample_percent, + owners_filename, + owners_line_number); } - LogContentionEvent(self, wait_ms, sample_percent, owners_filename, owners_line_number); } } ATRACE_END(); @@ -281,6 +430,8 @@ void Monitor::Lock(Thread* self) { monitor_lock_.Lock(self); // Reacquire locks in order. --num_waiters_; } + + AtraceMonitorLock(self, GetObject(), false /* is_wait */); } static void ThrowIllegalMonitorStateExceptionF(const char* fmt, ...) @@ -311,25 +462,34 @@ static std::string ThreadToString(Thread* thread) { return oss.str(); } -void Monitor::FailedUnlock(mirror::Object* o, Thread* expected_owner, Thread* found_owner, +void Monitor::FailedUnlock(mirror::Object* o, + uint32_t expected_owner_thread_id, + uint32_t found_owner_thread_id, Monitor* monitor) { - Thread* current_owner = nullptr; + // Acquire thread list lock so threads won't disappear from under us. std::string current_owner_string; std::string expected_owner_string; std::string found_owner_string; + uint32_t current_owner_thread_id = 0u; { - // TODO: isn't this too late to prevent threads from disappearing? - // Acquire thread list lock so threads won't disappear from under us. MutexLock mu(Thread::Current(), *Locks::thread_list_lock_); + ThreadList* const thread_list = Runtime::Current()->GetThreadList(); + Thread* expected_owner = thread_list->FindThreadByThreadId(expected_owner_thread_id); + Thread* found_owner = thread_list->FindThreadByThreadId(found_owner_thread_id); + // Re-read owner now that we hold lock. - current_owner = (monitor != nullptr) ? monitor->GetOwner() : nullptr; + Thread* current_owner = (monitor != nullptr) ? monitor->GetOwner() : nullptr; + if (current_owner != nullptr) { + current_owner_thread_id = current_owner->GetThreadId(); + } // Get short descriptions of the threads involved. current_owner_string = ThreadToString(current_owner); - expected_owner_string = ThreadToString(expected_owner); - found_owner_string = ThreadToString(found_owner); + expected_owner_string = expected_owner != nullptr ? ThreadToString(expected_owner) : "unnamed"; + found_owner_string = found_owner != nullptr ? ThreadToString(found_owner) : "unnamed"; } - if (current_owner == nullptr) { - if (found_owner == nullptr) { + + if (current_owner_thread_id == 0u) { + if (found_owner_thread_id == 0u) { ThrowIllegalMonitorStateExceptionF("unlock of unowned monitor on object of type '%s'" " on thread '%s'", PrettyTypeOf(o).c_str(), @@ -343,7 +503,7 @@ void Monitor::FailedUnlock(mirror::Object* o, Thread* expected_owner, Thread* fo expected_owner_string.c_str()); } } else { - if (found_owner == nullptr) { + if (found_owner_thread_id == 0u) { // Race: originally there was no owner, there is now ThrowIllegalMonitorStateExceptionF("unlock of monitor owned by '%s' on object of type '%s'" " (originally believed to be unowned) on thread '%s'", @@ -351,7 +511,7 @@ void Monitor::FailedUnlock(mirror::Object* o, Thread* expected_owner, Thread* fo PrettyTypeOf(o).c_str(), expected_owner_string.c_str()); } else { - if (found_owner != current_owner) { + if (found_owner_thread_id != current_owner_thread_id) { // Race: originally found and current owner have changed ThrowIllegalMonitorStateExceptionF("unlock of monitor originally owned by '%s' (now" " owned by '%s') on object of type '%s' on thread '%s'", @@ -372,27 +532,32 @@ void Monitor::FailedUnlock(mirror::Object* o, Thread* expected_owner, Thread* fo bool Monitor::Unlock(Thread* self) { DCHECK(self != nullptr); - MutexLock mu(self, monitor_lock_); - Thread* owner = owner_; - if (owner == self) { - // We own the monitor, so nobody else can be in here. - if (lock_count_ == 0) { - owner_ = nullptr; - locking_method_ = nullptr; - locking_dex_pc_ = 0; - // Wake a contender. - monitor_contenders_.Signal(self); - } else { - --lock_count_; + uint32_t owner_thread_id = 0u; + { + MutexLock mu(self, monitor_lock_); + Thread* owner = owner_; + if (owner != nullptr) { + owner_thread_id = owner->GetThreadId(); + } + if (owner == self) { + // We own the monitor, so nobody else can be in here. + AtraceMonitorUnlock(); + if (lock_count_ == 0) { + owner_ = nullptr; + locking_method_ = nullptr; + locking_dex_pc_ = 0; + // Wake a contender. + monitor_contenders_.Signal(self); + } else { + --lock_count_; + } + return true; } - } else { - // We don't own this, so we're not allowed to unlock it. - // The JNI spec says that we should throw IllegalMonitorStateException - // in this case. - FailedUnlock(GetObject(), self, owner, this); - return false; } - return true; + // We don't own this, so we're not allowed to unlock it. + // The JNI spec says that we should throw IllegalMonitorStateException in this case. + FailedUnlock(GetObject(), self->GetThreadId(), owner_thread_id, this); + return false; } void Monitor::Wait(Thread* self, int64_t ms, int32_t ns, @@ -443,6 +608,11 @@ void Monitor::Wait(Thread* self, int64_t ms, int32_t ns, uintptr_t saved_dex_pc = locking_dex_pc_; locking_dex_pc_ = 0; + AtraceMonitorUnlock(); // For the implict Unlock() just above. This will only end the deepest + // nesting, but that is enough for the visualization, and corresponds to + // the single Lock() we do afterwards. + AtraceMonitorLock(self, GetObject(), true /* is_wait */); + bool was_interrupted = false; { // Update thread state. If the GC wakes up, it'll ignore us, knowing @@ -506,6 +676,8 @@ void Monitor::Wait(Thread* self, int64_t ms, int32_t ns, self->ThrowNewException("Ljava/lang/InterruptedException;", nullptr); } + AtraceMonitorUnlock(); // End Wait(). + // Re-acquire the monitor and lock. Lock(self); monitor_lock_.Lock(self); @@ -695,6 +867,7 @@ mirror::Object* Monitor::MonitorEnter(Thread* self, mirror::Object* obj) { case LockWord::kUnlocked: { LockWord thin_locked(LockWord::FromThinLockId(thread_id, 0, lock_word.ReadBarrierState())); if (h_obj->CasLockWordWeakSequentiallyConsistent(lock_word, thin_locked)) { + AtraceMonitorLock(self, h_obj.Get(), false /* is_wait */); // CasLockWord enforces more than the acquire ordering we need here. return h_obj.Get(); // Success! } @@ -710,10 +883,12 @@ mirror::Object* Monitor::MonitorEnter(Thread* self, mirror::Object* obj) { lock_word.ReadBarrierState())); if (!kUseReadBarrier) { h_obj->SetLockWord(thin_locked, true); + AtraceMonitorLock(self, h_obj.Get(), false /* is_wait */); return h_obj.Get(); // Success! } else { // Use CAS to preserve the read barrier state. if (h_obj->CasLockWordWeakSequentiallyConsistent(lock_word, thin_locked)) { + AtraceMonitorLock(self, h_obj.Get(), false /* is_wait */); return h_obj.Get(); // Success! } } @@ -750,7 +925,7 @@ mirror::Object* Monitor::MonitorEnter(Thread* self, mirror::Object* obj) { continue; // Start from the beginning. default: { LOG(FATAL) << "Invalid monitor state " << lock_word.GetState(); - return h_obj.Get(); + UNREACHABLE(); } } } @@ -769,16 +944,13 @@ bool Monitor::MonitorExit(Thread* self, mirror::Object* obj) { case LockWord::kHashCode: // Fall-through. case LockWord::kUnlocked: - FailedUnlock(h_obj.Get(), self, nullptr, nullptr); + FailedUnlock(h_obj.Get(), self->GetThreadId(), 0u, nullptr); return false; // Failure. case LockWord::kThinLocked: { uint32_t thread_id = self->GetThreadId(); uint32_t owner_thread_id = lock_word.ThinLockOwner(); if (owner_thread_id != thread_id) { - // TODO: there's a race here with the owner dying while we unlock. - Thread* owner = - Runtime::Current()->GetThreadList()->FindThreadByThreadId(lock_word.ThinLockOwner()); - FailedUnlock(h_obj.Get(), self, owner, nullptr); + FailedUnlock(h_obj.Get(), thread_id, owner_thread_id, nullptr); return false; // Failure. } else { // We own the lock, decrease the recursion count. @@ -792,11 +964,13 @@ bool Monitor::MonitorExit(Thread* self, mirror::Object* obj) { if (!kUseReadBarrier) { DCHECK_EQ(new_lw.ReadBarrierState(), 0U); h_obj->SetLockWord(new_lw, true); + AtraceMonitorUnlock(); // Success! return true; } else { // Use CAS to preserve the read barrier state. if (h_obj->CasLockWordWeakSequentiallyConsistent(lock_word, new_lw)) { + AtraceMonitorUnlock(); // Success! return true; } @@ -1072,8 +1246,10 @@ bool Monitor::IsLocked() SHARED_REQUIRES(Locks::mutator_lock_) { return owner_ != nullptr; } -void Monitor::TranslateLocation(ArtMethod* method, uint32_t dex_pc, - const char** source_file, int32_t* line_number) const { +void Monitor::TranslateLocation(ArtMethod* method, + uint32_t dex_pc, + const char** source_file, + int32_t* line_number) { // If method is null, location is unknown if (method == nullptr) { *source_file = ""; diff --git a/runtime/monitor.h b/runtime/monitor.h index ae9b3cca82..7b4b8f9467 100644 --- a/runtime/monitor.h +++ b/runtime/monitor.h @@ -185,9 +185,12 @@ class Monitor { const char* owner_filename, int32_t owner_line_number) SHARED_REQUIRES(Locks::mutator_lock_); - static void FailedUnlock(mirror::Object* obj, Thread* expected_owner, Thread* found_owner, + static void FailedUnlock(mirror::Object* obj, + uint32_t expected_owner_thread_id, + uint32_t found_owner_thread_id, Monitor* mon) - REQUIRES(!Locks::thread_list_lock_) + REQUIRES(!Locks::thread_list_lock_, + !monitor_lock_) SHARED_REQUIRES(Locks::mutator_lock_); void Lock(Thread* self) @@ -208,6 +211,13 @@ class Monitor { REQUIRES(!monitor_lock_) SHARED_REQUIRES(Locks::mutator_lock_); + static std::string PrettyContentionInfo(const std::string& owner_name, + pid_t owner_tid, + ArtMethod* owners_method, + uint32_t owners_dex_pc, + size_t num_waiters) + REQUIRES(!Locks::thread_list_lock_) + SHARED_REQUIRES(Locks::mutator_lock_); // Wait on a monitor until timeout, interrupt, or notification. Used for Object.wait() and // (somewhat indirectly) Thread.sleep() and Thread.join(). @@ -233,12 +243,24 @@ class Monitor { SHARED_REQUIRES(Locks::mutator_lock_); // Translates the provided method and pc into its declaring class' source file and line number. - void TranslateLocation(ArtMethod* method, uint32_t pc, - const char** source_file, int32_t* line_number) const + static void TranslateLocation(ArtMethod* method, uint32_t pc, + const char** source_file, + int32_t* line_number) SHARED_REQUIRES(Locks::mutator_lock_); uint32_t GetOwnerThreadId() REQUIRES(!monitor_lock_); + // Support for systrace output of monitor operations. + ALWAYS_INLINE static void AtraceMonitorLock(Thread* self, + mirror::Object* obj, + bool is_wait) + SHARED_REQUIRES(Locks::mutator_lock_); + static void AtraceMonitorLockImpl(Thread* self, + mirror::Object* obj, + bool is_wait) + SHARED_REQUIRES(Locks::mutator_lock_); + ALWAYS_INLINE static void AtraceMonitorUnlock(); + static uint32_t lock_profiling_threshold_; Mutex monitor_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; diff --git a/runtime/monitor_pool.cc b/runtime/monitor_pool.cc index ce38e4f108..a47a4b2cf2 100644 --- a/runtime/monitor_pool.cc +++ b/runtime/monitor_pool.cc @@ -28,7 +28,11 @@ namespace mirror { } // namespace mirror MonitorPool::MonitorPool() - : num_chunks_(0), capacity_(0), first_free_(nullptr) { + : current_chunk_list_index_(0), num_chunks_(0), current_chunk_list_capacity_(0), + first_free_(nullptr) { + for (size_t i = 0; i < kMaxChunkLists; ++i) { + monitor_chunks_[i] = nullptr; // Not absolutely required, but ... + } AllocateChunk(); // Get our first chunk. } @@ -37,24 +41,19 @@ MonitorPool::MonitorPool() void MonitorPool::AllocateChunk() { DCHECK(first_free_ == nullptr); - // Do we need to resize? - if (num_chunks_ == capacity_) { - if (capacity_ == 0U) { - // Initialization. - capacity_ = kInitialChunkStorage; - uintptr_t* new_backing = new uintptr_t[capacity_](); - DCHECK(monitor_chunks_.LoadRelaxed() == nullptr); - monitor_chunks_.StoreRelaxed(new_backing); - } else { - size_t new_capacity = 2 * capacity_; - uintptr_t* new_backing = new uintptr_t[new_capacity](); - uintptr_t* old_backing = monitor_chunks_.LoadRelaxed(); - memcpy(new_backing, old_backing, sizeof(uintptr_t) * capacity_); - monitor_chunks_.StoreRelaxed(new_backing); - capacity_ = new_capacity; - old_chunk_arrays_.push_back(std::unique_ptr<uintptr_t[]>(old_backing)); - VLOG(monitor) << "Resizing to capacity " << capacity_; - } + // Do we need to allocate another chunk list? + if (num_chunks_ == current_chunk_list_capacity_) { + if (current_chunk_list_capacity_ != 0U) { + ++current_chunk_list_index_; + CHECK_LT(current_chunk_list_index_, kMaxChunkLists) << "Out of space for inflated monitors"; + VLOG(monitor) << "Expanding to capacity " + << 2 * ChunkListCapacity(current_chunk_list_index_) - kInitialChunkStorage; + } // else we're initializing + current_chunk_list_capacity_ = ChunkListCapacity(current_chunk_list_index_); + uintptr_t* new_list = new uintptr_t[current_chunk_list_capacity_](); + DCHECK(monitor_chunks_[current_chunk_list_index_] == nullptr); + monitor_chunks_[current_chunk_list_index_] = new_list; + num_chunks_ = 0; } // Allocate the chunk. @@ -65,7 +64,7 @@ void MonitorPool::AllocateChunk() { CHECK_EQ(0U, reinterpret_cast<uintptr_t>(chunk) % kMonitorAlignment); // Add the chunk. - *(monitor_chunks_.LoadRelaxed() + num_chunks_) = reinterpret_cast<uintptr_t>(chunk); + monitor_chunks_[current_chunk_list_index_][num_chunks_] = reinterpret_cast<uintptr_t>(chunk); num_chunks_++; // Set up the free list @@ -73,8 +72,8 @@ void MonitorPool::AllocateChunk() { (kChunkCapacity - 1) * kAlignedMonitorSize); last->next_free_ = nullptr; // Eagerly compute id. - last->monitor_id_ = OffsetToMonitorId((num_chunks_ - 1) * kChunkSize + - (kChunkCapacity - 1) * kAlignedMonitorSize); + last->monitor_id_ = OffsetToMonitorId(current_chunk_list_index_* (kMaxListSize * kChunkSize) + + (num_chunks_ - 1) * kChunkSize + (kChunkCapacity - 1) * kAlignedMonitorSize); for (size_t i = 0; i < kChunkCapacity - 1; ++i) { Monitor* before = reinterpret_cast<Monitor*>(reinterpret_cast<uintptr_t>(last) - kAlignedMonitorSize); @@ -91,21 +90,19 @@ void MonitorPool::AllocateChunk() { void MonitorPool::FreeInternal() { // This is on shutdown with NO_THREAD_SAFETY_ANALYSIS, can't/don't need to lock. - uintptr_t* backing = monitor_chunks_.LoadRelaxed(); - DCHECK(backing != nullptr); - DCHECK_GT(capacity_, 0U); - DCHECK_GT(num_chunks_, 0U); - - for (size_t i = 0; i < capacity_; ++i) { - if (i < num_chunks_) { - DCHECK_NE(backing[i], 0U); - allocator_.deallocate(reinterpret_cast<uint8_t*>(backing[i]), kChunkSize); - } else { - DCHECK_EQ(backing[i], 0U); + DCHECK_NE(current_chunk_list_capacity_, 0UL); + for (size_t i = 0; i <= current_chunk_list_index_; ++i) { + DCHECK_NE(monitor_chunks_[i], static_cast<uintptr_t*>(nullptr)); + for (size_t j = 0; j < ChunkListCapacity(i); ++j) { + if (i < current_chunk_list_index_ || j < num_chunks_) { + DCHECK_NE(monitor_chunks_[i][j], 0U); + allocator_.deallocate(reinterpret_cast<uint8_t*>(monitor_chunks_[i][j]), kChunkSize); + } else { + DCHECK_EQ(monitor_chunks_[i][j], 0U); + } } + delete[] monitor_chunks_[i]; } - - delete[] backing; } Monitor* MonitorPool::CreateMonitorInPool(Thread* self, Thread* owner, mirror::Object* obj, diff --git a/runtime/monitor_pool.h b/runtime/monitor_pool.h index 875b3fe73d..99810e0c82 100644 --- a/runtime/monitor_pool.h +++ b/runtime/monitor_pool.h @@ -128,12 +128,17 @@ class MonitorPool { void ReleaseMonitorToPool(Thread* self, Monitor* monitor); void ReleaseMonitorsToPool(Thread* self, MonitorList::Monitors* monitors); - // Note: This is safe as we do not ever move chunks. + // Note: This is safe as we do not ever move chunks. All needed entries in the monitor_chunks_ + // data structure are read-only once we get here. Updates happen-before this call because + // the lock word was stored with release semantics and we read it with acquire semantics to + // retrieve the id. Monitor* LookupMonitor(MonitorId mon_id) { size_t offset = MonitorIdToOffset(mon_id); size_t index = offset / kChunkSize; + size_t top_index = index / kMaxListSize; + size_t list_index = index % kMaxListSize; size_t offset_in_chunk = offset % kChunkSize; - uintptr_t base = *(monitor_chunks_.LoadRelaxed()+index); + uintptr_t base = monitor_chunks_[top_index][list_index]; return reinterpret_cast<Monitor*>(base + offset_in_chunk); } @@ -142,28 +147,37 @@ class MonitorPool { return base_addr <= mon_ptr && (mon_ptr - base_addr < kChunkSize); } - // Note: This is safe as we do not ever move chunks. MonitorId ComputeMonitorIdInPool(Monitor* mon, Thread* self) { MutexLock mu(self, *Locks::allocated_monitor_ids_lock_); - for (size_t index = 0; index < num_chunks_; ++index) { - uintptr_t chunk_addr = *(monitor_chunks_.LoadRelaxed() + index); - if (IsInChunk(chunk_addr, mon)) { - return OffsetToMonitorId( - reinterpret_cast<uintptr_t>(mon) - chunk_addr + index * kChunkSize); + for (size_t i = 0; i <= current_chunk_list_index_; ++i) { + for (size_t j = 0; j < ChunkListCapacity(i); ++j) { + if (j >= num_chunks_ && i == current_chunk_list_index_) { + break; + } + uintptr_t chunk_addr = monitor_chunks_[i][j]; + if (IsInChunk(chunk_addr, mon)) { + return OffsetToMonitorId( + reinterpret_cast<uintptr_t>(mon) - chunk_addr + + i * (kMaxListSize * kChunkSize) + j * kChunkSize); + } } } LOG(FATAL) << "Did not find chunk that contains monitor."; return 0; } - static size_t MonitorIdToOffset(MonitorId id) { + static constexpr size_t MonitorIdToOffset(MonitorId id) { return id << 3; } - static MonitorId OffsetToMonitorId(size_t offset) { + static constexpr MonitorId OffsetToMonitorId(size_t offset) { return static_cast<MonitorId>(offset >> 3); } + static constexpr size_t ChunkListCapacity(size_t index) { + return kInitialChunkStorage << index; + } + // TODO: There are assumptions in the code that monitor addresses are 8B aligned (>>3). static constexpr size_t kMonitorAlignment = 8; // Size of a monitor, rounded up to a multiple of alignment. @@ -174,20 +188,47 @@ class MonitorPool { // Chunk size that is referenced in the id. We can collapse this to the actually used storage // in a chunk, i.e., kChunkCapacity * kAlignedMonitorSize, but this will mean proper divisions. static constexpr size_t kChunkSize = kPageSize; - // The number of initial chunks storable in monitor_chunks_. The number is large enough to make - // resizing unlikely, but small enough to not waste too much memory. - static constexpr size_t kInitialChunkStorage = 8U; - - // List of memory chunks. Each chunk is kChunkSize. - Atomic<uintptr_t*> monitor_chunks_; - // Number of chunks stored. + static_assert(IsPowerOfTwo(kChunkSize), "kChunkSize must be power of 2"); + // The number of chunks of storage that can be referenced by the initial chunk list. + // The total number of usable monitor chunks is typically 255 times this number, so it + // should be large enough that we don't run out. We run out of address bits if it's > 512. + // Currently we set it a bit smaller, to save half a page per process. We make it tiny in + // debug builds to catch growth errors. The only value we really expect to tune. + static constexpr size_t kInitialChunkStorage = kIsDebugBuild ? 1U : 256U; + static_assert(IsPowerOfTwo(kInitialChunkStorage), "kInitialChunkStorage must be power of 2"); + // The number of lists, each containing pointers to storage chunks. + static constexpr size_t kMaxChunkLists = 8; // Dictated by 3 bit index. Don't increase above 8. + static_assert(IsPowerOfTwo(kMaxChunkLists), "kMaxChunkLists must be power of 2"); + static constexpr size_t kMaxListSize = kInitialChunkStorage << (kMaxChunkLists - 1); + // We lose 3 bits in monitor id due to 3 bit monitor_chunks_ index, and gain it back from + // the 3 bit alignment constraint on monitors: + static_assert(kMaxListSize * kChunkSize < (1 << LockWord::kMonitorIdSize), + "Monitor id bits don't fit"); + static_assert(IsPowerOfTwo(kMaxListSize), "kMaxListSize must be power of 2"); + + // Array of pointers to lists (again arrays) of pointers to chunks containing monitors. + // Zeroth entry points to a list (array) of kInitialChunkStorage pointers to chunks. + // Each subsequent list as twice as large as the preceding one. + // Monitor Ids are interpreted as follows: + // Top 3 bits (of 28): index into monitor_chunks_. + // Next 16 bits: index into the chunk list, i.e. monitor_chunks_[i]. + // Last 9 bits: offset within chunk, expressed as multiple of kMonitorAlignment. + // If we set kInitialChunkStorage to 512, this would allow us to use roughly 128K chunks of + // monitors, which is 0.5GB of monitors. With this maximum setting, the largest chunk list + // contains 64K entries, and we make full use of the available index space. With a + // kInitialChunkStorage value of 256, this is proportionately reduced to 0.25GB of monitors. + // Updates to monitor_chunks_ are guarded by allocated_monitor_ids_lock_ . + // No field in this entire data structure is ever updated once a monitor id whose lookup + // requires it has been made visible to another thread. Thus readers never race with + // updates, in spite of the fact that they acquire no locks. + uintptr_t* monitor_chunks_[kMaxChunkLists]; // uintptr_t is really a Monitor* . + // Highest currently used index in monitor_chunks_ . Used for newly allocated chunks. + size_t current_chunk_list_index_ GUARDED_BY(Locks::allocated_monitor_ids_lock_); + // Number of chunk pointers stored in monitor_chunks_[current_chunk_list_index_] so far. size_t num_chunks_ GUARDED_BY(Locks::allocated_monitor_ids_lock_); - // Number of chunks storable. - size_t capacity_ GUARDED_BY(Locks::allocated_monitor_ids_lock_); - - // To avoid race issues when resizing, we keep all the previous arrays. - std::vector<std::unique_ptr<uintptr_t[]>> old_chunk_arrays_ - GUARDED_BY(Locks::allocated_monitor_ids_lock_); + // After the initial allocation, this is always equal to + // ChunkListCapacity(current_chunk_list_index_). + size_t current_chunk_list_capacity_ GUARDED_BY(Locks::allocated_monitor_ids_lock_); typedef TrackingAllocator<uint8_t, kAllocatorTagMonitorPool> Allocator; Allocator allocator_; diff --git a/runtime/native/dalvik_system_DexFile.cc b/runtime/native/dalvik_system_DexFile.cc index 0abe39d872..8c7c966102 100644 --- a/runtime/native/dalvik_system_DexFile.cc +++ b/runtime/native/dalvik_system_DexFile.cc @@ -16,6 +16,8 @@ #include "dalvik_system_DexFile.h" +#include <sstream> + #include "base/logging.h" #include "base/stl_util.h" #include "base/stringprintf.h" @@ -27,6 +29,7 @@ #include "mirror/class_loader.h" #include "mirror/object-inl.h" #include "mirror/string.h" +#include "oat_file.h" #include "oat_file_assistant.h" #include "oat_file_manager.h" #include "os.h" @@ -275,9 +278,7 @@ static jclass DexFile_defineClassNative(JNIEnv* env, StackHandleScope<1> hs(soa.Self()); Handle<mirror::ClassLoader> class_loader( hs.NewHandle(soa.Decode<mirror::ClassLoader*>(javaLoader))); - class_linker->RegisterDexFile( - *dex_file, - class_linker->GetOrCreateAllocatorForClassLoader(class_loader.Get())); + class_linker->RegisterDexFile(*dex_file, class_loader.Get()); mirror::Class* result = class_linker->DefineClass(soa.Self(), descriptor.c_str(), hash, @@ -387,6 +388,61 @@ static jint GetDexOptNeeded(JNIEnv* env, return oat_file_assistant.GetDexOptNeeded(filter); } +static jstring DexFile_getDexFileStatus(JNIEnv* env, + jclass, + jstring javaFilename, + jstring javaInstructionSet) { + ScopedUtfChars filename(env, javaFilename); + if (env->ExceptionCheck()) { + return nullptr; + } + + ScopedUtfChars instruction_set(env, javaInstructionSet); + if (env->ExceptionCheck()) { + return nullptr; + } + + const InstructionSet target_instruction_set = GetInstructionSetFromString( + instruction_set.c_str()); + if (target_instruction_set == kNone) { + ScopedLocalRef<jclass> iae(env, env->FindClass("java/lang/IllegalArgumentException")); + std::string message(StringPrintf("Instruction set %s is invalid.", instruction_set.c_str())); + env->ThrowNew(iae.get(), message.c_str()); + return nullptr; + } + + OatFileAssistant oat_file_assistant(filename.c_str(), target_instruction_set, + false /* profile_changed */, + false /* load_executable */); + + std::ostringstream status; + bool oat_file_exists = false; + bool odex_file_exists = false; + if (oat_file_assistant.OatFileExists()) { + oat_file_exists = true; + status << *oat_file_assistant.OatFileName() << " [compilation_filter="; + status << CompilerFilter::NameOfFilter(oat_file_assistant.OatFileCompilerFilter()); + status << ", status=" << oat_file_assistant.OatFileStatus(); + } + + if (oat_file_assistant.OdexFileExists()) { + odex_file_exists = true; + if (oat_file_exists) { + status << "] "; + } + status << *oat_file_assistant.OdexFileName() << " [compilation_filter="; + status << CompilerFilter::NameOfFilter(oat_file_assistant.OdexFileCompilerFilter()); + status << ", status=" << oat_file_assistant.OdexFileStatus(); + } + + if (!oat_file_exists && !odex_file_exists) { + status << "invalid["; + } + + status << "]"; + return env->NewStringUTF(status.str().c_str()); +} + static jint DexFile_getDexOptNeeded(JNIEnv* env, jclass, jstring javaFilename, @@ -417,15 +473,22 @@ static jint DexFile_getDexOptNeeded(JNIEnv* env, // public API static jboolean DexFile_isDexOptNeeded(JNIEnv* env, jclass, jstring javaFilename) { - const char* instruction_set = GetInstructionSetString(kRuntimeISA); - ScopedUtfChars filename(env, javaFilename); - jint status = GetDexOptNeeded( - env, - filename.c_str(), - instruction_set, - "speed-profile", - /*profile_changed*/false); - return (status != OatFileAssistant::kNoDexOptNeeded) ? JNI_TRUE : JNI_FALSE; + ScopedUtfChars filename_utf(env, javaFilename); + if (env->ExceptionCheck()) { + return JNI_FALSE; + } + + const char* filename = filename_utf.c_str(); + if ((filename == nullptr) || !OS::FileExists(filename)) { + LOG(ERROR) << "DexFile_isDexOptNeeded file '" << filename << "' does not exist"; + ScopedLocalRef<jclass> fnfe(env, env->FindClass("java/io/FileNotFoundException")); + const char* message = (filename == nullptr) ? "<empty file name>" : filename; + env->ThrowNew(fnfe.get(), message); + return JNI_FALSE; + } + + OatFileAssistant oat_file_assistant(filename, kRuntimeISA, false, false); + return oat_file_assistant.IsUpToDate() ? JNI_FALSE : JNI_TRUE; } static jboolean DexFile_isValidCompilerFilter(JNIEnv* env, @@ -481,6 +544,16 @@ static jstring DexFile_getNonProfileGuidedCompilerFilter(JNIEnv* env, return env->NewStringUTF(new_filter_str.c_str()); } +static jboolean DexFile_isBackedByOatFile(JNIEnv* env, jclass, jobject cookie) { + const OatFile* oat_file = nullptr; + std::vector<const DexFile*> dex_files; + if (!ConvertJavaArrayToDexFiles(env, cookie, /*out */ dex_files, /* out */ oat_file)) { + DCHECK(env->ExceptionCheck()); + return false; + } + return oat_file != nullptr; +} + static JNINativeMethod gMethods[] = { NATIVE_METHOD(DexFile, closeDexFile, "(Ljava/lang/Object;)Z"), NATIVE_METHOD(DexFile, @@ -506,6 +579,9 @@ static JNINativeMethod gMethods[] = { NATIVE_METHOD(DexFile, getNonProfileGuidedCompilerFilter, "(Ljava/lang/String;)Ljava/lang/String;"), + NATIVE_METHOD(DexFile, isBackedByOatFile, "(Ljava/lang/Object;)Z"), + NATIVE_METHOD(DexFile, getDexFileStatus, + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;") }; void register_dalvik_system_DexFile(JNIEnv* env) { diff --git a/runtime/native/dalvik_system_VMRuntime.cc b/runtime/native/dalvik_system_VMRuntime.cc index d22c0c7158..79b18aa84e 100644 --- a/runtime/native/dalvik_system_VMRuntime.cc +++ b/runtime/native/dalvik_system_VMRuntime.cc @@ -16,7 +16,7 @@ #include "dalvik_system_VMRuntime.h" -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID extern "C" void android_set_application_target_sdk_version(uint32_t version); #endif #include <limits.h> @@ -145,6 +145,10 @@ static jboolean VMRuntime_isDebuggerActive(JNIEnv*, jobject) { return Dbg::IsDebuggerActive(); } +static jboolean VMRuntime_isNativeDebuggable(JNIEnv*, jobject) { + return Runtime::Current()->IsNativeDebuggable(); +} + static jobjectArray VMRuntime_properties(JNIEnv* env, jobject) { return toStringArray(env, Runtime::Current()->GetProperties()); } @@ -196,7 +200,7 @@ static void VMRuntime_setTargetSdkVersionNative(JNIEnv*, jobject, jint target_sd // Note that targetSdkVersion may be 0, meaning "current". Runtime::Current()->SetTargetSdkVersion(target_sdk_version); -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID // This part is letting libc/dynamic linker know about current app's // target sdk version to enable compatibility workarounds. android_set_application_target_sdk_version(static_cast<uint32_t>(target_sdk_version)); @@ -212,6 +216,10 @@ static void VMRuntime_registerNativeAllocation(JNIEnv* env, jobject, jint bytes) Runtime::Current()->GetHeap()->RegisterNativeAllocation(env, static_cast<size_t>(bytes)); } +static void VMRuntime_registerSensitiveThread(JNIEnv*, jobject) { + Runtime::Current()->RegisterSensitiveThread(); +} + static void VMRuntime_registerNativeFree(JNIEnv* env, jobject, jint bytes) { if (UNLIKELY(bytes < 0)) { ScopedObjectAccess soa(env); @@ -496,8 +504,7 @@ static void VMRuntime_preloadDexCaches(JNIEnv* env, jobject) { const DexFile* dex_file = boot_class_path[i]; CHECK(dex_file != nullptr); StackHandleScope<1> hs(soa.Self()); - Handle<mirror::DexCache> dex_cache( - hs.NewHandle(linker->RegisterDexFile(*dex_file, runtime->GetLinearAlloc()))); + Handle<mirror::DexCache> dex_cache(hs.NewHandle(linker->RegisterDexFile(*dex_file, nullptr))); if (kPreloadDexCachesStrings) { for (size_t j = 0; j < dex_cache->NumStrings(); j++) { @@ -637,12 +644,14 @@ static JNINativeMethod gMethods[] = { NATIVE_METHOD(VMRuntime, disableJitCompilation, "()V"), NATIVE_METHOD(VMRuntime, getTargetHeapUtilization, "()F"), NATIVE_METHOD(VMRuntime, isDebuggerActive, "!()Z"), + NATIVE_METHOD(VMRuntime, isNativeDebuggable, "!()Z"), NATIVE_METHOD(VMRuntime, nativeSetTargetHeapUtilization, "(F)V"), NATIVE_METHOD(VMRuntime, newNonMovableArray, "!(Ljava/lang/Class;I)Ljava/lang/Object;"), NATIVE_METHOD(VMRuntime, newUnpaddedArray, "!(Ljava/lang/Class;I)Ljava/lang/Object;"), NATIVE_METHOD(VMRuntime, properties, "()[Ljava/lang/String;"), NATIVE_METHOD(VMRuntime, setTargetSdkVersionNative, "(I)V"), NATIVE_METHOD(VMRuntime, registerNativeAllocation, "(I)V"), + NATIVE_METHOD(VMRuntime, registerSensitiveThread, "()V"), NATIVE_METHOD(VMRuntime, registerNativeFree, "(I)V"), NATIVE_METHOD(VMRuntime, requestConcurrentGC, "()V"), NATIVE_METHOD(VMRuntime, requestHeapTrim, "()V"), diff --git a/runtime/native/java_lang_Class.cc b/runtime/native/java_lang_Class.cc index c1899afc49..0624da38c8 100644 --- a/runtime/native/java_lang_Class.cc +++ b/runtime/native/java_lang_Class.cc @@ -238,12 +238,13 @@ static mirror::Field* GetPublicFieldRecursive( DCHECK(name != nullptr); DCHECK(self != nullptr); - StackHandleScope<1> hs(self); + StackHandleScope<2> hs(self); MutableHandle<mirror::Class> h_clazz(hs.NewHandle(clazz)); + Handle<mirror::String> h_name(hs.NewHandle(name)); // We search the current class, its direct interfaces then its superclass. while (h_clazz.Get() != nullptr) { - mirror::Field* result = GetDeclaredField(self, h_clazz.Get(), name); + mirror::Field* result = GetDeclaredField(self, h_clazz.Get(), h_name.Get()); if ((result != nullptr) && (result->GetAccessFlags() & kAccPublic)) { return result; } else if (UNLIKELY(self->IsExceptionPending())) { @@ -258,7 +259,7 @@ static mirror::Field* GetPublicFieldRecursive( self->AssertPendingException(); return nullptr; } - result = GetPublicFieldRecursive(self, iface, name); + result = GetPublicFieldRecursive(self, iface, h_name.Get()); if (result != nullptr) { DCHECK(result->GetAccessFlags() & kAccPublic); return result; @@ -517,14 +518,7 @@ static jint Class_getInnerClassFlags(JNIEnv* env, jobject javaThis, jint default ScopedFastNativeObjectAccess soa(env); StackHandleScope<1> hs(soa.Self()); Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis))); - if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) { - return defaultValue; - } - uint32_t flags; - if (!klass->GetDexFile().GetInnerClassFlags(klass, &flags)) { - return defaultValue; - } - return flags; + return mirror::Class::GetInnerClassFlags(klass, defaultValue); } static jstring Class_getInnerClassName(JNIEnv* env, jobject javaThis) { diff --git a/runtime/native/java_lang_reflect_Constructor.cc b/runtime/native/java_lang_reflect_Constructor.cc index ddcaadefa3..54b8afd1f3 100644 --- a/runtime/native/java_lang_reflect_Constructor.cc +++ b/runtime/native/java_lang_reflect_Constructor.cc @@ -34,20 +34,38 @@ static jobject Constructor_getAnnotationNative(JNIEnv* env, jobject javaMethod, ScopedFastNativeObjectAccess soa(env); StackHandleScope<1> hs(soa.Self()); ArtMethod* method = ArtMethod::FromReflectedMethod(soa, javaMethod); - Handle<mirror::Class> klass(hs.NewHandle(soa.Decode<mirror::Class*>(annotationType))); - return soa.AddLocalReference<jobject>( - method->GetDexFile()->GetAnnotationForMethod(method, klass)); + if (method->IsProxyMethod()) { + return nullptr; + } else { + Handle<mirror::Class> klass(hs.NewHandle(soa.Decode<mirror::Class*>(annotationType))); + return soa.AddLocalReference<jobject>( + method->GetDexFile()->GetAnnotationForMethod(method, klass)); + } } static jobjectArray Constructor_getDeclaredAnnotations(JNIEnv* env, jobject javaMethod) { ScopedFastNativeObjectAccess soa(env); ArtMethod* method = ArtMethod::FromReflectedMethod(soa, javaMethod); - return soa.AddLocalReference<jobjectArray>(method->GetDexFile()->GetAnnotationsForMethod(method)); + if (method->IsProxyMethod()) { + mirror::Class* class_class = mirror::Class::GetJavaLangClass(); + mirror::Class* class_array_class = + Runtime::Current()->GetClassLinker()->FindArrayClass(soa.Self(), &class_class); + if (class_array_class == nullptr) { + return nullptr; + } + mirror::ObjectArray<mirror::Class>* empty_array = + mirror::ObjectArray<mirror::Class>::Alloc(soa.Self(), class_array_class, 0); + return soa.AddLocalReference<jobjectArray>(empty_array); + } else { + return soa.AddLocalReference<jobjectArray>( + method->GetDexFile()->GetAnnotationsForMethod(method)); + } } static jobjectArray Constructor_getExceptionTypes(JNIEnv* env, jobject javaMethod) { ScopedFastNativeObjectAccess soa(env); - ArtMethod* method = ArtMethod::FromReflectedMethod(soa, javaMethod); + ArtMethod* method = ArtMethod::FromReflectedMethod(soa, javaMethod) + ->GetInterfaceMethodIfProxy(sizeof(void*)); mirror::ObjectArray<mirror::Class>* result_array = method->GetDexFile()->GetExceptionTypesForMethod(method); if (result_array == nullptr) { @@ -69,7 +87,12 @@ static jobjectArray Constructor_getExceptionTypes(JNIEnv* env, jobject javaMetho static jobjectArray Constructor_getParameterAnnotationsNative(JNIEnv* env, jobject javaMethod) { ScopedFastNativeObjectAccess soa(env); ArtMethod* method = ArtMethod::FromReflectedMethod(soa, javaMethod); - return soa.AddLocalReference<jobjectArray>(method->GetDexFile()->GetParameterAnnotations(method)); + if (method->IsProxyMethod()) { + return nullptr; + } else { + return soa.AddLocalReference<jobjectArray>( + method->GetDexFile()->GetParameterAnnotations(method)); + } } static jboolean Constructor_isAnnotationPresentNative(JNIEnv* env, jobject javaMethod, @@ -77,6 +100,10 @@ static jboolean Constructor_isAnnotationPresentNative(JNIEnv* env, jobject javaM ScopedFastNativeObjectAccess soa(env); StackHandleScope<1> hs(soa.Self()); ArtMethod* method = ArtMethod::FromReflectedMethod(soa, javaMethod); + if (method->IsProxyMethod()) { + // Proxies have no annotations. + return false; + } Handle<mirror::Class> klass(hs.NewHandle(soa.Decode<mirror::Class*>(annotationType))); return method->GetDexFile()->IsMethodAnnotationPresent(method, klass); } diff --git a/runtime/nth_caller_visitor.h b/runtime/nth_caller_visitor.h index 2295cb4664..e9b0d3c746 100644 --- a/runtime/nth_caller_visitor.h +++ b/runtime/nth_caller_visitor.h @@ -46,6 +46,7 @@ struct NthCallerVisitor : public StackVisitor { DCHECK(caller == nullptr); if (count == n) { caller = m; + caller_pc = GetCurrentQuickFramePc(); return false; } count++; @@ -57,6 +58,7 @@ struct NthCallerVisitor : public StackVisitor { const bool include_runtime_and_upcalls_; size_t count; ArtMethod* caller; + uintptr_t caller_pc; }; } // namespace art diff --git a/runtime/oat.cc b/runtime/oat.cc index 80231f3890..aab0e81047 100644 --- a/runtime/oat.cc +++ b/runtime/oat.cc @@ -182,8 +182,12 @@ void OatHeader::UpdateChecksumWithHeaderData() { void OatHeader::UpdateChecksum(const void* data, size_t length) { DCHECK(IsValid()); - const uint8_t* bytes = reinterpret_cast<const uint8_t*>(data); - adler32_checksum_ = adler32(adler32_checksum_, bytes, length); + if (data != nullptr) { + const uint8_t* bytes = reinterpret_cast<const uint8_t*>(data); + adler32_checksum_ = adler32(adler32_checksum_, bytes, length); + } else { + DCHECK_EQ(0U, length); + } } InstructionSet OatHeader::GetInstructionSet() const { diff --git a/runtime/oat.h b/runtime/oat.h index 543d99f2ad..57675dc738 100644 --- a/runtime/oat.h +++ b/runtime/oat.h @@ -43,7 +43,7 @@ class PACKED(4) OatHeader { static constexpr const char* kNativeDebuggableKey = "native-debuggable"; static constexpr const char* kCompilerFilter = "compiler-filter"; static constexpr const char* kClassPathKey = "classpath"; - static constexpr const char* kBootClassPath = "bootclasspath"; + static constexpr const char* kBootClassPathKey = "bootclasspath"; static constexpr const char kTrueValue[] = "true"; static constexpr const char kFalseValue[] = "false"; diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc index ccb8b2970b..62c723e76f 100644 --- a/runtime/oat_file.cc +++ b/runtime/oat_file.cc @@ -28,7 +28,7 @@ #include <sstream> // dlopen_ext support from bionic. -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID #include "android/dlext.h" #endif @@ -98,6 +98,8 @@ class OatFileBase : public OatFile { virtual const uint8_t* FindDynamicSymbolAddress(const std::string& symbol_name, std::string* error_msg) const = 0; + virtual void PreLoad() = 0; + virtual bool Load(const std::string& elf_filename, uint8_t* oat_file_begin, bool writable, @@ -138,6 +140,9 @@ OatFileBase* OatFileBase::OpenOatFile(const std::string& elf_filename, const char* abs_dex_location, std::string* error_msg) { std::unique_ptr<OatFileBase> ret(new kOatFileBaseSubType(location, executable)); + + ret->PreLoad(); + if (!ret->Load(elf_filename, oat_file_begin, writable, @@ -150,6 +155,7 @@ OatFileBase* OatFileBase::OpenOatFile(const std::string& elf_filename, if (!ret->ComputeFields(requested_base, elf_filename, error_msg)) { return nullptr; } + ret->PreSetup(elf_filename); if (!ret->Setup(abs_dex_location, error_msg)) { @@ -484,39 +490,24 @@ bool OatFileBase::Setup(const char* abs_dex_location, std::string* error_msg) { // OatFile via dlopen // //////////////////////// -static bool RegisterOatFileLocation(const std::string& location) { - if (!kIsTargetBuild) { - Runtime* const runtime = Runtime::Current(); - if (runtime != nullptr && !runtime->IsAotCompiler()) { - return runtime->GetOatFileManager().RegisterOatFileLocation(location); - } - return false; - } - return true; -} - -static void UnregisterOatFileLocation(const std::string& location) { - if (!kIsTargetBuild) { - Runtime* const runtime = Runtime::Current(); - if (runtime != nullptr && !runtime->IsAotCompiler()) { - runtime->GetOatFileManager().UnRegisterOatFileLocation(location); - } - } -} - class DlOpenOatFile FINAL : public OatFileBase { public: DlOpenOatFile(const std::string& filename, bool executable) : OatFileBase(filename, executable), dlopen_handle_(nullptr), - first_oat_(RegisterOatFileLocation(filename)) { + shared_objects_before_(0) { } ~DlOpenOatFile() { if (dlopen_handle_ != nullptr) { - dlclose(dlopen_handle_); + if (!kIsTargetBuild) { + MutexLock mu(Thread::Current(), *Locks::host_dlopen_handles_lock_); + host_dlopen_handles_.erase(dlopen_handle_); + dlclose(dlopen_handle_); + } else { + dlclose(dlopen_handle_); + } } - UnregisterOatFileLocation(GetLocation()); } protected: @@ -530,6 +521,8 @@ class DlOpenOatFile FINAL : public OatFileBase { return ptr; } + void PreLoad() OVERRIDE; + bool Load(const std::string& elf_filename, uint8_t* oat_file_begin, bool writable, @@ -545,18 +538,54 @@ class DlOpenOatFile FINAL : public OatFileBase { uint8_t* oat_file_begin, std::string* error_msg); + // On the host, if the same library is loaded again with dlopen the same + // file handle is returned. This differs from the behavior of dlopen on the + // target, where dlopen reloads the library at a different address every + // time you load it. The runtime relies on the target behavior to ensure + // each instance of the loaded library has a unique dex cache. To avoid + // problems, we fall back to our own linker in the case when the same + // library is opened multiple times on host. dlopen_handles_ is used to + // detect that case. + // Guarded by host_dlopen_handles_lock_; + static std::unordered_set<void*> host_dlopen_handles_; + // dlopen handle during runtime. void* dlopen_handle_; // TODO: Unique_ptr with custom deleter. // Dummy memory map objects corresponding to the regions mapped by dlopen. std::vector<std::unique_ptr<MemMap>> dlopen_mmaps_; - // Track the registration status (= was this the first oat file) for the location. - const bool first_oat_; + // The number of shared objects the linker told us about before loading. Used to + // (optimistically) optimize the PreSetup stage (see comment there). + size_t shared_objects_before_; DISALLOW_COPY_AND_ASSIGN(DlOpenOatFile); }; +std::unordered_set<void*> DlOpenOatFile::host_dlopen_handles_; + +void DlOpenOatFile::PreLoad() { +#ifdef __APPLE__ + UNUSED(shared_objects_before_); + LOG(FATAL) << "Should not reach here."; + UNREACHABLE(); +#else + // Count the entries in dl_iterate_phdr we get at this point in time. + struct dl_iterate_context { + static int callback(struct dl_phdr_info *info ATTRIBUTE_UNUSED, + size_t size ATTRIBUTE_UNUSED, + void *data) { + reinterpret_cast<dl_iterate_context*>(data)->count++; + return 0; // Continue iteration. + } + size_t count = 0; + } context; + + dl_iterate_phdr(dl_iterate_context::callback, &context); + shared_objects_before_ = context.count; +#endif +} + bool DlOpenOatFile::Load(const std::string& elf_filename, uint8_t* oat_file_begin, bool writable, @@ -593,12 +622,6 @@ bool DlOpenOatFile::Load(const std::string& elf_filename, *error_msg = "DlOpen disabled for host."; return false; } - // For RAII, tracking multiple loads is done in the constructor and destructor. The result is - // stored in the first_oat_ flag. - if (!first_oat_) { - *error_msg = "Loading oat files multiple times with dlopen not supported on host."; - return false; - } } bool success = Dlopen(elf_filename, oat_file_begin, error_msg); @@ -623,7 +646,7 @@ bool DlOpenOatFile::Dlopen(const std::string& elf_filename, *error_msg = StringPrintf("Failed to find absolute path for '%s'", elf_filename.c_str()); return false; } -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID android_dlextinfo extinfo; extinfo.flags = ANDROID_DLEXT_FORCE_LOAD | // Force-load, don't reuse handle // (open oat files multiple @@ -636,9 +659,19 @@ bool DlOpenOatFile::Dlopen(const std::string& elf_filename, } // (pic boot image). dlopen_handle_ = android_dlopen_ext(absolute_path.get(), RTLD_NOW, &extinfo); #else - dlopen_handle_ = dlopen(absolute_path.get(), RTLD_NOW); UNUSED(oat_file_begin); -#endif + static_assert(!kIsTargetBuild, "host_dlopen_handles_ will leak handles"); + MutexLock mu(Thread::Current(), *Locks::host_dlopen_handles_lock_); + dlopen_handle_ = dlopen(absolute_path.get(), RTLD_NOW); + if (dlopen_handle_ != nullptr) { + if (!host_dlopen_handles_.insert(dlopen_handle_).second) { + dlclose(dlopen_handle_); + dlopen_handle_ = nullptr; + *error_msg = StringPrintf("host dlopen re-opened '%s'", elf_filename.c_str()); + return false; + } + } +#endif // ART_TARGET_ANDROID } if (dlopen_handle_ == nullptr) { *error_msg = StringPrintf("Failed to dlopen '%s': %s", elf_filename.c_str(), dlerror()); @@ -657,6 +690,14 @@ void DlOpenOatFile::PreSetup(const std::string& elf_filename) { struct dl_iterate_context { static int callback(struct dl_phdr_info *info, size_t /* size */, void *data) { auto* context = reinterpret_cast<dl_iterate_context*>(data); + context->shared_objects_seen++; + if (context->shared_objects_seen < context->shared_objects_before) { + // We haven't been called yet for anything we haven't seen before. Just continue. + // Note: this is aggressively optimistic. If another thread was unloading a library, + // we may miss out here. However, this does not happen often in practice. + return 0; + } + // See whether this callback corresponds to the file which we have just loaded. bool contains_begin = false; for (int i = 0; i < info->dlpi_phnum; i++) { @@ -687,11 +728,22 @@ void DlOpenOatFile::PreSetup(const std::string& elf_filename) { } const uint8_t* const begin_; std::vector<std::unique_ptr<MemMap>>* const dlopen_mmaps_; - } context = { Begin(), &dlopen_mmaps_ }; + const size_t shared_objects_before; + size_t shared_objects_seen; + }; + dl_iterate_context context = { Begin(), &dlopen_mmaps_, shared_objects_before_, 0}; if (dl_iterate_phdr(dl_iterate_context::callback, &context) == 0) { - PrintFileToLog("/proc/self/maps", LogSeverity::WARNING); - LOG(ERROR) << "File " << elf_filename << " loaded with dlopen but cannot find its mmaps."; + // Hm. Maybe our optimization went wrong. Try another time with shared_objects_before == 0 + // before giving up. This should be unusual. + VLOG(oat) << "Need a second run in PreSetup, didn't find with shared_objects_before=" + << shared_objects_before_; + dl_iterate_context context0 = { Begin(), &dlopen_mmaps_, 0, 0}; + if (dl_iterate_phdr(dl_iterate_context::callback, &context0) == 0) { + // OK, give up and print an error. + PrintFileToLog("/proc/self/maps", LogSeverity::WARNING); + LOG(ERROR) << "File " << elf_filename << " loaded with dlopen but cannot find its mmaps."; + } } #endif } @@ -728,6 +780,9 @@ class ElfOatFile FINAL : public OatFileBase { return ptr; } + void PreLoad() OVERRIDE { + } + bool Load(const std::string& elf_filename, uint8_t* oat_file_begin, // Override where the file is loaded to if not null bool writable, @@ -897,7 +952,12 @@ OatFile* OatFile::Open(const std::string& filename, ScopedTrace trace("Open oat file " + location); CHECK(!filename.empty()) << location; CheckLocation(location); - std::unique_ptr<OatFile> ret; + + // Check that the file even exists, fast-fail. + if (!OS::FileExists(filename.c_str())) { + *error_msg = StringPrintf("File %s does not exist.", filename.c_str()); + return nullptr; + } // Try dlopen first, as it is required for native debuggability. This will fail fast if dlopen is // disabled. diff --git a/runtime/oat_file.h b/runtime/oat_file.h index 11a9d76dad..aa727ff45b 100644 --- a/runtime/oat_file.h +++ b/runtime/oat_file.h @@ -48,6 +48,9 @@ class DummyOatFile; class OatFile { public: + // Special classpath that skips shared library check. + static constexpr const char* kSpecialSharedLibrary = "&"; + typedef art::OatDexFile OatDexFile; // Opens an oat file contained within the given elf file. This is always opened as @@ -370,6 +373,10 @@ class OatDexFile FINAL { return lookup_table_data_; } + const uint8_t* GetDexFilePointer() const { + return dex_file_pointer_; + } + ~OatDexFile(); private: diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc index 78e372ad02..218c490b35 100644 --- a/runtime/oat_file_assistant.cc +++ b/runtime/oat_file_assistant.cc @@ -30,6 +30,7 @@ #include "base/logging.h" #include "base/stringprintf.h" +#include "compiler_filter.h" #include "class_linker.h" #include "gc/heap.h" #include "gc/space/image_space.h" @@ -43,6 +44,24 @@ namespace art { +std::ostream& operator << (std::ostream& stream, const OatFileAssistant::OatStatus status) { + switch (status) { + case OatFileAssistant::kOatOutOfDate: + stream << "kOatOutOfDate"; + break; + case OatFileAssistant::kOatUpToDate: + stream << "kOatUpToDate"; + break; + case OatFileAssistant::kOatNeedsRelocation: + stream << "kOatNeedsRelocation"; + break; + default: + UNREACHABLE(); + } + + return stream; +} + OatFileAssistant::OatFileAssistant(const char* dex_location, const InstructionSet isa, bool profile_changed, @@ -134,7 +153,7 @@ bool OatFileAssistant::OdexFileCompilerFilterIsOkay(CompilerFilter::Filter targe } OatFileAssistant::DexOptNeeded OatFileAssistant::GetDexOptNeeded(CompilerFilter::Filter target) { - bool compilation_desired = CompilerFilter::IsCompilationEnabled(target); + bool compilation_desired = CompilerFilter::IsBytecodeCompilationEnabled(target); // See if the oat file is in good shape as is. bool oat_okay = OatFileCompilerFilterIsOkay(target); @@ -179,11 +198,42 @@ OatFileAssistant::DexOptNeeded OatFileAssistant::GetDexOptNeeded(CompilerFilter: return HasOriginalDexFiles() ? kDex2OatNeeded : kNoDexOptNeeded; } +// Figure out the currently specified compile filter option in the runtime. +// Returns true on success, false if the compiler filter is invalid, in which +// case error_msg describes the problem. +static bool GetRuntimeCompilerFilterOption(CompilerFilter::Filter* filter, + std::string* error_msg) { + CHECK(filter != nullptr); + CHECK(error_msg != nullptr); + + *filter = CompilerFilter::kDefaultCompilerFilter; + for (StringPiece option : Runtime::Current()->GetCompilerOptions()) { + if (option.starts_with("--compiler-filter=")) { + const char* compiler_filter_string = option.substr(strlen("--compiler-filter=")).data(); + if (!CompilerFilter::ParseCompilerFilter(compiler_filter_string, filter)) { + *error_msg = std::string("Unknown --compiler-filter value: ") + + std::string(compiler_filter_string); + return false; + } + } + } + return true; +} + +bool OatFileAssistant::IsUpToDate() { + return OatFileIsUpToDate() || OdexFileIsUpToDate(); +} + OatFileAssistant::ResultOfAttemptToUpdate -OatFileAssistant::MakeUpToDate(CompilerFilter::Filter target, std::string* error_msg) { +OatFileAssistant::MakeUpToDate(std::string* error_msg) { + CompilerFilter::Filter target; + if (!GetRuntimeCompilerFilterOption(&target, error_msg)) { + return kUpdateNotAttempted; + } + switch (GetDexOptNeeded(target)) { case kNoDexOptNeeded: return kUpdateSucceeded; - case kDex2OatNeeded: return GenerateOatFile(target, error_msg); + case kDex2OatNeeded: return GenerateOatFile(error_msg); case kPatchOatNeeded: return RelocateOatFile(OdexFileName(), error_msg); case kSelfPatchOatNeeded: return RelocateOatFile(OatFileName(), error_msg); } @@ -350,6 +400,12 @@ bool OatFileAssistant::OdexFileIsUpToDate() { return cached_odex_file_is_up_to_date_; } +CompilerFilter::Filter OatFileAssistant::OdexFileCompilerFilter() { + const OatFile* odex_file = GetOdexFile(); + CHECK(odex_file != nullptr); + + return odex_file->GetCompilerFilter(); +} std::string OatFileAssistant::ArtFileName(const OatFile* oat_file) const { const std::string oat_file_location = oat_file->GetLocation(); // Replace extension with .art @@ -428,6 +484,13 @@ bool OatFileAssistant::OatFileIsUpToDate() { return cached_oat_file_is_up_to_date_; } +CompilerFilter::Filter OatFileAssistant::OatFileCompilerFilter() { + const OatFile* oat_file = GetOatFile(); + CHECK(oat_file != nullptr); + + return oat_file->GetCompilerFilter(); +} + OatFileAssistant::OatStatus OatFileAssistant::GivenOatFileStatus(const OatFile& file) { // TODO: This could cause GivenOatFileIsOutOfDate to be called twice, which // is more work than we need to do. If performance becomes a concern, and @@ -492,10 +555,21 @@ bool OatFileAssistant::GivenOatFileIsOutOfDate(const OatFile& file) { const ImageInfo* image_info = GetImageInfo(); if (image_info == nullptr) { VLOG(oat) << "No image for oat image checksum to match against."; - return true; - } - if (file.GetOatHeader().GetImageFileLocationOatChecksum() != GetCombinedImageChecksum()) { + if (HasOriginalDexFiles()) { + return true; + } + + // If there is no original dex file to fall back to, grudgingly accept + // the oat file. This could technically lead to crashes, but there's no + // way we could find a better oat file to use for this dex location, + // and it's better than being stuck in a boot loop with no way out. + // The problem will hopefully resolve itself the next time the runtime + // starts up. + LOG(WARNING) << "Dex location " << dex_location_ << " does not seem to include dex file. " + << "Allow oat file use. This is potentially dangerous."; + } else if (file.GetOatHeader().GetImageFileLocationOatChecksum() + != GetCombinedImageChecksum()) { VLOG(oat) << "Oat image checksum does not match image checksum."; return true; } @@ -530,7 +604,7 @@ bool OatFileAssistant::GivenOatFileIsUpToDate(const OatFile& file) { CompilerFilter::Filter current_compiler_filter = file.GetCompilerFilter(); - if (CompilerFilter::IsCompilationEnabled(current_compiler_filter)) { + if (CompilerFilter::IsBytecodeCompilationEnabled(current_compiler_filter)) { if (!file.IsPic()) { const ImageInfo* image_info = GetImageInfo(); if (image_info == nullptr) { @@ -623,7 +697,7 @@ OatFileAssistant::RelocateOatFile(const std::string* input_file, std::string* er } OatFileAssistant::ResultOfAttemptToUpdate -OatFileAssistant::GenerateOatFile(CompilerFilter::Filter target, std::string* error_msg) { +OatFileAssistant::GenerateOatFile(std::string* error_msg) { CHECK(error_msg != nullptr); Runtime* runtime = Runtime::Current(); @@ -667,7 +741,6 @@ OatFileAssistant::GenerateOatFile(CompilerFilter::Filter target, std::string* er args.push_back("--dex-file=" + dex_location_); args.push_back("--oat-fd=" + std::to_string(oat_file->Fd())); args.push_back("--oat-location=" + oat_file_name); - args.push_back("--compiler-filter=" + CompilerFilter::NameOfFilter(target)); if (!Dex2Oat(args, error_msg)) { // Manually delete the file. This ensures there is no garbage left over if @@ -702,7 +775,11 @@ bool OatFileAssistant::Dex2Oat(const std::vector<std::string>& args, argv.push_back("--runtime-arg"); argv.push_back("-classpath"); argv.push_back("--runtime-arg"); - argv.push_back(runtime->GetClassPathString()); + std::string class_path = runtime->GetClassPathString(); + if (class_path == "") { + class_path = OatFile::kSpecialSharedLibrary; + } + argv.push_back(class_path); if (runtime->IsDebuggable()) { argv.push_back("--debuggable"); } diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h index d3228deac7..bb7b40828e 100644 --- a/runtime/oat_file_assistant.h +++ b/runtime/oat_file_assistant.h @@ -19,6 +19,7 @@ #include <cstdint> #include <memory> +#include <sstream> #include <string> #include "arch/instruction_set.h" @@ -148,6 +149,10 @@ class OatFileAssistant { // given compiler filter. DexOptNeeded GetDexOptNeeded(CompilerFilter::Filter target_compiler_filter); + // Returns true if there is up-to-date code for this dex location, + // irrespective of the compiler filter of the up-to-date code. + bool IsUpToDate(); + // Return code used when attempting to generate updated code. enum ResultOfAttemptToUpdate { kUpdateFailed, // We tried making the code up to date, but @@ -159,15 +164,12 @@ class OatFileAssistant { }; // Attempts to generate or relocate the oat file as needed to make it up to - // date with in a way that is at least as good as an oat file generated with - // the given compiler filter. - // Returns the result of attempting to update the code. + // date based on the current runtime and compiler options. // // If the result is not kUpdateSucceeded, the value of error_msg will be set // to a string describing why there was a failure or the update was not // attempted. error_msg must not be null. - ResultOfAttemptToUpdate MakeUpToDate(CompilerFilter::Filter target_compiler_filter, - std::string* error_msg); + ResultOfAttemptToUpdate MakeUpToDate(std::string* error_msg); // Returns an oat file that can be used for loading dex files. // Returns null if no suitable oat file was found. @@ -214,6 +216,9 @@ class OatFileAssistant { bool OdexFileIsOutOfDate(); bool OdexFileNeedsRelocation(); bool OdexFileIsUpToDate(); + // Must only be called if the associated odex file exists, i.e, if + // |OdexFileExists() == true|. + CompilerFilter::Filter OdexFileCompilerFilter(); // When the dex files is compiled on the target device, the oat file is the // result. The oat file will have been relocated to some @@ -230,6 +235,9 @@ class OatFileAssistant { bool OatFileIsOutOfDate(); bool OatFileNeedsRelocation(); bool OatFileIsUpToDate(); + // Must only be called if the associated oat file exists, i.e, if + // |OatFileExists() == true|. + CompilerFilter::Filter OatFileCompilerFilter(); // Return image file name. Does not cache since it relies on the oat file. std::string ArtFileName(const OatFile* oat_file) const; @@ -250,14 +258,15 @@ class OatFileAssistant { // attempted. error_msg must not be null. ResultOfAttemptToUpdate RelocateOatFile(const std::string* input_file, std::string* error_msg); - // Generate the oat file from the dex file using the given compiler filter. + // Generate the oat file from the dex file using the current runtime + // compiler options. // This does not check the current status before attempting to generate the // oat file. // // If the result is not kUpdateSucceeded, the value of error_msg will be set // to a string describing why there was a failure or the update was not // attempted. error_msg must not be null. - ResultOfAttemptToUpdate GenerateOatFile(CompilerFilter::Filter filter, std::string* error_msg); + ResultOfAttemptToUpdate GenerateOatFile(std::string* error_msg); // Executes dex2oat using the current runtime configuration overridden with // the given arguments. This does not check to see if dex2oat is enabled in @@ -438,6 +447,8 @@ class OatFileAssistant { DISALLOW_COPY_AND_ASSIGN(OatFileAssistant); }; +std::ostream& operator << (std::ostream& stream, const OatFileAssistant::OatStatus status); + } // namespace art #endif // ART_RUNTIME_OAT_FILE_ASSISTANT_H_ diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc index f50d1cb748..c79a9a67b3 100644 --- a/runtime/oat_file_assistant_test.cc +++ b/runtime/oat_file_assistant_test.cc @@ -233,7 +233,7 @@ class OatFileAssistantTest : public CommonRuntimeTest { EXPECT_TRUE(odex_file->HasPatchInfo()); EXPECT_EQ(filter, odex_file->GetCompilerFilter()); - if (CompilerFilter::IsCompilationEnabled(filter)) { + if (CompilerFilter::IsBytecodeCompilationEnabled(filter)) { const std::vector<gc::space::ImageSpace*> image_spaces = runtime->GetHeap()->GetBootImageSpaces(); ASSERT_TRUE(!image_spaces.empty() && image_spaces[0] != nullptr); @@ -453,8 +453,7 @@ TEST_F(OatFileAssistantTest, NoDexNoOat) { // Trying to make the oat file up to date should not fail or crash. std::string error_msg; - EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, - oat_file_assistant.MakeUpToDate(CompilerFilter::kSpeed, &error_msg)); + EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, oat_file_assistant.MakeUpToDate(&error_msg)); // Trying to get the best oat file should fail, but not crash. std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile(); @@ -705,8 +704,9 @@ TEST_F(OatFileAssistantTest, StrippedDexOdexNoOat) { // Make the oat file up to date. std::string error_msg; + Runtime::Current()->AddCompilerOption("--compiler-filter=speed"); ASSERT_EQ(OatFileAssistant::kUpdateSucceeded, - oat_file_assistant.MakeUpToDate(CompilerFilter::kSpeed, &error_msg)) << error_msg; + oat_file_assistant.MakeUpToDate(&error_msg)) << error_msg; EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded, oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed)); @@ -768,8 +768,9 @@ TEST_F(OatFileAssistantTest, StrippedDexOdexOat) { // Make the oat file up to date. std::string error_msg; + Runtime::Current()->AddCompilerOption("--compiler-filter=speed"); ASSERT_EQ(OatFileAssistant::kUpdateSucceeded, - oat_file_assistant.MakeUpToDate(CompilerFilter::kSpeed, &error_msg)) << error_msg; + oat_file_assistant.MakeUpToDate(&error_msg)) << error_msg; EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded, oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed)); @@ -825,8 +826,9 @@ TEST_F(OatFileAssistantTest, ResourceOnlyDex) { // Make the oat file up to date. This should have no effect. std::string error_msg; + Runtime::Current()->AddCompilerOption("--compiler-filter=speed"); EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, - oat_file_assistant.MakeUpToDate(CompilerFilter::kSpeed, &error_msg)) << error_msg; + oat_file_assistant.MakeUpToDate(&error_msg)) << error_msg; EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded, oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed)); @@ -876,8 +878,9 @@ TEST_F(OatFileAssistantTest, SelfRelocation) { // Make the oat file up to date. std::string error_msg; + Runtime::Current()->AddCompilerOption("--compiler-filter=speed"); ASSERT_EQ(OatFileAssistant::kUpdateSucceeded, - oat_file_assistant.MakeUpToDate(CompilerFilter::kSpeed, &error_msg)) << error_msg; + oat_file_assistant.MakeUpToDate(&error_msg)) << error_msg; EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded, oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed)); @@ -920,8 +923,9 @@ TEST_F(OatFileAssistantTest, NoSelfRelocation) { // Make the oat file up to date. std::string error_msg; + Runtime::Current()->AddCompilerOption("--compiler-filter=speed"); ASSERT_EQ(OatFileAssistant::kUpdateSucceeded, - oat_file_assistant.MakeUpToDate(CompilerFilter::kSpeed, &error_msg)) << error_msg; + oat_file_assistant.MakeUpToDate(&error_msg)) << error_msg; EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded, oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed)); @@ -1100,8 +1104,9 @@ TEST_F(OatFileAssistantTest, LoadDexNoAlternateOat) { OatFileAssistant oat_file_assistant( dex_location.c_str(), oat_location.c_str(), kRuntimeISA, false, true); std::string error_msg; + Runtime::Current()->AddCompilerOption("--compiler-filter=speed"); ASSERT_EQ(OatFileAssistant::kUpdateSucceeded, - oat_file_assistant.MakeUpToDate(CompilerFilter::kSpeed, &error_msg)) << error_msg; + oat_file_assistant.MakeUpToDate(&error_msg)) << error_msg; std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile(); ASSERT_TRUE(oat_file.get() != nullptr); @@ -1131,8 +1136,9 @@ TEST_F(OatFileAssistantTest, LoadDexUnwriteableAlternateOat) { OatFileAssistant oat_file_assistant( dex_location.c_str(), oat_location.c_str(), kRuntimeISA, false, true); std::string error_msg; + Runtime::Current()->AddCompilerOption("--compiler-filter=speed"); ASSERT_EQ(OatFileAssistant::kUpdateNotAttempted, - oat_file_assistant.MakeUpToDate(CompilerFilter::kSpeed, &error_msg)); + oat_file_assistant.MakeUpToDate(&error_msg)); std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile(); ASSERT_TRUE(oat_file.get() == nullptr); @@ -1147,8 +1153,9 @@ TEST_F(OatFileAssistantTest, GenNoDex) { OatFileAssistant oat_file_assistant( dex_location.c_str(), oat_location.c_str(), kRuntimeISA, false, true); std::string error_msg; + Runtime::Current()->AddCompilerOption("--compiler-filter=speed"); EXPECT_EQ(OatFileAssistant::kUpdateNotAttempted, - oat_file_assistant.GenerateOatFile(CompilerFilter::kSpeed, &error_msg)); + oat_file_assistant.GenerateOatFile(&error_msg)); } // Turn an absolute path into a path relative to the current working @@ -1227,8 +1234,9 @@ TEST_F(OatFileAssistantTest, ShortDexLocation) { // Trying to make it up to date should have no effect. std::string error_msg; + Runtime::Current()->AddCompilerOption("--compiler-filter=speed"); EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, - oat_file_assistant.MakeUpToDate(CompilerFilter::kSpeed, &error_msg)); + oat_file_assistant.MakeUpToDate(&error_msg)); EXPECT_TRUE(error_msg.empty()); } @@ -1256,8 +1264,7 @@ TEST_F(OatFileAssistantTest, LongDexExtension) { class RaceGenerateTask : public Task { public: explicit RaceGenerateTask(const std::string& dex_location, const std::string& oat_location) - : dex_location_(dex_location), oat_location_(oat_location), - loaded_oat_file_(nullptr) + : dex_location_(dex_location), oat_location_(oat_location), loaded_oat_file_(nullptr) {} void Run(Thread* self ATTRIBUTE_UNUSED) { @@ -1368,6 +1375,34 @@ TEST_F(OatFileAssistantNoDex2OatTest, LoadMultiDexOdexNoOat) { EXPECT_EQ(2u, dex_files.size()); } +TEST_F(OatFileAssistantTest, RuntimeCompilerFilterOptionUsed) { + std::string dex_location = GetScratchDir() + "/RuntimeCompilerFilterOptionUsed.jar"; + Copy(GetDexSrc1(), dex_location); + + OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false, false); + + std::string error_msg; + Runtime::Current()->AddCompilerOption("--compiler-filter=interpret-only"); + EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, + oat_file_assistant.MakeUpToDate(&error_msg)) << error_msg; + EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded, + oat_file_assistant.GetDexOptNeeded(CompilerFilter::kInterpretOnly)); + EXPECT_EQ(OatFileAssistant::kDex2OatNeeded, + oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed)); + + Runtime::Current()->AddCompilerOption("--compiler-filter=speed"); + EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, + oat_file_assistant.MakeUpToDate(&error_msg)) << error_msg; + EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded, + oat_file_assistant.GetDexOptNeeded(CompilerFilter::kInterpretOnly)); + EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded, + oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed)); + + Runtime::Current()->AddCompilerOption("--compiler-filter=bogus"); + EXPECT_EQ(OatFileAssistant::kUpdateNotAttempted, + oat_file_assistant.MakeUpToDate(&error_msg)); +} + TEST(OatFileAssistantUtilsTest, DexFilenameToOdexFilename) { std::string error_msg; std::string odex_file; diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc index 94f6345bb0..d7d2b5c546 100644 --- a/runtime/oat_file_manager.cc +++ b/runtime/oat_file_manager.cc @@ -36,16 +36,9 @@ namespace art { -// For b/21333911. -// Only enabled for debug builds to prevent bit rot. There are too many performance regressions for -// normal builds. -static constexpr bool kDuplicateClassesCheck = kIsDebugBuild; - // If true, then we attempt to load the application image if it exists. static constexpr bool kEnableAppImage = true; -CompilerFilter::Filter OatFileManager::filter_ = CompilerFilter::Filter::kSpeed; - const OatFile* OatFileManager::RegisterOatFile(std::unique_ptr<const OatFile> oat_file) { WriterMutexLock mu(Thread::Current(), *Locks::oat_file_manager_lock_); DCHECK(oat_file != nullptr); @@ -74,6 +67,20 @@ void OatFileManager::UnRegisterAndDeleteOatFile(const OatFile* oat_file) { compare.release(); } +const OatFile* OatFileManager::FindOpenedOatFileFromDexLocation( + const std::string& dex_base_location) const { + ReaderMutexLock mu(Thread::Current(), *Locks::oat_file_manager_lock_); + for (const std::unique_ptr<const OatFile>& oat_file : oat_files_) { + const std::vector<const OatDexFile*>& oat_dex_files = oat_file->GetOatDexFiles(); + for (const OatDexFile* oat_dex_file : oat_dex_files) { + if (DexFile::GetBaseLocation(oat_dex_file->GetDexFileLocation()) == dex_base_location) { + return oat_file.get(); + } + } + } + return nullptr; +} + const OatFile* OatFileManager::FindOpenedOatFileFromOatLocation(const std::string& oat_location) const { ReaderMutexLock mu(Thread::Current(), *Locks::oat_file_manager_lock_); @@ -161,7 +168,7 @@ class DexFileAndClassPair : ValueObject { void Next() { ++current_class_index_; - cached_descriptor_ = GetClassDescriptor(dex_file_.get(), current_class_index_); + cached_descriptor_ = GetClassDescriptor(dex_file_, current_class_index_); } size_t GetCurrentClassIndex() const { @@ -173,7 +180,12 @@ class DexFileAndClassPair : ValueObject { } const DexFile* GetDexFile() const { - return dex_file_.get(); + return dex_file_; + } + + void DeleteDexFile() { + delete dex_file_; + dex_file_ = nullptr; } private: @@ -184,7 +196,7 @@ class DexFileAndClassPair : ValueObject { } const char* cached_descriptor_; - std::shared_ptr<const DexFile> dex_file_; + const DexFile* dex_file_; size_t current_class_index_; bool from_loaded_oat_; // We only need to compare mismatches between what we load now // and what was loaded before. Any old duplicates must have been @@ -207,53 +219,299 @@ static void AddDexFilesFromOat(const OatFile* oat_file, } static void AddNext(/*inout*/DexFileAndClassPair* original, - /*inout*/std::priority_queue<DexFileAndClassPair>* heap) { + /*inout*/std::priority_queue<DexFileAndClassPair>* heap, + bool owning_dex_files) { if (original->DexFileHasMoreClasses()) { original->Next(); heap->push(std::move(*original)); + } else if (owning_dex_files) { + original->DeleteDexFile(); + } +} + +static void FreeDexFilesInHeap(std::priority_queue<DexFileAndClassPair>* heap, + bool owning_dex_files) { + if (owning_dex_files) { + while (!heap->empty()) { + delete heap->top().GetDexFile(); + heap->pop(); + } + } +} + +static void IterateOverJavaDexFile(mirror::Object* dex_file, + ArtField* const cookie_field, + std::function<bool(const DexFile*)> fn) + SHARED_REQUIRES(Locks::mutator_lock_) { + if (dex_file != nullptr) { + mirror::LongArray* long_array = cookie_field->GetObject(dex_file)->AsLongArray(); + if (long_array == nullptr) { + // This should never happen so log a warning. + LOG(WARNING) << "Null DexFile::mCookie"; + return; + } + int32_t long_array_size = long_array->GetLength(); + // Start from 1 to skip the oat file. + for (int32_t j = 1; j < long_array_size; ++j) { + const DexFile* cp_dex_file = reinterpret_cast<const DexFile*>(static_cast<uintptr_t>( + long_array->GetWithoutChecks(j))); + if (!fn(cp_dex_file)) { + return; + } + } + } +} + +static void IterateOverPathClassLoader( + ScopedObjectAccessAlreadyRunnable& soa, + Handle<mirror::ClassLoader> class_loader, + MutableHandle<mirror::ObjectArray<mirror::Object>> dex_elements, + std::function<bool(const DexFile*)> fn) SHARED_REQUIRES(Locks::mutator_lock_) { + // Handle this step. + // Handle as if this is the child PathClassLoader. + // The class loader is a PathClassLoader which inherits from BaseDexClassLoader. + // We need to get the DexPathList and loop through it. + ArtField* const cookie_field = soa.DecodeField(WellKnownClasses::dalvik_system_DexFile_cookie); + ArtField* const dex_file_field = + soa.DecodeField(WellKnownClasses::dalvik_system_DexPathList__Element_dexFile); + mirror::Object* dex_path_list = + soa.DecodeField(WellKnownClasses::dalvik_system_PathClassLoader_pathList)-> + GetObject(class_loader.Get()); + if (dex_path_list != nullptr && dex_file_field != nullptr && cookie_field != nullptr) { + // DexPathList has an array dexElements of Elements[] which each contain a dex file. + mirror::Object* dex_elements_obj = + soa.DecodeField(WellKnownClasses::dalvik_system_DexPathList_dexElements)-> + GetObject(dex_path_list); + // Loop through each dalvik.system.DexPathList$Element's dalvik.system.DexFile and look + // at the mCookie which is a DexFile vector. + if (dex_elements_obj != nullptr) { + dex_elements.Assign(dex_elements_obj->AsObjectArray<mirror::Object>()); + for (int32_t i = 0; i < dex_elements->GetLength(); ++i) { + mirror::Object* element = dex_elements->GetWithoutChecks(i); + if (element == nullptr) { + // Should never happen, fall back to java code to throw a NPE. + break; + } + mirror::Object* dex_file = dex_file_field->GetObject(element); + IterateOverJavaDexFile(dex_file, cookie_field, fn); + } + } + } +} + +static bool GetDexFilesFromClassLoader( + ScopedObjectAccessAlreadyRunnable& soa, + mirror::ClassLoader* class_loader, + std::priority_queue<DexFileAndClassPair>* queue) SHARED_REQUIRES(Locks::mutator_lock_) { + if (ClassLinker::IsBootClassLoader(soa, class_loader)) { + // The boot class loader. We don't load any of these files, as we know we compiled against + // them correctly. + return true; + } + + // Unsupported class-loader? + if (class_loader->GetClass() != + soa.Decode<mirror::Class*>(WellKnownClasses::dalvik_system_PathClassLoader)) { + VLOG(class_linker) << "Unsupported class-loader " << PrettyClass(class_loader->GetClass()); + return false; + } + + bool recursive_result = GetDexFilesFromClassLoader(soa, class_loader->GetParent(), queue); + if (!recursive_result) { + // Something wrong up the chain. + return false; } + + // Collect all the dex files. + auto GetDexFilesFn = [&] (const DexFile* cp_dex_file) + SHARED_REQUIRES(Locks::mutator_lock_) { + if (cp_dex_file->NumClassDefs() > 0) { + queue->emplace(cp_dex_file, 0U, true); + } + return true; // Continue looking. + }; + + // Handle for dex-cache-element. + StackHandleScope<3> hs(soa.Self()); + MutableHandle<mirror::ObjectArray<mirror::Object>> dex_elements( + hs.NewHandle<mirror::ObjectArray<mirror::Object>>(nullptr)); + Handle<mirror::ClassLoader> h_class_loader(hs.NewHandle(class_loader)); + + IterateOverPathClassLoader(soa, h_class_loader, dex_elements, GetDexFilesFn); + + return true; +} + +static void GetDexFilesFromDexElementsArray( + ScopedObjectAccessAlreadyRunnable& soa, + Handle<mirror::ObjectArray<mirror::Object>> dex_elements, + std::priority_queue<DexFileAndClassPair>* queue) SHARED_REQUIRES(Locks::mutator_lock_) { + if (dex_elements.Get() == nullptr) { + // Nothing to do. + return; + } + + ArtField* const cookie_field = soa.DecodeField(WellKnownClasses::dalvik_system_DexFile_cookie); + ArtField* const dex_file_field = + soa.DecodeField(WellKnownClasses::dalvik_system_DexPathList__Element_dexFile); + const mirror::Class* const element_class = soa.Decode<mirror::Class*>( + WellKnownClasses::dalvik_system_DexPathList__Element); + const mirror::Class* const dexfile_class = soa.Decode<mirror::Class*>( + WellKnownClasses::dalvik_system_DexFile); + + // Collect all the dex files. + auto GetDexFilesFn = [&] (const DexFile* cp_dex_file) + SHARED_REQUIRES(Locks::mutator_lock_) { + if (cp_dex_file != nullptr && cp_dex_file->NumClassDefs() > 0) { + queue->emplace(cp_dex_file, 0U, true); + } + return true; // Continue looking. + }; + + for (int32_t i = 0; i < dex_elements->GetLength(); ++i) { + mirror::Object* element = dex_elements->GetWithoutChecks(i); + if (element == nullptr) { + continue; + } + + // We support this being dalvik.system.DexPathList$Element and dalvik.system.DexFile. + + mirror::Object* dex_file; + if (element->GetClass() == element_class) { + dex_file = dex_file_field->GetObject(element); + } else if (element->GetClass() == dexfile_class) { + dex_file = element; + } else { + LOG(WARNING) << "Unsupported element in dex_elements: " << PrettyClass(element->GetClass()); + continue; + } + + IterateOverJavaDexFile(dex_file, cookie_field, GetDexFilesFn); + } +} + +static bool AreSharedLibrariesOk(const std::string shared_libraries, + std::priority_queue<DexFileAndClassPair>& queue) { + if (shared_libraries.empty()) { + if (queue.empty()) { + // No shared libraries or oat files, as expected. + return true; + } + } else { + if (shared_libraries.compare(OatFile::kSpecialSharedLibrary) == 0) { + // If we find the special shared library, skip the shared libraries check. + return true; + } + // Shared libraries is a series of dex file paths and their checksums, each separated by '*'. + std::vector<std::string> shared_libraries_split; + Split(shared_libraries, '*', &shared_libraries_split); + + size_t index = 0; + std::priority_queue<DexFileAndClassPair> temp = queue; + while (!temp.empty() && index < shared_libraries_split.size() - 1) { + DexFileAndClassPair pair(temp.top()); + const DexFile* dex_file = pair.GetDexFile(); + std::string dex_filename(dex_file->GetLocation()); + uint32_t dex_checksum = dex_file->GetLocationChecksum(); + if (dex_filename != shared_libraries_split[index] || + dex_checksum != std::stoul(shared_libraries_split[index + 1])) { + break; + } + temp.pop(); + index += 2; + } + + // Check is successful if it made it through the queue and all the shared libraries. + return temp.empty() && index == shared_libraries_split.size(); + } + return false; } // Check for class-def collisions in dex files. // -// This works by maintaining a heap with one class from each dex file, sorted by the class -// descriptor. Then a dex-file/class pair is continually removed from the heap and compared +// This first walks the class loader chain, getting all the dex files from the class loader. If +// the class loader is null or one of the class loaders in the chain is unsupported, we collect +// dex files from all open non-boot oat files to be safe. +// +// This first checks whether the shared libraries are in the expected order and the oat files +// have the expected checksums. If so, we exit early. Otherwise, we do the collision check. +// +// The collision check works by maintaining a heap with one class from each dex file, sorted by the +// class descriptor. Then a dex-file/class pair is continually removed from the heap and compared // against the following top element. If the descriptor is the same, it is now checked whether // the two elements agree on whether their dex file was from an already-loaded oat-file or the // new oat file. Any disagreement indicates a collision. bool OatFileManager::HasCollisions(const OatFile* oat_file, + jobject class_loader, + jobjectArray dex_elements, std::string* error_msg /*out*/) const { DCHECK(oat_file != nullptr); DCHECK(error_msg != nullptr); - if (!kDuplicateClassesCheck) { - return false; + + std::priority_queue<DexFileAndClassPair> queue; + bool owning_dex_files = false; + + // Try to get dex files from the given class loader. If the class loader is null, or we do + // not support one of the class loaders in the chain, conservatively compare against all + // (non-boot) oat files. + bool class_loader_ok = false; + { + ScopedObjectAccess soa(Thread::Current()); + StackHandleScope<2> hs(Thread::Current()); + Handle<mirror::ClassLoader> h_class_loader = + hs.NewHandle(soa.Decode<mirror::ClassLoader*>(class_loader)); + Handle<mirror::ObjectArray<mirror::Object>> h_dex_elements = + hs.NewHandle(soa.Decode<mirror::ObjectArray<mirror::Object>*>(dex_elements)); + if (h_class_loader.Get() != nullptr && + GetDexFilesFromClassLoader(soa, h_class_loader.Get(), &queue)) { + class_loader_ok = true; + + // In this case, also take into account the dex_elements array, if given. We don't need to + // read it otherwise, as we'll compare against all open oat files anyways. + GetDexFilesFromDexElementsArray(soa, h_dex_elements, &queue); + } else if (h_class_loader.Get() != nullptr) { + VLOG(class_linker) << "Something unsupported with " + << PrettyClass(h_class_loader->GetClass()); + } } // Dex files are registered late - once a class is actually being loaded. We have to compare // against the open oat files. Take the oat_file_manager_lock_ that protects oat_files_ accesses. ReaderMutexLock mu(Thread::Current(), *Locks::oat_file_manager_lock_); - std::priority_queue<DexFileAndClassPair> queue; + if (!class_loader_ok) { + // Add dex files from already loaded oat files, but skip boot. - // Add dex files from already loaded oat files, but skip boot. - std::vector<const OatFile*> boot_oat_files = GetBootOatFiles(); - // The same OatFile can be loaded multiple times at different addresses. In this case, we don't - // need to check both against each other since they would have resolved the same way at compile - // time. - std::unordered_set<std::string> unique_locations; - for (const std::unique_ptr<const OatFile>& loaded_oat_file : oat_files_) { - DCHECK_NE(loaded_oat_file.get(), oat_file); - const std::string& location = loaded_oat_file->GetLocation(); - if (std::find(boot_oat_files.begin(), boot_oat_files.end(), loaded_oat_file.get()) == - boot_oat_files.end() && location != oat_file->GetLocation() && - unique_locations.find(location) == unique_locations.end()) { - unique_locations.insert(location); - AddDexFilesFromOat(loaded_oat_file.get(), /*already_loaded*/true, &queue); + // Clean up the queue. + while (!queue.empty()) { + queue.pop(); + } + + // Anything we load now is something we own and must be released later. + owning_dex_files = true; + + std::vector<const OatFile*> boot_oat_files = GetBootOatFiles(); + // The same OatFile can be loaded multiple times at different addresses. In this case, we don't + // need to check both against each other since they would have resolved the same way at compile + // time. + std::unordered_set<std::string> unique_locations; + for (const std::unique_ptr<const OatFile>& loaded_oat_file : oat_files_) { + DCHECK_NE(loaded_oat_file.get(), oat_file); + const std::string& location = loaded_oat_file->GetLocation(); + if (std::find(boot_oat_files.begin(), boot_oat_files.end(), loaded_oat_file.get()) == + boot_oat_files.end() && location != oat_file->GetLocation() && + unique_locations.find(location) == unique_locations.end()) { + unique_locations.insert(location); + AddDexFilesFromOat(loaded_oat_file.get(), /*already_loaded*/true, &queue); + } } } - if (queue.empty()) { - // No other oat files, return early. + // Exit if shared libraries are ok. Do a full duplicate classes check otherwise. + const std::string + shared_libraries(oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey)); + if (AreSharedLibrariesOk(shared_libraries, queue)) { + FreeDexFilesInHeap(&queue, owning_dex_files); return false; } @@ -278,16 +536,17 @@ bool OatFileManager::HasCollisions(const OatFile* oat_file, compare_pop.GetCachedDescriptor(), compare_pop.GetDexFile()->GetLocation().c_str(), top.GetDexFile()->GetLocation().c_str()); + FreeDexFilesInHeap(&queue, owning_dex_files); return true; } queue.pop(); - AddNext(&top, &queue); + AddNext(&top, &queue, owning_dex_files); } else { // Something else. Done here. break; } } - AddNext(&compare_pop, &queue); + AddNext(&compare_pop, &queue, owning_dex_files); } return false; @@ -327,22 +586,25 @@ std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( const OatFile* source_oat_file = nullptr; - // Update the oat file on disk if we can. This may fail, but that's okay. - // Best effort is all that matters here. - switch (oat_file_assistant.MakeUpToDate(filter_, /*out*/ &error_msg)) { - case OatFileAssistant::kUpdateFailed: - LOG(WARNING) << error_msg; - break; + if (!oat_file_assistant.IsUpToDate()) { + // Update the oat file on disk if we can, based on the --compiler-filter + // option derived from the current runtime options. + // This may fail, but that's okay. Best effort is all that matters here. + switch (oat_file_assistant.MakeUpToDate(/*out*/ &error_msg)) { + case OatFileAssistant::kUpdateFailed: + LOG(WARNING) << error_msg; + break; - case OatFileAssistant::kUpdateNotAttempted: - // Avoid spamming the logs if we decided not to attempt making the oat - // file up to date. - VLOG(oat) << error_msg; - break; + case OatFileAssistant::kUpdateNotAttempted: + // Avoid spamming the logs if we decided not to attempt making the oat + // file up to date. + VLOG(oat) << error_msg; + break; - case OatFileAssistant::kUpdateSucceeded: - // Nothing to do. - break; + case OatFileAssistant::kUpdateSucceeded: + // Nothing to do. + break; + } } // Get the oat file on disk. @@ -350,7 +612,8 @@ std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( if (oat_file != nullptr) { // Take the file only if it has no collisions, or we must take it because of preopting. - bool accept_oat_file = !HasCollisions(oat_file.get(), /*out*/ &error_msg); + bool accept_oat_file = + !HasCollisions(oat_file.get(), class_loader, dex_elements, /*out*/ &error_msg); if (!accept_oat_file) { // Failed the collision check. Print warning. if (Runtime::Current()->IsDexFileFallbackEnabled()) { @@ -364,7 +627,7 @@ std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( // However, if the app was part of /system and preopted, there is no original dex file // available. In that case grudgingly accept the oat file. - if (!DexFile::MaybeDex(dex_location)) { + if (!oat_file_assistant.HasOriginalDexFiles()) { accept_oat_file = true; LOG(WARNING) << "Dex location " << dex_location << " does not seem to include dex file. " << "Allow oat file use. This is potentially dangerous."; @@ -449,7 +712,8 @@ std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( if (Runtime::Current()->IsDexFileFallbackEnabled()) { if (!DexFile::Open(dex_location, dex_location, /*out*/ &error_msg, &dex_files)) { LOG(WARNING) << error_msg; - error_msgs->push_back("Failed to open dex files from " + std::string(dex_location)); + error_msgs->push_back("Failed to open dex files from " + std::string(dex_location) + + " because: " + error_msg); } } else { error_msgs->push_back("Fallback mode disabled, skipping dex files."); @@ -466,28 +730,6 @@ std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( return dex_files; } -bool OatFileManager::RegisterOatFileLocation(const std::string& oat_location) { - WriterMutexLock mu(Thread::Current(), *Locks::oat_file_count_lock_); - auto it = oat_file_count_.find(oat_location); - if (it != oat_file_count_.end()) { - ++it->second; - return false; - } - oat_file_count_.insert(std::pair<std::string, size_t>(oat_location, 1u)); - return true; -} - -void OatFileManager::UnRegisterOatFileLocation(const std::string& oat_location) { - WriterMutexLock mu(Thread::Current(), *Locks::oat_file_count_lock_); - auto it = oat_file_count_.find(oat_location); - if (it != oat_file_count_.end()) { - --it->second; - if (it->second == 0) { - oat_file_count_.erase(it); - } - } -} - void OatFileManager::DumpForSigQuit(std::ostream& os) { ReaderMutexLock mu(Thread::Current(), *Locks::oat_file_manager_lock_); std::vector<const OatFile*> boot_oat_files = GetBootOatFiles(); diff --git a/runtime/oat_file_manager.h b/runtime/oat_file_manager.h index 574d0e2582..45ac4b79e7 100644 --- a/runtime/oat_file_manager.h +++ b/runtime/oat_file_manager.h @@ -25,7 +25,6 @@ #include "base/macros.h" #include "base/mutex.h" -#include "compiler_filter.h" #include "jni.h" namespace art { @@ -60,15 +59,10 @@ class OatFileManager { const OatFile* FindOpenedOatFileFromOatLocation(const std::string& oat_location) const REQUIRES(!Locks::oat_file_manager_lock_); - // Attempt to reserve a location, returns false if it is already reserved or already in used by - // an oat file. - bool RegisterOatFileLocation(const std::string& oat_location) - REQUIRES(!Locks::oat_file_count_lock_); - - // Unreserve oat file location, should only be used for error cases since RegisterOatFile will - // remove the reserved location. - void UnRegisterOatFileLocation(const std::string& oat_location) - REQUIRES(!Locks::oat_file_count_lock_); + // Find the oat file which contains a dex files with the given dex base location, + // returns null if there are none. + const OatFile* FindOpenedOatFileFromDexLocation(const std::string& dex_base_location) const + REQUIRES(!Locks::oat_file_manager_lock_); // Returns true if we have a non pic oat file. bool HaveNonPicOatFile() const { @@ -111,26 +105,25 @@ class OatFileManager { void DumpForSigQuit(std::ostream& os); - static void SetCompilerFilter(CompilerFilter::Filter filter) { - filter_ = filter; - } - private: - // Check for duplicate class definitions of the given oat file against all open oat files. + // Check that the shared libraries in the given oat file match those in the given class loader and + // dex elements. If the class loader is null or we do not support one of the class loaders in the + // chain, compare against all non-boot oat files instead. If the shared libraries are not ok, + // check for duplicate class definitions of the given oat file against the oat files (either from + // the class loader and dex elements if possible or all non-boot oat files otherwise). // Return true if there are any class definition collisions in the oat_file. - bool HasCollisions(const OatFile* oat_file, /*out*/std::string* error_msg) const + bool HasCollisions(const OatFile* oat_file, + jobject class_loader, + jobjectArray dex_elements, + /*out*/ std::string* error_msg) const REQUIRES(!Locks::oat_file_manager_lock_); const OatFile* FindOpenedOatFileFromOatLocationLocked(const std::string& oat_location) const REQUIRES(Locks::oat_file_manager_lock_); std::set<std::unique_ptr<const OatFile>> oat_files_ GUARDED_BY(Locks::oat_file_manager_lock_); - std::unordered_map<std::string, size_t> oat_file_count_ GUARDED_BY(Locks::oat_file_count_lock_); bool have_non_pic_oat_file_; - // The compiler filter used for oat files loaded by the oat file manager. - static CompilerFilter::Filter filter_; - DISALLOW_COPY_AND_ASSIGN(OatFileManager); }; diff --git a/runtime/openjdkjvm/OpenjdkJvm.cc b/runtime/openjdkjvm/OpenjdkJvm.cc index aff9b61f3a..54ec5d32f5 100644 --- a/runtime/openjdkjvm/OpenjdkJvm.cc +++ b/runtime/openjdkjvm/OpenjdkJvm.cc @@ -58,11 +58,6 @@ #include <sys/socket.h> #include <sys/ioctl.h> -#ifdef __ANDROID__ -// This function is provided by android linker. -extern "C" void android_update_LD_LIBRARY_PATH(const char* ld_library_path); -#endif // __ANDROID__ - #undef LOG_TAG #define LOG_TAG "artopenjdk" @@ -74,11 +69,15 @@ using art::FATAL; /* posix open() with extensions; used by e.g. ZipFile */ JNIEXPORT jint JVM_Open(const char* fname, jint flags, jint mode) { /* - * The call is expected to handle JVM_O_DELETE, which causes the file - * to be removed after it is opened. Also, some code seems to - * want the special return value JVM_EEXIST if the file open fails - * due to O_EXCL. + * Some code seems to want the special return value JVM_EEXIST if the + * file open fails due to O_EXCL. */ + // Don't use JVM_O_DELETE, it's problematic with FUSE, see b/28901232. + if (flags & JVM_O_DELETE) { + LOG(FATAL) << "JVM_O_DELETE option is not supported (while opening: '" + << fname << "')"; + } + int fd = TEMP_FAILURE_RETRY(open(fname, flags & ~JVM_O_DELETE, mode)); if (fd < 0) { int err = errno; @@ -89,12 +88,6 @@ JNIEXPORT jint JVM_Open(const char* fname, jint flags, jint mode) { } } - if (flags & JVM_O_DELETE) { - if (unlink(fname) != 0) { - LOG(WARNING) << "Post-open deletion of '" << fname << "' failed: " << strerror(errno); - } - } - return fd; } @@ -116,7 +109,18 @@ JNIEXPORT jint JVM_Write(jint fd, char* buf, jint nbytes) { /* posix lseek() */ JNIEXPORT jlong JVM_Lseek(jint fd, jlong offset, jint whence) { - return TEMP_FAILURE_RETRY(lseek(fd, offset, whence)); +#if !defined(__APPLE__) + // NOTE: Using TEMP_FAILURE_RETRY here is busted for LP32 on glibc - the return + // value will be coerced into an int32_t. + // + // lseek64 isn't specified to return EINTR so it shouldn't be necessary + // anyway. + return lseek64(fd, offset, whence); +#else + // NOTE: This code is compiled for Mac OS but isn't ever run on that + // platform. + return lseek(fd, offset, whence); +#endif } /* @@ -313,22 +317,6 @@ JNIEXPORT __attribute__((noreturn)) void JVM_Exit(jint status) { exit(status); } -static void SetLdLibraryPath(JNIEnv* env, jstring javaLdLibraryPath) { -#ifdef __ANDROID__ - if (javaLdLibraryPath != nullptr) { - ScopedUtfChars ldLibraryPath(env, javaLdLibraryPath); - if (ldLibraryPath.c_str() != nullptr) { - android_update_LD_LIBRARY_PATH(ldLibraryPath.c_str()); - } - } - -#else - LOG(WARNING) << "android_update_LD_LIBRARY_PATH not found; .so dependencies will not work!"; - UNUSED(javaLdLibraryPath, env); -#endif -} - - JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env, jstring javaFilename, jobject javaLoader, @@ -338,17 +326,6 @@ JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env, return NULL; } - int32_t target_sdk_version = art::Runtime::Current()->GetTargetSdkVersion(); - - // Starting with N nativeLoad uses classloader local - // linker namespace instead of global LD_LIBRARY_PATH - // (23 is Marshmallow). This call is here to preserve - // backwards compatibility for the apps targeting sdk - // version <= 23 - if (target_sdk_version == 0) { - SetLdLibraryPath(env, javaLibrarySearchPath); - } - std::string error_msg; { art::JavaVMExt* vm = art::Runtime::Current()->GetJavaVM(); diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc index c8d429158c..eac5b43ff2 100644 --- a/runtime/parsed_options.cc +++ b/runtime/parsed_options.cc @@ -153,7 +153,7 @@ std::unique_ptr<RuntimeParser> ParsedOptions::MakeParser(bool ignore_unrecognize .Define("-Xusejit:_") .WithType<bool>() .WithValueMap({{"false", false}, {"true", true}}) - .IntoKey(M::UseJIT) + .IntoKey(M::UseJitCompilation) .Define("-Xjitinitialsize:_") .WithType<MemoryKiB>() .IntoKey(M::JITCodeCacheInitialCapacity) @@ -172,6 +172,9 @@ std::unique_ptr<RuntimeParser> ParsedOptions::MakeParser(bool ignore_unrecognize .Define("-Xjitprithreadweight:_") .WithType<unsigned int>() .IntoKey(M::JITPriorityThreadWeight) + .Define("-Xjittransitionweight:_") + .WithType<unsigned int>() + .IntoKey(M::JITInvokeTransitionWeight) .Define("-Xjitsaveprofilinginfo") .WithValue(true) .IntoKey(M::JITSaveProfilingInfo) @@ -289,9 +292,6 @@ std::unique_ptr<RuntimeParser> ParsedOptions::MakeParser(bool ignore_unrecognize .IntoKey(M::Experimental) .Define("-Xforce-nb-testing") .IntoKey(M::ForceNativeBridge) - .Define("-XOatFileManagerCompilerFilter:_") - .WithType<std::string>() - .IntoKey(M::OatFileManagerCompilerFilter) .Ignore({ "-ea", "-da", "-enableassertions", "-disableassertions", "--runtime-arg", "-esa", "-dsa", "-enablesystemassertions", "-disablesystemassertions", "-Xrs", "-Xint:_", @@ -471,6 +471,11 @@ bool ParsedOptions::DoParse(const RuntimeOptions& options, LOG(INFO) << "setting boot class path to " << *args.Get(M::BootClassPath); } + if (args.GetOrDefault(M::UseJitCompilation) && args.GetOrDefault(M::Interpret)) { + Usage("-Xusejit:true and -Xint cannot be specified together"); + Exit(0); + } + // Set a default boot class path if we didn't get an explicit one via command line. if (getenv("BOOTCLASSPATH") != nullptr) { args.SetIfMissing(M::BootClassPath, std::string(getenv("BOOTCLASSPATH"))); diff --git a/runtime/prebuilt_tools_test.cc b/runtime/prebuilt_tools_test.cc index eb226d496a..c2b34c859e 100644 --- a/runtime/prebuilt_tools_test.cc +++ b/runtime/prebuilt_tools_test.cc @@ -23,7 +23,7 @@ namespace art { // Run the tests only on host. -#ifndef __ANDROID__ +#ifndef ART_TARGET_ANDROID class PrebuiltToolsTest : public CommonRuntimeTest { }; @@ -61,6 +61,6 @@ TEST_F(PrebuiltToolsTest, CheckTargetTools) { } } -#endif // __ANDROID__ +#endif // ART_TARGET_ANDROID } // namespace art diff --git a/runtime/quick/inline_method_analyser.cc b/runtime/quick/inline_method_analyser.cc index c7ccee2125..1dea562b5e 100644 --- a/runtime/quick/inline_method_analyser.cc +++ b/runtime/quick/inline_method_analyser.cc @@ -434,7 +434,7 @@ static_assert(InlineMethodAnalyser::IGetVariant(Instruction::IGET_SHORT) == bool InlineMethodAnalyser::AnalyseMethodCode(verifier::MethodVerifier* verifier, InlineMethod* result) { DCHECK(verifier != nullptr); - if (!Runtime::Current()->UseJit()) { + if (!Runtime::Current()->UseJitCompilation()) { DCHECK_EQ(verifier->CanLoadClasses(), result != nullptr); } diff --git a/runtime/quick_exception_handler.cc b/runtime/quick_exception_handler.cc index a785ecba3b..e9dd7aa073 100644 --- a/runtime/quick_exception_handler.cc +++ b/runtime/quick_exception_handler.cc @@ -50,7 +50,8 @@ QuickExceptionHandler::QuickExceptionHandler(Thread* self, bool is_deoptimizatio handler_method_(nullptr), handler_dex_pc_(0), clear_exception_(false), - handler_frame_depth_(kInvalidFrameDepth) {} + handler_frame_depth_(kInvalidFrameDepth), + full_fragment_done_(false) {} // Finds catch handler. class CatchBlockStackVisitor FINAL : public StackVisitor { @@ -290,7 +291,8 @@ class DeoptimizeStackVisitor FINAL : public StackVisitor { single_frame_deopt_(single_frame), single_frame_done_(false), single_frame_deopt_method_(nullptr), - single_frame_deopt_quick_method_header_(nullptr) { + single_frame_deopt_quick_method_header_(nullptr), + callee_method_(nullptr) { } ArtMethod* GetSingleFrameDeoptMethod() const { @@ -301,23 +303,34 @@ class DeoptimizeStackVisitor FINAL : public StackVisitor { return single_frame_deopt_quick_method_header_; } + void FinishStackWalk() SHARED_REQUIRES(Locks::mutator_lock_) { + // This is the upcall, or the next full frame in single-frame deopt, or the + // code isn't deoptimizeable. We remember the frame and last pc so that we + // may long jump to them. + exception_handler_->SetHandlerQuickFramePc(GetCurrentQuickFramePc()); + exception_handler_->SetHandlerQuickFrame(GetCurrentQuickFrame()); + exception_handler_->SetHandlerMethodHeader(GetCurrentOatQuickMethodHeader()); + if (!stacked_shadow_frame_pushed_) { + // In case there is no deoptimized shadow frame for this upcall, we still + // need to push a nullptr to the stack since there is always a matching pop after + // the long jump. + GetThread()->PushStackedShadowFrame(nullptr, + StackedShadowFrameType::kDeoptimizationShadowFrame); + stacked_shadow_frame_pushed_ = true; + } + if (GetMethod() == nullptr) { + exception_handler_->SetFullFragmentDone(true); + } else { + CHECK(callee_method_ != nullptr) << art::PrettyMethod(GetMethod(), false); + exception_handler_->SetHandlerQuickArg0(reinterpret_cast<uintptr_t>(callee_method_)); + } + } + bool VisitFrame() OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_) { exception_handler_->SetHandlerFrameDepth(GetFrameDepth()); ArtMethod* method = GetMethod(); if (method == nullptr || single_frame_done_) { - // This is the upcall (or the next full frame in single-frame deopt), we remember the frame - // and last pc so that we may long jump to them. - exception_handler_->SetHandlerQuickFramePc(GetCurrentQuickFramePc()); - exception_handler_->SetHandlerQuickFrame(GetCurrentQuickFrame()); - exception_handler_->SetHandlerMethodHeader(GetCurrentOatQuickMethodHeader()); - if (!stacked_shadow_frame_pushed_) { - // In case there is no deoptimized shadow frame for this upcall, we still - // need to push a nullptr to the stack since there is always a matching pop after - // the long jump. - GetThread()->PushStackedShadowFrame(nullptr, - StackedShadowFrameType::kDeoptimizationShadowFrame); - stacked_shadow_frame_pushed_ = true; - } + FinishStackWalk(); return false; // End stack walk. } else if (method->IsRuntimeMethod()) { // Ignore callee save method. @@ -328,7 +341,14 @@ class DeoptimizeStackVisitor FINAL : public StackVisitor { // the native method. // The top method is a runtime method, the native method comes next. CHECK_EQ(GetFrameDepth(), 1U); + callee_method_ = method; return true; + } else if (!single_frame_deopt_ && + !Runtime::Current()->IsDeoptimizeable(GetCurrentQuickFramePc())) { + // We hit some code that's not deoptimizeable. However, Single-frame deoptimization triggered + // from compiled code is always allowed since HDeoptimize always saves the full environment. + FinishStackWalk(); + return false; // End stack walk. } else { // Check if a shadow frame already exists for debugger's set-local-value purpose. const size_t frame_id = GetFrameId(); @@ -356,20 +376,17 @@ class DeoptimizeStackVisitor FINAL : public StackVisitor { // right before interpreter::EnterInterpreterFromDeoptimize(). stacked_shadow_frame_pushed_ = true; GetThread()->PushStackedShadowFrame( - new_frame, - single_frame_deopt_ - ? StackedShadowFrameType::kSingleFrameDeoptimizationShadowFrame - : StackedShadowFrameType::kDeoptimizationShadowFrame); + new_frame, StackedShadowFrameType::kDeoptimizationShadowFrame); } prev_shadow_frame_ = new_frame; if (single_frame_deopt_ && !IsInInlinedFrame()) { // Single-frame deopt ends at the first non-inlined frame and needs to store that method. - exception_handler_->SetHandlerQuickArg0(reinterpret_cast<uintptr_t>(method)); single_frame_done_ = true; single_frame_deopt_method_ = method; single_frame_deopt_quick_method_header_ = GetCurrentOatQuickMethodHeader(); } + callee_method_ = method; return true; } } @@ -478,10 +495,30 @@ class DeoptimizeStackVisitor FINAL : public StackVisitor { bool single_frame_done_; ArtMethod* single_frame_deopt_method_; const OatQuickMethodHeader* single_frame_deopt_quick_method_header_; + ArtMethod* callee_method_; DISALLOW_COPY_AND_ASSIGN(DeoptimizeStackVisitor); }; +void QuickExceptionHandler::PrepareForLongJumpToInvokeStubOrInterpreterBridge() { + if (full_fragment_done_) { + // Restore deoptimization exception. When returning from the invoke stub, + // ArtMethod::Invoke() will see the special exception to know deoptimization + // is needed. + self_->SetException(Thread::GetDeoptimizationException()); + } else { + // PC needs to be of the quick-to-interpreter bridge. + int32_t offset; + #ifdef __LP64__ + offset = GetThreadOffset<8>(kQuickQuickToInterpreterBridge).Int32Value(); + #else + offset = GetThreadOffset<4>(kQuickQuickToInterpreterBridge).Int32Value(); + #endif + handler_quick_frame_pc_ = *reinterpret_cast<uintptr_t*>( + reinterpret_cast<uint8_t*>(self_) + offset); + } +} + void QuickExceptionHandler::DeoptimizeStack() { DCHECK(is_deoptimization_); if (kDebugExceptionDelivery) { @@ -490,9 +527,7 @@ void QuickExceptionHandler::DeoptimizeStack() { DeoptimizeStackVisitor visitor(self_, context_, this, false); visitor.WalkStack(true); - - // Restore deoptimization exception - self_->SetException(Thread::GetDeoptimizationException()); + PrepareForLongJumpToInvokeStubOrInterpreterBridge(); } void QuickExceptionHandler::DeoptimizeSingleFrame() { @@ -509,7 +544,7 @@ void QuickExceptionHandler::DeoptimizeSingleFrame() { // Compiled code made an explicit deoptimization. ArtMethod* deopt_method = visitor.GetSingleFrameDeoptMethod(); DCHECK(deopt_method != nullptr); - if (Runtime::Current()->UseJit()) { + if (Runtime::Current()->UseJitCompilation()) { Runtime::Current()->GetJit()->GetCodeCache()->InvalidateCompiledCodeFor( deopt_method, visitor.GetSingleFrameDeoptQuickMethodHeader()); } else { @@ -518,20 +553,21 @@ void QuickExceptionHandler::DeoptimizeSingleFrame() { deopt_method, GetQuickToInterpreterBridge()); } - // PC needs to be of the quick-to-interpreter bridge. - int32_t offset; - #ifdef __LP64__ - offset = GetThreadOffset<8>(kQuickQuickToInterpreterBridge).Int32Value(); - #else - offset = GetThreadOffset<4>(kQuickQuickToInterpreterBridge).Int32Value(); - #endif - handler_quick_frame_pc_ = *reinterpret_cast<uintptr_t*>( - reinterpret_cast<uint8_t*>(self_) + offset); + PrepareForLongJumpToInvokeStubOrInterpreterBridge(); } -void QuickExceptionHandler::DeoptimizeSingleFrameArchDependentFixup() { - // Architecture-dependent work. This is to get the LR right for x86 and x86-64. +void QuickExceptionHandler::DeoptimizePartialFragmentFixup(uintptr_t return_pc) { + // At this point, the instrumentation stack has been updated. We need to install + // the real return pc on stack, in case instrumentation stub is stored there, + // so that the interpreter bridge code can return to the right place. + if (return_pc != 0) { + uintptr_t* pc_addr = reinterpret_cast<uintptr_t*>(handler_quick_frame_); + CHECK(pc_addr != nullptr); + pc_addr--; + *reinterpret_cast<uintptr_t*>(pc_addr) = return_pc; + } + // Architecture-dependent work. This is to get the LR right for x86 and x86-64. if (kRuntimeISA == InstructionSet::kX86 || kRuntimeISA == InstructionSet::kX86_64) { // On x86, the return address is on the stack, so just reuse it. Otherwise we would have to // change how longjump works. @@ -581,7 +617,8 @@ class InstrumentationStackVisitor : public StackVisitor { DISALLOW_COPY_AND_ASSIGN(InstrumentationStackVisitor); }; -void QuickExceptionHandler::UpdateInstrumentationStack() { +uintptr_t QuickExceptionHandler::UpdateInstrumentationStack() { + uintptr_t return_pc = 0; if (method_tracing_active_) { InstrumentationStackVisitor visitor(self_, handler_frame_depth_); visitor.WalkStack(true); @@ -589,9 +626,10 @@ void QuickExceptionHandler::UpdateInstrumentationStack() { size_t instrumentation_frames_to_pop = visitor.GetInstrumentationFramesToPop(); instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation(); for (size_t i = 0; i < instrumentation_frames_to_pop; ++i) { - instrumentation->PopMethodForUnwind(self_, is_deoptimization_); + return_pc = instrumentation->PopMethodForUnwind(self_, is_deoptimization_); } } + return return_pc; } void QuickExceptionHandler::DoLongJump(bool smash_caller_saves) { @@ -611,7 +649,7 @@ void QuickExceptionHandler::DoLongJump(bool smash_caller_saves) { // Prints out methods with their type of frame. class DumpFramesWithTypeStackVisitor FINAL : public StackVisitor { public: - DumpFramesWithTypeStackVisitor(Thread* self, bool show_details = false) + explicit DumpFramesWithTypeStackVisitor(Thread* self, bool show_details = false) SHARED_REQUIRES(Locks::mutator_lock_) : StackVisitor(self, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames), show_details_(show_details) {} diff --git a/runtime/quick_exception_handler.h b/runtime/quick_exception_handler.h index eedf83f6df..74b7d0dab7 100644 --- a/runtime/quick_exception_handler.h +++ b/runtime/quick_exception_handler.h @@ -46,15 +46,29 @@ class QuickExceptionHandler { // Find the catch handler for the given exception. void FindCatch(mirror::Throwable* exception) SHARED_REQUIRES(Locks::mutator_lock_); - // Deoptimize the stack to the upcall. For every compiled frame, we create a "copy" - // shadow frame that will be executed with the interpreter. + // Deoptimize the stack to the upcall/some code that's not deoptimizeable. For + // every compiled frame, we create a "copy" shadow frame that will be executed + // with the interpreter. void DeoptimizeStack() SHARED_REQUIRES(Locks::mutator_lock_); + + // Deoptimize a single frame. It's directly triggered from compiled code. It + // has the following properties: + // - It deoptimizes a single frame, which can include multiple inlined frames. + // - It doesn't have return result or pending exception at the deoptimization point. + // - It always deoptimizes, even if IsDeoptimizeable() returns false for the + // code, since HDeoptimize always saves the full environment. So it overrides + // the result of IsDeoptimizeable(). + // - It can be either full-fragment, or partial-fragment deoptimization, depending + // on whether that single frame covers full or partial fragment. void DeoptimizeSingleFrame() SHARED_REQUIRES(Locks::mutator_lock_); - void DeoptimizeSingleFrameArchDependentFixup() SHARED_REQUIRES(Locks::mutator_lock_); + + void DeoptimizePartialFragmentFixup(uintptr_t return_pc) + SHARED_REQUIRES(Locks::mutator_lock_); // Update the instrumentation stack by removing all methods that will be unwound // by the exception being thrown. - void UpdateInstrumentationStack() SHARED_REQUIRES(Locks::mutator_lock_); + // Return the return pc of the last frame that's unwound. + uintptr_t UpdateInstrumentationStack() SHARED_REQUIRES(Locks::mutator_lock_); // Set up environment before delivering an exception to optimized code. void SetCatchEnvironmentForOptimizedHandler(StackVisitor* stack_visitor) @@ -103,8 +117,16 @@ class QuickExceptionHandler { handler_frame_depth_ = frame_depth; } + bool IsFullFragmentDone() const { + return full_fragment_done_; + } + + void SetFullFragmentDone(bool full_fragment_done) { + full_fragment_done_ = full_fragment_done; + } + // Walk the stack frames of the given thread, printing out non-runtime methods with their types - // of frames. Helps to verify that single-frame deopt really only deopted one frame. + // of frames. Helps to verify that partial-fragment deopt really works as expected. static void DumpFramesWithType(Thread* self, bool details = false) SHARED_REQUIRES(Locks::mutator_lock_); @@ -131,6 +153,13 @@ class QuickExceptionHandler { bool clear_exception_; // Frame depth of the catch handler or the upcall. size_t handler_frame_depth_; + // Does the handler successfully walk the full fragment (not stopped + // by some code that's not deoptimizeable)? Even single-frame deoptimization + // can set this to true if the fragment contains only one quick frame. + bool full_fragment_done_; + + void PrepareForLongJumpToInvokeStubOrInterpreterBridge() + SHARED_REQUIRES(Locks::mutator_lock_); DISALLOW_COPY_AND_ASSIGN(QuickExceptionHandler); }; diff --git a/runtime/runtime.cc b/runtime/runtime.cc index 37bb4c1fc1..caf554550e 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -60,7 +60,6 @@ #include "base/unix_file/fd_file.h" #include "class_linker-inl.h" #include "compiler_callbacks.h" -#include "compiler_filter.h" #include "debugger.h" #include "elf_file.h" #include "entrypoints/runtime_asm_entrypoints.h" @@ -545,7 +544,7 @@ bool Runtime::Start() { // If a debug host build, disable ptrace restriction for debugging and test timeout thread dump. // Only 64-bit as prctl() may fail in 32 bit userspace on a 64-bit kernel. -#if defined(__linux__) && !defined(__ANDROID__) && defined(__x86_64__) +#if defined(__linux__) && !defined(ART_TARGET_ANDROID) && defined(__x86_64__) if (kIsDebugBuild) { CHECK_EQ(prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY), 0); } @@ -558,23 +557,34 @@ bool Runtime::Start() { started_ = true; - if (jit_options_->UseJIT()) { + // Create the JIT either if we have to use JIT compilation or save profiling info. + // TODO(calin): We use the JIT class as a proxy for JIT compilation and for + // recoding profiles. Maybe we should consider changing the name to be more clear it's + // not only about compiling. b/28295073. + if (jit_options_->UseJitCompilation() || jit_options_->GetSaveProfilingInfo()) { std::string error_msg; if (!IsZygote()) { // If we are the zygote then we need to wait until after forking to create the code cache // due to SELinux restrictions on r/w/x memory regions. CreateJit(); - } else if (!jit::Jit::LoadCompilerLibrary(&error_msg)) { - // Try to load compiler pre zygote to reduce PSS. b/27744947 - LOG(WARNING) << "Failed to load JIT compiler with error " << error_msg; + } else if (jit_options_->UseJitCompilation()) { + if (!jit::Jit::LoadCompilerLibrary(&error_msg)) { + // Try to load compiler pre zygote to reduce PSS. b/27744947 + LOG(WARNING) << "Failed to load JIT compiler with error " << error_msg; + } } } if (!IsImageDex2OatEnabled() || !GetHeap()->HasBootImageSpace()) { ScopedObjectAccess soa(self); - StackHandleScope<1> hs(soa.Self()); - auto klass(hs.NewHandle<mirror::Class>(mirror::Class::GetJavaLangClass())); - class_linker_->EnsureInitialized(soa.Self(), klass, true, true); + StackHandleScope<2> hs(soa.Self()); + + auto class_class(hs.NewHandle<mirror::Class>(mirror::Class::GetJavaLangClass())); + auto field_class(hs.NewHandle<mirror::Class>(mirror::Field::StaticClass())); + + class_linker_->EnsureInitialized(soa.Self(), class_class, true, true); + // Field class is needed for register_java_net_InetAddress in libcore, b/28153851. + class_linker_->EnsureInitialized(soa.Self(), field_class, true, true); } // InitNativeMethods needs to be after started_ so that the classes @@ -713,7 +723,11 @@ void Runtime::InitNonZygoteOrPostFork( // before fork aren't attributed to an app. heap_->ResetGcPerformanceInfo(); - if (!is_system_server && !safe_mode_ && jit_options_->UseJIT() && jit_.get() == nullptr) { + + if (!is_system_server && + !safe_mode_ && + (jit_options_->UseJitCompilation() || jit_options_->GetSaveProfilingInfo()) && + jit_.get() == nullptr) { // Note that when running ART standalone (not zygote, nor zygote fork), // the jit may have already been created. CreateJit(); @@ -842,7 +856,7 @@ static bool OpenDexFilesFromImage(const std::string& image_location, if (index == 0) { // First file. See if this is a multi-image environment, and if so, enqueue the other images. const OatHeader& boot_oat_header = oat_file->GetOatHeader(); - const char* boot_cp = boot_oat_header.GetStoreValueByKey(OatHeader::kBootClassPath); + const char* boot_cp = boot_oat_header.GetStoreValueByKey(OatHeader::kBootClassPathKey); if (boot_cp != nullptr) { gc::space::ImageSpace::CreateMultiImageLocations(image_locations[0], boot_cp, @@ -957,16 +971,6 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { experimental_flags_ = runtime_options.GetOrDefault(Opt::Experimental); is_low_memory_mode_ = runtime_options.Exists(Opt::LowMemoryMode); - { - CompilerFilter::Filter filter; - std::string filter_str = runtime_options.GetOrDefault(Opt::OatFileManagerCompilerFilter); - if (!CompilerFilter::ParseCompilerFilter(filter_str.c_str(), &filter)) { - LOG(ERROR) << "Cannot parse compiler filter " << filter_str; - return false; - } - OatFileManager::SetCompilerFilter(filter); - } - XGcOption xgc_option = runtime_options.GetOrDefault(Opt::GcOption); heap_ = new gc::Heap(runtime_options.GetOrDefault(Opt::MemoryInitialSize), runtime_options.GetOrDefault(Opt::HeapGrowthLimit), @@ -1016,7 +1020,8 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { // this case. // If runtime_options doesn't have UseJIT set to true then CreateFromRuntimeArguments returns // null and we don't create the jit. - jit_options_->SetUseJIT(false); + jit_options_->SetUseJitCompilation(false); + jit_options_->SetSaveProfilingInfo(false); } // Allocate a global table of boxed lambda objects <-> closures. @@ -1613,18 +1618,19 @@ void Runtime::VisitImageRoots(RootVisitor* visitor) { } } -static ImtConflictTable::Entry empty_entry = { nullptr, nullptr }; - ArtMethod* Runtime::CreateImtConflictMethod(LinearAlloc* linear_alloc) { - auto* method = Runtime::Current()->GetClassLinker()->CreateRuntimeMethod(linear_alloc); + ClassLinker* const class_linker = GetClassLinker(); + ArtMethod* method = class_linker->CreateRuntimeMethod(linear_alloc); // When compiling, the code pointer will get set later when the image is loaded. + const size_t pointer_size = GetInstructionSetPointerSize(instruction_set_); if (IsAotCompiler()) { - size_t pointer_size = GetInstructionSetPointerSize(instruction_set_); method->SetEntryPointFromQuickCompiledCodePtrSize(nullptr, pointer_size); } else { method->SetEntryPointFromQuickCompiledCode(GetQuickImtConflictStub()); - method->SetImtConflictTable(reinterpret_cast<ImtConflictTable*>(&empty_entry)); } + // Create empty conflict table. + method->SetImtConflictTable(class_linker->CreateImtConflictTable(/*count*/0u, linear_alloc), + pointer_size); return method; } @@ -1632,9 +1638,6 @@ void Runtime::SetImtConflictMethod(ArtMethod* method) { CHECK(method != nullptr); CHECK(method->IsRuntimeMethod()); imt_conflict_method_ = method; - if (!IsAotCompiler()) { - method->SetImtConflictTable(reinterpret_cast<ImtConflictTable*>(&empty_entry)); - } } ArtMethod* Runtime::CreateResolutionMethod() { @@ -1915,22 +1918,12 @@ void Runtime::AddCurrentRuntimeFeaturesAsDex2OatArguments(std::vector<std::strin void Runtime::CreateJit() { CHECK(!IsAotCompiler()); - if (GetInstrumentation()->IsForcedInterpretOnly()) { - // Don't create JIT if forced interpret only. - return; + if (kIsDebugBuild && GetInstrumentation()->IsForcedInterpretOnly()) { + DCHECK(!jit_options_->UseJitCompilation()); } std::string error_msg; jit_.reset(jit::Jit::Create(jit_options_.get(), &error_msg)); - if (jit_.get() != nullptr) { - jit_->CreateInstrumentationCache(jit_options_->GetCompileThreshold(), - jit_options_->GetWarmupThreshold(), - jit_options_->GetOsrThreshold(), - jit_options_->GetPriorityThreadWeight()); - jit_->CreateThreadPool(); - - // Notify native debugger about the classes already loaded before the creation of the jit. - jit_->DumpTypeInfoForLoadedTypes(GetClassLinker()); - } else { + if (jit_.get() == nullptr) { LOG(WARNING) << "Failed to create JIT " << error_msg; } } @@ -1953,8 +1946,20 @@ void Runtime::SetImtUnimplementedMethod(ArtMethod* method) { CHECK(method != nullptr); CHECK(method->IsRuntimeMethod()); imt_unimplemented_method_ = method; - if (!IsAotCompiler()) { - method->SetImtConflictTable(reinterpret_cast<ImtConflictTable*>(&empty_entry)); +} + +void Runtime::FixupConflictTables() { + // We can only do this after the class linker is created. + const size_t pointer_size = GetClassLinker()->GetImagePointerSize(); + if (imt_unimplemented_method_->GetImtConflictTable(pointer_size) == nullptr) { + imt_unimplemented_method_->SetImtConflictTable( + ClassLinker::CreateImtConflictTable(/*count*/0u, GetLinearAlloc(), pointer_size), + pointer_size); + } + if (imt_conflict_method_->GetImtConflictTable(pointer_size) == nullptr) { + imt_conflict_method_->SetImtConflictTable( + ClassLinker::CreateImtConflictTable(/*count*/0u, GetLinearAlloc(), pointer_size), + pointer_size); } } @@ -1967,6 +1972,11 @@ bool Runtime::IsVerificationSoftFail() const { return verify_ == verifier::VerifyMode::kSoftFail; } +bool Runtime::IsDeoptimizeable(uintptr_t code) const + SHARED_REQUIRES(Locks::mutator_lock_) { + return !heap_->IsInBootImageOatFile(reinterpret_cast<void *>(code)); +} + LinearAlloc* Runtime::CreateLinearAlloc() { // For 64 bit compilers, it needs to be in low 4GB in the case where we are cross compiling for a // 32 bit target. In this case, we have 32 bit pointers in the dex cache arrays which can't hold @@ -1990,4 +2000,18 @@ void Runtime::UpdateProcessState(ProcessState process_state) { GetHeap()->UpdateProcessState(old_process_state, process_state); } +void Runtime::RegisterSensitiveThread() const { + Thread::SetJitSensitiveThread(); +} + +// Returns true if JIT compilations are enabled. GetJit() will be not null in this case. +bool Runtime::UseJitCompilation() const { + return (jit_ != nullptr) && jit_->UseJitCompilation(); +} + +// Returns true if profile saving is enabled. GetJit() will be not null in this case. +bool Runtime::SaveProfileInfo() const { + return (jit_ != nullptr) && jit_->SaveProfilingInfo(); +} + } // namespace art diff --git a/runtime/runtime.h b/runtime/runtime.h index ae25dd1c65..b7f377ddf9 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -127,7 +127,7 @@ class Runtime { // IsAotCompiler for compilers that don't have a running runtime. Only dex2oat currently. bool IsAotCompiler() const { - return !UseJit() && IsCompiler(); + return !UseJitCompilation() && IsCompiler(); } // IsCompiler is any runtime which has a running compiler, either dex2oat or JIT. @@ -383,6 +383,7 @@ class Runtime { return imt_conflict_method_ != nullptr; } + void FixupConflictTables(); void SetImtConflictMethod(ArtMethod* method) SHARED_REQUIRES(Locks::mutator_lock_); void SetImtUnimplementedMethod(ArtMethod* method) SHARED_REQUIRES(Locks::mutator_lock_); @@ -451,9 +452,11 @@ class Runtime { jit::Jit* GetJit() { return jit_.get(); } - bool UseJit() const { - return jit_.get() != nullptr; - } + + // Returns true if JIT compilations are enabled. GetJit() will be not null in this case. + bool UseJitCompilation() const; + // Returns true if profile saving is enabled. GetJit() will be not null in this case. + bool SaveProfileInfo() const; void PreZygoteFork(); bool InitZygote(); @@ -635,6 +638,8 @@ class Runtime { return process_state_ == kProcessStateJankPerceptible; } + void RegisterSensitiveThread() const; + void SetZygoteNoThreadSection(bool val) { zygote_no_threads_ = val; } @@ -643,6 +648,10 @@ class Runtime { return zygote_no_threads_; } + // Returns if the code can be deoptimized. Code may be compiled with some + // optimization that makes it impossible to deoptimize. + bool IsDeoptimizeable(uintptr_t code) const SHARED_REQUIRES(Locks::mutator_lock_); + private: static void InitPlatformSignalHandlers(); diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def index 6433c3352f..635ff51697 100644 --- a/runtime/runtime_options.def +++ b/runtime/runtime_options.def @@ -66,12 +66,13 @@ RUNTIME_OPTIONS_KEY (Unit, IgnoreMaxFootprint) RUNTIME_OPTIONS_KEY (Unit, LowMemoryMode) RUNTIME_OPTIONS_KEY (bool, UseTLAB, (kUseTlab || kUseReadBarrier)) RUNTIME_OPTIONS_KEY (bool, EnableHSpaceCompactForOOM, true) -RUNTIME_OPTIONS_KEY (bool, UseJIT, false) +RUNTIME_OPTIONS_KEY (bool, UseJitCompilation, false) RUNTIME_OPTIONS_KEY (bool, DumpNativeStackOnSigQuit, true) RUNTIME_OPTIONS_KEY (unsigned int, JITCompileThreshold, jit::Jit::kDefaultCompileThreshold) RUNTIME_OPTIONS_KEY (unsigned int, JITWarmupThreshold) RUNTIME_OPTIONS_KEY (unsigned int, JITOsrThreshold) RUNTIME_OPTIONS_KEY (unsigned int, JITPriorityThreadWeight) +RUNTIME_OPTIONS_KEY (unsigned int, JITInvokeTransitionWeight) RUNTIME_OPTIONS_KEY (MemoryKiB, JITCodeCacheInitialCapacity, jit::JitCodeCache::kInitialCapacity) RUNTIME_OPTIONS_KEY (MemoryKiB, JITCodeCacheMaxCapacity, jit::JitCodeCache::kMaxCapacity) RUNTIME_OPTIONS_KEY (bool, JITSaveProfilingInfo, false) @@ -128,11 +129,12 @@ RUNTIME_OPTIONS_KEY (CompilerCallbacks*, CompilerCallbacksPtr) // TDOO: make u RUNTIME_OPTIONS_KEY (bool (*)(), HookIsSensitiveThread) RUNTIME_OPTIONS_KEY (int32_t (*)(FILE* stream, const char* format, va_list ap), \ HookVfprintf, vfprintf) +// Use _exit instead of exit so that we won't get DCHECK failures in global data +// destructors. b/28106055. RUNTIME_OPTIONS_KEY (void (*)(int32_t status), \ - HookExit, exit) + HookExit, _exit) // We don't call abort(3) by default; see // Runtime::Abort. RUNTIME_OPTIONS_KEY (void (*)(), HookAbort, nullptr) -RUNTIME_OPTIONS_KEY (std::string, OatFileManagerCompilerFilter, "speed") #undef RUNTIME_OPTIONS_KEY diff --git a/runtime/runtime_options.h b/runtime/runtime_options.h index 4610f6f4a1..ab69d4f1cb 100644 --- a/runtime/runtime_options.h +++ b/runtime/runtime_options.h @@ -73,7 +73,7 @@ struct TestProfilerOptions; using Key = RuntimeArgumentMapKey<TValue>; // List of key declarations, shorthand for 'static const Key<T> Name' -#define RUNTIME_OPTIONS_KEY(Type, Name, ...) static const Key<Type> Name; +#define RUNTIME_OPTIONS_KEY(Type, Name, ...) static const Key<Type> (Name); #include "runtime_options.def" }; diff --git a/runtime/stack.cc b/runtime/stack.cc index 56ef5aaa90..a5ca527aa2 100644 --- a/runtime/stack.cc +++ b/runtime/stack.cc @@ -637,8 +637,8 @@ static void AssertPcIsWithinQuickCode(ArtMethod* method, uintptr_t pc) // If we are the JIT then we may have just compiled the method after the // IsQuickToInterpreterBridge check. - jit::Jit* const jit = Runtime::Current()->GetJit(); - if (jit != nullptr && jit->GetCodeCache()->ContainsPc(code)) { + Runtime* runtime = Runtime::Current(); + if (runtime->UseJitCompilation() && runtime->GetJit()->GetCodeCache()->ContainsPc(code)) { return; } @@ -678,8 +678,10 @@ void StackVisitor::SanityCheckFrame() const { if (space->IsImageSpace()) { auto* image_space = space->AsImageSpace(); const auto& header = image_space->GetImageHeader(); - const auto* methods = &header.GetMethodsSection(); - if (methods->Contains(reinterpret_cast<const uint8_t*>(method) - image_space->Begin())) { + const ImageSection& methods = header.GetMethodsSection(); + const ImageSection& runtime_methods = header.GetRuntimeMethodsSection(); + const size_t offset = reinterpret_cast<const uint8_t*>(method) - image_space->Begin(); + if (methods.Contains(offset) || runtime_methods.Contains(offset)) { in_image = true; break; } @@ -948,7 +950,7 @@ int StackVisitor::GetVRegOffsetFromQuickCode(const DexFile::CodeItem* code_item, } } -void LockCountData::AddMonitorInternal(Thread* self, mirror::Object* obj) { +void LockCountData::AddMonitor(Thread* self, mirror::Object* obj) { if (obj == nullptr) { return; } @@ -965,7 +967,7 @@ void LockCountData::AddMonitorInternal(Thread* self, mirror::Object* obj) { monitors_->push_back(obj); } -void LockCountData::RemoveMonitorInternal(Thread* self, const mirror::Object* obj) { +void LockCountData::RemoveMonitorOrThrow(Thread* self, const mirror::Object* obj) { if (obj == nullptr) { return; } @@ -998,7 +1000,7 @@ void MonitorExitHelper(Thread* self, mirror::Object* obj) NO_THREAD_SAFETY_ANALY obj->MonitorExit(self); } -bool LockCountData::CheckAllMonitorsReleasedInternal(Thread* self) { +bool LockCountData::CheckAllMonitorsReleasedOrThrow(Thread* self) { DCHECK(self != nullptr); if (monitors_ != nullptr) { if (!monitors_->empty()) { diff --git a/runtime/stack.h b/runtime/stack.h index 7301184a9e..e77ab4647e 100644 --- a/runtime/stack.h +++ b/runtime/stack.h @@ -80,39 +80,18 @@ class LockCountData { public: // Add the given object to the list of monitors, that is, objects that have been locked. This // will not throw (but be skipped if there is an exception pending on entry). - template <bool kLockCounting> - void AddMonitor(Thread* self, mirror::Object* obj) SHARED_REQUIRES(Locks::mutator_lock_) { - DCHECK(self != nullptr); - if (!kLockCounting) { - return; - } - AddMonitorInternal(self, obj); - } + void AddMonitor(Thread* self, mirror::Object* obj) SHARED_REQUIRES(Locks::mutator_lock_); // Try to remove the given object from the monitor list, indicating an unlock operation. // This will throw an IllegalMonitorStateException (clearing any already pending exception), in // case that there wasn't a lock recorded for the object. - template <bool kLockCounting> void RemoveMonitorOrThrow(Thread* self, - const mirror::Object* obj) SHARED_REQUIRES(Locks::mutator_lock_) { - DCHECK(self != nullptr); - if (!kLockCounting) { - return; - } - RemoveMonitorInternal(self, obj); - } + const mirror::Object* obj) SHARED_REQUIRES(Locks::mutator_lock_); // Check whether all acquired monitors have been released. This will potentially throw an // IllegalMonitorStateException, clearing any already pending exception. Returns true if the // check shows that everything is OK wrt/ lock counting, false otherwise. - template <bool kLockCounting> - bool CheckAllMonitorsReleasedOrThrow(Thread* self) SHARED_REQUIRES(Locks::mutator_lock_) { - DCHECK(self != nullptr); - if (!kLockCounting) { - return true; - } - return CheckAllMonitorsReleasedInternal(self); - } + bool CheckAllMonitorsReleasedOrThrow(Thread* self) SHARED_REQUIRES(Locks::mutator_lock_); template <typename T, typename... Args> void VisitMonitors(T visitor, Args&&... args) SHARED_REQUIRES(Locks::mutator_lock_) { @@ -125,12 +104,6 @@ class LockCountData { } private: - // Internal implementations. - void AddMonitorInternal(Thread* self, mirror::Object* obj) SHARED_REQUIRES(Locks::mutator_lock_); - void RemoveMonitorInternal(Thread* self, const mirror::Object* obj) - SHARED_REQUIRES(Locks::mutator_lock_); - bool CheckAllMonitorsReleasedInternal(Thread* self) SHARED_REQUIRES(Locks::mutator_lock_); - // Stores references to the locked-on objects. As noted, this should be visited during thread // marking. std::unique_ptr<std::vector<mirror::Object*>> monitors_; diff --git a/runtime/thread-inl.h b/runtime/thread-inl.h index f5d20bd608..d98f82a541 100644 --- a/runtime/thread-inl.h +++ b/runtime/thread-inl.h @@ -19,7 +19,7 @@ #include "thread.h" -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID #include <bionic_tls.h> // Access to our own TLS slot. #endif @@ -45,7 +45,7 @@ inline Thread* Thread::Current() { if (!is_started_) { return nullptr; } else { -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID void* thread = __get_tls()[TLS_SLOT_ART_THREAD_SELF]; #else void* thread = pthread_getspecific(Thread::pthread_key_self_); diff --git a/runtime/thread.cc b/runtime/thread.cc index e015833c9c..f1f4a122b4 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -55,6 +55,7 @@ #include "mirror/object_array-inl.h" #include "mirror/stack_trace_element.h" #include "monitor.h" +#include "nth_caller_visitor.h" #include "oat_quick_method_header.h" #include "object_lock.h" #include "quick_exception_handler.h" @@ -84,11 +85,14 @@ namespace art { +extern "C" NO_RETURN void artDeoptimize(Thread* self); + bool Thread::is_started_ = false; pthread_key_t Thread::pthread_key_self_; ConditionVariable* Thread::resume_cond_ = nullptr; const size_t Thread::kStackOverflowImplicitCheckSize = GetStackOverflowReservedBytes(kRuntimeISA); bool (*Thread::is_sensitive_thread_hook_)() = nullptr; +Thread* Thread::jit_sensitive_thread_ = nullptr; static constexpr bool kVerifyImageObjectsMarked = kIsDebugBuild; @@ -269,7 +273,6 @@ ShadowFrame* Thread::PopStackedShadowFrame(StackedShadowFrameType type, bool mus StackedShadowFrameRecord* record = tlsPtr_.stacked_shadow_frame_record; if (must_be_present) { DCHECK(record != nullptr); - DCHECK_EQ(record->GetType(), type); } else { if (record == nullptr || record->GetType() != type) { return nullptr; @@ -512,14 +515,20 @@ static size_t FixStackSize(size_t stack_size) { return stack_size; } +// Return the nearest page-aligned address below the current stack top. +NO_INLINE +static uint8_t* FindStackTop() { + return reinterpret_cast<uint8_t*>( + AlignDown(__builtin_frame_address(0), kPageSize)); +} + // Install a protected region in the stack. This is used to trigger a SIGSEGV if a stack // overflow is detected. It is located right below the stack_begin_. ATTRIBUTE_NO_SANITIZE_ADDRESS void Thread::InstallImplicitProtection() { uint8_t* pregion = tlsPtr_.stack_begin - kStackOverflowProtectedSize; - uint8_t* stack_himem = tlsPtr_.stack_end; - uint8_t* stack_top = reinterpret_cast<uint8_t*>(reinterpret_cast<uintptr_t>(&stack_himem) & - ~(kPageSize - 1)); // Page containing current top of stack. + // Page containing current top of stack. + uint8_t* stack_top = FindStackTop(); // Try to directly protect the stack. VLOG(threads) << "installing stack protected region at " << std::hex << @@ -704,7 +713,7 @@ bool Thread::Init(ThreadList* thread_list, JavaVMExt* java_vm, JNIEnvExt* jni_en InitTid(); interpreter::InitInterpreterTls(this); -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID __get_tls()[TLS_SLOT_ART_THREAD_SELF] = this; #else CHECK_PTHREAD_CALL(pthread_setspecific, (Thread::pthread_key_self_, this), "attach self"); @@ -739,7 +748,7 @@ Thread* Thread::Attach(const char* thread_name, bool as_daemon, jobject thread_g { MutexLock mu(nullptr, *Locks::runtime_shutdown_lock_); if (runtime->IsShuttingDownLocked()) { - LOG(ERROR) << "Thread attaching while runtime is shutting down: " << thread_name; + LOG(WARNING) << "Thread attaching while runtime is shutting down: " << thread_name; return nullptr; } else { Runtime::Current()->StartThreadBirth(); @@ -931,8 +940,7 @@ bool Thread::InitStackHwm() { } // Sanity check. - int stack_variable; - CHECK_GT(&stack_variable, reinterpret_cast<void*>(tlsPtr_.stack_end)); + CHECK_GT(FindStackTop(), reinterpret_cast<void*>(tlsPtr_.stack_end)); return true; } @@ -1542,7 +1550,7 @@ void Thread::ThreadExitCallback(void* arg) { LOG(WARNING) << "Native thread exiting without having called DetachCurrentThread (maybe it's " "going to use a pthread_key_create destructor?): " << *self; CHECK(is_started_); -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID __get_tls()[TLS_SLOT_ART_THREAD_SELF] = self; #else CHECK_PTHREAD_CALL(pthread_setspecific, (Thread::pthread_key_self_, self), "reattach self"); @@ -2392,7 +2400,7 @@ void Thread::DumpFromGdb() const { std::string str(ss.str()); // log to stderr for debugging command line processes std::cerr << str; -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID // log to logcat for debugging frameworks processes LOG(INFO) << str; #endif @@ -2405,8 +2413,8 @@ template void Thread::DumpThreadOffset<8>(std::ostream& os, uint32_t offset); template<size_t ptr_size> void Thread::DumpThreadOffset(std::ostream& os, uint32_t offset) { #define DO_THREAD_OFFSET(x, y) \ - if (offset == x.Uint32Value()) { \ - os << y; \ + if (offset == (x).Uint32Value()) { \ + os << (y); \ return; \ } DO_THREAD_OFFSET(ThreadFlagsOffset<ptr_size>(), "state_and_flags") @@ -2577,38 +2585,42 @@ void Thread::QuickDeliverException() { // Get exception from thread. mirror::Throwable* exception = GetException(); CHECK(exception != nullptr); - bool is_deoptimization = (exception == GetDeoptimizationException()); - if (!is_deoptimization) { - // This is a real exception: let the instrumentation know about it. - instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation(); - if (instrumentation->HasExceptionCaughtListeners() && - IsExceptionThrownByCurrentMethod(exception)) { - // Instrumentation may cause GC so keep the exception object safe. - StackHandleScope<1> hs(this); - HandleWrapper<mirror::Throwable> h_exception(hs.NewHandleWrapper(&exception)); - instrumentation->ExceptionCaughtEvent(this, exception); - } - // Does instrumentation need to deoptimize the stack? - // Note: we do this *after* reporting the exception to instrumentation in case it - // now requires deoptimization. It may happen if a debugger is attached and requests - // new events (single-step, breakpoint, ...) when the exception is reported. - is_deoptimization = Dbg::IsForcedInterpreterNeededForException(this); - if (is_deoptimization) { + if (exception == GetDeoptimizationException()) { + artDeoptimize(this); + UNREACHABLE(); + } + + // This is a real exception: let the instrumentation know about it. + instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation(); + if (instrumentation->HasExceptionCaughtListeners() && + IsExceptionThrownByCurrentMethod(exception)) { + // Instrumentation may cause GC so keep the exception object safe. + StackHandleScope<1> hs(this); + HandleWrapper<mirror::Throwable> h_exception(hs.NewHandleWrapper(&exception)); + instrumentation->ExceptionCaughtEvent(this, exception); + } + // Does instrumentation need to deoptimize the stack? + // Note: we do this *after* reporting the exception to instrumentation in case it + // now requires deoptimization. It may happen if a debugger is attached and requests + // new events (single-step, breakpoint, ...) when the exception is reported. + if (Dbg::IsForcedInterpreterNeededForException(this)) { + NthCallerVisitor visitor(this, 0, false); + visitor.WalkStack(); + if (Runtime::Current()->IsDeoptimizeable(visitor.caller_pc)) { // Save the exception into the deoptimization context so it can be restored // before entering the interpreter. PushDeoptimizationContext( JValue(), /*is_reference */ false, /* from_code */ false, exception); + artDeoptimize(this); + UNREACHABLE(); } } + // Don't leave exception visible while we try to find the handler, which may cause class // resolution. ClearException(); - QuickExceptionHandler exception_handler(this, is_deoptimization); - if (is_deoptimization) { - exception_handler.DeoptimizeStack(); - } else { - exception_handler.FindCatch(exception); - } + QuickExceptionHandler exception_handler(this, false); + exception_handler.FindCatch(exception); exception_handler.UpdateInstrumentationStack(); exception_handler.DoLongJump(); } @@ -2763,7 +2775,7 @@ class ReferenceMapVisitor : public StackVisitor { VisitDeclaringClass(m); // Process register map (which native and runtime methods don't have) - if (!m->IsNative() && !m->IsRuntimeMethod() && !m->IsProxyMethod()) { + if (!m->IsNative() && !m->IsRuntimeMethod() && (!m->IsProxyMethod() || m->IsConstructor())) { const OatQuickMethodHeader* method_header = GetCurrentOatQuickMethodHeader(); DCHECK(method_header->IsOptimized()); auto* vreg_base = reinterpret_cast<StackReference<mirror::Object>*>( @@ -3010,7 +3022,6 @@ size_t Thread::NumberOfHeldMutexes() const { return count; } - void Thread::DeoptimizeWithDeoptimizationException(JValue* result) { DCHECK_EQ(GetException(), Thread::GetDeoptimizationException()); ClearException(); @@ -3019,7 +3030,6 @@ void Thread::DeoptimizeWithDeoptimizationException(JValue* result) { mirror::Throwable* pending_exception = nullptr; bool from_code = false; PopDeoptimizationContext(result, &pending_exception, &from_code); - CHECK(!from_code) << "Deoptimizing from code should be done with single frame deoptimization"; SetTopOfStack(nullptr); SetTopOfShadowStack(shadow_frame); @@ -3031,4 +3041,11 @@ void Thread::DeoptimizeWithDeoptimizationException(JValue* result) { interpreter::EnterInterpreterFromDeoptimize(this, shadow_frame, from_code, result); } +void Thread::SetException(mirror::Throwable* new_exception) { + CHECK(new_exception != nullptr); + // TODO: DCHECK(!IsExceptionPending()); + tlsPtr_.exception = new_exception; + // LOG(ERROR) << new_exception->Dump(); +} + } // namespace art diff --git a/runtime/thread.h b/runtime/thread.h index 2218b5a9d8..3c367ee5b6 100644 --- a/runtime/thread.h +++ b/runtime/thread.h @@ -110,7 +110,6 @@ enum ThreadFlag { enum class StackedShadowFrameType { kShadowFrameUnderConstruction, kDeoptimizationShadowFrame, - kSingleFrameDeoptimizationShadowFrame }; // This should match RosAlloc::kNumThreadLocalSizeBrackets. @@ -312,6 +311,7 @@ class Thread { */ static int GetNativePriority(); + // Guaranteed to be non-zero. uint32_t GetThreadId() const { return tls32_.thin_lock_thread_id; } @@ -362,12 +362,7 @@ class Thread { void AssertNoPendingException() const; void AssertNoPendingExceptionForNewException(const char* msg) const; - void SetException(mirror::Throwable* new_exception) - SHARED_REQUIRES(Locks::mutator_lock_) { - CHECK(new_exception != nullptr); - // TODO: DCHECK(!IsExceptionPending()); - tlsPtr_.exception = new_exception; - } + void SetException(mirror::Throwable* new_exception) SHARED_REQUIRES(Locks::mutator_lock_); void ClearException() SHARED_REQUIRES(Locks::mutator_lock_) { tlsPtr_.exception = nullptr; @@ -1097,6 +1092,12 @@ class Thread { return debug_disallow_read_barrier_; } + // Returns true if the current thread is the jit sensitive thread. + bool IsJitSensitiveThread() const { + return this == jit_sensitive_thread_; + } + + // Returns true if StrictMode events are traced for the current thread. static bool IsSensitiveThread() { if (is_sensitive_thread_hook_ != nullptr) { return (*is_sensitive_thread_hook_)(); @@ -1117,7 +1118,7 @@ class Thread { SHARED_REQUIRES(Locks::mutator_lock_); // Avoid use, callers should use SetState. Used only by SignalCatcher::HandleSigQuit, ~Thread and - // Dbg::Disconnected. + // Dbg::ManageDeoptimization. ThreadState SetStateUnsafe(ThreadState new_state) { ThreadState old_state = GetState(); if (old_state == kRunnable && new_state != kRunnable) { @@ -1179,6 +1180,16 @@ class Thread { ALWAYS_INLINE void PassActiveSuspendBarriers() REQUIRES(!Locks::thread_suspend_count_lock_, !Roles::uninterruptible_); + // Registers the current thread as the jit sensitive thread. Should be called just once. + static void SetJitSensitiveThread() { + if (jit_sensitive_thread_ == nullptr) { + jit_sensitive_thread_ = Thread::Current(); + } else { + LOG(WARNING) << "Attempt to set the sensitive thread twice. Tid:" + << Thread::Current()->GetTid(); + } + } + static void SetSensitiveThreadHook(bool (*is_sensitive_thread_hook)()) { is_sensitive_thread_hook_ = is_sensitive_thread_hook; } @@ -1228,6 +1239,8 @@ class Thread { // Hook passed by framework which returns true // when StrictMode events are traced for the current thread. static bool (*is_sensitive_thread_hook_)(); + // Stores the jit sensitive thread (which for now is the UI thread). + static Thread* jit_sensitive_thread_; /***********************************************************************************************/ // Thread local storage. Fields are grouped by size to enable 32 <-> 64 searching to account for diff --git a/runtime/thread_linux.cc b/runtime/thread_linux.cc index 9563b994f8..b922d94617 100644 --- a/runtime/thread_linux.cc +++ b/runtime/thread_linux.cc @@ -44,7 +44,7 @@ static constexpr int kHostAltSigStackSize = void Thread::SetUpAlternateSignalStack() { // Create and set an alternate signal stack. -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID LOG(FATAL) << "Invalid use of alternate signal stack on Android"; #endif stack_t ss; diff --git a/runtime/thread_list.cc b/runtime/thread_list.cc index a9ce056fd9..da214796b9 100644 --- a/runtime/thread_list.cc +++ b/runtime/thread_list.cc @@ -923,12 +923,9 @@ Thread* ThreadList::SuspendThreadByThreadId(uint32_t thread_id, } } -Thread* ThreadList::FindThreadByThreadId(uint32_t thin_lock_id) { - Thread* self = Thread::Current(); - MutexLock mu(self, *Locks::thread_list_lock_); +Thread* ThreadList::FindThreadByThreadId(uint32_t thread_id) { for (const auto& thread : list_) { - if (thread->GetThreadId() == thin_lock_id) { - CHECK(thread == self || thread->IsSuspended()); + if (thread->GetThreadId() == thread_id) { return thread; } } @@ -1280,7 +1277,7 @@ void ThreadList::Unregister(Thread* self) { // Clear the TLS data, so that the underlying native thread is recognizably detached. // (It may wish to reattach later.) -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID __get_tls()[TLS_SLOT_ART_THREAD_SELF] = nullptr; #else CHECK_PTHREAD_CALL(pthread_setspecific, (Thread::pthread_key_self_, nullptr), "detach self"); diff --git a/runtime/thread_list.h b/runtime/thread_list.h index f97ecd34a5..df81ad1a7b 100644 --- a/runtime/thread_list.h +++ b/runtime/thread_list.h @@ -89,8 +89,8 @@ class ThreadList { !Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_); - // Find an already suspended thread (or self) by its id. - Thread* FindThreadByThreadId(uint32_t thin_lock_id); + // Find an existing thread (or self) by its thread id (not tid). + Thread* FindThreadByThreadId(uint32_t thread_id) REQUIRES(Locks::thread_list_lock_); // Run a checkpoint on threads, running threads are not suspended but run the checkpoint inside // of the suspend check. Returns how many checkpoints that are expected to run, including for diff --git a/runtime/thread_pool.cc b/runtime/thread_pool.cc index 2fba805fa2..b14f340c4c 100644 --- a/runtime/thread_pool.cc +++ b/runtime/thread_pool.cc @@ -61,7 +61,7 @@ ThreadPoolWorker::~ThreadPoolWorker() { void ThreadPoolWorker::SetPthreadPriority(int priority) { CHECK_GE(priority, PRIO_MIN); CHECK_LE(priority, PRIO_MAX); -#if defined(__ANDROID__) +#if defined(ART_TARGET_ANDROID) int result = setpriority(PRIO_PROCESS, pthread_gettid_np(pthread_), priority); if (result != 0) { PLOG(ERROR) << "Failed to setpriority to :" << priority; diff --git a/runtime/utils.cc b/runtime/utils.cc index 472a85c042..6a50b8eee2 100644 --- a/runtime/utils.cc +++ b/runtime/utils.cc @@ -1459,6 +1459,14 @@ bool FileExists(const std::string& filename) { return stat(filename.c_str(), &buffer) == 0; } +bool FileExistsAndNotEmpty(const std::string& filename) { + struct stat buffer; + if (stat(filename.c_str(), &buffer) != 0) { + return false; + } + return buffer.st_size > 0; +} + std::string PrettyDescriptor(Primitive::Type type) { return PrettyDescriptor(Primitive::Descriptor(type)); } diff --git a/runtime/utils.h b/runtime/utils.h index 83ac0b870e..c1e88a4feb 100644 --- a/runtime/utils.h +++ b/runtime/utils.h @@ -296,6 +296,7 @@ int ExecAndReturnCode(std::vector<std::string>& arg_vector, std::string* error_m // Returns true if the file exists. bool FileExists(const std::string& filename); +bool FileExistsAndNotEmpty(const std::string& filename); class VoidFunctor { public: diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc index 83da6b7b4e..8ad79fb572 100644 --- a/runtime/verifier/method_verifier.cc +++ b/runtime/verifier/method_verifier.cc @@ -396,6 +396,18 @@ MethodVerifier::FailureData MethodVerifier::VerifyMethod(Thread* self, << PrettyMethod(method_idx, *dex_file) << "\n"); } result.kind = kSoftFailure; + if (method != nullptr && + !CanCompilerHandleVerificationFailure(verifier.encountered_failure_types_)) { + method->SetAccessFlags(method->GetAccessFlags() | kAccCompileDontBother); + } + } + if (method != nullptr) { + if (verifier.HasInstructionThatWillThrow()) { + method->SetAccessFlags(method->GetAccessFlags() | kAccCompileDontBother); + } + if ((verifier.encountered_failure_types_ & VerifyError::VERIFY_ERROR_LOCKING) != 0) { + method->SetAccessFlags(method->GetAccessFlags() | kAccMustCountLocks); + } } } else { // Bad method data. @@ -790,9 +802,16 @@ bool MethodVerifier::Verify() { } else if (method_access_flags_ & kAccFinal) { Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "interfaces may not have final methods"; return false; - } else if (!(method_access_flags_ & kAccPublic)) { - Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "interfaces may not have non-public members"; - return false; + } else { + uint32_t access_flag_options = kAccPublic; + if (dex_file_->GetVersion() >= DexFile::kDefaultMethodsVersion) { + access_flag_options |= kAccPrivate; + } + if (!(method_access_flags_ & access_flag_options)) { + Fail(VERIFY_ERROR_BAD_CLASS_HARD) + << "interfaces may not have protected or package-private members"; + return false; + } } } } @@ -3794,9 +3813,12 @@ ArtMethod* MethodVerifier::ResolveMethodAndCheckAccess( // Note: this check must be after the initializer check, as those are required to fail a class, // while this check implies an IncompatibleClassChangeError. if (klass->IsInterface()) { - // methods called on interfaces should be invoke-interface, invoke-super, or invoke-static. + // methods called on interfaces should be invoke-interface, invoke-super, invoke-direct (if + // dex file version is 37 or greater), or invoke-static. if (method_type != METHOD_INTERFACE && method_type != METHOD_STATIC && + ((dex_file_->GetVersion() < DexFile::kDefaultMethodsVersion) || + method_type != METHOD_DIRECT) && method_type != METHOD_SUPER) { Fail(VERIFY_ERROR_CLASS_CHANGE) << "non-interface method " << PrettyMethod(dex_method_idx, *dex_file_) @@ -4079,8 +4101,8 @@ ArtMethod* MethodVerifier::VerifyInvocationArgs( << " to super " << PrettyMethod(res_method); return nullptr; } - mirror::Class* super_klass = super.GetClass(); - if (res_method->GetMethodIndex() >= super_klass->GetVTableLength()) { + if (!reference_class->IsAssignableFrom(GetDeclaringClass().GetClass()) || + (res_method->GetMethodIndex() >= super.GetClass()->GetVTableLength())) { Fail(VERIFY_ERROR_NO_METHOD) << "invalid invoke-super from " << PrettyMethod(dex_method_idx_, *dex_file_) << " to super " << super @@ -4567,8 +4589,18 @@ ArtField* MethodVerifier::GetInstanceField(const RegType& obj_type, int field_id // Trying to access C1.field1 using reference of type C2, which is neither C1 or a sub-class // of C1. For resolution to occur the declared class of the field must be compatible with // obj_type, we've discovered this wasn't so, so report the field didn't exist. - Fail(VERIFY_ERROR_NO_FIELD) << "cannot access instance field " << PrettyField(field) - << " from object of type " << obj_type; + VerifyError type; + bool is_aot = Runtime::Current()->IsAotCompiler(); + if (is_aot && (field_klass.IsUnresolvedTypes() || obj_type.IsUnresolvedTypes())) { + // Compiler & unresolved types involved, retry at runtime. + type = VerifyError::VERIFY_ERROR_NO_CLASS; + } else { + // Classes known (resolved; and thus assignability check is precise), or we are at runtime + // and still missing classes. This is a hard failure. + type = VerifyError::VERIFY_ERROR_BAD_CLASS_HARD; + } + Fail(type) << "cannot access instance field " << PrettyField(field) + << " from object of type " << obj_type; return nullptr; } else { return field; @@ -4620,7 +4652,7 @@ void MethodVerifier::VerifyISFieldAccess(const Instruction* inst, const RegType& if (field->IsFinal() && field->GetDeclaringClass() != GetDeclaringClass().GetClass()) { Fail(VERIFY_ERROR_ACCESS_FIELD) << "cannot modify final field " << PrettyField(field) << " from other class " << GetDeclaringClass(); - return; + // Keep hunting for possible hard fails. } } @@ -4896,6 +4928,9 @@ bool MethodVerifier::UpdateRegisters(uint32_t next_insn, RegisterLine* merge_lin // Initialize them as conflicts so they don't add to GC and deoptimization information. const Instruction* ret_inst = Instruction::At(code_item_->insns_ + next_insn); AdjustReturnLine(this, ret_inst, target_line); + if (have_pending_hard_failure_) { + return false; + } } } else { RegisterLineArenaUniquePtr copy; diff --git a/runtime/verifier/method_verifier.h b/runtime/verifier/method_verifier.h index ebb0b8c1fb..2592a21e58 100644 --- a/runtime/verifier/method_verifier.h +++ b/runtime/verifier/method_verifier.h @@ -142,6 +142,14 @@ class MethodVerifier { kHardFailure, }; + static bool CanCompilerHandleVerificationFailure(uint32_t encountered_failure_types) { + constexpr uint32_t unresolved_mask = verifier::VerifyError::VERIFY_ERROR_NO_CLASS + | verifier::VerifyError::VERIFY_ERROR_ACCESS_CLASS + | verifier::VerifyError::VERIFY_ERROR_ACCESS_FIELD + | verifier::VerifyError::VERIFY_ERROR_ACCESS_METHOD; + return (encountered_failure_types & (~unresolved_mask)) == 0; + } + // Verify a class. Returns "kNoFailure" on success. static FailureKind VerifyClass(Thread* self, mirror::Class* klass, diff --git a/sigchainlib/sigchain.cc b/sigchainlib/sigchain.cc index b76555b00b..f29301d222 100644 --- a/sigchainlib/sigchain.cc +++ b/sigchainlib/sigchain.cc @@ -14,7 +14,7 @@ * limitations under the License. */ -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID #include <android/log.h> #else #include <stdarg.h> @@ -103,7 +103,7 @@ static void log(const char* format, ...) { va_list ap; va_start(ap, format); vsnprintf(buf, sizeof(buf), format, ap); -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID __android_log_write(ANDROID_LOG_ERROR, "libsigchain", buf); #else std::cout << buf << "\n"; diff --git a/sigchainlib/sigchain_dummy.cc b/sigchainlib/sigchain_dummy.cc index dfe0c6f981..aa3c360b3a 100644 --- a/sigchainlib/sigchain_dummy.cc +++ b/sigchainlib/sigchain_dummy.cc @@ -17,7 +17,7 @@ #include <stdio.h> #include <stdlib.h> -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID #include <android/log.h> #else #include <stdarg.h> @@ -38,7 +38,7 @@ static void log(const char* format, ...) { va_list ap; va_start(ap, format); vsnprintf(buf, sizeof(buf), format, ap); -#ifdef __ANDROID__ +#ifdef ART_TARGET_ANDROID __android_log_write(ANDROID_LOG_ERROR, "libsigchain", buf); #else std::cout << buf << "\n"; diff --git a/test/004-JniTest/jni_test.cc b/test/004-JniTest/jni_test.cc index 2bdf8d1e71..8619ff7f3e 100644 --- a/test/004-JniTest/jni_test.cc +++ b/test/004-JniTest/jni_test.cc @@ -14,23 +14,22 @@ * limitations under the License. */ -#include <assert.h> #include <iostream> #include <pthread.h> #include <stdio.h> #include <vector> +#include "art_method-inl.h" +#include "base/logging.h" #include "jni.h" -#if defined(NDEBUG) -#error test code compiled without NDEBUG -#endif +namespace art { static JavaVM* jvm = nullptr; extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void*) { - assert(vm != nullptr); - assert(jvm == nullptr); + CHECK(vm != nullptr); + CHECK(jvm == nullptr); jvm = vm; std::cout << "JNI_OnLoad called" << std::endl; return JNI_VERSION_1_6; @@ -39,24 +38,24 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void*) { extern "C" JNIEXPORT void JNI_OnUnload(JavaVM*, void*) { // std::cout since LOG(INFO) adds extra stuff like pid. std::cout << "JNI_OnUnload called" << std::endl; - // Clear jvm for assert in test 004-JniTest. + // Clear jvm for CHECK in test 004-JniTest. jvm = nullptr; } static void* AttachHelper(void* arg) { - assert(jvm != nullptr); + CHECK(jvm != nullptr); JNIEnv* env = nullptr; JavaVMAttachArgs args = { JNI_VERSION_1_6, __FUNCTION__, nullptr }; int attach_result = jvm->AttachCurrentThread(&env, &args); - assert(attach_result == 0); + CHECK_EQ(attach_result, 0); typedef void (*Fn)(JNIEnv*); Fn fn = reinterpret_cast<Fn>(arg); fn(env); int detach_result = jvm->DetachCurrentThread(); - assert(detach_result == 0); + CHECK_EQ(detach_result, 0); return nullptr; } @@ -64,19 +63,19 @@ static void PthreadHelper(void (*fn)(JNIEnv*)) { pthread_t pthread; int pthread_create_result = pthread_create(&pthread, nullptr, AttachHelper, reinterpret_cast<void*>(fn)); - assert(pthread_create_result == 0); + CHECK_EQ(pthread_create_result, 0); int pthread_join_result = pthread_join(pthread, nullptr); - assert(pthread_join_result == 0); + CHECK_EQ(pthread_join_result, 0); } static void testFindClassOnAttachedNativeThread(JNIEnv* env) { jclass clazz = env->FindClass("Main"); - assert(clazz != nullptr); - assert(!env->ExceptionCheck()); + CHECK(clazz != nullptr); + CHECK(!env->ExceptionCheck()); jobjectArray array = env->NewObjectArray(0, clazz, nullptr); - assert(array != nullptr); - assert(!env->ExceptionCheck()); + CHECK(array != nullptr); + CHECK(!env->ExceptionCheck()); } // http://b/10994325 @@ -86,12 +85,12 @@ extern "C" JNIEXPORT void JNICALL Java_Main_testFindClassOnAttachedNativeThread( static void testFindFieldOnAttachedNativeThread(JNIEnv* env) { jclass clazz = env->FindClass("Main"); - assert(clazz != nullptr); - assert(!env->ExceptionCheck()); + CHECK(clazz != nullptr); + CHECK(!env->ExceptionCheck()); jfieldID field = env->GetStaticFieldID(clazz, "testFindFieldOnAttachedNativeThreadField", "Z"); - assert(field != nullptr); - assert(!env->ExceptionCheck()); + CHECK(field != nullptr); + CHECK(!env->ExceptionCheck()); env->SetStaticBooleanField(clazz, field, JNI_TRUE); } @@ -103,38 +102,38 @@ extern "C" JNIEXPORT void JNICALL Java_Main_testFindFieldOnAttachedNativeThreadN static void testReflectFieldGetFromAttachedNativeThread(JNIEnv* env) { jclass clazz = env->FindClass("Main"); - assert(clazz != nullptr); - assert(!env->ExceptionCheck()); + CHECK(clazz != nullptr); + CHECK(!env->ExceptionCheck()); jclass class_clazz = env->FindClass("java/lang/Class"); - assert(class_clazz != nullptr); - assert(!env->ExceptionCheck()); + CHECK(class_clazz != nullptr); + CHECK(!env->ExceptionCheck()); jmethodID getFieldMetodId = env->GetMethodID(class_clazz, "getField", "(Ljava/lang/String;)Ljava/lang/reflect/Field;"); - assert(getFieldMetodId != nullptr); - assert(!env->ExceptionCheck()); + CHECK(getFieldMetodId != nullptr); + CHECK(!env->ExceptionCheck()); jstring field_name = env->NewStringUTF("testReflectFieldGetFromAttachedNativeThreadField"); - assert(field_name != nullptr); - assert(!env->ExceptionCheck()); + CHECK(field_name != nullptr); + CHECK(!env->ExceptionCheck()); jobject field = env->CallObjectMethod(clazz, getFieldMetodId, field_name); - assert(field != nullptr); - assert(!env->ExceptionCheck()); + CHECK(field != nullptr); + CHECK(!env->ExceptionCheck()); jclass field_clazz = env->FindClass("java/lang/reflect/Field"); - assert(field_clazz != nullptr); - assert(!env->ExceptionCheck()); + CHECK(field_clazz != nullptr); + CHECK(!env->ExceptionCheck()); jmethodID getBooleanMetodId = env->GetMethodID(field_clazz, "getBoolean", "(Ljava/lang/Object;)Z"); - assert(getBooleanMetodId != nullptr); - assert(!env->ExceptionCheck()); + CHECK(getBooleanMetodId != nullptr); + CHECK(!env->ExceptionCheck()); jboolean value = env->CallBooleanMethod(field, getBooleanMetodId, /* ignored */ clazz); - assert(value == false); - assert(!env->ExceptionCheck()); + CHECK(value == false); + CHECK(!env->ExceptionCheck()); } // http://b/15539150 @@ -148,22 +147,22 @@ extern "C" JNIEXPORT void JNICALL Java_Main_testReflectFieldGetFromAttachedNativ extern "C" JNIEXPORT void JNICALL Java_Main_testCallStaticVoidMethodOnSubClassNative(JNIEnv* env, jclass) { jclass super_class = env->FindClass("Main$testCallStaticVoidMethodOnSubClass_SuperClass"); - assert(super_class != nullptr); + CHECK(super_class != nullptr); jmethodID execute = env->GetStaticMethodID(super_class, "execute", "()V"); - assert(execute != nullptr); + CHECK(execute != nullptr); jclass sub_class = env->FindClass("Main$testCallStaticVoidMethodOnSubClass_SubClass"); - assert(sub_class != nullptr); + CHECK(sub_class != nullptr); env->CallStaticVoidMethod(sub_class, execute); } extern "C" JNIEXPORT jobject JNICALL Java_Main_testGetMirandaMethodNative(JNIEnv* env, jclass) { jclass abstract_class = env->FindClass("Main$testGetMirandaMethod_MirandaAbstract"); - assert(abstract_class != nullptr); + CHECK(abstract_class != nullptr); jmethodID miranda_method = env->GetMethodID(abstract_class, "inInterface", "()Z"); - assert(miranda_method != nullptr); + CHECK(miranda_method != nullptr); return env->ToReflectedMethod(abstract_class, miranda_method, JNI_FALSE); } @@ -171,11 +170,11 @@ extern "C" JNIEXPORT jobject JNICALL Java_Main_testGetMirandaMethodNative(JNIEnv extern "C" void JNICALL Java_Main_testZeroLengthByteBuffers(JNIEnv* env, jclass) { std::vector<uint8_t> buffer(1); jobject byte_buffer = env->NewDirectByteBuffer(&buffer[0], 0); - assert(byte_buffer != nullptr); - assert(!env->ExceptionCheck()); + CHECK(byte_buffer != nullptr); + CHECK(!env->ExceptionCheck()); - assert(env->GetDirectBufferAddress(byte_buffer) == &buffer[0]); - assert(env->GetDirectBufferCapacity(byte_buffer) == 0); + CHECK_EQ(env->GetDirectBufferAddress(byte_buffer), &buffer[0]); + CHECK_EQ(env->GetDirectBufferCapacity(byte_buffer), 0); } constexpr size_t kByteReturnSize = 7; @@ -185,18 +184,18 @@ extern "C" jbyte JNICALL Java_Main_byteMethod(JNIEnv*, jclass, jbyte b1, jbyte b jbyte b3, jbyte b4, jbyte b5, jbyte b6, jbyte b7, jbyte b8, jbyte b9, jbyte b10) { // We use b1 to drive the output. - assert(b2 == 2); - assert(b3 == -3); - assert(b4 == 4); - assert(b5 == -5); - assert(b6 == 6); - assert(b7 == -7); - assert(b8 == 8); - assert(b9 == -9); - assert(b10 == 10); - - assert(0 <= b1); - assert(b1 < static_cast<jbyte>(kByteReturnSize)); + CHECK_EQ(b2, 2); + CHECK_EQ(b3, -3); + CHECK_EQ(b4, 4); + CHECK_EQ(b5, -5); + CHECK_EQ(b6, 6); + CHECK_EQ(b7, -7); + CHECK_EQ(b8, 8); + CHECK_EQ(b9, -9); + CHECK_EQ(b10, 10); + + CHECK_LE(0, b1); + CHECK_LT(b1, static_cast<jbyte>(kByteReturnSize)); return byte_returns[b1]; } @@ -210,18 +209,18 @@ extern "C" jshort JNICALL Java_Main_shortMethod(JNIEnv*, jclass, jshort s1, jsho jshort s3, jshort s4, jshort s5, jshort s6, jshort s7, jshort s8, jshort s9, jshort s10) { // We use s1 to drive the output. - assert(s2 == 2); - assert(s3 == -3); - assert(s4 == 4); - assert(s5 == -5); - assert(s6 == 6); - assert(s7 == -7); - assert(s8 == 8); - assert(s9 == -9); - assert(s10 == 10); - - assert(0 <= s1); - assert(s1 < static_cast<jshort>(kShortReturnSize)); + CHECK_EQ(s2, 2); + CHECK_EQ(s3, -3); + CHECK_EQ(s4, 4); + CHECK_EQ(s5, -5); + CHECK_EQ(s6, 6); + CHECK_EQ(s7, -7); + CHECK_EQ(s8, 8); + CHECK_EQ(s9, -9); + CHECK_EQ(s10, 10); + + CHECK_LE(0, s1); + CHECK_LT(s1, static_cast<jshort>(kShortReturnSize)); return short_returns[s1]; } @@ -231,17 +230,17 @@ extern "C" jboolean JNICALL Java_Main_booleanMethod(JNIEnv*, jclass, jboolean b1 jboolean b5, jboolean b6, jboolean b7, jboolean b8, jboolean b9, jboolean b10) { // We use b1 to drive the output. - assert(b2 == JNI_TRUE); - assert(b3 == JNI_FALSE); - assert(b4 == JNI_TRUE); - assert(b5 == JNI_FALSE); - assert(b6 == JNI_TRUE); - assert(b7 == JNI_FALSE); - assert(b8 == JNI_TRUE); - assert(b9 == JNI_FALSE); - assert(b10 == JNI_TRUE); - - assert(b1 == JNI_TRUE || b1 == JNI_FALSE); + CHECK_EQ(b2, JNI_TRUE); + CHECK_EQ(b3, JNI_FALSE); + CHECK_EQ(b4, JNI_TRUE); + CHECK_EQ(b5, JNI_FALSE); + CHECK_EQ(b6, JNI_TRUE); + CHECK_EQ(b7, JNI_FALSE); + CHECK_EQ(b8, JNI_TRUE); + CHECK_EQ(b9, JNI_FALSE); + CHECK_EQ(b10, JNI_TRUE); + + CHECK(b1 == JNI_TRUE || b1 == JNI_FALSE); return b1; } @@ -252,17 +251,17 @@ extern "C" jchar JNICALL Java_Main_charMethod(JNIEnv*, jclass, jchar c1, jchar c jchar c3, jchar c4, jchar c5, jchar c6, jchar c7, jchar c8, jchar c9, jchar c10) { // We use c1 to drive the output. - assert(c2 == 'a'); - assert(c3 == 'b'); - assert(c4 == 'c'); - assert(c5 == '0'); - assert(c6 == '1'); - assert(c7 == '2'); - assert(c8 == 1234); - assert(c9 == 2345); - assert(c10 == 3456); - - assert(c1 < static_cast<jchar>(kCharReturnSize)); + CHECK_EQ(c2, 'a'); + CHECK_EQ(c3, 'b'); + CHECK_EQ(c4, 'c'); + CHECK_EQ(c5, '0'); + CHECK_EQ(c6, '1'); + CHECK_EQ(c7, '2'); + CHECK_EQ(c8, 1234); + CHECK_EQ(c9, 2345); + CHECK_EQ(c10, 3456); + + CHECK_LT(c1, static_cast<jchar>(kCharReturnSize)); return char_returns[c1]; } @@ -281,39 +280,39 @@ static void testShallowGetCallingClassLoader(JNIEnv* env) { // Test direct call. { jclass vmstack_clazz = env->FindClass("dalvik/system/VMStack"); - assert(vmstack_clazz != nullptr); - assert(!env->ExceptionCheck()); + CHECK(vmstack_clazz != nullptr); + CHECK(!env->ExceptionCheck()); jmethodID getCallingClassLoaderMethodId = env->GetStaticMethodID(vmstack_clazz, "getCallingClassLoader", "()Ljava/lang/ClassLoader;"); - assert(getCallingClassLoaderMethodId != nullptr); - assert(!env->ExceptionCheck()); + CHECK(getCallingClassLoaderMethodId != nullptr); + CHECK(!env->ExceptionCheck()); jobject class_loader = env->CallStaticObjectMethod(vmstack_clazz, getCallingClassLoaderMethodId); - assert(class_loader == nullptr); - assert(!env->ExceptionCheck()); + CHECK(class_loader == nullptr); + CHECK(!env->ExceptionCheck()); } // Test one-level call. Use System.loadLibrary(). { jclass system_clazz = env->FindClass("java/lang/System"); - assert(system_clazz != nullptr); - assert(!env->ExceptionCheck()); + CHECK(system_clazz != nullptr); + CHECK(!env->ExceptionCheck()); jmethodID loadLibraryMethodId = env->GetStaticMethodID(system_clazz, "loadLibrary", "(Ljava/lang/String;)V"); - assert(loadLibraryMethodId != nullptr); - assert(!env->ExceptionCheck()); + CHECK(loadLibraryMethodId != nullptr); + CHECK(!env->ExceptionCheck()); // Create a string object. jobject library_string = env->NewStringUTF("non_existing_library"); - assert(library_string != nullptr); - assert(!env->ExceptionCheck()); + CHECK(library_string != nullptr); + CHECK(!env->ExceptionCheck()); env->CallStaticVoidMethod(system_clazz, loadLibraryMethodId, library_string); - assert(env->ExceptionCheck()); + CHECK(env->ExceptionCheck()); // We expect UnsatisfiedLinkError. jthrowable thrown = env->ExceptionOccurred(); @@ -321,7 +320,7 @@ static void testShallowGetCallingClassLoader(JNIEnv* env) { jclass unsatisfied_link_error_clazz = env->FindClass("java/lang/UnsatisfiedLinkError"); jclass thrown_class = env->GetObjectClass(thrown); - assert(env->IsSameObject(unsatisfied_link_error_clazz, thrown_class)); + CHECK(env->IsSameObject(unsatisfied_link_error_clazz, thrown_class)); } } @@ -333,31 +332,31 @@ extern "C" JNIEXPORT void JNICALL Java_Main_nativeTestShallowGetCallingClassLoad static void testShallowGetStackClass2(JNIEnv* env) { jclass vmstack_clazz = env->FindClass("dalvik/system/VMStack"); - assert(vmstack_clazz != nullptr); - assert(!env->ExceptionCheck()); + CHECK(vmstack_clazz != nullptr); + CHECK(!env->ExceptionCheck()); // Test direct call. { jmethodID getStackClass2MethodId = env->GetStaticMethodID(vmstack_clazz, "getStackClass2", "()Ljava/lang/Class;"); - assert(getStackClass2MethodId != nullptr); - assert(!env->ExceptionCheck()); + CHECK(getStackClass2MethodId != nullptr); + CHECK(!env->ExceptionCheck()); jobject caller_class = env->CallStaticObjectMethod(vmstack_clazz, getStackClass2MethodId); - assert(caller_class == nullptr); - assert(!env->ExceptionCheck()); + CHECK(caller_class == nullptr); + CHECK(!env->ExceptionCheck()); } // Test one-level call. Use VMStack.getStackClass1(). { jmethodID getStackClass1MethodId = env->GetStaticMethodID(vmstack_clazz, "getStackClass1", "()Ljava/lang/Class;"); - assert(getStackClass1MethodId != nullptr); - assert(!env->ExceptionCheck()); + CHECK(getStackClass1MethodId != nullptr); + CHECK(!env->ExceptionCheck()); jobject caller_class = env->CallStaticObjectMethod(vmstack_clazz, getStackClass1MethodId); - assert(caller_class == nullptr); - assert(!env->ExceptionCheck()); + CHECK(caller_class == nullptr); + CHECK(!env->ExceptionCheck()); } // For better testing we would need to compile against libcore and have a two-deep stack @@ -416,8 +415,8 @@ class JniCallNonvirtualVoidMethodTest { env_->ExceptionDescribe(); env_->FatalError(__FUNCTION__); } - assert(!env_->ExceptionCheck()); - assert(c != nullptr); + CHECK(!env_->ExceptionCheck()); + CHECK(c != nullptr); return c; } @@ -429,7 +428,7 @@ class JniCallNonvirtualVoidMethodTest { env_->ExceptionDescribe(); env_->FatalError(__FUNCTION__); } - assert(m != nullptr); + CHECK(m != nullptr); return m; } @@ -439,7 +438,7 @@ class JniCallNonvirtualVoidMethodTest { env_->ExceptionDescribe(); env_->FatalError(__FUNCTION__); } - assert(o != nullptr); + CHECK(o != nullptr); return o; } @@ -467,7 +466,7 @@ class JniCallNonvirtualVoidMethodTest { env_->ExceptionDescribe(); env_->FatalError(__FUNCTION__); } - assert(m != nullptr); + CHECK(m != nullptr); return m; } @@ -508,21 +507,21 @@ class JniCallNonvirtualVoidMethodTest { jobject sub_super = CallConstructor(sub_, super_constructor_); jobject sub_sub = CallConstructor(sub_, sub_constructor_); - assert(env_->IsInstanceOf(super_super, super_)); - assert(!env_->IsInstanceOf(super_super, sub_)); + CHECK(env_->IsInstanceOf(super_super, super_)); + CHECK(!env_->IsInstanceOf(super_super, sub_)); // Note that even though we called (and ran) the subclass // constructor, we are not the subclass. - assert(env_->IsInstanceOf(super_sub, super_)); - assert(!env_->IsInstanceOf(super_sub, sub_)); + CHECK(env_->IsInstanceOf(super_sub, super_)); + CHECK(!env_->IsInstanceOf(super_sub, sub_)); // Note that even though we called the superclass constructor, we // are still the subclass. - assert(env_->IsInstanceOf(sub_super, super_)); - assert(env_->IsInstanceOf(sub_super, sub_)); + CHECK(env_->IsInstanceOf(sub_super, super_)); + CHECK(env_->IsInstanceOf(sub_super, sub_)); - assert(env_->IsInstanceOf(sub_sub, super_)); - assert(env_->IsInstanceOf(sub_sub, sub_)); + CHECK(env_->IsInstanceOf(sub_sub, super_)); + CHECK(env_->IsInstanceOf(sub_sub, sub_)); } void TestnonstaticCallNonvirtualMethod(bool super_object, bool super_class, bool super_method, const char* test_case) { @@ -542,8 +541,8 @@ class JniCallNonvirtualVoidMethodTest { CallMethod(o, c, m, true, test_case); jboolean super_field = GetBooleanField(o, super_field_); jboolean sub_field = GetBooleanField(o, sub_field_); - assert(super_field == super_method); - assert(sub_field != super_method); + CHECK_EQ(super_field, super_method); + CHECK_NE(sub_field, super_method); } void TestnonstaticCallNonvirtualMethod() { @@ -565,20 +564,20 @@ extern "C" void JNICALL Java_Main_testCallNonvirtual(JNIEnv* env, jclass) { extern "C" JNIEXPORT void JNICALL Java_Main_testNewStringObject(JNIEnv* env, jclass) { jclass c = env->FindClass("java/lang/String"); - assert(c != nullptr); + CHECK(c != nullptr); jmethodID mid1 = env->GetMethodID(c, "<init>", "()V"); - assert(mid1 != nullptr); - assert(!env->ExceptionCheck()); + CHECK(mid1 != nullptr); + CHECK(!env->ExceptionCheck()); jmethodID mid2 = env->GetMethodID(c, "<init>", "([B)V"); - assert(mid2 != nullptr); - assert(!env->ExceptionCheck()); + CHECK(mid2 != nullptr); + CHECK(!env->ExceptionCheck()); jmethodID mid3 = env->GetMethodID(c, "<init>", "([C)V"); - assert(mid3 != nullptr); - assert(!env->ExceptionCheck()); + CHECK(mid3 != nullptr); + CHECK(!env->ExceptionCheck()); jmethodID mid4 = env->GetMethodID(c, "<init>", "(Ljava/lang/String;)V"); - assert(mid4 != nullptr); - assert(!env->ExceptionCheck()); + CHECK(mid4 != nullptr); + CHECK(!env->ExceptionCheck()); const char* test_array = "Test"; int byte_array_length = strlen(test_array); @@ -587,22 +586,22 @@ extern "C" JNIEXPORT void JNICALL Java_Main_testNewStringObject(JNIEnv* env, jcl // Test NewObject jstring s = reinterpret_cast<jstring>(env->NewObject(c, mid2, byte_array)); - assert(s != nullptr); - assert(env->GetStringLength(s) == byte_array_length); - assert(env->GetStringUTFLength(s) == byte_array_length); + CHECK(s != nullptr); + CHECK_EQ(env->GetStringLength(s), byte_array_length); + CHECK_EQ(env->GetStringUTFLength(s), byte_array_length); const char* chars = env->GetStringUTFChars(s, nullptr); - assert(strcmp(test_array, chars) == 0); + CHECK_EQ(strcmp(test_array, chars), 0); env->ReleaseStringUTFChars(s, chars); // Test AllocObject and Call(Nonvirtual)VoidMethod jstring s1 = reinterpret_cast<jstring>(env->AllocObject(c)); - assert(s1 != nullptr); + CHECK(s1 != nullptr); jstring s2 = reinterpret_cast<jstring>(env->AllocObject(c)); - assert(s2 != nullptr); + CHECK(s2 != nullptr); jstring s3 = reinterpret_cast<jstring>(env->AllocObject(c)); - assert(s3 != nullptr); + CHECK(s3 != nullptr); jstring s4 = reinterpret_cast<jstring>(env->AllocObject(c)); - assert(s4 != nullptr); + CHECK(s4 != nullptr); jcharArray char_array = env->NewCharArray(5); jstring string_arg = env->NewStringUTF("helloworld"); @@ -621,18 +620,18 @@ extern "C" JNIEXPORT void JNICALL Java_Main_testNewStringObject(JNIEnv* env, jcl // Test with global and weak global references jstring s5 = reinterpret_cast<jstring>(env->AllocObject(c)); - assert(s5 != nullptr); + CHECK(s5 != nullptr); s5 = reinterpret_cast<jstring>(env->NewGlobalRef(s5)); jstring s6 = reinterpret_cast<jstring>(env->AllocObject(c)); - assert(s6 != nullptr); + CHECK(s6 != nullptr); s6 = reinterpret_cast<jstring>(env->NewWeakGlobalRef(s6)); env->CallVoidMethod(s5, mid1); env->CallNonvirtualVoidMethod(s6, c, mid2, byte_array); - assert(env->GetStringLength(s5) == 0); - assert(env->GetStringLength(s6) == byte_array_length); + CHECK_EQ(env->GetStringLength(s5), 0); + CHECK_EQ(env->GetStringLength(s6), byte_array_length); const char* chars6 = env->GetStringUTFChars(s6, nullptr); - assert(strcmp(test_array, chars6) == 0); + CHECK_EQ(strcmp(test_array, chars6), 0); env->ReleaseStringUTFChars(s6, chars6); } @@ -664,8 +663,8 @@ class JniCallDefaultMethodsTest { public: explicit JniCallDefaultMethodsTest(JNIEnv* env) : env_(env), concrete_class_(env_->FindClass("ConcreteClass")) { - assert(!env_->ExceptionCheck()); - assert(concrete_class_ != nullptr); + CHECK(!env_->ExceptionCheck()); + CHECK(concrete_class_ != nullptr); } void Test() { @@ -688,14 +687,14 @@ class JniCallDefaultMethodsTest { void TestCalls(const char* declaring_class, std::vector<const char*> methods) { jmethodID new_method = env_->GetMethodID(concrete_class_, "<init>", "()V"); jobject obj = env_->NewObject(concrete_class_, new_method); - assert(!env_->ExceptionCheck()); - assert(obj != nullptr); + CHECK(!env_->ExceptionCheck()); + CHECK(obj != nullptr); jclass decl_class = env_->FindClass(declaring_class); - assert(!env_->ExceptionCheck()); - assert(decl_class != nullptr); + CHECK(!env_->ExceptionCheck()); + CHECK(decl_class != nullptr); for (const char* method : methods) { jmethodID method_id = env_->GetMethodID(decl_class, method, "()V"); - assert(!env_->ExceptionCheck()); + CHECK(!env_->ExceptionCheck()); printf("Calling method %s->%s on object of type ConcreteClass\n", declaring_class, method); env_->CallVoidMethod(obj, method_id); if (env_->ExceptionCheck()) { @@ -704,10 +703,10 @@ class JniCallDefaultMethodsTest { jmethodID to_string = env_->GetMethodID( env_->FindClass("java/lang/Object"), "toString", "()Ljava/lang/String;"); jstring exception_string = (jstring) env_->CallObjectMethod(thrown, to_string); - assert(!env_->ExceptionCheck()); + CHECK(!env_->ExceptionCheck()); const char* exception_string_utf8 = env_->GetStringUTFChars(exception_string, nullptr); - assert(!env_->ExceptionCheck()); - assert(exception_string_utf8 != nullptr); + CHECK(!env_->ExceptionCheck()); + CHECK(exception_string_utf8 != nullptr); printf("EXCEPTION OCCURED: %s\n", exception_string_utf8); env_->ReleaseStringUTFChars(exception_string, exception_string_utf8); } @@ -724,12 +723,12 @@ extern "C" JNIEXPORT void JNICALL Java_Main_testCallDefaultMethods(JNIEnv* env) static void InvokeSpecificMethod(JNIEnv* env, jobject obj, const char* method) { jclass lambda_class = env->FindClass("LambdaInterface"); - assert(!env->ExceptionCheck()); - assert(lambda_class != nullptr); + CHECK(!env->ExceptionCheck()); + CHECK(lambda_class != nullptr); jmethodID method_id = env->GetMethodID(lambda_class, method, "()V"); - assert(!env->ExceptionCheck()); + CHECK(!env->ExceptionCheck()); env->CallVoidMethod(obj, method_id); - assert(!env->ExceptionCheck()); + CHECK(!env->ExceptionCheck()); } extern "C" JNIEXPORT void JNICALL Java_Main_testInvokeLambdaDefaultMethod( @@ -740,3 +739,6 @@ extern "C" JNIEXPORT void JNICALL Java_Main_testInvokeLambdaDefaultMethod( extern "C" JNIEXPORT void JNICALL Java_Main_testInvokeLambdaMethod(JNIEnv* e, jclass, jobject l) { InvokeSpecificMethod(e, l, "sayHi"); } + +} // namespace art + diff --git a/test/004-ReferenceMap/stack_walk_refmap_jni.cc b/test/004-ReferenceMap/stack_walk_refmap_jni.cc index 284e5544fb..5304590ad0 100644 --- a/test/004-ReferenceMap/stack_walk_refmap_jni.cc +++ b/test/004-ReferenceMap/stack_walk_refmap_jni.cc @@ -14,6 +14,7 @@ * limitations under the License. */ +#include "art_method-inl.h" #include "check_reference_map_visitor.h" #include "jni.h" diff --git a/test/004-StackWalk/stack_walk_jni.cc b/test/004-StackWalk/stack_walk_jni.cc index 51bb68f24a..420224dd21 100644 --- a/test/004-StackWalk/stack_walk_jni.cc +++ b/test/004-StackWalk/stack_walk_jni.cc @@ -14,6 +14,7 @@ * limitations under the License. */ +#include "art_method-inl.h" #include "check_reference_map_visitor.h" #include "jni.h" diff --git a/test/005-annotations/src/android/test/anno/TestAnnotations.java b/test/005-annotations/src/android/test/anno/TestAnnotations.java index d36d43e1ba..51254b4220 100644 --- a/test/005-annotations/src/android/test/anno/TestAnnotations.java +++ b/test/005-annotations/src/android/test/anno/TestAnnotations.java @@ -159,7 +159,23 @@ public class TestAnnotations { System.out.println(""); } - + public static void testVisibilityCompatibility() throws Exception { + if (!VMRuntime.isAndroid()) { + return; + } + Object runtime = VMRuntime.getRuntime(); + int currentSdkVersion = VMRuntime.getTargetSdkVersion(runtime); + // SDK version 23 is M. + int oldSdkVersion = 23; + VMRuntime.setTargetSdkVersion(runtime, oldSdkVersion); + // This annotation has CLASS retention, but is visible to the runtime in M and earlier. + Annotation anno = SimplyNoted.class.getAnnotation(AnnoSimpleTypeInvis.class); + if (anno == null) { + System.out.println("testVisibilityCompatibility failed: " + + "SimplyNoted.get(AnnoSimpleTypeInvis) should not be null"); + } + VMRuntime.setTargetSdkVersion(runtime, currentSdkVersion); + } public static void main(String[] args) { System.out.println("TestAnnotations..."); @@ -229,5 +245,55 @@ public class TestAnnotations { } catch (NoSuchFieldError expected) { System.out.println("Got expected NoSuchFieldError"); } + + // Test if annotations marked VISIBILITY_BUILD are visible to runtime in M and earlier. + try { + testVisibilityCompatibility(); + } catch (Exception e) { + System.out.println("testVisibilityCompatibility failed: " + e); + } + } + + private static class VMRuntime { + private static Class vmRuntimeClass; + private static Method getRuntimeMethod; + private static Method getTargetSdkVersionMethod; + private static Method setTargetSdkVersionMethod; + static { + init(); + } + + private static void init() { + try { + vmRuntimeClass = Class.forName("dalvik.system.VMRuntime"); + } catch (Exception e) { + return; + } + try { + getRuntimeMethod = vmRuntimeClass.getDeclaredMethod("getRuntime"); + getTargetSdkVersionMethod = + vmRuntimeClass.getDeclaredMethod("getTargetSdkVersion"); + setTargetSdkVersionMethod = + vmRuntimeClass.getDeclaredMethod("setTargetSdkVersion", Integer.TYPE); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static boolean isAndroid() { + return vmRuntimeClass != null; + } + + public static Object getRuntime() throws Exception { + return getRuntimeMethod.invoke(null); + } + + public static int getTargetSdkVersion(Object runtime) throws Exception { + return (int) getTargetSdkVersionMethod.invoke(runtime); + } + + public static void setTargetSdkVersion(Object runtime, int version) throws Exception { + setTargetSdkVersionMethod.invoke(runtime, version); + } } } diff --git a/test/044-proxy/expected.txt b/test/044-proxy/expected.txt index be7023e49d..2a5f0b90db 100644 --- a/test/044-proxy/expected.txt +++ b/test/044-proxy/expected.txt @@ -95,3 +95,5 @@ Proxy narrowed invocation return type passed 5.8 JNI_OnLoad called callback +Found constructor. +Found constructors with 0 exceptions diff --git a/test/044-proxy/src/ConstructorProxy.java b/test/044-proxy/src/ConstructorProxy.java new file mode 100644 index 0000000000..95d150cbbd --- /dev/null +++ b/test/044-proxy/src/ConstructorProxy.java @@ -0,0 +1,53 @@ +/* + * Copyright 2016 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. + */ + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +/** + * Tests proxies when used with constructor methods. + */ +class ConstructorProxy implements InvocationHandler { + public static void main() { + try { + new ConstructorProxy().runTest(); + } catch (Exception e) { + System.out.println("Unexpected failure occured"); + e.printStackTrace(); + } + } + + public void runTest() throws Exception { + Class<?> proxyClass = Proxy.getProxyClass( + getClass().getClassLoader(), + new Class<?>[] { Runnable.class } + ); + Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class); + System.out.println("Found constructor."); + // We used to crash when asking the exception types of the constructor, because the runtime was + // not using the non-proxy ArtMethod + Object[] exceptions = constructor.getExceptionTypes(); + System.out.println("Found constructors with " + exceptions.length + " exceptions"); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + return args[0]; + } +} + diff --git a/test/044-proxy/src/Main.java b/test/044-proxy/src/Main.java index 1f23b95cf0..9dadb7c6ea 100644 --- a/test/044-proxy/src/Main.java +++ b/test/044-proxy/src/Main.java @@ -31,6 +31,7 @@ public class Main { NarrowingTest.main(null); FloatSelect.main(null); NativeProxy.main(args); + ConstructorProxy.main(); } // The following code maps from the actual proxy class names (eg $Proxy2) to their test output diff --git a/test/051-thread/thread_test.cc b/test/051-thread/thread_test.cc index 4215207c97..079ad40238 100644 --- a/test/051-thread/thread_test.cc +++ b/test/051-thread/thread_test.cc @@ -28,7 +28,7 @@ extern "C" JNIEXPORT jint JNICALL Java_Main_getNativePriority(JNIEnv* env, extern "C" JNIEXPORT jboolean JNICALL Java_Main_supportsThreadPriorities( JNIEnv* env ATTRIBUTE_UNUSED, jclass clazz ATTRIBUTE_UNUSED) { -#if defined(__ANDROID__) +#if defined(ART_TARGET_ANDROID) return JNI_TRUE; #else return JNI_FALSE; diff --git a/test/082-inline-execute/src/Main.java b/test/082-inline-execute/src/Main.java index 9aaed9d589..bf561e9a8b 100644 --- a/test/082-inline-execute/src/Main.java +++ b/test/082-inline-execute/src/Main.java @@ -809,6 +809,7 @@ public class Main { Assert.assertEquals(Math.round(-3.0d), -3l); Assert.assertEquals(Math.round(0.49999999999999994d), 0l); Assert.assertEquals(Math.round(9007199254740991.0d), 9007199254740991l); // 2^53 - 1 + Assert.assertEquals(Math.round(-9007199254740991.0d), -9007199254740991l); // -(2^53 - 1) Assert.assertEquals(Math.round(Double.NaN), (long)+0.0d); Assert.assertEquals(Math.round(Long.MAX_VALUE + 1.0d), Long.MAX_VALUE); Assert.assertEquals(Math.round(Long.MIN_VALUE - 1.0d), Long.MIN_VALUE); @@ -832,7 +833,16 @@ public class Main { Assert.assertEquals(Math.round(-3.0f), -3); // 0.4999999701976776123046875 Assert.assertEquals(Math.round(Float.intBitsToFloat(0x3EFFFFFF)), (int)+0.0f); + Assert.assertEquals(Math.round(8388607.0f), 8388607); // 2^23 - 1 + Assert.assertEquals(Math.round(8388607.5f), 8388608); // 2^23 - 0.5 + Assert.assertEquals(Math.round(8388608.0f), 8388608); // 2^23 + Assert.assertEquals(Math.round(-8388607.0f), -8388607); // -(2^23 - 1) + Assert.assertEquals(Math.round(-8388607.5f), -8388607); // -(2^23 - 0.5) + Assert.assertEquals(Math.round(-8388608.0f), -8388608); // -2^23 Assert.assertEquals(Math.round(16777215.0f), 16777215); // 2^24 - 1 + Assert.assertEquals(Math.round(16777216.0f), 16777216); // 2^24 + Assert.assertEquals(Math.round(-16777215.0f), -16777215); // -(2^24 - 1) + Assert.assertEquals(Math.round(-16777216.0f), -16777216); // -2^24 Assert.assertEquals(Math.round(Float.NaN), (int)+0.0f); Assert.assertEquals(Math.round(Integer.MAX_VALUE + 1.0f), Integer.MAX_VALUE); Assert.assertEquals(Math.round(Integer.MIN_VALUE - 1.0f), Integer.MIN_VALUE); @@ -1144,6 +1154,7 @@ public class Main { Assert.assertEquals(StrictMath.round(-3.0d), -3l); Assert.assertEquals(StrictMath.round(0.49999999999999994d), 0l); Assert.assertEquals(StrictMath.round(9007199254740991.0d), 9007199254740991l); // 2^53 - 1 + Assert.assertEquals(StrictMath.round(-9007199254740991.0d), -9007199254740991l); // -(2^53 - 1) Assert.assertEquals(StrictMath.round(Double.NaN), (long)+0.0d); Assert.assertEquals(StrictMath.round(Long.MAX_VALUE + 1.0d), Long.MAX_VALUE); Assert.assertEquals(StrictMath.round(Long.MIN_VALUE - 1.0d), Long.MIN_VALUE); @@ -1167,7 +1178,16 @@ public class Main { Assert.assertEquals(StrictMath.round(-3.0f), -3); // 0.4999999701976776123046875 Assert.assertEquals(StrictMath.round(Float.intBitsToFloat(0x3EFFFFFF)), (int)+0.0f); + Assert.assertEquals(StrictMath.round(8388607.0f), 8388607); // 2^23 - 1 + Assert.assertEquals(StrictMath.round(8388607.5f), 8388608); // 2^23 - 0.5 + Assert.assertEquals(StrictMath.round(8388608.0f), 8388608); // 2^23 + Assert.assertEquals(StrictMath.round(-8388607.0f), -8388607); // -(2^23 - 1) + Assert.assertEquals(StrictMath.round(-8388607.5f), -8388607); // -(2^23 - 0.5) + Assert.assertEquals(StrictMath.round(-8388608.0f), -8388608); // -2^23 Assert.assertEquals(StrictMath.round(16777215.0f), 16777215); // 2^24 - 1 + Assert.assertEquals(StrictMath.round(16777216.0f), 16777216); // 2^24 + Assert.assertEquals(StrictMath.round(-16777215.0f), -16777215); // -(2^24 - 1) + Assert.assertEquals(StrictMath.round(-16777216.0f), -16777216); // -2^24 Assert.assertEquals(StrictMath.round(Float.NaN), (int)+0.0f); Assert.assertEquals(StrictMath.round(Integer.MAX_VALUE + 1.0f), Integer.MAX_VALUE); Assert.assertEquals(StrictMath.round(Integer.MIN_VALUE - 1.0f), Integer.MIN_VALUE); diff --git a/test/098-ddmc/src/Main.java b/test/098-ddmc/src/Main.java index f41ff2a94a..50bbe5178e 100644 --- a/test/098-ddmc/src/Main.java +++ b/test/098-ddmc/src/Main.java @@ -44,7 +44,12 @@ public class Main { System.out.println("Confirm when we overflow, we don't roll over to zero. b/17392248"); final int overflowAllocations = 64 * 1024; // Won't fit in unsigned 16-bit value. for (int i = 0; i < overflowAllocations; i++) { - new Object(); + new Object() { + // Add a finalizer so that the allocation won't be eliminated. + public void finalize() { + System.out.print(""); + } + }; } Allocations after = new Allocations(DdmVmInternal.getRecentAllocations()); System.out.println("before < overflowAllocations=" + (before.numberOfEntries < overflowAllocations)); diff --git a/test/099-vmdebug/src/Main.java b/test/099-vmdebug/src/Main.java index 1be5765155..8068721219 100644 --- a/test/099-vmdebug/src/Main.java +++ b/test/099-vmdebug/src/Main.java @@ -133,7 +133,7 @@ public class Main { System.out.println("Got null string"); return; } - long n = Long.valueOf(s); + long n = Long.parseLong(s); if (n < 0) { System.out.println("Got negative number " + n); } @@ -157,8 +157,8 @@ public class Main { System.out.println("Got bad bucket " + bucket); continue; } - long key = Long.valueOf(kv[0]); - long value = Long.valueOf(kv[1]); + long key = Long.parseLong(kv[0]); + long value = Long.parseLong(kv[1]); if (key < 0 || value < 0) { System.out.println("Got negative key or value " + bucket); continue; diff --git a/test/100-reflect2/expected.txt b/test/100-reflect2/expected.txt index e4988c9b5f..d878e69aed 100644 --- a/test/100-reflect2/expected.txt +++ b/test/100-reflect2/expected.txt @@ -32,8 +32,8 @@ z (class java.lang.Character) 62 (class java.lang.Long) 14 (class java.lang.Short) [java.lang.String(int,int,char[]), public java.lang.String(), public java.lang.String(byte[]), public java.lang.String(byte[],int), public java.lang.String(byte[],int,int), public java.lang.String(byte[],int,int,int), public java.lang.String(byte[],int,int,java.lang.String) throws java.io.UnsupportedEncodingException, public java.lang.String(byte[],int,int,java.nio.charset.Charset), public java.lang.String(byte[],java.lang.String) throws java.io.UnsupportedEncodingException, public java.lang.String(byte[],java.nio.charset.Charset), public java.lang.String(char[]), public java.lang.String(char[],int,int), public java.lang.String(int[],int,int), public java.lang.String(java.lang.String), public java.lang.String(java.lang.StringBuffer), public java.lang.String(java.lang.StringBuilder)] -[private final int java.lang.String.count, private int java.lang.String.hash, private static final java.io.ObjectStreamField[] java.lang.String.serialPersistentFields, private static final long java.lang.String.serialVersionUID, private static int java.lang.String.HASHING_SEED, public static final java.util.Comparator java.lang.String.CASE_INSENSITIVE_ORDER] -[int java.lang.String.hash32(), native void java.lang.String.getCharsNoCheck(int,int,char[],int), native void java.lang.String.setCharAt(int,char), private int java.lang.String.indexOfSupplementary(int,int), private int java.lang.String.lastIndexOfSupplementary(int,int), private native int java.lang.String.fastIndexOf(int,int), private native java.lang.String java.lang.String.fastSubstring(int,int), private static int java.lang.String.getHashingSeed(), public boolean java.lang.String.contains(java.lang.CharSequence), public boolean java.lang.String.contentEquals(java.lang.CharSequence), public boolean java.lang.String.contentEquals(java.lang.StringBuffer), public boolean java.lang.String.endsWith(java.lang.String), public boolean java.lang.String.equals(java.lang.Object), public boolean java.lang.String.equalsIgnoreCase(java.lang.String), public boolean java.lang.String.isEmpty(), public boolean java.lang.String.matches(java.lang.String), public boolean java.lang.String.regionMatches(boolean,int,java.lang.String,int,int), public boolean java.lang.String.regionMatches(int,java.lang.String,int,int), public boolean java.lang.String.startsWith(java.lang.String), public boolean java.lang.String.startsWith(java.lang.String,int), public byte[] java.lang.String.getBytes(), public byte[] java.lang.String.getBytes(java.lang.String) throws java.io.UnsupportedEncodingException, public byte[] java.lang.String.getBytes(java.nio.charset.Charset), public int java.lang.String.codePointAt(int), public int java.lang.String.codePointBefore(int), public int java.lang.String.codePointCount(int,int), public int java.lang.String.compareTo(java.lang.Object), public int java.lang.String.compareToIgnoreCase(java.lang.String), public int java.lang.String.hashCode(), public int java.lang.String.indexOf(int), public int java.lang.String.indexOf(int,int), public int java.lang.String.indexOf(java.lang.String), public int java.lang.String.indexOf(java.lang.String,int), public int java.lang.String.lastIndexOf(int), public int java.lang.String.lastIndexOf(int,int), public int java.lang.String.lastIndexOf(java.lang.String), public int java.lang.String.lastIndexOf(java.lang.String,int), public int java.lang.String.length(), public int java.lang.String.offsetByCodePoints(int,int), public java.lang.CharSequence java.lang.String.subSequence(int,int), public java.lang.String java.lang.String.replace(char,char), public java.lang.String java.lang.String.replace(java.lang.CharSequence,java.lang.CharSequence), public java.lang.String java.lang.String.replaceAll(java.lang.String,java.lang.String), public java.lang.String java.lang.String.replaceFirst(java.lang.String,java.lang.String), public java.lang.String java.lang.String.substring(int), public java.lang.String java.lang.String.substring(int,int), public java.lang.String java.lang.String.toLowerCase(), public java.lang.String java.lang.String.toLowerCase(java.util.Locale), public java.lang.String java.lang.String.toString(), public java.lang.String java.lang.String.toUpperCase(), public java.lang.String java.lang.String.toUpperCase(java.util.Locale), public java.lang.String java.lang.String.trim(), public java.lang.String[] java.lang.String.split(java.lang.String), public java.lang.String[] java.lang.String.split(java.lang.String,int), public native char java.lang.String.charAt(int), public native char[] java.lang.String.toCharArray(), public native int java.lang.String.compareTo(java.lang.String), public native java.lang.String java.lang.String.concat(java.lang.String), public native java.lang.String java.lang.String.intern(), public static java.lang.String java.lang.String.copyValueOf(char[]), public static java.lang.String java.lang.String.copyValueOf(char[],int,int), public static java.lang.String java.lang.String.format(java.lang.String,java.lang.Object[]), public static java.lang.String java.lang.String.format(java.util.Locale,java.lang.String,java.lang.Object[]), public static java.lang.String java.lang.String.valueOf(boolean), public static java.lang.String java.lang.String.valueOf(char), public static java.lang.String java.lang.String.valueOf(char[]), public static java.lang.String java.lang.String.valueOf(char[],int,int), public static java.lang.String java.lang.String.valueOf(double), public static java.lang.String java.lang.String.valueOf(float), public static java.lang.String java.lang.String.valueOf(int), public static java.lang.String java.lang.String.valueOf(java.lang.Object), public static java.lang.String java.lang.String.valueOf(long), public void java.lang.String.getBytes(int,int,byte[],int), public void java.lang.String.getChars(int,int,char[],int), static int java.lang.String.indexOf(char[],int,int,char[],int,int,int), static int java.lang.String.indexOf(java.lang.String,java.lang.String,int), static int java.lang.String.lastIndexOf(char[],int,int,char[],int,int,int), static int java.lang.String.lastIndexOf(java.lang.String,java.lang.String,int)] +[private final int java.lang.String.count, private int java.lang.String.hash, private static final java.io.ObjectStreamField[] java.lang.String.serialPersistentFields, private static final long java.lang.String.serialVersionUID, public static final java.util.Comparator java.lang.String.CASE_INSENSITIVE_ORDER] +[native void java.lang.String.getCharsNoCheck(int,int,char[],int), native void java.lang.String.setCharAt(int,char), private int java.lang.String.indexOfSupplementary(int,int), private int java.lang.String.lastIndexOfSupplementary(int,int), private native int java.lang.String.fastIndexOf(int,int), private native java.lang.String java.lang.String.fastSubstring(int,int), public boolean java.lang.String.contains(java.lang.CharSequence), public boolean java.lang.String.contentEquals(java.lang.CharSequence), public boolean java.lang.String.contentEquals(java.lang.StringBuffer), public boolean java.lang.String.endsWith(java.lang.String), public boolean java.lang.String.equals(java.lang.Object), public boolean java.lang.String.equalsIgnoreCase(java.lang.String), public boolean java.lang.String.isEmpty(), public boolean java.lang.String.matches(java.lang.String), public boolean java.lang.String.regionMatches(boolean,int,java.lang.String,int,int), public boolean java.lang.String.regionMatches(int,java.lang.String,int,int), public boolean java.lang.String.startsWith(java.lang.String), public boolean java.lang.String.startsWith(java.lang.String,int), public byte[] java.lang.String.getBytes(), public byte[] java.lang.String.getBytes(java.lang.String) throws java.io.UnsupportedEncodingException, public byte[] java.lang.String.getBytes(java.nio.charset.Charset), public int java.lang.String.codePointAt(int), public int java.lang.String.codePointBefore(int), public int java.lang.String.codePointCount(int,int), public int java.lang.String.compareTo(java.lang.Object), public int java.lang.String.compareToIgnoreCase(java.lang.String), public int java.lang.String.hashCode(), public int java.lang.String.indexOf(int), public int java.lang.String.indexOf(int,int), public int java.lang.String.indexOf(java.lang.String), public int java.lang.String.indexOf(java.lang.String,int), public int java.lang.String.lastIndexOf(int), public int java.lang.String.lastIndexOf(int,int), public int java.lang.String.lastIndexOf(java.lang.String), public int java.lang.String.lastIndexOf(java.lang.String,int), public int java.lang.String.length(), public int java.lang.String.offsetByCodePoints(int,int), public java.lang.CharSequence java.lang.String.subSequence(int,int), public java.lang.String java.lang.String.replace(char,char), public java.lang.String java.lang.String.replace(java.lang.CharSequence,java.lang.CharSequence), public java.lang.String java.lang.String.replaceAll(java.lang.String,java.lang.String), public java.lang.String java.lang.String.replaceFirst(java.lang.String,java.lang.String), public java.lang.String java.lang.String.substring(int), public java.lang.String java.lang.String.substring(int,int), public java.lang.String java.lang.String.toLowerCase(), public java.lang.String java.lang.String.toLowerCase(java.util.Locale), public java.lang.String java.lang.String.toString(), public java.lang.String java.lang.String.toUpperCase(), public java.lang.String java.lang.String.toUpperCase(java.util.Locale), public java.lang.String java.lang.String.trim(), public java.lang.String[] java.lang.String.split(java.lang.String), public java.lang.String[] java.lang.String.split(java.lang.String,int), public native char java.lang.String.charAt(int), public native char[] java.lang.String.toCharArray(), public native int java.lang.String.compareTo(java.lang.String), public native java.lang.String java.lang.String.concat(java.lang.String), public native java.lang.String java.lang.String.intern(), public static java.lang.String java.lang.String.copyValueOf(char[]), public static java.lang.String java.lang.String.copyValueOf(char[],int,int), public static java.lang.String java.lang.String.format(java.lang.String,java.lang.Object[]), public static java.lang.String java.lang.String.format(java.util.Locale,java.lang.String,java.lang.Object[]), public static java.lang.String java.lang.String.valueOf(boolean), public static java.lang.String java.lang.String.valueOf(char), public static java.lang.String java.lang.String.valueOf(char[]), public static java.lang.String java.lang.String.valueOf(char[],int,int), public static java.lang.String java.lang.String.valueOf(double), public static java.lang.String java.lang.String.valueOf(float), public static java.lang.String java.lang.String.valueOf(int), public static java.lang.String java.lang.String.valueOf(java.lang.Object), public static java.lang.String java.lang.String.valueOf(long), public void java.lang.String.getBytes(int,int,byte[],int), public void java.lang.String.getChars(int,int,char[],int), static int java.lang.String.indexOf(char[],int,int,char[],int,int,int), static int java.lang.String.indexOf(java.lang.String,java.lang.String,int), static int java.lang.String.lastIndexOf(char[],int,int,char[],int,int,int), static int java.lang.String.lastIndexOf(java.lang.String,java.lang.String,int)] [] [interface java.io.Serializable, interface java.lang.Comparable, interface java.lang.CharSequence] 0 diff --git a/test/117-nopatchoat/nopatchoat.cc b/test/117-nopatchoat/nopatchoat.cc index 0dab4007a7..c6a2e9a5a8 100644 --- a/test/117-nopatchoat/nopatchoat.cc +++ b/test/117-nopatchoat/nopatchoat.cc @@ -55,7 +55,7 @@ class NoPatchoatTest { const OatFile* oat_file = oat_dex_file->GetOatFile(); return !oat_file->IsPic() - && CompilerFilter::IsCompilationEnabled(oat_file->GetCompilerFilter()); + && CompilerFilter::IsBytecodeCompilationEnabled(oat_file->GetCompilerFilter()); } }; diff --git a/test/136-daemon-jni-shutdown/daemon_jni_shutdown.cc b/test/136-daemon-jni-shutdown/daemon_jni_shutdown.cc index 54879fbad9..c9110a905d 100644 --- a/test/136-daemon-jni-shutdown/daemon_jni_shutdown.cc +++ b/test/136-daemon-jni-shutdown/daemon_jni_shutdown.cc @@ -14,6 +14,7 @@ * limitations under the License. */ +#include <dlfcn.h> #include <iostream> #include "base/casts.h" @@ -45,6 +46,10 @@ extern "C" JNIEXPORT void JNICALL Java_Main_destroyJavaVMAndExit(JNIEnv* env, jc self->SetTopOfShadowStack(nullptr); JavaVM* vm = down_cast<JNIEnvExt*>(env)->vm; vm->DetachCurrentThread(); + // Open ourself again to make sure the native library does not get unloaded from + // underneath us due to DestroyJavaVM. b/28406866 + void* handle = dlopen(kIsDebugBuild ? "libarttestd.so" : "libarttest.so", RTLD_NOW); + CHECK(handle != nullptr); vm->DestroyJavaVM(); vm_was_shutdown.store(true); // Give threads some time to get stuck in ExceptionCheck. diff --git a/test/138-duplicate-classes-check/src/FancyLoader.java b/test/138-duplicate-classes-check/src/FancyLoader.java deleted file mode 100644 index 03ec948767..0000000000 --- a/test/138-duplicate-classes-check/src/FancyLoader.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright (C) 2008 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. - */ - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.lang.reflect.InvocationTargetException; - -/** - * A class loader with atypical behavior: we try to load a private - * class implementation before asking the system or boot loader. This - * is used to create multiple classes with identical names in a single VM. - * - * If DexFile is available, we use that; if not, we assume we're not in - * Dalvik and instantiate the class with defineClass(). - * - * The location of the DEX files and class data is dependent upon the - * test framework. - */ -public class FancyLoader extends ClassLoader { - /* this is where the "alternate" .class files live */ - static final String CLASS_PATH = "classes-ex/"; - - /* this is the "alternate" DEX/Jar file */ - static final String DEX_FILE = System.getenv("DEX_LOCATION") + - "/138-duplicate-classes-check-ex.jar"; - - /* on Dalvik, this is a DexFile; otherwise, it's null */ - private Class mDexClass; - - private Object mDexFile; - - /** - * Construct FancyLoader, grabbing a reference to the DexFile class - * if we're running under Dalvik. - */ - public FancyLoader(ClassLoader parent) { - super(parent); - - try { - mDexClass = parent.loadClass("dalvik.system.DexFile"); - } catch (ClassNotFoundException cnfe) { - // ignore -- not running Dalvik - } - } - - /** - * Finds the class with the specified binary name. - * - * We search for a file in CLASS_PATH or pull an entry from DEX_FILE. - * If we don't find a match, we throw an exception. - */ - protected Class<?> findClass(String name) throws ClassNotFoundException - { - if (mDexClass != null) { - return findClassDalvik(name); - } else { - return findClassNonDalvik(name); - } - } - - /** - * Finds the class with the specified binary name, from a DEX file. - */ - private Class<?> findClassDalvik(String name) - throws ClassNotFoundException { - - if (mDexFile == null) { - synchronized (FancyLoader.class) { - Constructor ctor; - /* - * Construct a DexFile object through reflection. - */ - try { - ctor = mDexClass.getConstructor(new Class[] {String.class}); - } catch (NoSuchMethodException nsme) { - throw new ClassNotFoundException("getConstructor failed", - nsme); - } - - try { - mDexFile = ctor.newInstance(DEX_FILE); - } catch (InstantiationException ie) { - throw new ClassNotFoundException("newInstance failed", ie); - } catch (IllegalAccessException iae) { - throw new ClassNotFoundException("newInstance failed", iae); - } catch (InvocationTargetException ite) { - throw new ClassNotFoundException("newInstance failed", ite); - } - } - } - - /* - * Call DexFile.loadClass(String, ClassLoader). - */ - Method meth; - - try { - meth = mDexClass.getMethod("loadClass", - new Class[] { String.class, ClassLoader.class }); - } catch (NoSuchMethodException nsme) { - throw new ClassNotFoundException("getMethod failed", nsme); - } - - try { - meth.invoke(mDexFile, name, this); - } catch (IllegalAccessException iae) { - throw new ClassNotFoundException("loadClass failed", iae); - } catch (InvocationTargetException ite) { - throw new ClassNotFoundException("loadClass failed", - ite.getCause()); - } - - return null; - } - - /** - * Finds the class with the specified binary name, from .class files. - */ - private Class<?> findClassNonDalvik(String name) - throws ClassNotFoundException { - - String pathName = CLASS_PATH + name + ".class"; - //System.out.println("--- Fancy: looking for " + pathName); - - File path = new File(pathName); - RandomAccessFile raf; - - try { - raf = new RandomAccessFile(path, "r"); - } catch (FileNotFoundException fnfe) { - throw new ClassNotFoundException("Not found: " + pathName); - } - - /* read the entire file in */ - byte[] fileData; - try { - fileData = new byte[(int) raf.length()]; - raf.readFully(fileData); - } catch (IOException ioe) { - throw new ClassNotFoundException("Read error: " + pathName); - } finally { - try { - raf.close(); - } catch (IOException ioe) { - // drop - } - } - - /* create the class */ - //System.out.println("--- Fancy: defining " + name); - try { - return defineClass(name, fileData, 0, fileData.length); - } catch (Throwable th) { - throw new ClassNotFoundException("defineClass failed", th); - } - } - - /** - * Load a class. - * - * Normally a class loader wouldn't override this, but we want our - * version of the class to take precedence over an already-loaded - * version. - * - * We still want the system classes (e.g. java.lang.Object) from the - * bootstrap class loader. - */ - protected Class<?> loadClass(String name, boolean resolve) - throws ClassNotFoundException - { - Class res; - - /* - * 1. Invoke findLoadedClass(String) to check if the class has - * already been loaded. - * - * This doesn't change. - */ - res = findLoadedClass(name); - if (res != null) { - System.out.println("FancyLoader.loadClass: " - + name + " already loaded"); - if (resolve) - resolveClass(res); - return res; - } - - /* - * 3. Invoke the findClass(String) method to find the class. - */ - try { - res = findClass(name); - if (resolve) - resolveClass(res); - } - catch (ClassNotFoundException e) { - // we couldn't find it, so eat the exception and keep going - } - - /* - * 2. Invoke the loadClass method on the parent class loader. If - * the parent loader is null the class loader built-in to the - * virtual machine is used, instead. - * - * (Since we're not in java.lang, we can't actually invoke the - * parent's loadClass() method, but we passed our parent to the - * super-class which can take care of it for us.) - */ - res = super.loadClass(name, resolve); // returns class or throws - return res; - } -} diff --git a/test/138-duplicate-classes-check/src/Main.java b/test/138-duplicate-classes-check/src/Main.java index a9b5bb04ea..a2ef281939 100644 --- a/test/138-duplicate-classes-check/src/Main.java +++ b/test/138-duplicate-classes-check/src/Main.java @@ -14,6 +14,7 @@ * limitations under the License. */ +import dalvik.system.DexClassLoader; import java.io.File; import java.lang.reflect.Method; @@ -30,7 +31,11 @@ public class Main { // Now run the class from the -ex file. - FancyLoader loader = new FancyLoader(getClass().getClassLoader()); + String dexPath = System.getenv("DEX_LOCATION") + "/138-duplicate-classes-check-ex.jar"; + String optimizedDirectory = System.getenv("DEX_LOCATION"); + String librarySearchPath = null; + DexClassLoader loader = new DexClassLoader(dexPath, optimizedDirectory, librarySearchPath, + getClass().getClassLoader()); try { Class testEx = loader.loadClass("TestEx"); diff --git a/test/141-class-unload/jni_unload.cc b/test/141-class-unload/jni_unload.cc index d913efe53e..bbbb0a6036 100644 --- a/test/141-class-unload/jni_unload.cc +++ b/test/141-class-unload/jni_unload.cc @@ -19,7 +19,6 @@ #include <iostream> #include "jit/jit.h" -#include "jit/jit_instrumentation.h" #include "runtime.h" #include "thread-inl.h" @@ -29,7 +28,7 @@ namespace { extern "C" JNIEXPORT void JNICALL Java_IntHolder_waitForCompilation(JNIEnv*, jclass) { jit::Jit* jit = Runtime::Current()->GetJit(); if (jit != nullptr) { - jit->GetInstrumentationCache()->WaitForCompilationToFinish(Thread::Current()); + jit->WaitForCompilationToFinish(Thread::Current()); } } diff --git a/test/141-class-unload/src/Main.java b/test/141-class-unload/src/Main.java index 15683b0b1e..17a6049dbf 100644 --- a/test/141-class-unload/src/Main.java +++ b/test/141-class-unload/src/Main.java @@ -23,6 +23,7 @@ import java.lang.reflect.Method; public class Main { static final String DEX_FILE = System.getenv("DEX_LOCATION") + "/141-class-unload-ex.jar"; + static final String LIBRARY_SEARCH_PATH = System.getProperty("java.library.path"); static String nativeLibraryName; public static void main(String[] args) throws Exception { @@ -32,7 +33,7 @@ public class Main { throw new AssertionError("Couldn't find path class loader class"); } Constructor constructor = - pathClassLoader.getDeclaredConstructor(String.class, ClassLoader.class); + pathClassLoader.getDeclaredConstructor(String.class, String.class, ClassLoader.class); try { testUnloadClass(constructor); testUnloadLoader(constructor); @@ -49,7 +50,7 @@ public class Main { // Test that the oat files are unloaded. testOatFilesUnloaded(getPid()); } catch (Exception e) { - System.out.println(e); + e.printStackTrace(); } } @@ -118,7 +119,7 @@ public class Main { private static void testNoUnloadInvoke(Constructor constructor) throws Exception { WeakReference<ClassLoader> loader = new WeakReference((ClassLoader) constructor.newInstance( - DEX_FILE, ClassLoader.getSystemClassLoader())); + DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader())); WeakReference<Class> intHolder = new WeakReference(loader.get().loadClass("IntHolder")); intHolder.get().getDeclaredMethod("runGC").invoke(intHolder.get()); boolean isNull = loader.get() == null; @@ -128,7 +129,7 @@ public class Main { private static void testNoUnloadInstance(Constructor constructor) throws Exception { WeakReference<ClassLoader> loader = new WeakReference((ClassLoader) constructor.newInstance( - DEX_FILE, ClassLoader.getSystemClassLoader())); + DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader())); WeakReference<Class> intHolder = new WeakReference(loader.get().loadClass("IntHolder")); Object o = intHolder.get().newInstance(); Runtime.getRuntime().gc(); @@ -138,7 +139,7 @@ public class Main { private static WeakReference<Class> setUpUnloadClass(Constructor constructor) throws Exception { ClassLoader loader = (ClassLoader) constructor.newInstance( - DEX_FILE, ClassLoader.getSystemClassLoader()); + DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader()); Class intHolder = loader.loadClass("IntHolder"); Method getValue = intHolder.getDeclaredMethod("getValue"); Method setValue = intHolder.getDeclaredMethod("setValue", Integer.TYPE); @@ -155,7 +156,7 @@ public class Main { boolean waitForCompilation) throws Exception { ClassLoader loader = (ClassLoader) constructor.newInstance( - DEX_FILE, ClassLoader.getSystemClassLoader()); + DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader()); Class intHolder = loader.loadClass("IntHolder"); Method setValue = intHolder.getDeclaredMethod("setValue", Integer.TYPE); setValue.invoke(intHolder, 2); @@ -177,7 +178,7 @@ public class Main { private static WeakReference<ClassLoader> setUpLoadLibrary(Constructor constructor) throws Exception { ClassLoader loader = (ClassLoader) constructor.newInstance( - DEX_FILE, ClassLoader.getSystemClassLoader()); + DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader()); Class intHolder = loader.loadClass("IntHolder"); Method loadLibrary = intHolder.getDeclaredMethod("loadLibrary", String.class); loadLibrary.invoke(intHolder, nativeLibraryName); diff --git a/test/147-stripped-dex-fallback/expected.txt b/test/147-stripped-dex-fallback/expected.txt new file mode 100644 index 0000000000..af5626b4a1 --- /dev/null +++ b/test/147-stripped-dex-fallback/expected.txt @@ -0,0 +1 @@ +Hello, world! diff --git a/test/147-stripped-dex-fallback/info.txt b/test/147-stripped-dex-fallback/info.txt new file mode 100644 index 0000000000..72a2ca8d4c --- /dev/null +++ b/test/147-stripped-dex-fallback/info.txt @@ -0,0 +1,2 @@ +Verify that we fallback to running out of dex code in the oat file if there is +no image and the original dex code has been stripped. diff --git a/test/147-stripped-dex-fallback/run b/test/147-stripped-dex-fallback/run new file mode 100755 index 0000000000..e594010b9e --- /dev/null +++ b/test/147-stripped-dex-fallback/run @@ -0,0 +1,24 @@ +#!/bin/bash +# +# Copyright (C) 2014 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. + +# ensure flags includes prebuild. +flags="$@" +if [[ "${flags}" == *--no-prebuild* ]] ; then + echo "Test 147-stripped-dex-fallback is not intended to run in no-prebuild mode." + exit 1 +fi + +${RUN} ${flags} --strip-dex --no-dex2oat diff --git a/test/147-stripped-dex-fallback/src/Main.java b/test/147-stripped-dex-fallback/src/Main.java new file mode 100644 index 0000000000..1ef6289559 --- /dev/null +++ b/test/147-stripped-dex-fallback/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2011 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. + */ + +public class Main { + public static void main(String[] args) { + System.out.println("Hello, world!"); + } +} diff --git a/test/148-multithread-gc-annotations/check b/test/148-multithread-gc-annotations/check new file mode 100755 index 0000000000..842bdc6ae8 --- /dev/null +++ b/test/148-multithread-gc-annotations/check @@ -0,0 +1,22 @@ +#!/bin/bash +# +# 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. + +# Check that the string "error" isn't present +if grep error "$2"; then + exit 1 +else + exit 0 +fi diff --git a/test/148-multithread-gc-annotations/expected.txt b/test/148-multithread-gc-annotations/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/148-multithread-gc-annotations/expected.txt diff --git a/test/148-multithread-gc-annotations/gc_coverage.cc b/test/148-multithread-gc-annotations/gc_coverage.cc new file mode 100644 index 0000000000..263eefd3ab --- /dev/null +++ b/test/148-multithread-gc-annotations/gc_coverage.cc @@ -0,0 +1,42 @@ +/* + * 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 "gc/heap.h" +#include "jni.h" +#include "runtime.h" +#include "scoped_thread_state_change.h" +#include "thread-inl.h" + +namespace art { +namespace { + +extern "C" JNIEXPORT jboolean JNICALL Java_MovingGCThread_performHomogeneousSpaceCompact(JNIEnv*, jclass) { + return Runtime::Current()->GetHeap()->PerformHomogeneousSpaceCompact() == gc::kSuccess ? + JNI_TRUE : JNI_FALSE; +} + +extern "C" JNIEXPORT jboolean JNICALL Java_MovingGCThread_supportHomogeneousSpaceCompact(JNIEnv*, jclass) { + return Runtime::Current()->GetHeap()->SupportHomogeneousSpaceCompactAndCollectorTransitions() ? + JNI_TRUE : JNI_FALSE; +} + +extern "C" JNIEXPORT jlong JNICALL Java_MovingGCThread_objectAddress(JNIEnv* env, jclass, jobject object) { + ScopedObjectAccess soa(env); + return reinterpret_cast<jlong>(soa.Decode<mirror::Object*>(object)); +} + +} // namespace +} // namespace art diff --git a/test/148-multithread-gc-annotations/info.txt b/test/148-multithread-gc-annotations/info.txt new file mode 100644 index 0000000000..c62e544e08 --- /dev/null +++ b/test/148-multithread-gc-annotations/info.txt @@ -0,0 +1 @@ +Tests that getting annotations works during moving gc. diff --git a/test/148-multithread-gc-annotations/src/AnnoClass1.java b/test/148-multithread-gc-annotations/src/AnnoClass1.java new file mode 100644 index 0000000000..b82c61fd5b --- /dev/null +++ b/test/148-multithread-gc-annotations/src/AnnoClass1.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2016 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. + */ + +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface AnnoClass1 { + Class value(); +} diff --git a/test/148-multithread-gc-annotations/src/AnnoClass2.java b/test/148-multithread-gc-annotations/src/AnnoClass2.java new file mode 100644 index 0000000000..c75d950e2a --- /dev/null +++ b/test/148-multithread-gc-annotations/src/AnnoClass2.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2016 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. + */ + +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface AnnoClass2 { + Class value(); +} diff --git a/test/148-multithread-gc-annotations/src/AnnoClass3.java b/test/148-multithread-gc-annotations/src/AnnoClass3.java new file mode 100644 index 0000000000..5b4a378091 --- /dev/null +++ b/test/148-multithread-gc-annotations/src/AnnoClass3.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2016 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. + */ + +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface AnnoClass3 { + Class value(); +} diff --git a/test/148-multithread-gc-annotations/src/AnnotationThread.java b/test/148-multithread-gc-annotations/src/AnnotationThread.java new file mode 100644 index 0000000000..ebc14e96bb --- /dev/null +++ b/test/148-multithread-gc-annotations/src/AnnotationThread.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2016 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. + */ + +import java.lang.annotation.*; + +@AnnoClass1(AnnoClass2.class) +@AnnoClass2(AnnoClass3.class) +@AnnoClass3(AnnoClass1.class) +public class AnnotationThread implements Runnable { + public void run() { + for (int i = 0; i < 20; i++) { + Annotation[] annotations = AnnotationThread.class.getAnnotations(); + if (annotations == null) { + System.out.println("error: AnnotationThread class has no annotations"); + return; + } + } + } +} diff --git a/test/148-multithread-gc-annotations/src/Main.java b/test/148-multithread-gc-annotations/src/Main.java new file mode 100644 index 0000000000..b652ed6519 --- /dev/null +++ b/test/148-multithread-gc-annotations/src/Main.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2016 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. + */ + +public class Main { + public static void main(String[] args) { + System.loadLibrary(args[0]); + Thread annoThread = new Thread(new AnnotationThread(), "Annotation thread"); + Thread gcThread = new Thread(new MovingGCThread(), "Moving GC thread"); + annoThread.start(); + gcThread.start(); + try { + annoThread.join(); + gcThread.join(); + } catch (InterruptedException e) { + System.out.println("error: " + e); + } + System.out.println("Done."); + } +} diff --git a/test/148-multithread-gc-annotations/src/MovingGCThread.java b/test/148-multithread-gc-annotations/src/MovingGCThread.java new file mode 100644 index 0000000000..87de9f4ec6 --- /dev/null +++ b/test/148-multithread-gc-annotations/src/MovingGCThread.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2016 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. + */ + +import java.util.TreeMap; + +public class MovingGCThread implements Runnable { + private static TreeMap treeMap = new TreeMap(); + + public void run() { + for (int i = 0; i < 20; i++) { + testHomogeneousCompaction(); + } + } + + public static void testHomogeneousCompaction() { + final boolean supportHSC = supportHomogeneousSpaceCompact(); + if (!supportHSC) { + return; + } + Object o = new Object(); + long addressBefore = objectAddress(o); + allocateStuff(); + final boolean success = performHomogeneousSpaceCompact(); + allocateStuff(); + if (!success) { + System.out.println("error: Expected " + supportHSC + " but got " + success); + } + allocateStuff(); + long addressAfter = objectAddress(o); + // This relies on the compaction copying from one space to another space and there being + // no overlap. + if (addressBefore == addressAfter) { + System.out.println("error: Expected different adddress " + addressBefore + " vs " + + addressAfter); + } + } + + private static void allocateStuff() { + for (int i = 0; i < 1000; ++i) { + Object o = new Object(); + treeMap.put(o.hashCode(), o); + } + } + + // Methods to get access to ART internals. + private static native boolean supportHomogeneousSpaceCompact(); + private static native boolean performHomogeneousSpaceCompact(); + private static native long objectAddress(Object object); +} diff --git a/test/444-checker-nce/src/Main.java b/test/444-checker-nce/src/Main.java index c96b18c71b..ddc2f77e89 100644 --- a/test/444-checker-nce/src/Main.java +++ b/test/444-checker-nce/src/Main.java @@ -28,10 +28,6 @@ public class Main { } /// CHECK-START: Main Main.thisTest() builder (after) - /// CHECK: NullCheck - /// CHECK: InvokeStaticOrDirect - - /// CHECK-START: Main Main.thisTest() instruction_simplifier (after) /// CHECK-NOT: NullCheck /// CHECK: InvokeStaticOrDirect public Main thisTest() { @@ -40,12 +36,10 @@ public class Main { /// CHECK-START: Main Main.newInstanceRemoveTest() builder (after) /// CHECK: NewInstance - /// CHECK: NullCheck /// CHECK: InvokeStaticOrDirect - /// CHECK: NullCheck /// CHECK: InvokeStaticOrDirect - /// CHECK-START: Main Main.newInstanceRemoveTest() instruction_simplifier (after) + /// CHECK-START: Main Main.newInstanceRemoveTest() builder (after) /// CHECK-NOT: NullCheck public Main newInstanceRemoveTest() { Main m = new Main(); @@ -54,13 +48,10 @@ public class Main { /// CHECK-START: Main Main.newArrayRemoveTest() builder (after) /// CHECK: NewArray - /// CHECK: NullCheck /// CHECK: ArrayGet - /// CHECK-START: Main Main.newArrayRemoveTest() instruction_simplifier (after) - /// CHECK: NewArray + /// CHECK-START: Main Main.newArrayRemoveTest() builder (after) /// CHECK-NOT: NullCheck - /// CHECK: ArrayGet public Main newArrayRemoveTest() { Main[] ms = new Main[1]; return ms[0]; @@ -179,9 +170,6 @@ public class Main { } /// CHECK-START: Main Main.scopeRemoveTest(int, Main) builder (after) - /// CHECK: NullCheck - - /// CHECK-START: Main Main.scopeRemoveTest(int, Main) instruction_simplifier (after) /// CHECK-NOT: NullCheck public Main scopeRemoveTest(int count, Main a) { Main m = null; diff --git a/test/449-checker-bce/src/Main.java b/test/449-checker-bce/src/Main.java index 66e1d92cc2..41771b52c4 100644 --- a/test/449-checker-bce/src/Main.java +++ b/test/449-checker-bce/src/Main.java @@ -927,6 +927,32 @@ public class Main { } } + /// CHECK-START: void Main.nonzeroLength(int[]) BCE (before) + /// CHECK-DAG: BoundsCheck + // + /// CHECK-START: void Main.nonzeroLength(int[]) BCE (after) + /// CHECK-NOT: BoundsCheck + /// CHECK-NOT: Deoptimize + public static void nonzeroLength(int[] a) { + if (a.length != 0) { + a[0] = 112; + } + } + + /// CHECK-START: void Main.knownLength(int[]) BCE (before) + /// CHECK-DAG: BoundsCheck + /// CHECK-DAG: BoundsCheck + // + /// CHECK-START: void Main.knownLength(int[]) BCE (after) + /// CHECK-NOT: BoundsCheck + /// CHECK-NOT: Deoptimize + public static void knownLength(int[] a) { + if (a.length == 2) { + a[0] = -1; + a[1] = -2; + } + } + static int[][] mA; /// CHECK-START: void Main.dynamicBCEAndIntrinsic(int) BCE (before) @@ -1586,6 +1612,26 @@ public class Main { } } + nonzeroLength(array); + if (array[0] != 112) { + System.out.println("nonzero length failed!"); + } + + knownLength(array); + if (array[0] != 112 || array[1] != 1) { + System.out.println("nonzero length failed!"); + } + array = new int[2]; + knownLength(array); + if (array[0] != -1 || array[1] != -2) { + System.out.println("nonzero length failed!"); + } + + // Zero length array does not break. + array = new int[0]; + nonzeroLength(array); + knownLength(array); + mA = new int[4][4]; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { diff --git a/test/525-checker-arrays-and-fields/info.txt b/test/525-checker-arrays-and-fields/info.txt deleted file mode 100644 index 3e16abf204..0000000000 --- a/test/525-checker-arrays-and-fields/info.txt +++ /dev/null @@ -1 +0,0 @@ -Test on (in)variant static and instance field and array references in loops. diff --git a/test/525-checker-arrays-and-fields/expected.txt b/test/525-checker-arrays-fields1/expected.txt index b0aad4deb5..b0aad4deb5 100644 --- a/test/525-checker-arrays-and-fields/expected.txt +++ b/test/525-checker-arrays-fields1/expected.txt diff --git a/test/525-checker-arrays-fields1/info.txt b/test/525-checker-arrays-fields1/info.txt new file mode 100644 index 0000000000..7d0a088934 --- /dev/null +++ b/test/525-checker-arrays-fields1/info.txt @@ -0,0 +1 @@ +Test on (in)variant static field and array references in loops. diff --git a/test/525-checker-arrays-and-fields/src/Main.java b/test/525-checker-arrays-fields1/src/Main.java index b3fbf44751..ba0476af5f 100644 --- a/test/525-checker-arrays-and-fields/src/Main.java +++ b/test/525-checker-arrays-fields1/src/Main.java @@ -15,7 +15,7 @@ */ // -// Test on (in)variant static and instance field and array references in loops. +// Test on (in)variant static field and array references in loops. // public class Main { @@ -51,159 +51,131 @@ public class Main { private static Object[] sArrL; // - // Instance fields. - // - - private boolean mZ; - private byte mB; - private char mC; - private short mS; - private int mI; - private long mJ; - private float mF; - private double mD; - private Object mL; - - // - // Instance arrays. - // - - private boolean[] mArrZ; - private byte[] mArrB; - private char[] mArrC; - private short[] mArrS; - private int[] mArrI; - private long[] mArrJ; - private float[] mArrF; - private double[] mArrD; - private Object[] mArrL; - - // // Loops on static arrays with invariant static field references. // The checker is used to ensure hoisting occurred. // - /// CHECK-START: void Main.SInvLoopZ() licm (before) + /// CHECK-START: void Main.InvLoopZ() licm (before) /// CHECK-DAG: StaticFieldGet loop:{{B\d+}} /// CHECK-DAG: StaticFieldGet loop:{{B\d+}} - /// CHECK-START: void Main.SInvLoopZ() licm (after) + /// CHECK-START: void Main.InvLoopZ() licm (after) /// CHECK-DAG: StaticFieldGet loop:none /// CHECK-DAG: StaticFieldGet loop:none - private static void SInvLoopZ() { + private static void InvLoopZ() { for (int i = 0; i < sArrZ.length; i++) { sArrZ[i] = sZ; } } - /// CHECK-START: void Main.SInvLoopB() licm (before) + /// CHECK-START: void Main.InvLoopB() licm (before) /// CHECK-DAG: StaticFieldGet loop:{{B\d+}} /// CHECK-DAG: StaticFieldGet loop:{{B\d+}} - /// CHECK-START: void Main.SInvLoopB() licm (after) + /// CHECK-START: void Main.InvLoopB() licm (after) /// CHECK-DAG: StaticFieldGet loop:none /// CHECK-DAG: StaticFieldGet loop:none - private static void SInvLoopB() { + private static void InvLoopB() { for (int i = 0; i < sArrB.length; i++) { sArrB[i] = sB; } } - /// CHECK-START: void Main.SInvLoopC() licm (before) + /// CHECK-START: void Main.InvLoopC() licm (before) /// CHECK-DAG: StaticFieldGet loop:{{B\d+}} /// CHECK-DAG: StaticFieldGet loop:{{B\d+}} - /// CHECK-START: void Main.SInvLoopC() licm (after) + /// CHECK-START: void Main.InvLoopC() licm (after) /// CHECK-DAG: StaticFieldGet loop:none /// CHECK-DAG: StaticFieldGet loop:none - private static void SInvLoopC() { + private static void InvLoopC() { for (int i = 0; i < sArrC.length; i++) { sArrC[i] = sC; } } - /// CHECK-START: void Main.SInvLoopS() licm (before) + /// CHECK-START: void Main.InvLoopS() licm (before) /// CHECK-DAG: StaticFieldGet loop:{{B\d+}} /// CHECK-DAG: StaticFieldGet loop:{{B\d+}} - /// CHECK-START: void Main.SInvLoopS() licm (after) + /// CHECK-START: void Main.InvLoopS() licm (after) /// CHECK-DAG: StaticFieldGet loop:none /// CHECK-DAG: StaticFieldGet loop:none - private static void SInvLoopS() { + private static void InvLoopS() { for (int i = 0; i < sArrS.length; i++) { sArrS[i] = sS; } } - /// CHECK-START: void Main.SInvLoopI() licm (before) + /// CHECK-START: void Main.InvLoopI() licm (before) /// CHECK-DAG: StaticFieldGet loop:{{B\d+}} /// CHECK-DAG: StaticFieldGet loop:{{B\d+}} - /// CHECK-START: void Main.SInvLoopI() licm (after) + /// CHECK-START: void Main.InvLoopI() licm (after) /// CHECK-DAG: StaticFieldGet loop:none /// CHECK-DAG: StaticFieldGet loop:none - private static void SInvLoopI() { + private static void InvLoopI() { for (int i = 0; i < sArrI.length; i++) { sArrI[i] = sI; } } - /// CHECK-START: void Main.SInvLoopJ() licm (before) + /// CHECK-START: void Main.InvLoopJ() licm (before) /// CHECK-DAG: StaticFieldGet loop:{{B\d+}} /// CHECK-DAG: StaticFieldGet loop:{{B\d+}} - /// CHECK-START: void Main.SInvLoopJ() licm (after) + /// CHECK-START: void Main.InvLoopJ() licm (after) /// CHECK-DAG: StaticFieldGet loop:none /// CHECK-DAG: StaticFieldGet loop:none - private static void SInvLoopJ() { + private static void InvLoopJ() { for (int i = 0; i < sArrJ.length; i++) { sArrJ[i] = sJ; } } - /// CHECK-START: void Main.SInvLoopF() licm (before) + /// CHECK-START: void Main.InvLoopF() licm (before) /// CHECK-DAG: StaticFieldGet loop:{{B\d+}} /// CHECK-DAG: StaticFieldGet loop:{{B\d+}} - /// CHECK-START: void Main.SInvLoopF() licm (after) + /// CHECK-START: void Main.InvLoopF() licm (after) /// CHECK-DAG: StaticFieldGet loop:none /// CHECK-DAG: StaticFieldGet loop:none - private static void SInvLoopF() { + private static void InvLoopF() { for (int i = 0; i < sArrF.length; i++) { sArrF[i] = sF; } } - /// CHECK-START: void Main.SInvLoopD() licm (before) + /// CHECK-START: void Main.InvLoopD() licm (before) /// CHECK-DAG: StaticFieldGet loop:{{B\d+}} /// CHECK-DAG: StaticFieldGet loop:{{B\d+}} - /// CHECK-START: void Main.SInvLoopD() licm (after) + /// CHECK-START: void Main.InvLoopD() licm (after) /// CHECK-DAG: StaticFieldGet loop:none /// CHECK-DAG: StaticFieldGet loop:none - private static void SInvLoopD() { + private static void InvLoopD() { for (int i = 0; i < sArrD.length; i++) { sArrD[i] = sD; } } - /// CHECK-START: void Main.SInvLoopL() licm (before) + /// CHECK-START: void Main.InvLoopL() licm (before) /// CHECK-DAG: StaticFieldGet loop:{{B\d+}} /// CHECK-DAG: StaticFieldGet loop:{{B\d+}} - /// CHECK-START: void Main.SInvLoopL() licm (after) + /// CHECK-START: void Main.InvLoopL() licm (after) /// CHECK-DAG: StaticFieldGet loop:none /// CHECK-DAG: StaticFieldGet loop:none - private static void SInvLoopL() { + private static void InvLoopL() { for (int i = 0; i < sArrL.length; i++) { sArrL[i] = sL; } @@ -214,7 +186,7 @@ public class Main { // Incorrect hoisting is detected by incorrect outcome. // - private static void SVarLoopZ() { + private static void VarLoopZ() { for (int i = 0; i < sArrZ.length; i++) { sArrZ[i] = sZ; if (i == 10) @@ -222,7 +194,7 @@ public class Main { } } - private static void SVarLoopB() { + private static void VarLoopB() { for (int i = 0; i < sArrB.length; i++) { sArrB[i] = sB; if (i == 10) @@ -230,7 +202,7 @@ public class Main { } } - private static void SVarLoopC() { + private static void VarLoopC() { for (int i = 0; i < sArrC.length; i++) { sArrC[i] = sC; if (i == 10) @@ -238,7 +210,7 @@ public class Main { } } - private static void SVarLoopS() { + private static void VarLoopS() { for (int i = 0; i < sArrS.length; i++) { sArrS[i] = sS; if (i == 10) @@ -246,7 +218,7 @@ public class Main { } } - private static void SVarLoopI() { + private static void VarLoopI() { for (int i = 0; i < sArrI.length; i++) { sArrI[i] = sI; if (i == 10) @@ -254,7 +226,7 @@ public class Main { } } - private static void SVarLoopJ() { + private static void VarLoopJ() { for (int i = 0; i < sArrJ.length; i++) { sArrJ[i] = sJ; if (i == 10) @@ -262,7 +234,7 @@ public class Main { } } - private static void SVarLoopF() { + private static void VarLoopF() { for (int i = 0; i < sArrF.length; i++) { sArrF[i] = sF; if (i == 10) @@ -270,7 +242,7 @@ public class Main { } } - private static void SVarLoopD() { + private static void VarLoopD() { for (int i = 0; i < sArrD.length; i++) { sArrD[i] = sD; if (i == 10) @@ -278,7 +250,7 @@ public class Main { } } - private static void SVarLoopL() { + private static void VarLoopL() { for (int i = 0; i < sArrL.length; i++) { sArrL[i] = sL; if (i == 10) @@ -292,474 +264,161 @@ public class Main { // In addition, the checker is used to detect no hoisting. // - /// CHECK-START: void Main.SCrossOverLoopZ() licm (before) - /// CHECK-DAG: ArrayGet loop:{{B\d+}} - /// CHECK-DAG: ArraySet loop:{{B\d+}} - - /// CHECK-START: void Main.SCrossOverLoopZ() licm (after) - /// CHECK-DAG: ArrayGet loop:{{B\d+}} - /// CHECK-DAG: ArraySet loop:{{B\d+}} - - private static void SCrossOverLoopZ() { - for (int i = 0; i < sArrZ.length; i++) { - sArrZ[i] = !sArrZ[20]; - } - } - - /// CHECK-START: void Main.SCrossOverLoopB() licm (before) - /// CHECK-DAG: ArrayGet loop:{{B\d+}} - /// CHECK-DAG: ArraySet loop:{{B\d+}} - - /// CHECK-START: void Main.SCrossOverLoopB() licm (after) - /// CHECK-DAG: ArrayGet loop:{{B\d+}} - /// CHECK-DAG: ArraySet loop:{{B\d+}} - - private static void SCrossOverLoopB() { - for (int i = 0; i < sArrB.length; i++) { - sArrB[i] = (byte)(sArrB[20] + 2); - } - } - - /// CHECK-START: void Main.SCrossOverLoopC() licm (before) - /// CHECK-DAG: ArrayGet loop:{{B\d+}} - /// CHECK-DAG: ArraySet loop:{{B\d+}} - - /// CHECK-START: void Main.SCrossOverLoopC() licm (after) - /// CHECK-DAG: ArrayGet loop:{{B\d+}} - /// CHECK-DAG: ArraySet loop:{{B\d+}} - - private static void SCrossOverLoopC() { - for (int i = 0; i < sArrC.length; i++) { - sArrC[i] = (char)(sArrC[20] + 2); - } - } - - /// CHECK-START: void Main.SCrossOverLoopS() licm (before) - /// CHECK-DAG: ArrayGet loop:{{B\d+}} - /// CHECK-DAG: ArraySet loop:{{B\d+}} - - /// CHECK-START: void Main.SCrossOverLoopS() licm (after) - /// CHECK-DAG: ArrayGet loop:{{B\d+}} - /// CHECK-DAG: ArraySet loop:{{B\d+}} - - private static void SCrossOverLoopS() { - for (int i = 0; i < sArrS.length; i++) { - sArrS[i] = (short)(sArrS[20] + 2); - } - } - - /// CHECK-START: void Main.SCrossOverLoopI() licm (before) - /// CHECK-DAG: ArrayGet loop:{{B\d+}} - /// CHECK-DAG: ArraySet loop:{{B\d+}} - - /// CHECK-START: void Main.SCrossOverLoopI() licm (after) - /// CHECK-DAG: ArrayGet loop:{{B\d+}} - /// CHECK-DAG: ArraySet loop:{{B\d+}} - - private static void SCrossOverLoopI() { - for (int i = 0; i < sArrI.length; i++) { - sArrI[i] = sArrI[20] + 2; - } - } - - /// CHECK-START: void Main.SCrossOverLoopJ() licm (before) - /// CHECK-DAG: ArrayGet loop:{{B\d+}} - /// CHECK-DAG: ArraySet loop:{{B\d+}} - - /// CHECK-START: void Main.SCrossOverLoopJ() licm (after) - /// CHECK-DAG: ArrayGet loop:{{B\d+}} - /// CHECK-DAG: ArraySet loop:{{B\d+}} - - private static void SCrossOverLoopJ() { - for (int i = 0; i < sArrJ.length; i++) { - sArrJ[i] = sArrJ[20] + 2; - } - } - - /// CHECK-START: void Main.SCrossOverLoopF() licm (before) - /// CHECK-DAG: ArrayGet loop:{{B\d+}} - /// CHECK-DAG: ArraySet loop:{{B\d+}} - - /// CHECK-START: void Main.SCrossOverLoopF() licm (after) - /// CHECK-DAG: ArrayGet loop:{{B\d+}} - /// CHECK-DAG: ArraySet loop:{{B\d+}} - - private static void SCrossOverLoopF() { - for (int i = 0; i < sArrF.length; i++) { - sArrF[i] = sArrF[20] + 2; - } - } - - /// CHECK-START: void Main.SCrossOverLoopD() licm (before) - /// CHECK-DAG: ArrayGet loop:{{B\d+}} - /// CHECK-DAG: ArraySet loop:{{B\d+}} - - /// CHECK-START: void Main.SCrossOverLoopD() licm (after) - /// CHECK-DAG: ArrayGet loop:{{B\d+}} - /// CHECK-DAG: ArraySet loop:{{B\d+}} - - private static void SCrossOverLoopD() { - for (int i = 0; i < sArrD.length; i++) { - sArrD[i] = sArrD[20] + 2; - } - } - - /// CHECK-START: void Main.SCrossOverLoopL() licm (before) - /// CHECK-DAG: ArrayGet loop:{{B\d+}} - /// CHECK-DAG: ArraySet loop:{{B\d+}} - - /// CHECK-START: void Main.SCrossOverLoopL() licm (after) - /// CHECK-DAG: ArrayGet loop:{{B\d+}} - /// CHECK-DAG: ArraySet loop:{{B\d+}} - - private static void SCrossOverLoopL() { - for (int i = 0; i < sArrL.length; i++) { - sArrL[i] = (sArrL[20] == anObject) ? anotherObject : anObject; - } - } - - // - // Loops on instance arrays with invariant instance field references. - // The checker is used to ensure hoisting occurred. - // - - /// CHECK-START: void Main.InvLoopZ() licm (before) - /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} - /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} - - /// CHECK-START: void Main.InvLoopZ() licm (after) - /// CHECK-DAG: InstanceFieldGet loop:none - /// CHECK-DAG: InstanceFieldGet loop:none - - private void InvLoopZ() { - for (int i = 0; i < mArrZ.length; i++) { - mArrZ[i] = mZ; - } - } - - /// CHECK-START: void Main.InvLoopB() licm (before) - /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} - /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} - - /// CHECK-START: void Main.InvLoopB() licm (after) - /// CHECK-DAG: InstanceFieldGet loop:none - /// CHECK-DAG: InstanceFieldGet loop:none - - private void InvLoopB() { - for (int i = 0; i < mArrB.length; i++) { - mArrB[i] = mB; - } - } - - /// CHECK-START: void Main.InvLoopC() licm (before) - /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} - /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} - - /// CHECK-START: void Main.InvLoopC() licm (after) - /// CHECK-DAG: InstanceFieldGet loop:none - /// CHECK-DAG: InstanceFieldGet loop:none - - private void InvLoopC() { - for (int i = 0; i < mArrC.length; i++) { - mArrC[i] = mC; - } - } - - /// CHECK-START: void Main.InvLoopS() licm (before) - /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} - /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} - - /// CHECK-START: void Main.InvLoopS() licm (after) - /// CHECK-DAG: InstanceFieldGet loop:none - /// CHECK-DAG: InstanceFieldGet loop:none - - private void InvLoopS() { - for (int i = 0; i < mArrS.length; i++) { - mArrS[i] = mS; - } - } - - /// CHECK-START: void Main.InvLoopI() licm (before) - /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} - /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} - - /// CHECK-START: void Main.InvLoopI() licm (after) - /// CHECK-DAG: InstanceFieldGet loop:none - /// CHECK-DAG: InstanceFieldGet loop:none - - private void InvLoopI() { - for (int i = 0; i < mArrI.length; i++) { - mArrI[i] = mI; - } - } - - /// CHECK-START: void Main.InvLoopJ() licm (before) - /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} - /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} - - /// CHECK-START: void Main.InvLoopJ() licm (after) - /// CHECK-DAG: InstanceFieldGet loop:none - /// CHECK-DAG: InstanceFieldGet loop:none - - private void InvLoopJ() { - for (int i = 0; i < mArrJ.length; i++) { - mArrJ[i] = mJ; - } - } - - /// CHECK-START: void Main.InvLoopF() licm (before) - /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} - /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} - - /// CHECK-START: void Main.InvLoopF() licm (after) - /// CHECK-DAG: InstanceFieldGet loop:none - /// CHECK-DAG: InstanceFieldGet loop:none - - private void InvLoopF() { - for (int i = 0; i < mArrF.length; i++) { - mArrF[i] = mF; - } - } - - /// CHECK-START: void Main.InvLoopD() licm (before) - /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} - /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} - - /// CHECK-START: void Main.InvLoopD() licm (after) - /// CHECK-DAG: InstanceFieldGet loop:none - /// CHECK-DAG: InstanceFieldGet loop:none - - private void InvLoopD() { - for (int i = 0; i < mArrD.length; i++) { - mArrD[i] = mD; - } - } - - /// CHECK-START: void Main.InvLoopL() licm (before) - /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} - /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} - - /// CHECK-START: void Main.InvLoopL() licm (after) - /// CHECK-DAG: InstanceFieldGet loop:none - /// CHECK-DAG: InstanceFieldGet loop:none - - private void InvLoopL() { - for (int i = 0; i < mArrL.length; i++) { - mArrL[i] = mL; - } - } - - // - // Loops on instance arrays with variant instance field references. - // Incorrect hoisting is detected by incorrect outcome. - // - - private void VarLoopZ() { - for (int i = 0; i < mArrZ.length; i++) { - mArrZ[i] = mZ; - if (i == 10) - mZ = !mZ; - } - } - - private void VarLoopB() { - for (int i = 0; i < mArrB.length; i++) { - mArrB[i] = mB; - if (i == 10) - mB++; - } - } - - private void VarLoopC() { - for (int i = 0; i < mArrC.length; i++) { - mArrC[i] = mC; - if (i == 10) - mC++; - } - } - - private void VarLoopS() { - for (int i = 0; i < mArrS.length; i++) { - mArrS[i] = mS; - if (i == 10) - mS++; - } - } - - private void VarLoopI() { - for (int i = 0; i < mArrI.length; i++) { - mArrI[i] = mI; - if (i == 10) - mI++; - } - } - - private void VarLoopJ() { - for (int i = 0; i < mArrJ.length; i++) { - mArrJ[i] = mJ; - if (i == 10) - mJ++; - } - } - - private void VarLoopF() { - for (int i = 0; i < mArrF.length; i++) { - mArrF[i] = mF; - if (i == 10) - mF++; - } - } - - private void VarLoopD() { - for (int i = 0; i < mArrD.length; i++) { - mArrD[i] = mD; - if (i == 10) - mD++; - } - } - - private void VarLoopL() { - for (int i = 0; i < mArrL.length; i++) { - mArrL[i] = mL; - if (i == 10) - mL = anotherObject; - } - } - - // - // Loops on instance arrays with a cross-over reference. - // Incorrect hoisting is detected by incorrect outcome. - // In addition, the checker is used to detect no hoisting. - // - /// CHECK-START: void Main.CrossOverLoopZ() licm (before) + /// CHECK-DAG: ArraySet loop:none /// CHECK-DAG: ArrayGet loop:{{B\d+}} /// CHECK-DAG: ArraySet loop:{{B\d+}} /// CHECK-START: void Main.CrossOverLoopZ() licm (after) + /// CHECK-DAG: ArraySet loop:none /// CHECK-DAG: ArrayGet loop:{{B\d+}} /// CHECK-DAG: ArraySet loop:{{B\d+}} - private void CrossOverLoopZ() { - for (int i = 0; i < mArrZ.length; i++) { - mArrZ[i] = !mArrZ[20]; + private static void CrossOverLoopZ() { + sArrZ[20] = false; + for (int i = 0; i < sArrZ.length; i++) { + sArrZ[i] = !sArrZ[20]; } } /// CHECK-START: void Main.CrossOverLoopB() licm (before) + /// CHECK-DAG: ArraySet loop:none /// CHECK-DAG: ArrayGet loop:{{B\d+}} /// CHECK-DAG: ArraySet loop:{{B\d+}} /// CHECK-START: void Main.CrossOverLoopB() licm (after) + /// CHECK-DAG: ArraySet loop:none /// CHECK-DAG: ArrayGet loop:{{B\d+}} /// CHECK-DAG: ArraySet loop:{{B\d+}} - private void CrossOverLoopB() { - for (int i = 0; i < mArrB.length; i++) { - mArrB[i] = (byte)(mArrB[20] + 2); + private static void CrossOverLoopB() { + sArrB[20] = 11; + for (int i = 0; i < sArrB.length; i++) { + sArrB[i] = (byte)(sArrB[20] + 2); } } /// CHECK-START: void Main.CrossOverLoopC() licm (before) + /// CHECK-DAG: ArraySet loop:none /// CHECK-DAG: ArrayGet loop:{{B\d+}} /// CHECK-DAG: ArraySet loop:{{B\d+}} /// CHECK-START: void Main.CrossOverLoopC() licm (after) + /// CHECK-DAG: ArraySet loop:none /// CHECK-DAG: ArrayGet loop:{{B\d+}} /// CHECK-DAG: ArraySet loop:{{B\d+}} - private void CrossOverLoopC() { - for (int i = 0; i < mArrC.length; i++) { - mArrC[i] = (char)(mArrC[20] + 2); + private static void CrossOverLoopC() { + sArrC[20] = 11; + for (int i = 0; i < sArrC.length; i++) { + sArrC[i] = (char)(sArrC[20] + 2); } } /// CHECK-START: void Main.CrossOverLoopS() licm (before) + /// CHECK-DAG: ArraySet loop:none /// CHECK-DAG: ArrayGet loop:{{B\d+}} /// CHECK-DAG: ArraySet loop:{{B\d+}} /// CHECK-START: void Main.CrossOverLoopS() licm (after) + /// CHECK-DAG: ArraySet loop:none /// CHECK-DAG: ArrayGet loop:{{B\d+}} /// CHECK-DAG: ArraySet loop:{{B\d+}} - private void CrossOverLoopS() { - for (int i = 0; i < mArrS.length; i++) { - mArrS[i] = (short)(mArrS[20] + 2); + private static void CrossOverLoopS() { + sArrS[20] = 11; + for (int i = 0; i < sArrS.length; i++) { + sArrS[i] = (short)(sArrS[20] + 2); } } /// CHECK-START: void Main.CrossOverLoopI() licm (before) + /// CHECK-DAG: ArraySet loop:none /// CHECK-DAG: ArrayGet loop:{{B\d+}} /// CHECK-DAG: ArraySet loop:{{B\d+}} /// CHECK-START: void Main.CrossOverLoopI() licm (after) + /// CHECK-DAG: ArraySet loop:none /// CHECK-DAG: ArrayGet loop:{{B\d+}} /// CHECK-DAG: ArraySet loop:{{B\d+}} - private void CrossOverLoopI() { - for (int i = 0; i < mArrI.length; i++) { - mArrI[i] = mArrI[20] + 2; + private static void CrossOverLoopI() { + sArrI[20] = 11; + for (int i = 0; i < sArrI.length; i++) { + sArrI[i] = sArrI[20] + 2; } } /// CHECK-START: void Main.CrossOverLoopJ() licm (before) + /// CHECK-DAG: ArraySet loop:none /// CHECK-DAG: ArrayGet loop:{{B\d+}} /// CHECK-DAG: ArraySet loop:{{B\d+}} /// CHECK-START: void Main.CrossOverLoopJ() licm (after) + /// CHECK-DAG: ArraySet loop:none /// CHECK-DAG: ArrayGet loop:{{B\d+}} /// CHECK-DAG: ArraySet loop:{{B\d+}} - private void CrossOverLoopJ() { - for (int i = 0; i < mArrJ.length; i++) { - mArrJ[i] = mArrJ[20] + 2; + private static void CrossOverLoopJ() { + sArrJ[20] = 11; + for (int i = 0; i < sArrJ.length; i++) { + sArrJ[i] = sArrJ[20] + 2; } } /// CHECK-START: void Main.CrossOverLoopF() licm (before) + /// CHECK-DAG: ArraySet loop:none /// CHECK-DAG: ArrayGet loop:{{B\d+}} /// CHECK-DAG: ArraySet loop:{{B\d+}} /// CHECK-START: void Main.CrossOverLoopF() licm (after) + /// CHECK-DAG: ArraySet loop:none /// CHECK-DAG: ArrayGet loop:{{B\d+}} /// CHECK-DAG: ArraySet loop:{{B\d+}} - private void CrossOverLoopF() { - for (int i = 0; i < mArrF.length; i++) { - mArrF[i] = mArrF[20] + 2; + private static void CrossOverLoopF() { + sArrF[20] = 11; + for (int i = 0; i < sArrF.length; i++) { + sArrF[i] = sArrF[20] + 2; } } /// CHECK-START: void Main.CrossOverLoopD() licm (before) + /// CHECK-DAG: ArraySet loop:none /// CHECK-DAG: ArrayGet loop:{{B\d+}} /// CHECK-DAG: ArraySet loop:{{B\d+}} /// CHECK-START: void Main.CrossOverLoopD() licm (after) + /// CHECK-DAG: ArraySet loop:none /// CHECK-DAG: ArrayGet loop:{{B\d+}} /// CHECK-DAG: ArraySet loop:{{B\d+}} - private void CrossOverLoopD() { - for (int i = 0; i < mArrD.length; i++) { - mArrD[i] = mArrD[20] + 2; + private static void CrossOverLoopD() { + sArrD[20] = 11; + for (int i = 0; i < sArrD.length; i++) { + sArrD[i] = sArrD[20] + 2; } } /// CHECK-START: void Main.CrossOverLoopL() licm (before) + /// CHECK-DAG: ArraySet loop:none /// CHECK-DAG: ArrayGet loop:{{B\d+}} /// CHECK-DAG: ArraySet loop:{{B\d+}} /// CHECK-START: void Main.CrossOverLoopL() licm (after) + /// CHECK-DAG: ArraySet loop:none /// CHECK-DAG: ArrayGet loop:{{B\d+}} /// CHECK-DAG: ArraySet loop:{{B\d+}} - private void CrossOverLoopL() { - for (int i = 0; i < mArrL.length; i++) { - mArrL[i] = (mArrL[20] == anObject) ? anotherObject : anObject; + private static void CrossOverLoopL() { + sArrL[20] = anotherObject; + for (int i = 0; i < sArrL.length; i++) { + sArrL[i] = (sArrL[20] == anObject) ? anotherObject : anObject; } } // - // Misc. tests on false cross-over loops with data types (I/F and J/D) that used + // False cross-over loops on static arrays with data types (I/F and J/D) that used // to be aliased in an older version of the compiler. This alias has been removed, // however, which enables hoisting the invariant array reference. // @@ -833,12 +492,11 @@ public class Main { } // - // Driver and testers. + // Main driver and testers. // public static void main(String[] args) { DoStaticTests(); - new Main().DoInstanceTests(); System.out.println("passed"); } @@ -846,139 +504,139 @@ public class Main { // Type Z. sZ = true; sArrZ = new boolean[100]; - SInvLoopZ(); + InvLoopZ(); for (int i = 0; i < sArrZ.length; i++) { expectEquals(true, sArrZ[i]); } - SVarLoopZ(); + VarLoopZ(); for (int i = 0; i < sArrZ.length; i++) { expectEquals(i <= 10, sArrZ[i]); } - SCrossOverLoopZ(); + CrossOverLoopZ(); for (int i = 0; i < sArrZ.length; i++) { expectEquals(i <= 20, sArrZ[i]); } // Type B. sB = 1; sArrB = new byte[100]; - SInvLoopB(); + InvLoopB(); for (int i = 0; i < sArrB.length; i++) { expectEquals(1, sArrB[i]); } - SVarLoopB(); + VarLoopB(); for (int i = 0; i < sArrB.length; i++) { expectEquals(i <= 10 ? 1 : 2, sArrB[i]); } - SCrossOverLoopB(); + CrossOverLoopB(); for (int i = 0; i < sArrB.length; i++) { - expectEquals(i <= 20 ? 4 : 6, sArrB[i]); + expectEquals(i <= 20 ? 13 : 15, sArrB[i]); } // Type C. sC = 2; sArrC = new char[100]; - SInvLoopC(); + InvLoopC(); for (int i = 0; i < sArrC.length; i++) { expectEquals(2, sArrC[i]); } - SVarLoopC(); + VarLoopC(); for (int i = 0; i < sArrC.length; i++) { expectEquals(i <= 10 ? 2 : 3, sArrC[i]); } - SCrossOverLoopC(); + CrossOverLoopC(); for (int i = 0; i < sArrC.length; i++) { - expectEquals(i <= 20 ? 5 : 7, sArrC[i]); + expectEquals(i <= 20 ? 13 : 15, sArrC[i]); } // Type S. sS = 3; sArrS = new short[100]; - SInvLoopS(); + InvLoopS(); for (int i = 0; i < sArrS.length; i++) { expectEquals(3, sArrS[i]); } - SVarLoopS(); + VarLoopS(); for (int i = 0; i < sArrS.length; i++) { expectEquals(i <= 10 ? 3 : 4, sArrS[i]); } - SCrossOverLoopS(); + CrossOverLoopS(); for (int i = 0; i < sArrS.length; i++) { - expectEquals(i <= 20 ? 6 : 8, sArrS[i]); + expectEquals(i <= 20 ? 13 : 15, sArrS[i]); } // Type I. sI = 4; sArrI = new int[100]; - SInvLoopI(); + InvLoopI(); for (int i = 0; i < sArrI.length; i++) { expectEquals(4, sArrI[i]); } - SVarLoopI(); + VarLoopI(); for (int i = 0; i < sArrI.length; i++) { expectEquals(i <= 10 ? 4 : 5, sArrI[i]); } - SCrossOverLoopI(); + CrossOverLoopI(); for (int i = 0; i < sArrI.length; i++) { - expectEquals(i <= 20 ? 7 : 9, sArrI[i]); + expectEquals(i <= 20 ? 13 : 15, sArrI[i]); } // Type J. sJ = 5; sArrJ = new long[100]; - SInvLoopJ(); + InvLoopJ(); for (int i = 0; i < sArrJ.length; i++) { expectEquals(5, sArrJ[i]); } - SVarLoopJ(); + VarLoopJ(); for (int i = 0; i < sArrJ.length; i++) { expectEquals(i <= 10 ? 5 : 6, sArrJ[i]); } - SCrossOverLoopJ(); + CrossOverLoopJ(); for (int i = 0; i < sArrJ.length; i++) { - expectEquals(i <= 20 ? 8 : 10, sArrJ[i]); + expectEquals(i <= 20 ? 13 : 15, sArrJ[i]); } // Type F. sF = 6.0f; sArrF = new float[100]; - SInvLoopF(); + InvLoopF(); for (int i = 0; i < sArrF.length; i++) { expectEquals(6, sArrF[i]); } - SVarLoopF(); + VarLoopF(); for (int i = 0; i < sArrF.length; i++) { expectEquals(i <= 10 ? 6 : 7, sArrF[i]); } - SCrossOverLoopF(); + CrossOverLoopF(); for (int i = 0; i < sArrF.length; i++) { - expectEquals(i <= 20 ? 9 : 11, sArrF[i]); + expectEquals(i <= 20 ? 13 : 15, sArrF[i]); } // Type D. sD = 7.0; sArrD = new double[100]; - SInvLoopD(); + InvLoopD(); for (int i = 0; i < sArrD.length; i++) { expectEquals(7.0, sArrD[i]); } - SVarLoopD(); + VarLoopD(); for (int i = 0; i < sArrD.length; i++) { expectEquals(i <= 10 ? 7 : 8, sArrD[i]); } - SCrossOverLoopD(); + CrossOverLoopD(); for (int i = 0; i < sArrD.length; i++) { - expectEquals(i <= 20 ? 10 : 12, sArrD[i]); + expectEquals(i <= 20 ? 13 : 15, sArrD[i]); } // Type L. sL = anObject; sArrL = new Object[100]; - SInvLoopL(); + InvLoopL(); for (int i = 0; i < sArrL.length; i++) { expectEquals(anObject, sArrL[i]); } - SVarLoopL(); + VarLoopL(); for (int i = 0; i < sArrL.length; i++) { expectEquals(i <= 10 ? anObject : anotherObject, sArrL[i]); } - SCrossOverLoopL(); + CrossOverLoopL(); for (int i = 0; i < sArrL.length; i++) { expectEquals(i <= 20 ? anObject : anotherObject, sArrL[i]); } - // Misc. tests. + // False cross-over. FalseCrossOverLoop1(); for (int i = 0; i < sArrI.length; i++) { expectEquals(-3, sArrI[i]); @@ -997,144 +655,6 @@ public class Main { } } - private void DoInstanceTests() { - // Type Z. - mZ = true; - mArrZ = new boolean[100]; - InvLoopZ(); - for (int i = 0; i < mArrZ.length; i++) { - expectEquals(true, mArrZ[i]); - } - VarLoopZ(); - for (int i = 0; i < mArrZ.length; i++) { - expectEquals(i <= 10, mArrZ[i]); - } - CrossOverLoopZ(); - for (int i = 0; i < mArrZ.length; i++) { - expectEquals(i <= 20, mArrZ[i]); - } - // Type B. - mB = 1; - mArrB = new byte[100]; - InvLoopB(); - for (int i = 0; i < mArrB.length; i++) { - expectEquals(1, mArrB[i]); - } - VarLoopB(); - for (int i = 0; i < mArrB.length; i++) { - expectEquals(i <= 10 ? 1 : 2, mArrB[i]); - } - CrossOverLoopB(); - for (int i = 0; i < mArrB.length; i++) { - expectEquals(i <= 20 ? 4 : 6, mArrB[i]); - } - // Type C. - mC = 2; - mArrC = new char[100]; - InvLoopC(); - for (int i = 0; i < mArrC.length; i++) { - expectEquals(2, mArrC[i]); - } - VarLoopC(); - for (int i = 0; i < mArrC.length; i++) { - expectEquals(i <= 10 ? 2 : 3, mArrC[i]); - } - CrossOverLoopC(); - for (int i = 0; i < mArrC.length; i++) { - expectEquals(i <= 20 ? 5 : 7, mArrC[i]); - } - // Type S. - mS = 3; - mArrS = new short[100]; - InvLoopS(); - for (int i = 0; i < mArrS.length; i++) { - expectEquals(3, mArrS[i]); - } - VarLoopS(); - for (int i = 0; i < mArrS.length; i++) { - expectEquals(i <= 10 ? 3 : 4, mArrS[i]); - } - CrossOverLoopS(); - for (int i = 0; i < mArrS.length; i++) { - expectEquals(i <= 20 ? 6 : 8, mArrS[i]); - } - // Type I. - mI = 4; - mArrI = new int[100]; - InvLoopI(); - for (int i = 0; i < mArrI.length; i++) { - expectEquals(4, mArrI[i]); - } - VarLoopI(); - for (int i = 0; i < mArrI.length; i++) { - expectEquals(i <= 10 ? 4 : 5, mArrI[i]); - } - CrossOverLoopI(); - for (int i = 0; i < mArrI.length; i++) { - expectEquals(i <= 20 ? 7 : 9, mArrI[i]); - } - // Type J. - mJ = 5; - mArrJ = new long[100]; - InvLoopJ(); - for (int i = 0; i < mArrJ.length; i++) { - expectEquals(5, mArrJ[i]); - } - VarLoopJ(); - for (int i = 0; i < mArrJ.length; i++) { - expectEquals(i <= 10 ? 5 : 6, mArrJ[i]); - } - CrossOverLoopJ(); - for (int i = 0; i < mArrJ.length; i++) { - expectEquals(i <= 20 ? 8 : 10, mArrJ[i]); - } - // Type F. - mF = 6.0f; - mArrF = new float[100]; - InvLoopF(); - for (int i = 0; i < mArrF.length; i++) { - expectEquals(6, mArrF[i]); - } - VarLoopF(); - for (int i = 0; i < mArrF.length; i++) { - expectEquals(i <= 10 ? 6 : 7, mArrF[i]); - } - CrossOverLoopF(); - for (int i = 0; i < mArrF.length; i++) { - expectEquals(i <= 20 ? 9 : 11, mArrF[i]); - } - // Type D. - mD = 7.0; - mArrD = new double[100]; - InvLoopD(); - for (int i = 0; i < mArrD.length; i++) { - expectEquals(7.0, mArrD[i]); - } - VarLoopD(); - for (int i = 0; i < mArrD.length; i++) { - expectEquals(i <= 10 ? 7 : 8, mArrD[i]); - } - CrossOverLoopD(); - for (int i = 0; i < mArrD.length; i++) { - expectEquals(i <= 20 ? 10 : 12, mArrD[i]); - } - // Type L. - mL = anObject; - mArrL = new Object[100]; - InvLoopL(); - for (int i = 0; i < mArrL.length; i++) { - expectEquals(anObject, mArrL[i]); - } - VarLoopL(); - for (int i = 0; i < mArrL.length; i++) { - expectEquals(i <= 10 ? anObject : anotherObject, mArrL[i]); - } - CrossOverLoopL(); - for (int i = 0; i < mArrL.length; i++) { - expectEquals(i <= 20 ? anObject : anotherObject, mArrL[i]); - } - } - private static void expectEquals(boolean expected, boolean result) { if (expected != result) { throw new Error("Expected: " + expected + ", found: " + result); diff --git a/test/525-checker-arrays-fields2/expected.txt b/test/525-checker-arrays-fields2/expected.txt new file mode 100644 index 0000000000..b0aad4deb5 --- /dev/null +++ b/test/525-checker-arrays-fields2/expected.txt @@ -0,0 +1 @@ +passed diff --git a/test/525-checker-arrays-fields2/info.txt b/test/525-checker-arrays-fields2/info.txt new file mode 100644 index 0000000000..3464e540a6 --- /dev/null +++ b/test/525-checker-arrays-fields2/info.txt @@ -0,0 +1 @@ +Test on (in)variant instance field and array references in loops. diff --git a/test/525-checker-arrays-fields2/src/Main.java b/test/525-checker-arrays-fields2/src/Main.java new file mode 100644 index 0000000000..2aa40fcdc1 --- /dev/null +++ b/test/525-checker-arrays-fields2/src/Main.java @@ -0,0 +1,711 @@ +/* + * 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. + */ + +// +// Test on (in)variant instance field and array references in loops. +// +public class Main { + + private static Object anObject = new Object(); + private static Object anotherObject = new Object(); + + // + // Instance fields. + // + + private boolean mZ; + private byte mB; + private char mC; + private short mS; + private int mI; + private long mJ; + private float mF; + private double mD; + private Object mL; + + // + // Instance arrays. + // + + private boolean[] mArrZ; + private byte[] mArrB; + private char[] mArrC; + private short[] mArrS; + private int[] mArrI; + private long[] mArrJ; + private float[] mArrF; + private double[] mArrD; + private Object[] mArrL; + + // + // Loops on instance arrays with invariant instance field references. + // The checker is used to ensure hoisting occurred. + // + + /// CHECK-START: void Main.InvLoopZ() licm (before) + /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} + /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} + + /// CHECK-START: void Main.InvLoopZ() licm (after) + /// CHECK-DAG: InstanceFieldGet loop:none + /// CHECK-DAG: InstanceFieldGet loop:none + + private void InvLoopZ() { + for (int i = 0; i < mArrZ.length; i++) { + mArrZ[i] = mZ; + } + } + + /// CHECK-START: void Main.InvLoopB() licm (before) + /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} + /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} + + /// CHECK-START: void Main.InvLoopB() licm (after) + /// CHECK-DAG: InstanceFieldGet loop:none + /// CHECK-DAG: InstanceFieldGet loop:none + + private void InvLoopB() { + for (int i = 0; i < mArrB.length; i++) { + mArrB[i] = mB; + } + } + + /// CHECK-START: void Main.InvLoopC() licm (before) + /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} + /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} + + /// CHECK-START: void Main.InvLoopC() licm (after) + /// CHECK-DAG: InstanceFieldGet loop:none + /// CHECK-DAG: InstanceFieldGet loop:none + + private void InvLoopC() { + for (int i = 0; i < mArrC.length; i++) { + mArrC[i] = mC; + } + } + + /// CHECK-START: void Main.InvLoopS() licm (before) + /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} + /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} + + /// CHECK-START: void Main.InvLoopS() licm (after) + /// CHECK-DAG: InstanceFieldGet loop:none + /// CHECK-DAG: InstanceFieldGet loop:none + + private void InvLoopS() { + for (int i = 0; i < mArrS.length; i++) { + mArrS[i] = mS; + } + } + + /// CHECK-START: void Main.InvLoopI() licm (before) + /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} + /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} + + /// CHECK-START: void Main.InvLoopI() licm (after) + /// CHECK-DAG: InstanceFieldGet loop:none + /// CHECK-DAG: InstanceFieldGet loop:none + + private void InvLoopI() { + for (int i = 0; i < mArrI.length; i++) { + mArrI[i] = mI; + } + } + + /// CHECK-START: void Main.InvLoopJ() licm (before) + /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} + /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} + + /// CHECK-START: void Main.InvLoopJ() licm (after) + /// CHECK-DAG: InstanceFieldGet loop:none + /// CHECK-DAG: InstanceFieldGet loop:none + + private void InvLoopJ() { + for (int i = 0; i < mArrJ.length; i++) { + mArrJ[i] = mJ; + } + } + + /// CHECK-START: void Main.InvLoopF() licm (before) + /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} + /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} + + /// CHECK-START: void Main.InvLoopF() licm (after) + /// CHECK-DAG: InstanceFieldGet loop:none + /// CHECK-DAG: InstanceFieldGet loop:none + + private void InvLoopF() { + for (int i = 0; i < mArrF.length; i++) { + mArrF[i] = mF; + } + } + + /// CHECK-START: void Main.InvLoopD() licm (before) + /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} + /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} + + /// CHECK-START: void Main.InvLoopD() licm (after) + /// CHECK-DAG: InstanceFieldGet loop:none + /// CHECK-DAG: InstanceFieldGet loop:none + + private void InvLoopD() { + for (int i = 0; i < mArrD.length; i++) { + mArrD[i] = mD; + } + } + + /// CHECK-START: void Main.InvLoopL() licm (before) + /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} + /// CHECK-DAG: InstanceFieldGet loop:{{B\d+}} + + /// CHECK-START: void Main.InvLoopL() licm (after) + /// CHECK-DAG: InstanceFieldGet loop:none + /// CHECK-DAG: InstanceFieldGet loop:none + + private void InvLoopL() { + for (int i = 0; i < mArrL.length; i++) { + mArrL[i] = mL; + } + } + + // + // Loops on instance arrays with variant instance field references. + // Incorrect hoisting is detected by incorrect outcome. + // + + private void VarLoopZ() { + for (int i = 0; i < mArrZ.length; i++) { + mArrZ[i] = mZ; + if (i == 10) + mZ = !mZ; + } + } + + private void VarLoopB() { + for (int i = 0; i < mArrB.length; i++) { + mArrB[i] = mB; + if (i == 10) + mB++; + } + } + + private void VarLoopC() { + for (int i = 0; i < mArrC.length; i++) { + mArrC[i] = mC; + if (i == 10) + mC++; + } + } + + private void VarLoopS() { + for (int i = 0; i < mArrS.length; i++) { + mArrS[i] = mS; + if (i == 10) + mS++; + } + } + + private void VarLoopI() { + for (int i = 0; i < mArrI.length; i++) { + mArrI[i] = mI; + if (i == 10) + mI++; + } + } + + private void VarLoopJ() { + for (int i = 0; i < mArrJ.length; i++) { + mArrJ[i] = mJ; + if (i == 10) + mJ++; + } + } + + private void VarLoopF() { + for (int i = 0; i < mArrF.length; i++) { + mArrF[i] = mF; + if (i == 10) + mF++; + } + } + + private void VarLoopD() { + for (int i = 0; i < mArrD.length; i++) { + mArrD[i] = mD; + if (i == 10) + mD++; + } + } + + private void VarLoopL() { + for (int i = 0; i < mArrL.length; i++) { + mArrL[i] = mL; + if (i == 10) + mL = anotherObject; + } + } + + // + // Loops on instance arrays with a cross-over reference. + // Incorrect hoisting is detected by incorrect outcome. + // In addition, the checker is used to detect no hoisting. + // + + /// CHECK-START: void Main.CrossOverLoopZ() licm (before) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + + /// CHECK-START: void Main.CrossOverLoopZ() licm (after) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + + private void CrossOverLoopZ() { + mArrZ[20] = false; + for (int i = 0; i < mArrZ.length; i++) { + mArrZ[i] = !mArrZ[20]; + } + } + + /// CHECK-START: void Main.CrossOverLoopB() licm (before) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + + /// CHECK-START: void Main.CrossOverLoopB() licm (after) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + + private void CrossOverLoopB() { + mArrB[20] = 111; + for (int i = 0; i < mArrB.length; i++) { + mArrB[i] = (byte)(mArrB[20] + 2); + } + } + + /// CHECK-START: void Main.CrossOverLoopC() licm (before) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + + /// CHECK-START: void Main.CrossOverLoopC() licm (after) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + + private void CrossOverLoopC() { + mArrC[20] = 111; + for (int i = 0; i < mArrC.length; i++) { + mArrC[i] = (char)(mArrC[20] + 2); + } + } + + /// CHECK-START: void Main.CrossOverLoopS() licm (before) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + + /// CHECK-START: void Main.CrossOverLoopS() licm (after) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + + private void CrossOverLoopS() { + mArrS[20] = 111; + for (int i = 0; i < mArrS.length; i++) { + mArrS[i] = (short)(mArrS[20] + 2); + } + } + + /// CHECK-START: void Main.CrossOverLoopI() licm (before) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + + /// CHECK-START: void Main.CrossOverLoopI() licm (after) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + + private void CrossOverLoopI() { + mArrI[20] = 111; + for (int i = 0; i < mArrI.length; i++) { + mArrI[i] = mArrI[20] + 2; + } + } + + /// CHECK-START: void Main.CrossOverLoopJ() licm (before) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + + /// CHECK-START: void Main.CrossOverLoopJ() licm (after) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + + private void CrossOverLoopJ() { + mArrJ[20] = 111; + for (int i = 0; i < mArrJ.length; i++) { + mArrJ[i] = mArrJ[20] + 2; + } + } + + /// CHECK-START: void Main.CrossOverLoopF() licm (before) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + + /// CHECK-START: void Main.CrossOverLoopF() licm (after) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + + private void CrossOverLoopF() { + mArrF[20] = 111; + for (int i = 0; i < mArrF.length; i++) { + mArrF[i] = mArrF[20] + 2; + } + } + + /// CHECK-START: void Main.CrossOverLoopD() licm (before) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + + /// CHECK-START: void Main.CrossOverLoopD() licm (after) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + + private void CrossOverLoopD() { + mArrD[20] = 111; + for (int i = 0; i < mArrD.length; i++) { + mArrD[i] = mArrD[20] + 2; + } + } + + /// CHECK-START: void Main.CrossOverLoopL() licm (before) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + + /// CHECK-START: void Main.CrossOverLoopL() licm (after) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + + private void CrossOverLoopL() { + mArrL[20] = anotherObject; + for (int i = 0; i < mArrL.length; i++) { + mArrL[i] = (mArrL[20] == anObject) ? anotherObject : anObject; + } + } + + // + // False cross-over loops on instance arrays with data types (I/F and J/D) that used + // to be aliased in an older version of the compiler. This alias has been removed, + // however, which enables hoisting the invariant array reference. + // + + /// CHECK-START: void Main.FalseCrossOverLoop1() licm (before) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + + /// CHECK-START: void Main.FalseCrossOverLoop1() licm (after) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:none + /// CHECK-DAG: ArraySet loop:{{B\d+}} + + private void FalseCrossOverLoop1() { + mArrF[20] = -1; + for (int i = 0; i < mArrI.length; i++) { + mArrI[i] = (int) mArrF[20] - 2; + } + } + + /// CHECK-START: void Main.FalseCrossOverLoop2() licm (before) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + + /// CHECK-START: void Main.FalseCrossOverLoop2() licm (after) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:none + /// CHECK-DAG: ArraySet loop:{{B\d+}} + + private void FalseCrossOverLoop2() { + mArrI[20] = -2; + for (int i = 0; i < mArrF.length; i++) { + mArrF[i] = mArrI[20] - 2; + } + } + + /// CHECK-START: void Main.FalseCrossOverLoop3() licm (before) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + + /// CHECK-START: void Main.FalseCrossOverLoop3() licm (after) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:none + /// CHECK-DAG: ArraySet loop:{{B\d+}} + + private void FalseCrossOverLoop3() { + mArrD[20] = -3; + for (int i = 0; i < mArrJ.length; i++) { + mArrJ[i] = (long) mArrD[20] - 2; + } + } + + /// CHECK-START: void Main.FalseCrossOverLoop4() licm (before) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + + /// CHECK-START: void Main.FalseCrossOverLoop4() licm (after) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:none + /// CHECK-DAG: ArraySet loop:{{B\d+}} + + private void FalseCrossOverLoop4() { + mArrJ[20] = -4; + for (int i = 0; i < mArrD.length; i++) { + mArrD[i] = mArrJ[20] - 2; + } + } + + // + // Main driver and testers. + // + + public static void main(String[] args) { + new Main().DoInstanceTests(); + System.out.println("passed"); + } + + private void DoInstanceTests() { + // Type Z. + mZ = true; + mArrZ = new boolean[100]; + InvLoopZ(); + for (int i = 0; i < mArrZ.length; i++) { + expectEquals(true, mArrZ[i]); + } + VarLoopZ(); + for (int i = 0; i < mArrZ.length; i++) { + expectEquals(i <= 10, mArrZ[i]); + } + CrossOverLoopZ(); + for (int i = 0; i < mArrZ.length; i++) { + expectEquals(i <= 20, mArrZ[i]); + } + // Type B. + mB = 1; + mArrB = new byte[100]; + InvLoopB(); + for (int i = 0; i < mArrB.length; i++) { + expectEquals(1, mArrB[i]); + } + VarLoopB(); + for (int i = 0; i < mArrB.length; i++) { + expectEquals(i <= 10 ? 1 : 2, mArrB[i]); + } + CrossOverLoopB(); + for (int i = 0; i < mArrB.length; i++) { + expectEquals(i <= 20 ? 113 : 115, mArrB[i]); + } + // Type C. + mC = 2; + mArrC = new char[100]; + InvLoopC(); + for (int i = 0; i < mArrC.length; i++) { + expectEquals(2, mArrC[i]); + } + VarLoopC(); + for (int i = 0; i < mArrC.length; i++) { + expectEquals(i <= 10 ? 2 : 3, mArrC[i]); + } + CrossOverLoopC(); + for (int i = 0; i < mArrC.length; i++) { + expectEquals(i <= 20 ? 113 : 115, mArrC[i]); + } + // Type S. + mS = 3; + mArrS = new short[100]; + InvLoopS(); + for (int i = 0; i < mArrS.length; i++) { + expectEquals(3, mArrS[i]); + } + VarLoopS(); + for (int i = 0; i < mArrS.length; i++) { + expectEquals(i <= 10 ? 3 : 4, mArrS[i]); + } + CrossOverLoopS(); + for (int i = 0; i < mArrS.length; i++) { + expectEquals(i <= 20 ? 113 : 115, mArrS[i]); + } + // Type I. + mI = 4; + mArrI = new int[100]; + InvLoopI(); + for (int i = 0; i < mArrI.length; i++) { + expectEquals(4, mArrI[i]); + } + VarLoopI(); + for (int i = 0; i < mArrI.length; i++) { + expectEquals(i <= 10 ? 4 : 5, mArrI[i]); + } + CrossOverLoopI(); + for (int i = 0; i < mArrI.length; i++) { + expectEquals(i <= 20 ? 113 : 115, mArrI[i]); + } + // Type J. + mJ = 5; + mArrJ = new long[100]; + InvLoopJ(); + for (int i = 0; i < mArrJ.length; i++) { + expectEquals(5, mArrJ[i]); + } + VarLoopJ(); + for (int i = 0; i < mArrJ.length; i++) { + expectEquals(i <= 10 ? 5 : 6, mArrJ[i]); + } + CrossOverLoopJ(); + for (int i = 0; i < mArrJ.length; i++) { + expectEquals(i <= 20 ? 113 : 115, mArrJ[i]); + } + // Type F. + mF = 6.0f; + mArrF = new float[100]; + InvLoopF(); + for (int i = 0; i < mArrF.length; i++) { + expectEquals(6, mArrF[i]); + } + VarLoopF(); + for (int i = 0; i < mArrF.length; i++) { + expectEquals(i <= 10 ? 6 : 7, mArrF[i]); + } + CrossOverLoopF(); + for (int i = 0; i < mArrF.length; i++) { + expectEquals(i <= 20 ? 113 : 115, mArrF[i]); + } + // Type D. + mD = 7.0; + mArrD = new double[100]; + InvLoopD(); + for (int i = 0; i < mArrD.length; i++) { + expectEquals(7.0, mArrD[i]); + } + VarLoopD(); + for (int i = 0; i < mArrD.length; i++) { + expectEquals(i <= 10 ? 7 : 8, mArrD[i]); + } + CrossOverLoopD(); + for (int i = 0; i < mArrD.length; i++) { + expectEquals(i <= 20 ? 113 : 115, mArrD[i]); + } + // Type L. + mL = anObject; + mArrL = new Object[100]; + InvLoopL(); + for (int i = 0; i < mArrL.length; i++) { + expectEquals(anObject, mArrL[i]); + } + VarLoopL(); + for (int i = 0; i < mArrL.length; i++) { + expectEquals(i <= 10 ? anObject : anotherObject, mArrL[i]); + } + CrossOverLoopL(); + for (int i = 0; i < mArrL.length; i++) { + expectEquals(i <= 20 ? anObject : anotherObject, mArrL[i]); + } + // False cross-over. + FalseCrossOverLoop1(); + for (int i = 0; i < mArrI.length; i++) { + expectEquals(-3, mArrI[i]); + } + FalseCrossOverLoop2(); + for (int i = 0; i < mArrF.length; i++) { + expectEquals(-4, mArrF[i]); + } + FalseCrossOverLoop3(); + for (int i = 0; i < mArrJ.length; i++) { + expectEquals(-5, mArrJ[i]); + } + FalseCrossOverLoop4(); + for (int i = 0; i < mArrD.length; i++) { + expectEquals(-6, mArrD[i]); + } + } + + private static void expectEquals(boolean expected, boolean result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } + + private static void expectEquals(byte expected, byte result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } + + private static void expectEquals(char expected, char result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } + + private static void expectEquals(short expected, short result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } + + private static void expectEquals(int expected, int result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } + + private static void expectEquals(long expected, long result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } + + private static void expectEquals(float expected, float result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } + + private static void expectEquals(double expected, double result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } + + private static void expectEquals(Object expected, Object result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } +} diff --git a/test/529-checker-unresolved/src/Main.java b/test/529-checker-unresolved/src/Main.java index 872fa6d0dd..a934377ddf 100644 --- a/test/529-checker-unresolved/src/Main.java +++ b/test/529-checker-unresolved/src/Main.java @@ -114,6 +114,21 @@ public class Main extends UnresolvedSuperClass { expectEquals(o, c.instanceObject); } + static public void callUnresolvedNull(UnresolvedClass c) { + int x = 0; + try { + x = c.instanceInt; + throw new Error("Expected NPE"); + } catch (NullPointerException e) { + } + expectEquals(0, x); + try { + c.instanceInt = -1; + throw new Error("Expected NPE"); + } catch (NullPointerException e) { + } + } + static public void testInstanceOf(Object o) { if (o instanceof UnresolvedSuperClass) { System.out.println("instanceof ok"); @@ -136,6 +151,7 @@ public class Main extends UnresolvedSuperClass { callInvokeUnresolvedSuper(m); callUnresolvedStaticFieldAccess(); callUnresolvedInstanceFieldAccess(c); + callUnresolvedNull(null); testInstanceOf(m); testCheckCast(m); testLicm(2); @@ -185,7 +201,7 @@ public class Main extends UnresolvedSuperClass { } } - public static void expectEquals(float expected, float result) { + public static void expectEquals(float expected, float result) { if (expected != result) { throw new Error("Expected: " + expected + ", found: " + result); } diff --git a/test/530-checker-loops2/src/Main.java b/test/530-checker-loops2/src/Main.java index c644692f03..b12fbd6091 100644 --- a/test/530-checker-loops2/src/Main.java +++ b/test/530-checker-loops2/src/Main.java @@ -710,8 +710,8 @@ public class Main { // making them a candidate for deoptimization based on constant indices. // Compiler should ensure the array loads are not subsequently hoisted // "above" the deoptimization "barrier" on the bounds. - a[0][i] = 1; - a[1][i] = 2; + a[1][i] = 1; + a[2][i] = 2; a[99][i] = 3; } } @@ -1042,11 +1042,11 @@ public class Main { a = new int[100][10]; expectEquals(55, dynamicBCEAndConstantIndices(x, a, 0, 10)); for (int i = 0; i < 10; i++) { - expectEquals((i % 10) != 0 ? 1 : 0, a[0][i]); - expectEquals((i % 10) != 0 ? 2 : 0, a[1][i]); + expectEquals((i % 10) != 0 ? 1 : 0, a[1][i]); + expectEquals((i % 10) != 0 ? 2 : 0, a[2][i]); expectEquals((i % 10) != 0 ? 3 : 0, a[99][i]); } - a = new int[2][10]; + a = new int[3][10]; sResult = 0; try { expectEquals(55, dynamicBCEAndConstantIndices(x, a, 0, 10)); @@ -1054,8 +1054,8 @@ public class Main { sResult = 1; } expectEquals(1, sResult); - expectEquals(a[0][1], 1); - expectEquals(a[1][1], 2); + expectEquals(a[1][1], 1); + expectEquals(a[2][1], 2); // Dynamic BCE combined with constant indices of all types. boolean[] x1 = { true }; diff --git a/test/530-checker-lse/src/Main.java b/test/530-checker-lse/src/Main.java index 4d6ea06fe0..89875d7fef 100644 --- a/test/530-checker-lse/src/Main.java +++ b/test/530-checker-lse/src/Main.java @@ -70,6 +70,10 @@ class Finalizable { } } +interface Filter { + public boolean isValid(int i); +} + public class Main { /// CHECK-START: double Main.calcCircleArea(double) load_store_elimination (before) @@ -78,7 +82,7 @@ public class Main { /// CHECK: InstanceFieldGet /// CHECK-START: double Main.calcCircleArea(double) load_store_elimination (after) - /// CHECK: NewInstance + /// CHECK-NOT: NewInstance /// CHECK-NOT: InstanceFieldSet /// CHECK-NOT: InstanceFieldGet @@ -124,7 +128,6 @@ public class Main { } /// CHECK-START: int Main.test3(TestClass) load_store_elimination (before) - /// CHECK: NewInstance /// CHECK: StaticFieldGet /// CHECK: NewInstance /// CHECK: InstanceFieldSet @@ -137,7 +140,6 @@ public class Main { /// CHECK: InstanceFieldGet /// CHECK-START: int Main.test3(TestClass) load_store_elimination (after) - /// CHECK: NewInstance /// CHECK: StaticFieldGet /// CHECK: NewInstance /// CHECK: InstanceFieldSet @@ -149,9 +151,6 @@ public class Main { // A new allocation (even non-singleton) shouldn't alias with pre-existing values. static int test3(TestClass obj) { - // Do an allocation here to avoid the HLoadClass and HClinitCheck - // at the second allocation. - new TestClass(); TestClass obj1 = TestClass.sTestClassObj; TestClass obj2 = new TestClass(); // Cannot alias with obj or obj1 which pre-exist. obj.next = obj2; // Make obj2 a non-singleton. @@ -256,7 +255,7 @@ public class Main { /// CHECK: InstanceFieldGet /// CHECK-START: int Main.test8() load_store_elimination (after) - /// CHECK: NewInstance + /// CHECK-NOT: NewInstance /// CHECK-NOT: InstanceFieldSet /// CHECK: InvokeVirtual /// CHECK-NOT: NullCheck @@ -414,7 +413,7 @@ public class Main { /// CHECK: InstanceFieldGet /// CHECK-START: int Main.test16() load_store_elimination (after) - /// CHECK: NewInstance + /// CHECK-NOT: NewInstance /// CHECK-NOT: InstanceFieldSet /// CHECK-NOT: InstanceFieldGet @@ -431,7 +430,7 @@ public class Main { /// CHECK-START: int Main.test17() load_store_elimination (after) /// CHECK: <<Const0:i\d+>> IntConstant 0 - /// CHECK: NewInstance + /// CHECK-NOT: NewInstance /// CHECK-NOT: InstanceFieldSet /// CHECK-NOT: InstanceFieldGet /// CHECK: Return [<<Const0>>] @@ -527,12 +526,12 @@ public class Main { /// CHECK: InstanceFieldGet /// CHECK-START: int Main.test22() load_store_elimination (after) - /// CHECK: NewInstance + /// CHECK-NOT: NewInstance /// CHECK-NOT: InstanceFieldSet - /// CHECK: NewInstance + /// CHECK-NOT: NewInstance /// CHECK-NOT: InstanceFieldSet /// CHECK-NOT: InstanceFieldGet - /// CHECK: NewInstance + /// CHECK-NOT: NewInstance /// CHECK-NOT: InstanceFieldSet /// CHECK-NOT: InstanceFieldGet /// CHECK-NOT: InstanceFieldGet @@ -673,7 +672,7 @@ public class Main { /// CHECK: Select // Test that HSelect creates alias. - public static int $noinline$testHSelect(boolean b) { + static int $noinline$testHSelect(boolean b) { if (sFlag) { throw new Error(); } @@ -686,19 +685,51 @@ public class Main { return obj2.i; } - public static void assertIntEquals(int result, int expected) { + static int sumWithFilter(int[] array, Filter f) { + int sum = 0; + for (int i = 0; i < array.length; i++) { + if (f.isValid(array[i])) { + sum += array[i]; + } + } + return sum; + } + + /// CHECK-START: int Main.sumWithinRange(int[], int, int) load_store_elimination (before) + /// CHECK: NewInstance + /// CHECK: InstanceFieldSet + /// CHECK: InstanceFieldSet + /// CHECK: InstanceFieldGet + /// CHECK: InstanceFieldGet + + /// CHECK-START: int Main.sumWithinRange(int[], int, int) load_store_elimination (after) + /// CHECK-NOT: NewInstance + /// CHECK-NOT: InstanceFieldSet + /// CHECK-NOT: InstanceFieldGet + + // A lambda-style allocation can be eliminated after inlining. + static int sumWithinRange(int[] array, final int low, final int high) { + Filter filter = new Filter() { + public boolean isValid(int i) { + return (i >= low) && (i <= high); + } + }; + return sumWithFilter(array, filter); + } + + static void assertIntEquals(int result, int expected) { if (expected != result) { throw new Error("Expected: " + expected + ", found: " + result); } } - public static void assertFloatEquals(float result, float expected) { + static void assertFloatEquals(float result, float expected) { if (expected != result) { throw new Error("Expected: " + expected + ", found: " + result); } } - public static void assertDoubleEquals(double result, double expected) { + static void assertDoubleEquals(double result, double expected) { if (expected != result) { throw new Error("Expected: " + expected + ", found: " + result); } @@ -746,6 +777,8 @@ public class Main { assertFloatEquals(test24(), 8.0f); testFinalizableByForcingGc(); assertIntEquals($noinline$testHSelect(true), 0xdead); + int[] array = {2, 5, 9, -1, -3, 10, 8, 4}; + assertIntEquals(sumWithinRange(array, 1, 5), 11); } static boolean sFlag; diff --git a/test/536-checker-intrinsic-optimization/src/Main.java b/test/536-checker-intrinsic-optimization/src/Main.java index be666e94fa..24ed2feff7 100644 --- a/test/536-checker-intrinsic-optimization/src/Main.java +++ b/test/536-checker-intrinsic-optimization/src/Main.java @@ -16,9 +16,69 @@ public class Main { + public static boolean doThrow = false; + + public static void assertIntEquals(int expected, int result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } + + public static void assertBooleanEquals(boolean expected, boolean result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } + public static void main(String[] args) { stringEqualsSame(); stringArgumentNotNull("Foo"); + + assertIntEquals(0, $opt$noinline$getStringLength("")); + assertIntEquals(3, $opt$noinline$getStringLength("abc")); + assertIntEquals(10, $opt$noinline$getStringLength("0123456789")); + + assertBooleanEquals(true, $opt$noinline$isStringEmpty("")); + assertBooleanEquals(false, $opt$noinline$isStringEmpty("abc")); + assertBooleanEquals(false, $opt$noinline$isStringEmpty("0123456789")); + } + + /// CHECK-START: int Main.$opt$noinline$getStringLength(java.lang.String) instruction_simplifier (before) + /// CHECK-DAG: <<Length:i\d+>> InvokeVirtual intrinsic:StringLength + /// CHECK-DAG: Return [<<Length>>] + + /// CHECK-START: int Main.$opt$noinline$getStringLength(java.lang.String) instruction_simplifier (after) + /// CHECK-DAG: <<String:l\d+>> ParameterValue + /// CHECK-DAG: <<NullCk:l\d+>> NullCheck [<<String>>] + /// CHECK-DAG: <<Length:i\d+>> ArrayLength [<<NullCk>>] is_string_length:true + /// CHECK-DAG: Return [<<Length>>] + + /// CHECK-START: int Main.$opt$noinline$getStringLength(java.lang.String) instruction_simplifier (after) + /// CHECK-NOT: InvokeVirtual intrinsic:StringLength + + static public int $opt$noinline$getStringLength(String s) { + if (doThrow) { throw new Error(); } + return s.length(); + } + + /// CHECK-START: boolean Main.$opt$noinline$isStringEmpty(java.lang.String) instruction_simplifier (before) + /// CHECK-DAG: <<IsEmpty:z\d+>> InvokeVirtual intrinsic:StringIsEmpty + /// CHECK-DAG: Return [<<IsEmpty>>] + + /// CHECK-START: boolean Main.$opt$noinline$isStringEmpty(java.lang.String) instruction_simplifier (after) + /// CHECK-DAG: <<String:l\d+>> ParameterValue + /// CHECK-DAG: <<Const0:i\d+>> IntConstant 0 + /// CHECK-DAG: <<NullCk:l\d+>> NullCheck [<<String>>] + /// CHECK-DAG: <<Length:i\d+>> ArrayLength [<<NullCk>>] is_string_length:true + /// CHECK-DAG: <<IsEmpty:z\d+>> Equal [<<Length>>,<<Const0>>] + /// CHECK-DAG: Return [<<IsEmpty>>] + + /// CHECK-START: boolean Main.$opt$noinline$isStringEmpty(java.lang.String) instruction_simplifier (after) + /// CHECK-NOT: InvokeVirtual intrinsic:StringIsEmpty + + static public boolean $opt$noinline$isStringEmpty(String s) { + if (doThrow) { throw new Error(); } + return s.isEmpty(); } /// CHECK-START: boolean Main.stringEqualsSame() instruction_simplifier (before) @@ -47,8 +107,28 @@ public class Main { } /// CHECK-START-X86: boolean Main.stringArgumentNotNull(java.lang.Object) disassembly (after) - /// CHECK: InvokeVirtual {{.*\.equals.*}} + /// CHECK: InvokeVirtual {{.*\.equals.*}} intrinsic:StringEquals /// CHECK-NOT: test + + /// CHECK-START-X86_64: boolean Main.stringArgumentNotNull(java.lang.Object) disassembly (after) + /// CHECK: InvokeVirtual {{.*\.equals.*}} intrinsic:StringEquals + /// CHECK-NOT: test + + /// CHECK-START-ARM: boolean Main.stringArgumentNotNull(java.lang.Object) disassembly (after) + /// CHECK: InvokeVirtual {{.*\.equals.*}} intrinsic:StringEquals + // CompareAndBranchIfZero() may emit either CBZ or CMP+BEQ. + /// CHECK-NOT: cbz + /// CHECK-NOT: cmp {{r\d+}}, #0 + // Terminate the scope for the CHECK-NOT search at the reference or length comparison, + // whichever comes first. + /// CHECK: cmp {{r\d+}}, {{r\d+}} + + /// CHECK-START-ARM64: boolean Main.stringArgumentNotNull(java.lang.Object) disassembly (after) + /// CHECK: InvokeVirtual {{.*\.equals.*}} intrinsic:StringEquals + /// CHECK-NOT: cbz + // Terminate the scope for the CHECK-NOT search at the reference or length comparison, + // whichever comes first. + /// CHECK: cmp {{w.*,}} {{w.*}} public static boolean stringArgumentNotNull(Object obj) { obj.getClass(); return "foo".equals(obj); @@ -56,12 +136,53 @@ public class Main { // Test is very brittle as it depends on the order we emit instructions. /// CHECK-START-X86: boolean Main.stringArgumentIsString() disassembly (after) - /// CHECK: InvokeVirtual - /// CHECK: test - /// CHECK: jz/eq + /// CHECK: InvokeVirtual intrinsic:StringEquals + /// CHECK: test + /// CHECK: jz/eq + // Check that we don't try to compare the classes. + /// CHECK-NOT: mov + /// CHECK: cmp + + // Test is very brittle as it depends on the order we emit instructions. + /// CHECK-START-X86_64: boolean Main.stringArgumentIsString() disassembly (after) + /// CHECK: InvokeVirtual intrinsic:StringEquals + /// CHECK: test + /// CHECK: jz/eq + // Check that we don't try to compare the classes. + /// CHECK-NOT: mov + /// CHECK: cmp + + // Test is brittle as it depends on the class offset being 0. + /// CHECK-START-ARM: boolean Main.stringArgumentIsString() disassembly (after) + /// CHECK: InvokeVirtual intrinsic:StringEquals + /// CHECK: {{cbz|cmp}} + // Check that we don't try to compare the classes. + // The dissassembler currently explicitly emits the offset 0 but don't rely on it. + // We want to terminate the CHECK-NOT search after two CMPs, one for reference + // equality and one for length comparison but these may be emitted in different order, + // so repeat the check twice. + /// CHECK-NOT: ldr{{(|.w)}} {{r\d+}}, [{{r\d+}}] + /// CHECK-NOT: ldr{{(|.w)}} {{r\d+}}, [{{r\d+}}, #0] + /// CHECK: cmp {{r\d+}}, {{r\d+}} + /// CHECK-NOT: ldr{{(|.w)}} {{r\d+}}, [{{r\d+}}] + /// CHECK-NOT: ldr{{(|.w)}} {{r\d+}}, [{{r\d+}}, #0] + /// CHECK: cmp {{r\d+}}, {{r\d+}} + + // Test is brittle as it depends on the class offset being 0. + /// CHECK-START-ARM64: boolean Main.stringArgumentIsString() disassembly (after) + /// CHECK: InvokeVirtual intrinsic:StringEquals + /// CHECK: cbz // Check that we don't try to compare the classes. - /// CHECK-NOT: mov - /// CHECK: cmp + // The dissassembler currently does not explicitly emits the offset 0 but don't rely on it. + // We want to terminate the CHECK-NOT search after two CMPs, one for reference + // equality and one for length comparison but these may be emitted in different order, + // so repeat the check twice. + /// CHECK-NOT: ldr {{w\d+}}, [{{x\d+}}] + /// CHECK-NOT: ldr {{w\d+}}, [{{x\d+}}, #0] + /// CHECK: cmp {{w\d+}}, {{w\d+}} + /// CHECK-NOT: ldr {{w\d+}}, [{{x\d+}}] + /// CHECK-NOT: ldr {{w\d+}}, [{{x\d+}}, #0] + /// CHECK: cmp {{w\d+}}, {{w\d+}} public static boolean stringArgumentIsString() { return "foo".equals(myString); } diff --git a/test/551-checker-shifter-operand/src/Main.java b/test/551-checker-shifter-operand/src/Main.java index edb8a68b47..a4561b83da 100644 --- a/test/551-checker-shifter-operand/src/Main.java +++ b/test/551-checker-shifter-operand/src/Main.java @@ -500,9 +500,9 @@ public class Main { assertIntEquals(a + $noinline$IntShl(b, 16), a + (b << 16)); assertIntEquals(a + $noinline$IntShl(b, 30), a + (b << 30)); assertIntEquals(a + $noinline$IntShl(b, 31), a + (b << 31)); - assertIntEquals(a + $noinline$IntShl(b, 32), a + (b << 32)); - assertIntEquals(a + $noinline$IntShl(b, 62), a + (b << 62)); - assertIntEquals(a + $noinline$IntShl(b, 63), a + (b << 63)); + assertIntEquals(a + $noinline$IntShl(b, 32), a + (b << $opt$inline$IntConstant32())); + assertIntEquals(a + $noinline$IntShl(b, 62), a + (b << $opt$inline$IntConstant62())); + assertIntEquals(a + $noinline$IntShl(b, 63), a + (b << $opt$inline$IntConstant63())); assertIntEquals(a - $noinline$IntShr(b, 1), a - (b >> 1)); assertIntEquals(a - $noinline$IntShr(b, 6), a - (b >> 6)); @@ -513,9 +513,9 @@ public class Main { assertIntEquals(a - $noinline$IntShr(b, 16), a - (b >> 16)); assertIntEquals(a - $noinline$IntShr(b, 30), a - (b >> 30)); assertIntEquals(a - $noinline$IntShr(b, 31), a - (b >> 31)); - assertIntEquals(a - $noinline$IntShr(b, 32), a - (b >> 32)); - assertIntEquals(a - $noinline$IntShr(b, 62), a - (b >> 62)); - assertIntEquals(a - $noinline$IntShr(b, 63), a - (b >> 63)); + assertIntEquals(a - $noinline$IntShr(b, 32), a - (b >> $opt$inline$IntConstant32())); + assertIntEquals(a - $noinline$IntShr(b, 62), a - (b >> $opt$inline$IntConstant62())); + assertIntEquals(a - $noinline$IntShr(b, 63), a - (b >> $opt$inline$IntConstant63())); assertIntEquals(a ^ $noinline$IntUshr(b, 1), a ^ (b >>> 1)); assertIntEquals(a ^ $noinline$IntUshr(b, 6), a ^ (b >>> 6)); @@ -526,11 +526,17 @@ public class Main { assertIntEquals(a ^ $noinline$IntUshr(b, 16), a ^ (b >>> 16)); assertIntEquals(a ^ $noinline$IntUshr(b, 30), a ^ (b >>> 30)); assertIntEquals(a ^ $noinline$IntUshr(b, 31), a ^ (b >>> 31)); - assertIntEquals(a ^ $noinline$IntUshr(b, 32), a ^ (b >>> 32)); - assertIntEquals(a ^ $noinline$IntUshr(b, 62), a ^ (b >>> 62)); - assertIntEquals(a ^ $noinline$IntUshr(b, 63), a ^ (b >>> 63)); + assertIntEquals(a ^ $noinline$IntUshr(b, 32), a ^ (b >>> $opt$inline$IntConstant32())); + assertIntEquals(a ^ $noinline$IntUshr(b, 62), a ^ (b >>> $opt$inline$IntConstant62())); + assertIntEquals(a ^ $noinline$IntUshr(b, 63), a ^ (b >>> $opt$inline$IntConstant63())); } + // Hiding constants outside the range [0, 32) used for int shifts from Jack. + // (Jack extracts only the low 5 bits.) + public static int $opt$inline$IntConstant32() { return 32; } + public static int $opt$inline$IntConstant62() { return 62; } + public static int $opt$inline$IntConstant63() { return 63; } + static long $noinline$LongShl(long b, long c) { if (doThrow) throw new Error(); diff --git a/test/557-checker-instruction-simplifier-ror/src/Main.java b/test/557-checker-instruction-simplifier-ror/src/Main.java index 027f262db1..6d8b74d1ec 100644 --- a/test/557-checker-instruction-simplifier-ror/src/Main.java +++ b/test/557-checker-instruction-simplifier-ror/src/Main.java @@ -175,7 +175,7 @@ public class Main { // (i >>> #distance) | (i << #-distance) - /// CHECK-START: int Main.ror_int_constant_c_negc(int) instruction_simplifier (before) + /// CHECK-START: int Main.ror_int_constant_c_negc(int) instruction_simplifier_after_bce (before) /// CHECK: <<ArgValue:i\d+>> ParameterValue /// CHECK: <<Const2:i\d+>> IntConstant 2 /// CHECK: <<ConstNeg2:i\d+>> IntConstant -2 @@ -184,19 +184,23 @@ public class Main { /// CHECK: <<Or:i\d+>> Or [<<UShr>>,<<Shl>>] /// CHECK: Return [<<Or>>] - /// CHECK-START: int Main.ror_int_constant_c_negc(int) instruction_simplifier (after) + /// CHECK-START: int Main.ror_int_constant_c_negc(int) instruction_simplifier_after_bce (after) /// CHECK: <<ArgValue:i\d+>> ParameterValue /// CHECK: <<Const2:i\d+>> IntConstant 2 /// CHECK: <<Ror:i\d+>> Ror [<<ArgValue>>,<<Const2>>] /// CHECK: Return [<<Ror>>] - /// CHECK-START: int Main.ror_int_constant_c_negc(int) instruction_simplifier (after) + /// CHECK-START: int Main.ror_int_constant_c_negc(int) instruction_simplifier_after_bce (after) /// CHECK-NOT: UShr /// CHECK-NOT: Shl public static int ror_int_constant_c_negc(int value) { - return (value >>> 2) | (value << -2); + return (value >>> 2) | (value << $opt$inline$IntConstantM2()); } + // Hiding constants outside the range [0, 32) used for int shifts from Jack. + // (Jack extracts only the low 5 bits.) + public static int $opt$inline$IntConstantM2() { return -2; } + // (j >>> #distance) | (j << #-distance) /// CHECK-START: long Main.ror_long_constant_c_negc(long) instruction_simplifier (before) diff --git a/test/562-bce-preheader/src/Main.java b/test/562-bce-preheader/src/Main.java index 8b527b4f63..4397f679b9 100644 --- a/test/562-bce-preheader/src/Main.java +++ b/test/562-bce-preheader/src/Main.java @@ -90,6 +90,25 @@ public class Main { return a; } + /** + * Example shows that we can hoist ArrayGet to pre-header only if + * its execution is guaranteed. + */ + public static int hoistcheck(int[] c) { + int i = 0, i2 = 0, i3 = 0, k = 0; + int n = c.length; + for (i = -100000000; i < 20; i += 10000000) { + i3 = i; + i2 = 0; + while (i2++ < 1) { + if (i3 >= 0 && i3 < n) { + k += c[i3]; + } + } + } + return k; + } + public static void main(String args[]) { int[][] x = new int[2][2]; int y; @@ -119,6 +138,9 @@ public class Main { int[] z = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; expectEquals(10, doit(z)); + int c[] = { 1, 2, 3, 5 }; + expectEquals(1, hoistcheck(c)); + System.out.println("passed"); } diff --git a/test/566-polymorphic-inlining/polymorphic_inline.cc b/test/566-polymorphic-inlining/polymorphic_inline.cc index 167a5757f9..7b2c6cbcd5 100644 --- a/test/566-polymorphic-inlining/polymorphic_inline.cc +++ b/test/566-polymorphic-inlining/polymorphic_inline.cc @@ -60,6 +60,7 @@ extern "C" JNIEXPORT void JNICALL Java_Main_ensureJittedAndPolymorphicInline(JNI do_checks(cls, "testInvokeVirtual"); do_checks(cls, "testInvokeInterface"); + do_checks(cls, "$noinline$testInlineToSameTarget"); } } // namespace art diff --git a/test/566-polymorphic-inlining/src/Main.java b/test/566-polymorphic-inlining/src/Main.java index 7283e86227..a59ce5b344 100644 --- a/test/566-polymorphic-inlining/src/Main.java +++ b/test/566-polymorphic-inlining/src/Main.java @@ -25,6 +25,12 @@ public class Main implements Itf { } } + public static void assertEquals(int expected, int actual) { + if (expected != actual) { + throw new Error("Expected " + expected + ", got " + actual); + } + } + public static void main(String[] args) throws Exception { System.loadLibrary(args[0]); Main[] mains = new Main[3]; @@ -41,6 +47,8 @@ public class Main implements Itf { testInvokeVirtual(mains[1]); testInvokeInterface(itfs[0]); testInvokeInterface(itfs[1]); + $noinline$testInlineToSameTarget(mains[0]); + $noinline$testInlineToSameTarget(mains[1]); } ensureJittedAndPolymorphicInline(); @@ -56,6 +64,10 @@ public class Main implements Itf { // This will trigger a deoptimization of the compiled code. assertEquals(OtherSubclass.class, testInvokeVirtual(mains[2])); assertEquals(OtherSubclass.class, testInvokeInterface(itfs[2])); + + // Run this once to make sure we execute the JITted code. + $noinline$testInlineToSameTarget(mains[0]); + assertEquals(20001, counter); } public Class sameInvokeVirtual() { @@ -76,9 +88,21 @@ public class Main implements Itf { return m.sameInvokeVirtual(); } + public static void $noinline$testInlineToSameTarget(Main m) { + if (doThrow) throw new Error(""); + m.increment(); + } + public Object field = new Object(); public static native void ensureJittedAndPolymorphicInline(); + + public void increment() { + field.getClass(); // null check to ensure we get an inlined frame in the CodeInfo + counter++; + } + public static int counter = 0; + public static boolean doThrow = false; } class Subclass extends Main { diff --git a/test/567-checker-compare/src/Main.java b/test/567-checker-compare/src/Main.java index f95ff1a7db..85879500d9 100644 --- a/test/567-checker-compare/src/Main.java +++ b/test/567-checker-compare/src/Main.java @@ -16,6 +16,32 @@ public class Main { + public static boolean doThrow = false; + + /// CHECK-START: void Main.$opt$noinline$testReplaceInputWithItself(int) intrinsics_recognition (after) + /// CHECK-DAG: <<ArgX:i\d+>> ParameterValue + /// CHECK-DAG: <<Method:[ij]\d+>> CurrentMethod + /// CHECK-DAG: <<Zero:i\d+>> IntConstant 0 + /// CHECK-DAG: <<Cmp:i\d+>> InvokeStaticOrDirect [<<ArgX>>,<<Zero>>,<<Method>>] intrinsic:IntegerCompare + /// CHECK-DAG: GreaterThanOrEqual [<<Cmp>>,<<Zero>>] + + /// CHECK-START: void Main.$opt$noinline$testReplaceInputWithItself(int) instruction_simplifier (after) + /// CHECK-DAG: <<ArgX:i\d+>> ParameterValue + /// CHECK-DAG: <<Zero:i\d+>> IntConstant 0 + /// CHECK-DAG: GreaterThanOrEqual [<<ArgX>>,<<Zero>>] + + public static void $opt$noinline$testReplaceInputWithItself(int x) { + if (doThrow) { throw new Error(); } + + // The instruction simplifier first replaces Integer.compare(x, 0) with Compare HIR + // and then merges the Compare into the GreaterThanOrEqual. This is a regression + // test that to check that it is allowed to replace the second input of the + // GreaterThanOrEqual, i.e. <<Zero>>, with the very same instruction. + if (Integer.compare(x, 0) < 0) { + System.out.println("OOOPS"); + } + } + /// CHECK-START: int Main.compareBooleans(boolean, boolean) intrinsics_recognition (after) /// CHECK-DAG: <<Method:[ij]\d+>> CurrentMethod /// CHECK-DAG: <<Zero:i\d+>> IntConstant 0 @@ -890,6 +916,8 @@ public class Main { public static void main(String args[]) { + $opt$noinline$testReplaceInputWithItself(42); + testCompareBooleans(); testCompareBytes(); testCompareShorts(); diff --git a/test/570-checker-osr/expected.txt b/test/570-checker-osr/expected.txt index 25fb2200eb..65447be9c3 100644 --- a/test/570-checker-osr/expected.txt +++ b/test/570-checker-osr/expected.txt @@ -3,3 +3,4 @@ JNI_OnLoad called 200000 300000 400000 +b28210356 passed. diff --git a/test/570-checker-osr/osr.cc b/test/570-checker-osr/osr.cc index 09e97ea751..2fa5800e5c 100644 --- a/test/570-checker-osr/osr.cc +++ b/test/570-checker-osr/osr.cc @@ -14,21 +14,23 @@ * limitations under the License. */ -#include "art_method.h" +#include "art_method-inl.h" #include "jit/jit.h" #include "jit/jit_code_cache.h" #include "jit/profiling_info.h" #include "oat_quick_method_header.h" #include "scoped_thread_state_change.h" +#include "ScopedUtfChars.h" #include "stack_map.h" namespace art { class OsrVisitor : public StackVisitor { public: - explicit OsrVisitor(Thread* thread) + explicit OsrVisitor(Thread* thread, const char* method_name) SHARED_REQUIRES(Locks::mutator_lock_) : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames), + method_name_(method_name), in_osr_method_(false), in_interpreter_(false) {} @@ -36,13 +38,7 @@ class OsrVisitor : public StackVisitor { ArtMethod* m = GetMethod(); std::string m_name(m->GetName()); - if ((m_name.compare("$noinline$returnInt") == 0) || - (m_name.compare("$noinline$returnFloat") == 0) || - (m_name.compare("$noinline$returnDouble") == 0) || - (m_name.compare("$noinline$returnLong") == 0) || - (m_name.compare("$noinline$deopt") == 0) || - (m_name.compare("$noinline$inlineCache") == 0) || - (m_name.compare("$noinline$stackOverflow") == 0)) { + if (m_name.compare(method_name_) == 0) { const OatQuickMethodHeader* header = Runtime::Current()->GetJit()->GetCodeCache()->LookupOsrMethodHeader(m); if (header != nullptr && header == GetCurrentOatQuickMethodHeader()) { @@ -55,74 +51,89 @@ class OsrVisitor : public StackVisitor { return true; } + const char* const method_name_; bool in_osr_method_; bool in_interpreter_; }; -extern "C" JNIEXPORT jboolean JNICALL Java_Main_ensureInOsrCode(JNIEnv*, jclass) { +extern "C" JNIEXPORT jboolean JNICALL Java_Main_isInOsrCode(JNIEnv* env, + jclass, + jstring method_name) { jit::Jit* jit = Runtime::Current()->GetJit(); if (jit == nullptr) { // Just return true for non-jit configurations to stop the infinite loop. return JNI_TRUE; } + ScopedUtfChars chars(env, method_name); + CHECK(chars.c_str() != nullptr); ScopedObjectAccess soa(Thread::Current()); - OsrVisitor visitor(soa.Self()); + OsrVisitor visitor(soa.Self(), chars.c_str()); visitor.WalkStack(); return visitor.in_osr_method_; } -extern "C" JNIEXPORT jboolean JNICALL Java_Main_ensureInInterpreter(JNIEnv*, jclass) { - if (!Runtime::Current()->UseJit()) { +extern "C" JNIEXPORT jboolean JNICALL Java_Main_isInInterpreter(JNIEnv* env, + jclass, + jstring method_name) { + if (!Runtime::Current()->UseJitCompilation()) { // The return value is irrelevant if we're not using JIT. return false; } + ScopedUtfChars chars(env, method_name); + CHECK(chars.c_str() != nullptr); ScopedObjectAccess soa(Thread::Current()); - OsrVisitor visitor(soa.Self()); + OsrVisitor visitor(soa.Self(), chars.c_str()); visitor.WalkStack(); return visitor.in_interpreter_; } class ProfilingInfoVisitor : public StackVisitor { public: - explicit ProfilingInfoVisitor(Thread* thread) + explicit ProfilingInfoVisitor(Thread* thread, const char* method_name) SHARED_REQUIRES(Locks::mutator_lock_) - : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames) {} + : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames), + method_name_(method_name) {} bool VisitFrame() SHARED_REQUIRES(Locks::mutator_lock_) { ArtMethod* m = GetMethod(); std::string m_name(m->GetName()); - if ((m_name.compare("$noinline$inlineCache") == 0) || - (m_name.compare("$noinline$stackOverflow") == 0)) { + if (m_name.compare(method_name_) == 0) { ProfilingInfo::Create(Thread::Current(), m, /* retry_allocation */ true); return false; } return true; } + + const char* const method_name_; }; -extern "C" JNIEXPORT void JNICALL Java_Main_ensureHasProfilingInfo(JNIEnv*, jclass) { - if (!Runtime::Current()->UseJit()) { +extern "C" JNIEXPORT void JNICALL Java_Main_ensureHasProfilingInfo(JNIEnv* env, + jclass, + jstring method_name) { + if (!Runtime::Current()->UseJitCompilation()) { return; } + ScopedUtfChars chars(env, method_name); + CHECK(chars.c_str() != nullptr); ScopedObjectAccess soa(Thread::Current()); - ProfilingInfoVisitor visitor(soa.Self()); + ProfilingInfoVisitor visitor(soa.Self(), chars.c_str()); visitor.WalkStack(); } class OsrCheckVisitor : public StackVisitor { public: - explicit OsrCheckVisitor(Thread* thread) + OsrCheckVisitor(Thread* thread, const char* method_name) SHARED_REQUIRES(Locks::mutator_lock_) - : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames) {} + : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames), + method_name_(method_name) {} bool VisitFrame() SHARED_REQUIRES(Locks::mutator_lock_) { ArtMethod* m = GetMethod(); std::string m_name(m->GetName()); jit::Jit* jit = Runtime::Current()->GetJit(); - if ((m_name.compare("$noinline$inlineCache") == 0) || - (m_name.compare("$noinline$stackOverflow") == 0)) { + if (m_name.compare(method_name_) == 0) { while (jit->GetCodeCache()->LookupOsrMethodHeader(m) == nullptr) { // Sleep to yield to the compiler thread. sleep(0); @@ -133,14 +144,20 @@ class OsrCheckVisitor : public StackVisitor { } return true; } + + const char* const method_name_; }; -extern "C" JNIEXPORT void JNICALL Java_Main_ensureHasOsrCode(JNIEnv*, jclass) { - if (!Runtime::Current()->UseJit()) { +extern "C" JNIEXPORT void JNICALL Java_Main_ensureHasOsrCode(JNIEnv* env, + jclass, + jstring method_name) { + if (!Runtime::Current()->UseJitCompilation()) { return; } + ScopedUtfChars chars(env, method_name); + CHECK(chars.c_str() != nullptr); ScopedObjectAccess soa(Thread::Current()); - OsrCheckVisitor visitor(soa.Self()); + OsrCheckVisitor visitor(soa.Self(), chars.c_str()); visitor.WalkStack(); } diff --git a/test/570-checker-osr/src/Main.java b/test/570-checker-osr/src/Main.java index 6514334af1..15c232d6a8 100644 --- a/test/570-checker-osr/src/Main.java +++ b/test/570-checker-osr/src/Main.java @@ -61,8 +61,23 @@ public class Main { throw new Error("Unexpected return value"); } + $noinline$inlineCache2(new Main(), /* isSecondInvocation */ false); + if ($noinline$inlineCache2(new SubMain(), /* isSecondInvocation */ true) != SubMain.class) { + throw new Error("Unexpected return value"); + } + + // Test polymorphic inline cache to the same target (inlineCache3). + $noinline$inlineCache3(new Main(), /* isSecondInvocation */ false); + $noinline$inlineCache3(new SubMain(), /* isSecondInvocation */ false); + if ($noinline$inlineCache3(new SubMain(), /* isSecondInvocation */ true) != null) { + throw new Error("Unexpected return value"); + } + $noinline$stackOverflow(new Main(), /* isSecondInvocation */ false); $noinline$stackOverflow(new SubMain(), /* isSecondInvocation */ true); + + $opt$noinline$testOsrInlineLoop(null); + System.out.println("b28210356 passed."); } public static int $noinline$returnInt() { @@ -70,7 +85,7 @@ public class Main { int i = 0; for (; i < 100000; ++i) { } - while (!ensureInOsrCode()) {} + while (!isInOsrCode("$noinline$returnInt")) {} System.out.println(i); return 53; } @@ -80,7 +95,7 @@ public class Main { int i = 0; for (; i < 200000; ++i) { } - while (!ensureInOsrCode()) {} + while (!isInOsrCode("$noinline$returnFloat")) {} System.out.println(i); return 42.2f; } @@ -90,7 +105,7 @@ public class Main { int i = 0; for (; i < 300000; ++i) { } - while (!ensureInOsrCode()) {} + while (!isInOsrCode("$noinline$returnDouble")) {} System.out.println(i); return Double.longBitsToDouble(0xF000000000001111L); } @@ -100,7 +115,7 @@ public class Main { int i = 0; for (; i < 400000; ++i) { } - while (!ensureInOsrCode()) {} + while (!isInOsrCode("$noinline$returnLong")) {} System.out.println(i); return 0xFFFF000000001111L; } @@ -110,22 +125,22 @@ public class Main { int i = 0; for (; i < 100000; ++i) { } - while (!ensureInOsrCode()) {} + while (!isInOsrCode("$noinline$deopt")) {} DeoptimizationController.startDeoptimization(); } public static Class $noinline$inlineCache(Main m, boolean isSecondInvocation) { // If we are running in non-JIT mode, or were unlucky enough to get this method // already JITted, just return the expected value. - if (!ensureInInterpreter()) { + if (!isInInterpreter("$noinline$inlineCache")) { return SubMain.class; } - ensureHasProfilingInfo(); + ensureHasProfilingInfo("$noinline$inlineCache"); // Ensure that we have OSR code to jump to. if (isSecondInvocation) { - ensureHasOsrCode(); + ensureHasOsrCode("$noinline$inlineCache"); } // This call will be optimized in the OSR compiled code @@ -137,17 +152,83 @@ public class Main { // code we are jumping to will have wrongly optimize other as being a // 'Main'. if (isSecondInvocation) { - while (!ensureInOsrCode()) {} + while (!isInOsrCode("$noinline$inlineCache")) {} } // We used to wrongly optimize this call and assume 'other' was a 'Main'. return other.returnClass(); } + public static Class $noinline$inlineCache2(Main m, boolean isSecondInvocation) { + // If we are running in non-JIT mode, or were unlucky enough to get this method + // already JITted, just return the expected value. + if (!isInInterpreter("$noinline$inlineCache2")) { + return SubMain.class; + } + + ensureHasProfilingInfo("$noinline$inlineCache2"); + + // Ensure that we have OSR code to jump to. + if (isSecondInvocation) { + ensureHasOsrCode("$noinline$inlineCache2"); + } + + // This call will be optimized in the OSR compiled code + // to check and deoptimize if m is not of type 'Main'. + Main other = m.inlineCache2(); + + // Jump to OSR compiled code. The second run + // of this method will have 'm' as a SubMain, and the compiled + // code we are jumping to will have wrongly optimize other as being null. + if (isSecondInvocation) { + while (!isInOsrCode("$noinline$inlineCache2")) {} + } + + // We used to wrongly optimize this code and assume 'other' was always null. + return (other == null) ? null : other.returnClass(); + } + + public static Class $noinline$inlineCache3(Main m, boolean isSecondInvocation) { + // If we are running in non-JIT mode, or were unlucky enough to get this method + // already JITted, just return the expected value. + if (!isInInterpreter("$noinline$inlineCache3")) { + return null; + } + + ensureHasProfilingInfo("$noinline$inlineCache3"); + + // Ensure that we have OSR code to jump to. + if (isSecondInvocation) { + ensureHasOsrCode("$noinline$inlineCache3"); + } + + // This call will be optimized in the OSR compiled code + // to check and deoptimize if m is not of type 'Main'. + Main other = m.inlineCache3(); + + // Jump to OSR compiled code. The second run + // of this method will have 'm' as a SubMain, and the compiled + // code we are jumping to will have wrongly optimize other as being null. + if (isSecondInvocation) { + while (!isInOsrCode("$noinline$inlineCache3")) {} + } + + // We used to wrongly optimize this code and assume 'other' was always null. + return (other == null) ? null : other.returnClass(); + } + public Main inlineCache() { return new Main(); } + public Main inlineCache2() { + return null; + } + + public Main inlineCache3() { + return null; + } + public Class returnClass() { return Main.class; } @@ -159,16 +240,16 @@ public class Main { public static void $noinline$stackOverflow(Main m, boolean isSecondInvocation) { // If we are running in non-JIT mode, or were unlucky enough to get this method // already JITted, just return the expected value. - if (!ensureInInterpreter()) { + if (!isInInterpreter("$noinline$stackOverflow")) { return; } // We need a ProfilingInfo object to populate the 'otherInlineCache' call. - ensureHasProfilingInfo(); + ensureHasProfilingInfo("$noinline$stackOverflow"); if (isSecondInvocation) { // Ensure we have an OSR code and we jump to it. - while (!ensureInOsrCode()) {} + while (!isInOsrCode("$noinline$stackOverflow")) {} } for (int i = 0; i < (isSecondInvocation ? 10000000 : 1); ++i) { @@ -179,10 +260,46 @@ public class Main { } } - public static native boolean ensureInInterpreter(); - public static native boolean ensureInOsrCode(); - public static native void ensureHasProfilingInfo(); - public static native void ensureHasOsrCode(); + public static void $opt$noinline$testOsrInlineLoop(String[] args) { + // Regression test for inlining a method with a loop to a method without a loop in OSR mode. + if (doThrow) throw new Error(); + assertIntEquals(12, $opt$inline$testRemoveSuspendCheck(12, 5)); + // Since we cannot have a loop directly in this method, we need to force the OSR + // compilation from native code. + ensureHasProfilingInfo("$opt$noinline$testOsrInlineLoop"); + ensureHasOsrCode("$opt$noinline$testOsrInlineLoop"); + } + + public static int $opt$inline$testRemoveSuspendCheck(int x, int y) { + // For this test we need an inlined loop and have DCE re-run loop analysis + // after inlining. + while (y > 0) { + while ($opt$inline$inlineFalse() || !$opt$inline$inlineTrue()) { + x++; + } + y--; + } + return x; + } + + public static boolean $opt$inline$inlineTrue() { + return true; + } + + public static boolean $opt$inline$inlineFalse() { + return false; + } + + public static void assertIntEquals(int expected, int result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } + + public static native boolean isInOsrCode(String methodName); + public static native boolean isInInterpreter(String methodName); + public static native void ensureHasProfilingInfo(String methodName); + public static native void ensureHasOsrCode(String methodName); public static boolean doThrow = false; } @@ -196,6 +313,10 @@ class SubMain extends Main { return new SubMain(); } + public Main inlineCache2() { + return new SubMain(); + } + public void otherInlineCache() { return; } diff --git a/test/572-checker-array-get-regression/src/Main.java b/test/572-checker-array-get-regression/src/Main.java index b55be706f4..89b97ed316 100644 --- a/test/572-checker-array-get-regression/src/Main.java +++ b/test/572-checker-array-get-regression/src/Main.java @@ -25,13 +25,11 @@ public class Main { /// CHECK-DAG: <<Const2P19:i\d+>> IntConstant 524288 /// CHECK-DAG: <<ConstM1:i\d+>> IntConstant -1 /// CHECK-DAG: <<Array:l\d+>> NewArray [<<Const2P19>>,<<Method>>] - /// CHECK-DAG: <<NullCheck1:l\d+>> NullCheck [<<Array>>] - /// CHECK-DAG: <<Length1:i\d+>> ArrayLength [<<NullCheck1>>] + /// CHECK-DAG: <<Length1:i\d+>> ArrayLength [<<Array>>] /// CHECK-DAG: <<Index:i\d+>> Add [<<Length1>>,<<ConstM1>>] - /// CHECK-DAG: <<NullCheck2:l\d+>> NullCheck [<<Array>>] - /// CHECK-DAG: <<Length2:i\d+>> ArrayLength [<<NullCheck2>>] + /// CHECK-DAG: <<Length2:i\d+>> ArrayLength [<<Array>>] /// CHECK-DAG: <<BoundsCheck:i\d+>> BoundsCheck [<<Index>>,<<Length2>>] - /// CHECK-DAG: <<LastElement:l\d+>> ArrayGet [<<NullCheck2>>,<<BoundsCheck>>] + /// CHECK-DAG: <<LastElement:l\d+>> ArrayGet [<<Array>>,<<BoundsCheck>>] /// CHECK-DAG: Return [<<LastElement>>] diff --git a/test/586-checker-null-array-get/src/Main.java b/test/586-checker-null-array-get/src/Main.java index 332cfb0a53..e0782bc84d 100644 --- a/test/586-checker-null-array-get/src/Main.java +++ b/test/586-checker-null-array-get/src/Main.java @@ -14,10 +14,20 @@ * limitations under the License. */ +class Test1 { + int[] iarr; +} + +class Test2 { + float[] farr; +} + public class Main { public static Object[] getObjectArray() { return null; } public static long[] getLongArray() { return null; } public static Object getNull() { return null; } + public static Test1 getNullTest1() { return null; } + public static Test2 getNullTest2() { return null; } public static void main(String[] args) { try { @@ -26,13 +36,25 @@ public class Main { } catch (NullPointerException e) { // Expected. } + try { + bar(); + throw new Error("Expected NullPointerException"); + } catch (NullPointerException e) { + // Expected. + } + try { + test1(); + throw new Error("Expected NullPointerException"); + } catch (NullPointerException e) { + // Expected. + } } /// CHECK-START: void Main.foo() load_store_elimination (after) - /// CHECK-DAG: <<Null:l\d+>> NullConstant - /// CHECK-DAG: <<Check:l\d+>> NullCheck [<<Null>>] - /// CHECK-DAG: <<Get1:j\d+>> ArrayGet [<<Check>>,{{i\d+}}] - /// CHECK-DAG: <<Get2:l\d+>> ArrayGet [<<Check>>,{{i\d+}}] + /// CHECK-DAG: <<Null:l\d+>> NullConstant + /// CHECK-DAG: <<Check:l\d+>> NullCheck [<<Null>>] + /// CHECK-DAG: <<Get1:j\d+>> ArrayGet [<<Check>>,{{i\d+}}] + /// CHECK-DAG: <<Get2:l\d+>> ArrayGet [<<Check>>,{{i\d+}}] public static void foo() { longField = getLongArray()[0]; objectField = getObjectArray()[0]; @@ -56,7 +78,7 @@ public class Main { // elimination pass to add a HDeoptimize. Not having the bounds check helped // the load store elimination think it could merge two ArrayGet with different // types. - String[] array = ((String[])getNull()); + String[] array = (String[])getNull(); objectField = array[0]; objectField = array[1]; objectField = array[2]; @@ -68,6 +90,23 @@ public class Main { longField = longArray[3]; } + /// CHECK-START: float Main.test1() load_store_elimination (after) + /// CHECK-DAG: <<Null:l\d+>> NullConstant + /// CHECK-DAG: <<Check1:l\d+>> NullCheck [<<Null>>] + /// CHECK-DAG: <<FieldGet1:l\d+>> InstanceFieldGet [<<Check1>>] field_name:Test1.iarr + /// CHECK-DAG: <<Check2:l\d+>> NullCheck [<<FieldGet1>>] + /// CHECK-DAG: <<ArrayGet1:i\d+>> ArrayGet [<<Check2>>,{{i\d+}}] + /// CHECK-DAG: <<ArrayGet2:f\d+>> ArrayGet [<<Check2>>,{{i\d+}}] + /// CHECK-DAG: Return [<<ArrayGet2>>] + public static float test1() { + Test1 test1 = getNullTest1(); + Test2 test2 = getNullTest2();; + int[] iarr = test1.iarr; + float[] farr = test2.farr; + iarr[0] = iarr[1]; + return farr[0]; + } + public static long longField; public static Object objectField; } diff --git a/test/594-checker-array-alias/expected.txt b/test/594-checker-array-alias/expected.txt new file mode 100644 index 0000000000..b0aad4deb5 --- /dev/null +++ b/test/594-checker-array-alias/expected.txt @@ -0,0 +1 @@ +passed diff --git a/test/594-checker-array-alias/info.txt b/test/594-checker-array-alias/info.txt new file mode 100644 index 0000000000..57c6de541f --- /dev/null +++ b/test/594-checker-array-alias/info.txt @@ -0,0 +1 @@ +Tests on array parameters with and without alias. diff --git a/test/594-checker-array-alias/src/Main.java b/test/594-checker-array-alias/src/Main.java new file mode 100644 index 0000000000..5ece2e295e --- /dev/null +++ b/test/594-checker-array-alias/src/Main.java @@ -0,0 +1,255 @@ +/* + * 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. + */ + +import java.util.Arrays; + +// +// Test on array parameters with or without potential aliasing. +// +public class Main { + + // + // Cross-over on parameters with potential aliasing on parameters. + // The arrays a and b may point to the same memory, which (without + // further runtime tests) prevents hoisting the seemingly invariant + // array reference. + // + + /// CHECK-START: void Main.CrossOverLoop1(int[], int[]) licm (before) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + // + /// CHECK-START: void Main.CrossOverLoop1(int[], int[]) licm (after) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + private static void CrossOverLoop1(int a[], int b[]) { + b[20] = 99; + for (int i = 0; i < a.length; i++) { + a[i] = b[20] - 7; + } + } + + /// CHECK-START: void Main.CrossOverLoop2(float[], float[]) licm (before) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + // + /// CHECK-START: void Main.CrossOverLoop2(float[], float[]) licm (after) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + private static void CrossOverLoop2(float a[], float b[]) { + b[20] = 99; + for (int i = 0; i < a.length; i++) { + a[i] = b[20] - 7; + } + } + + /// CHECK-START: void Main.CrossOverLoop3(long[], long[]) licm (before) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + // + /// CHECK-START: void Main.CrossOverLoop3(long[], long[]) licm (after) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + private static void CrossOverLoop3(long a[], long b[]) { + b[20] = 99; + for (int i = 0; i < a.length; i++) { + a[i] = b[20] - 7; + } + } + + /// CHECK-START: void Main.CrossOverLoop4(double[], double[]) licm (before) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + // + /// CHECK-START: void Main.CrossOverLoop4(double[], double[]) licm (after) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + private static void CrossOverLoop4(double a[], double b[]) { + b[20] = 99; + for (int i = 0; i < a.length; i++) { + a[i] = b[20] - 7; + } + } + + // + // False cross-over on parameters. Parameters have same width (which used to + // cause a false type aliasing in an older version of the compiler), but since + // the types are different cannot be aliased. Thus, the invariant array + // reference can be hoisted. + // + + /// CHECK-START: void Main.FalseCrossOverLoop1(int[], float[]) licm (before) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + // + /// CHECK-START: void Main.FalseCrossOverLoop1(int[], float[]) licm (after) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:none + /// CHECK-DAG: ArraySet loop:{{B\d+}} + private static void FalseCrossOverLoop1(int a[], float b[]) { + b[20] = -99; + for (int i = 0; i < a.length; i++) { + a[i] = (int) b[20] - 7; + } + } + + /// CHECK-START: void Main.FalseCrossOverLoop2(float[], int[]) licm (before) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + // + /// CHECK-START: void Main.FalseCrossOverLoop2(float[], int[]) licm (after) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:none + /// CHECK-DAG: ArraySet loop:{{B\d+}} + private static void FalseCrossOverLoop2(float a[], int b[]) { + b[20] = -99; + for (int i = 0; i < a.length; i++) { + a[i] = b[20] - 7; + } + } + + /// CHECK-START: void Main.FalseCrossOverLoop3(long[], double[]) licm (before) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + // + /// CHECK-START: void Main.FalseCrossOverLoop3(long[], double[]) licm (after) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:none + /// CHECK-DAG: ArraySet loop:{{B\d+}} + private static void FalseCrossOverLoop3(long a[], double b[]) { + b[20] = -99; + for (int i = 0; i < a.length; i++) { + a[i] = (long) b[20] - 7; + } + } + + /// CHECK-START: void Main.FalseCrossOverLoop4(double[], long[]) licm (before) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:{{B\d+}} + /// CHECK-DAG: ArraySet loop:{{B\d+}} + // + /// CHECK-START: void Main.FalseCrossOverLoop4(double[], long[]) licm (after) + /// CHECK-DAG: ArraySet loop:none + /// CHECK-DAG: ArrayGet loop:none + /// CHECK-DAG: ArraySet loop:{{B\d+}} + private static void FalseCrossOverLoop4(double a[], long b[]) { + b[20] = -99; + for (int i = 0; i < a.length; i++) { + a[i] = b[20] - 7; + } + } + + // + // Main driver and testers. + // + + public static void main(String[] args) { + int[] aI = new int[100]; + float[] aF = new float[100]; + long[] aJ = new long[100]; + double[] aD = new double[100]; + + // Type I. + CrossOverLoop1(aI, aI); + for (int i = 0; i < aI.length; i++) { + expectEquals(i <= 20 ? 92 : 85, aI[i]); + } + // Type F. + CrossOverLoop2(aF, aF); + for (int i = 0; i < aF.length; i++) { + expectEquals(i <= 20 ? 92 : 85, aF[i]); + } + // Type J. + CrossOverLoop3(aJ, aJ); + for (int i = 0; i < aJ.length; i++) { + expectEquals(i <= 20 ? 92 : 85, aJ[i]); + } + // Type D. + CrossOverLoop4(aD, aD); + for (int i = 0; i < aD.length; i++) { + expectEquals(i <= 20 ? 92 : 85, aD[i]); + } + + // Type I vs F. + FalseCrossOverLoop1(aI, aF); + for (int i = 0; i < aI.length; i++) { + expectEquals(-106, aI[i]); + } + // Type F vs I. + FalseCrossOverLoop2(aF, aI); + for (int i = 0; i < aF.length; i++) { + expectEquals(-106, aF[i]); + } + // Type J vs D. + FalseCrossOverLoop3(aJ, aD); + for (int i = 0; i < aJ.length; i++) { + expectEquals(-106, aJ[i]); + } + // Type D vs J. + FalseCrossOverLoop4(aD, aJ); + for (int i = 0; i < aD.length; i++) { + expectEquals(-106, aD[i]); + } + + // Real-world example where incorrect type assignment could introduce a bug. + // The library sorting algorithm is heavy on array reads and writes, and + // assigning the wrong J/D type to one of these would introduce errors. + for (int i = 0; i < aD.length; i++) { + aD[i] = aD.length - i - 1; + } + Arrays.sort(aD); + for (int i = 0; i < aD.length; i++) { + expectEquals((double) i, aD[i]); + } + + System.out.println("passed"); + } + + private static void expectEquals(int expected, int result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } + + private static void expectEquals(long expected, long result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } + + private static void expectEquals(float expected, float result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } + + private static void expectEquals(double expected, double result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } +} diff --git a/test/594-checker-irreducible-linorder/expected.txt b/test/594-checker-irreducible-linorder/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/594-checker-irreducible-linorder/expected.txt diff --git a/test/594-checker-irreducible-linorder/info.txt b/test/594-checker-irreducible-linorder/info.txt new file mode 100644 index 0000000000..a1783f8ec1 --- /dev/null +++ b/test/594-checker-irreducible-linorder/info.txt @@ -0,0 +1,2 @@ +Regression test for a failing DCHECK in SSA liveness analysis in the presence +of irreducible loops. diff --git a/test/594-checker-irreducible-linorder/smali/IrreducibleLoop.smali b/test/594-checker-irreducible-linorder/smali/IrreducibleLoop.smali new file mode 100644 index 0000000000..ef53ee867a --- /dev/null +++ b/test/594-checker-irreducible-linorder/smali/IrreducibleLoop.smali @@ -0,0 +1,123 @@ +# Copyright (C) 2016 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. + +.class public LIrreducibleLoop; +.super Ljava/lang/Object; + +# Test case where liveness analysis produces linear order where loop blocks are +# not adjacent. + +## CHECK-START: int IrreducibleLoop.liveness(boolean, boolean, boolean, int) builder (after) +## CHECK-DAG: Add loop:none +## CHECK-DAG: Mul loop:<<Loop:B\d+>> +## CHECK-DAG: Not loop:<<Loop>> + +## CHECK-START: int IrreducibleLoop.liveness(boolean, boolean, boolean, int) liveness (after) +## CHECK-DAG: Add liveness:<<LPreEntry:\d+>> +## CHECK-DAG: Mul liveness:<<LHeader:\d+>> +## CHECK-DAG: Not liveness:<<LBackEdge:\d+>> +## CHECK-EVAL: (<<LHeader>> < <<LPreEntry>>) and (<<LPreEntry>> < <<LBackEdge>>) + +.method public static liveness(ZZZI)I + .registers 10 + const/16 v0, 42 + + if-eqz p0, :header + + :pre_entry + add-int/2addr p3, p3 + invoke-static {v0}, Ljava/lang/System;->exit(I)V + goto :body1 + + # Trivially dead code to ensure linear order verification skips removed blocks (b/28252537). + :dead_code + nop + goto :dead_code + + :header + mul-int/2addr p3, p3 + if-eqz p1, :body2 + + :body1 + goto :body_merge + + :body2 + invoke-static {v0}, Ljava/lang/System;->exit(I)V + goto :body_merge + + :body_merge + if-eqz p2, :exit + + :back_edge + not-int p3, p3 + goto :header + + :exit + return p3 + +.end method + +## CHECK-START: int IrreducibleLoop.liveness2(boolean, boolean, boolean, int) builder (after) +## CHECK-DAG: Mul loop:<<Loop:B\d+>> +## CHECK-DAG: Not loop:<<Loop>> + +## CHECK-START: int IrreducibleLoop.liveness2(boolean, boolean, boolean, int) liveness (after) +## CHECK-DAG: Mul liveness:<<LPreEntry2:\d+>> +## CHECK-DAG: Not liveness:<<LBackEdge1:\d+>> +## CHECK-EVAL: <<LBackEdge1>> < <<LPreEntry2>> + +.method public liveness2(ZZZI)I + .registers 10 + + const v1, 1 + + :header1 + if-eqz p0, :body1 + + :exit + return p3 + + :body1 + # The test will generate an incorrect linear order when the following IF swaps + # its successors. To do that, load a boolean value and compare NotEqual to 1. + sget-boolean v2, LIrreducibleLoop;->f:Z + const v3, 1 + if-ne v2, v3, :pre_header2 + + :pre_entry2 + # This constant has a use in a phi in :back_edge2 and a back edge use in + # :back_edge1. Because the linear order is wrong, the back edge use has + # a lower liveness than the phi use. + const v0, 42 + mul-int/2addr p3, p3 + goto :back_edge2 + + :back_edge2 + add-int/2addr p3, v0 + add-int/2addr v0, v1 + goto :header2 + + :header2 + if-eqz p2, :back_edge2 + + :back_edge1 + not-int p3, p3 + goto :header1 + + :pre_header2 + const v0, 42 + goto :header2 +.end method + +.field public static f:Z diff --git a/test/594-checker-irreducible-linorder/src/Main.java b/test/594-checker-irreducible-linorder/src/Main.java new file mode 100644 index 0000000000..38b2ab4384 --- /dev/null +++ b/test/594-checker-irreducible-linorder/src/Main.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2016 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. + */ + +public class Main { + // Workaround for b/18051191. + class InnerClass {} + + public static void main(String[] args) { + // Nothing to run. This regression test merely makes sure the smali test + // case successfully compiles. + } +} diff --git a/test/594-invoke-super/expected.txt b/test/594-invoke-super/expected.txt new file mode 100644 index 0000000000..de26026c99 --- /dev/null +++ b/test/594-invoke-super/expected.txt @@ -0,0 +1,7 @@ +new A +I am A's foo +new B +I am B's foo +new A +new B +passed diff --git a/test/594-invoke-super/info.txt b/test/594-invoke-super/info.txt new file mode 100644 index 0000000000..440d8b8c66 --- /dev/null +++ b/test/594-invoke-super/info.txt @@ -0,0 +1 @@ +Invoke-super on various references. diff --git a/test/594-invoke-super/smali/invoke-super.smali b/test/594-invoke-super/smali/invoke-super.smali new file mode 100644 index 0000000000..6f787dd170 --- /dev/null +++ b/test/594-invoke-super/smali/invoke-super.smali @@ -0,0 +1,31 @@ +# +# Copyright (C) 2016 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. + +.class public LZ; +.super LA; + +.method public constructor <init>()V +.registers 1 + invoke-direct {v0}, LA;-><init>()V + return-void +.end method + +.method public foo()V +.registers 3 + new-instance v0, LY; + invoke-direct {v0}, LY;-><init>()V + invoke-super {v0}, LY;->foo()V + return-void +.end method diff --git a/test/594-invoke-super/src/Main.java b/test/594-invoke-super/src/Main.java new file mode 100644 index 0000000000..53f2bbf67a --- /dev/null +++ b/test/594-invoke-super/src/Main.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2016 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. + */ + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +// +// Two classes A and B with method foo(). +// + +class A { + A() { System.out.println("new A"); } + + public void foo() { System.out.println("I am A's foo"); } + + // We previously used to invoke this method with a Y instance, due + // to invoke-super underspecified behavior. + public void bar() { System.out.println("I am A's bar"); } +} + +class B { + B() { System.out.println("new B"); } + + public void foo() { System.out.println("I am B's foo"); } +} + +// +// Two subclasses X and Y that call foo() on super. +// + +class X extends A { + public void foo() { super.foo(); } +} + +class Y extends B { + public void foo() { super.foo(); } +} + +// +// Driver class. +// + +public class Main { + + public static void main(String[] args) throws Exception { + // The normal stuff, X's super goes to A, Y's super goes to B. + new X().foo(); + new Y().foo(); + + // And now it gets interesting. + + // In bytecode, we define a class Z that is a subclass of A, and we call + // invoke-super on an instance of Y. + Class<?> z = Class.forName("Z"); + Method m = z.getMethod("foo"); + try { + m.invoke(z.newInstance()); + throw new Error("Expected InvocationTargetException"); + } catch (InvocationTargetException e) { + if (!(e.getCause() instanceof NoSuchMethodError)) { + throw new Error("Expected NoSuchMethodError"); + } + } + + System.out.println("passed"); + } +} diff --git a/test/594-load-string-regression/expected.txt b/test/594-load-string-regression/expected.txt new file mode 100644 index 0000000000..365b0e168f --- /dev/null +++ b/test/594-load-string-regression/expected.txt @@ -0,0 +1 @@ +String: "" diff --git a/test/594-load-string-regression/info.txt b/test/594-load-string-regression/info.txt new file mode 100644 index 0000000000..6a07ace81b --- /dev/null +++ b/test/594-load-string-regression/info.txt @@ -0,0 +1,2 @@ +Regression test for LoadString listing side effects when it doesn't have any +and triggering a DCHECK() failure when merging ClinitCheck into NewInstance. diff --git a/test/594-load-string-regression/src/Main.java b/test/594-load-string-regression/src/Main.java new file mode 100644 index 0000000000..0b9f7b52a1 --- /dev/null +++ b/test/594-load-string-regression/src/Main.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2016 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. + */ + +public class Main { + static boolean doThrow = false; + + // Note: We're not doing checker tests as we cannot do them specifically for a non-PIC + // configuration. The check here would be "prepare_for_register_allocation (before)" + // CHECK: LoadClass + // CHECK-NEXT: ClinitCheck + // CHECK-NEXT: LoadString load_kind:BootImageAddress + // CHECK-NEXT: NewInstance + // and "prepare_for_register_allocation (after)" + // CHECK: LoadString + // CHECK-NEXT: NewInstance + // but the order of instructions for non-PIC mode is different. + public static int $noinline$test() { + if (doThrow) { throw new Error(); } + + int r = 0x12345678; + do { + // LICM pulls the LoadClass and ClinitCheck out of the loop, leaves NewInstance in the loop. + Helper h = new Helper(); + // For non-PIC mode, LICM pulls the boot image LoadString out of the loop. + // (For PIC mode, the LoadString can throw and will not be moved out of the loop.) + String s = ""; // Empty string is known to be in the boot image. + r = r ^ (r >> 5); + h.$noinline$printString(s); + // During DCE after inlining, the loop back-edge disappears and the pre-header is + // merged with the body, leaving consecutive LoadClass, ClinitCheck, LoadString + // and NewInstance in non-PIC mode. The prepare_for_register_allocation pass + // merges the LoadClass and ClinitCheck with the NewInstance and checks that + // there are no instructions with side effects in between. This check used to + // fail because LoadString was always listing SideEffects::CanTriggerGC() even + // when it doesn't really have any side effects, i.e. for direct references to + // boot image Strings or for Strings known to be in the dex cache. + } while ($inline$shouldContinue()); + return r; + } + + static boolean $inline$shouldContinue() { + return false; + } + + public static void main(String[] args) { + assertIntEquals(0x12345678 ^ (0x12345678 >> 5), $noinline$test()); + } + + public static void assertIntEquals(int expected, int result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } +} + +class Helper { + static boolean doThrow = false; + + public void $noinline$printString(String s) { + if (doThrow) { throw new Error(); } + + System.out.println("String: \"" + s + "\""); + } +} diff --git a/test/595-error-class/expected.txt b/test/595-error-class/expected.txt new file mode 100644 index 0000000000..b0aad4deb5 --- /dev/null +++ b/test/595-error-class/expected.txt @@ -0,0 +1 @@ +passed diff --git a/test/595-error-class/info.txt b/test/595-error-class/info.txt new file mode 100644 index 0000000000..a58b8b31b4 --- /dev/null +++ b/test/595-error-class/info.txt @@ -0,0 +1 @@ +Regression test on merging array type with error component type. diff --git a/test/595-error-class/smali/error.smali b/test/595-error-class/smali/error.smali new file mode 100644 index 0000000000..925c34b293 --- /dev/null +++ b/test/595-error-class/smali/error.smali @@ -0,0 +1,23 @@ +# Copyright (C) 2016 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. + +.class public final LAnError; + +.super LSuperOfAnError; + +# Override a final method to put this class in the error state. +.method public foo()V + .registers 1 + return-void +.end method diff --git a/test/595-error-class/smali/merge.smali b/test/595-error-class/smali/merge.smali new file mode 100644 index 0000000000..2f8b41504e --- /dev/null +++ b/test/595-error-class/smali/merge.smali @@ -0,0 +1,31 @@ +# +# Copyright (C) 2016 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. + +.class public LMerge; +.super Ljava/lang/Object; + +# Method that selects between x = new Integer[] or new AnError[], +# Reference type propagation should correctly see error in component type. +.method public static select(Z)Ljava/lang/Object; + .registers 2 + const/16 v0, 10 + if-eqz v1, :Skip + new-array v0, v0, [LAnError; + goto :Done +:Skip + new-array v0, v0, [Ljava/lang/Integer; +:Done + return-object v0 +.end method diff --git a/test/595-error-class/smali/super.smali b/test/595-error-class/smali/super.smali new file mode 100644 index 0000000000..da7467d164 --- /dev/null +++ b/test/595-error-class/smali/super.smali @@ -0,0 +1,22 @@ +# Copyright (C) 2016 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. + +.class public LSuperOfAnError; + +.super Ljava/lang/Object; + +.method public final foo()V + .registers 1 + return-void +.end method diff --git a/compiler/utils/arena_allocator_test.cc b/test/595-error-class/src/Main.java index 7f67ef14bd..655fa4336a 100644 --- a/compiler/utils/arena_allocator_test.cc +++ b/test/595-error-class/src/Main.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 The Android Open Source Project + * Copyright (C) 2016 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. @@ -14,20 +14,17 @@ * limitations under the License. */ -#include "base/arena_allocator.h" -#include "base/arena_bit_vector.h" -#include "gtest/gtest.h" +import java.lang.reflect.*; -namespace art { +public class Main { -TEST(ArenaAllocator, Test) { - ArenaPool pool; - ArenaAllocator arena(&pool); - ArenaBitVector bv(&arena, 10, true); - bv.SetBit(5); - EXPECT_EQ(1U, bv.GetStorageSize()); - bv.SetBit(35); - EXPECT_EQ(2U, bv.GetStorageSize()); + public static void main(String args[]) throws Throwable { + Class<?> c = Class.forName("Merge"); + Method m = c.getMethod("select", boolean.class); + Object x = m.invoke(null, true); + if (x == null) { + throw new Error("Did not get array"); + } + System.out.println("passed"); + } } - -} // namespace art diff --git a/test/595-profile-saving/expected.txt b/test/595-profile-saving/expected.txt new file mode 100644 index 0000000000..6a5618ebc6 --- /dev/null +++ b/test/595-profile-saving/expected.txt @@ -0,0 +1 @@ +JNI_OnLoad called diff --git a/test/595-profile-saving/info.txt b/test/595-profile-saving/info.txt new file mode 100644 index 0000000000..5d318f5b15 --- /dev/null +++ b/test/595-profile-saving/info.txt @@ -0,0 +1 @@ +Check that profile recording works even when JIT compilation is not enabled. diff --git a/test/595-profile-saving/profile-saving.cc b/test/595-profile-saving/profile-saving.cc new file mode 100644 index 0000000000..0d26f454c6 --- /dev/null +++ b/test/595-profile-saving/profile-saving.cc @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2016 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 "dex_file.h" + +#include "art_method-inl.h" +#include "jit/offline_profiling_info.h" +#include "jit/profile_saver.h" +#include "jni.h" +#include "method_reference.h" +#include "mirror/class-inl.h" +#include "oat_file_assistant.h" +#include "oat_file_manager.h" +#include "scoped_thread_state_change.h" +#include "ScopedUtfChars.h" +#include "thread.h" + +namespace art { +namespace { + +class CreateProfilingInfoVisitor : public StackVisitor { + public: + explicit CreateProfilingInfoVisitor(Thread* thread, const char* method_name) + SHARED_REQUIRES(Locks::mutator_lock_) + : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames), + method_name_(method_name) {} + + bool VisitFrame() SHARED_REQUIRES(Locks::mutator_lock_) { + ArtMethod* m = GetMethod(); + std::string m_name(m->GetName()); + + if (m_name.compare(method_name_) == 0) { + ProfilingInfo::Create(Thread::Current(), m, /* retry_allocation */ true); + method_index_ = m->GetDexMethodIndex(); + return false; + } + return true; + } + + int method_index_ = -1; + const char* const method_name_; +}; + +extern "C" JNIEXPORT jint JNICALL Java_Main_ensureProfilingInfo(JNIEnv* env, + jclass, + jstring method_name) { + ScopedUtfChars chars(env, method_name); + CHECK(chars.c_str() != nullptr); + ScopedObjectAccess soa(Thread::Current()); + CreateProfilingInfoVisitor visitor(soa.Self(), chars.c_str()); + visitor.WalkStack(); + return visitor.method_index_; +} + +extern "C" JNIEXPORT void JNICALL Java_Main_ensureProfileProcessing(JNIEnv*, jclass) { + ProfileSaver::ForceProcessProfiles(); +} + +extern "C" JNIEXPORT jboolean JNICALL Java_Main_presentInProfile( + JNIEnv* env, jclass cls, jstring filename, jint method_index) { + ScopedUtfChars filename_chars(env, filename); + CHECK(filename_chars.c_str() != nullptr); + ScopedObjectAccess soa(Thread::Current()); + const DexFile* dex_file = soa.Decode<mirror::Class*>(cls)->GetDexCache()->GetDexFile(); + return ProfileSaver::HasSeenMethod(std::string(filename_chars.c_str()), + dex_file, + static_cast<uint16_t>(method_index)); +} + +} // namespace +} // namespace art diff --git a/test/595-profile-saving/run b/test/595-profile-saving/run new file mode 100644 index 0000000000..068ad03ce0 --- /dev/null +++ b/test/595-profile-saving/run @@ -0,0 +1,27 @@ +#!/bin/bash +# +# Copyright 2016 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. + +# Use +# --compiler-filter=interpret-only to make sure that the test is not compiled AOT +# and to make sure the test is not compiled when loaded (by PathClassLoader) +# -Xjitsaveprofilinginfo to enable profile saving +# -Xusejit:false to disable jit and only test profiles. +exec ${RUN} \ + -Xcompiler-option --compiler-filter=interpret-only \ + --runtime-option '-Xcompiler-option --compiler-filter=interpret-only' \ + --runtime-option -Xjitsaveprofilinginfo \ + --runtime-option -Xusejit:false \ + "${@}" diff --git a/test/595-profile-saving/src/Main.java b/test/595-profile-saving/src/Main.java new file mode 100644 index 0000000000..039503f7a4 --- /dev/null +++ b/test/595-profile-saving/src/Main.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2016 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. + */ + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; + +public class Main { + + public static void main(String[] args) throws Exception { + System.loadLibrary(args[0]); + + File file = null; + try { + file = createTempFile(); + // String codePath = getDexBaseLocation(); + String codePath = System.getenv("DEX_LOCATION") + "/595-profile-saving.jar"; + VMRuntime.registerAppInfo(file.getPath(), + System.getenv("DEX_LOCATION"), + new String[] {codePath}, + /* foreignProfileDir */ null); + + int methodIdx = $opt$noinline$testProfile(); + ensureProfileProcessing(); + if (!presentInProfile(file.getPath(), methodIdx)) { + throw new RuntimeException("Method with index " + methodIdx + " not in the profile"); + } + } finally { + if (file != null) { + file.delete(); + } + } + } + + public static int $opt$noinline$testProfile() { + if (doThrow) throw new Error(); + // Make sure we have a profile info for this method without the need to loop. + return ensureProfilingInfo("$opt$noinline$testProfile"); + } + + // Return the dex method index. + public static native int ensureProfilingInfo(String methodName); + // Ensures the profile saver does its usual processing. + public static native void ensureProfileProcessing(); + // Checks if the profiles saver knows about the method. + public static native boolean presentInProfile(String profile, int methodIdx); + + public static boolean doThrow = false; + private static final String TEMP_FILE_NAME_PREFIX = "dummy"; + private static final String TEMP_FILE_NAME_SUFFIX = "-file"; + + static native String getProfileInfoDump( + String filename); + + private static File createTempFile() throws Exception { + try { + return File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX); + } catch (IOException e) { + System.setProperty("java.io.tmpdir", "/data/local/tmp"); + try { + return File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX); + } catch (IOException e2) { + System.setProperty("java.io.tmpdir", "/sdcard"); + return File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX); + } + } + } + + private static class VMRuntime { + private static final Method registerAppInfoMethod; + static { + try { + Class<? extends Object> c = Class.forName("dalvik.system.VMRuntime"); + registerAppInfoMethod = c.getDeclaredMethod("registerAppInfo", + String.class, String.class, String[].class, String.class); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void registerAppInfo(String profile, String appDir, + String[] codePaths, String foreignDir) throws Exception { + registerAppInfoMethod.invoke(null, profile, appDir, codePaths, foreignDir); + } + } +} diff --git a/test/596-app-images/app_images.cc b/test/596-app-images/app_images.cc new file mode 100644 index 0000000000..a5bbf5fbaa --- /dev/null +++ b/test/596-app-images/app_images.cc @@ -0,0 +1,68 @@ +/* + * Copyright 2016 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 <iostream> +#include <pthread.h> +#include <stdio.h> +#include <vector> + +#include "gc/heap.h" +#include "gc/space/image_space.h" +#include "gc/space/space-inl.h" +#include "image.h" +#include "jni.h" +#include "mirror/class.h" +#include "runtime.h" +#include "scoped_thread_state_change.h" + +namespace art { + +namespace { + +extern "C" JNIEXPORT jboolean JNICALL Java_Main_checkAppImageLoaded(JNIEnv*, jclass) { + ScopedObjectAccess soa(Thread::Current()); + for (auto* space : Runtime::Current()->GetHeap()->GetContinuousSpaces()) { + if (space->IsImageSpace()) { + auto* image_space = space->AsImageSpace(); + const auto& image_header = image_space->GetImageHeader(); + if (image_header.IsAppImage()) { + return JNI_TRUE; + } + } + } + return JNI_FALSE; +} + +extern "C" JNIEXPORT jboolean JNICALL Java_Main_checkAppImageContains(JNIEnv*, jclass, jclass c) { + ScopedObjectAccess soa(Thread::Current()); + mirror::Class* klass_ptr = soa.Decode<mirror::Class*>(c); + for (auto* space : Runtime::Current()->GetHeap()->GetContinuousSpaces()) { + if (space->IsImageSpace()) { + auto* image_space = space->AsImageSpace(); + const auto& image_header = image_space->GetImageHeader(); + if (image_header.IsAppImage()) { + if (image_space->HasAddress(klass_ptr)) { + return JNI_TRUE; + } + } + } + } + return JNI_FALSE; +} + +} // namespace + +} // namespace art diff --git a/test/596-app-images/expected.txt b/test/596-app-images/expected.txt new file mode 100644 index 0000000000..6a5618ebc6 --- /dev/null +++ b/test/596-app-images/expected.txt @@ -0,0 +1 @@ +JNI_OnLoad called diff --git a/test/596-app-images/info.txt b/test/596-app-images/info.txt new file mode 100644 index 0000000000..a3d5e7ea70 --- /dev/null +++ b/test/596-app-images/info.txt @@ -0,0 +1 @@ +Tests that app-images are loaded and used. diff --git a/test/596-app-images/src/Main.java b/test/596-app-images/src/Main.java new file mode 100644 index 0000000000..75b31b8061 --- /dev/null +++ b/test/596-app-images/src/Main.java @@ -0,0 +1,33 @@ +/* + * Copyright 2016 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. + */ + +class Main { + static class Inner { + public static int abc = 0; + } + + public static void main(String[] args) { + System.loadLibrary(args[0]); + if (!checkAppImageLoaded()) { + System.out.println("App image is not loaded!"); + } else if (!checkAppImageContains(Inner.class)) { + System.out.println("App image does not contain Inner!"); + } + } + + public static native boolean checkAppImageLoaded(); + public static native boolean checkAppImageContains(Class<?> klass); +} diff --git a/test/596-checker-dead-phi/expected.txt b/test/596-checker-dead-phi/expected.txt new file mode 100644 index 0000000000..d81cc0710e --- /dev/null +++ b/test/596-checker-dead-phi/expected.txt @@ -0,0 +1 @@ +42 diff --git a/test/596-checker-dead-phi/info.txt b/test/596-checker-dead-phi/info.txt new file mode 100644 index 0000000000..7f7cf0f9e6 --- /dev/null +++ b/test/596-checker-dead-phi/info.txt @@ -0,0 +1,2 @@ +Regression test for optimizing where we used to replace a dead loop +phi with its first incoming input. diff --git a/test/596-checker-dead-phi/smali/IrreducibleLoop.smali b/test/596-checker-dead-phi/smali/IrreducibleLoop.smali new file mode 100644 index 0000000000..bab2ba99cc --- /dev/null +++ b/test/596-checker-dead-phi/smali/IrreducibleLoop.smali @@ -0,0 +1,74 @@ +# Copyright (C) 2016 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. + +.class public LIrreducibleLoop; + +.super Ljava/lang/Object; + +# Test case where liveness analysis produces linear order where loop blocks are +# not adjacent. This revealed a bug in our SSA builder, where a dead loop phi would +# be replaced by its incoming input during SsaRedundantPhiElimination. + +# Check that the outer loop suspend check environment only has the parameter vreg. +## CHECK-START: int IrreducibleLoop.liveness(int) builder (after) +## CHECK-DAG: <<Phi:i\d+>> Phi reg:4 loop:{{B\d+}} irreducible:false +## CHECK-DAG: SuspendCheck env:[[_,_,_,_,<<Phi>>]] loop:{{B\d+}} irreducible:false + +# Check that the linear order has non-adjacent loop blocks. +## CHECK-START: int IrreducibleLoop.liveness(int) liveness (after) +## CHECK-DAG: Mul liveness:<<LPreEntry2:\d+>> +## CHECK-DAG: Add liveness:<<LBackEdge1:\d+>> +## CHECK-EVAL: <<LBackEdge1>> < <<LPreEntry2>> + +.method public static liveness(I)I + .registers 5 + + const-string v1, "MyString" + + :header1 + if-eqz p0, :body1 + + :exit + return p0 + + :body1 + # The test will generate an incorrect linear order when the following IF swaps + # its successors. To do that, load a boolean value and compare NotEqual to 1. + sget-boolean v2, LIrreducibleLoop;->f:Z + const v3, 1 + if-ne v2, v3, :pre_header2 + + :pre_entry2 + # Add a marker on the irreducible loop entry. + mul-int/2addr p0, p0 + goto :back_edge2 + + :back_edge2 + goto :header2 + + :header2 + if-eqz p0, :back_edge2 + + :back_edge1 + # Add a marker on the outer loop back edge. + add-int/2addr p0, p0 + # Set a wide register, to have v1 undefined at the back edge. + const-wide/16 v0, 0x1 + goto :header1 + + :pre_header2 + goto :header2 +.end method + +.field public static f:Z diff --git a/test/596-checker-dead-phi/src/Main.java b/test/596-checker-dead-phi/src/Main.java new file mode 100644 index 0000000000..5a3fffc8f0 --- /dev/null +++ b/test/596-checker-dead-phi/src/Main.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2016 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. + */ + +import java.lang.reflect.Method; + +public class Main { + // Workaround for b/18051191. + class InnerClass {} + + public static void main(String[] args) throws Exception { + Class<?> c = Class.forName("IrreducibleLoop"); + // Note that we don't actually enter the loops in the 'liveness' + // method, so this is just a sanity check that part of the code we + // generated for that method is correct. + Method m = c.getMethod("liveness", int.class); + Object[] arguments = { 42 }; + System.out.println(m.invoke(null, arguments)); + } +} diff --git a/test/597-deopt-new-string/deopt.cc b/test/597-deopt-new-string/deopt.cc new file mode 100644 index 0000000000..844a786e1e --- /dev/null +++ b/test/597-deopt-new-string/deopt.cc @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2016 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 "jni.h" +#include "mirror/class-inl.h" +#include "runtime.h" +#include "thread_list.h" +#include "thread_state.h" +#include "gc/gc_cause.h" +#include "gc/scoped_gc_critical_section.h" + +namespace art { + +extern "C" JNIEXPORT void JNICALL Java_Main_deoptimizeAll( + JNIEnv* env, + jclass cls ATTRIBUTE_UNUSED) { + ScopedObjectAccess soa(env); + ScopedThreadSuspension sts(Thread::Current(), kWaitingForDeoptimization); + gc::ScopedGCCriticalSection gcs(Thread::Current(), + gc::kGcCauseInstrumentation, + gc::kCollectorTypeInstrumentation); + // We need to suspend mutator threads first. + ScopedSuspendAll ssa(__FUNCTION__); + static bool first = true; + if (first) { + // We need to enable deoptimization once in order to call DeoptimizeEverything(). + Runtime::Current()->GetInstrumentation()->EnableDeoptimization(); + first = false; + } + Runtime::Current()->GetInstrumentation()->DeoptimizeEverything("test"); +} + +extern "C" JNIEXPORT void JNICALL Java_Main_undeoptimizeAll( + JNIEnv* env, + jclass cls ATTRIBUTE_UNUSED) { + ScopedObjectAccess soa(env); + ScopedThreadSuspension sts(Thread::Current(), kWaitingForDeoptimization); + gc::ScopedGCCriticalSection gcs(Thread::Current(), + gc::kGcCauseInstrumentation, + gc::kCollectorTypeInstrumentation); + // We need to suspend mutator threads first. + ScopedSuspendAll ssa(__FUNCTION__); + Runtime::Current()->GetInstrumentation()->UndeoptimizeEverything("test"); +} + +} // namespace art diff --git a/test/597-deopt-new-string/expected.txt b/test/597-deopt-new-string/expected.txt new file mode 100644 index 0000000000..f993efcdad --- /dev/null +++ b/test/597-deopt-new-string/expected.txt @@ -0,0 +1,2 @@ +JNI_OnLoad called +Finishing diff --git a/test/597-deopt-new-string/info.txt b/test/597-deopt-new-string/info.txt new file mode 100644 index 0000000000..1bd1f79c04 --- /dev/null +++ b/test/597-deopt-new-string/info.txt @@ -0,0 +1 @@ +Regression test for b/28555675 diff --git a/test/597-deopt-new-string/run b/test/597-deopt-new-string/run new file mode 100644 index 0000000000..9776ab3eb4 --- /dev/null +++ b/test/597-deopt-new-string/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright (C) 2016 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. + +# We want to run in debuggable mode which keeps the call into StringFactory.newEmptyString(). +exec ${RUN} -Xcompiler-option --debuggable "${@}" diff --git a/test/597-deopt-new-string/src/Main.java b/test/597-deopt-new-string/src/Main.java new file mode 100644 index 0000000000..e78f0d36ec --- /dev/null +++ b/test/597-deopt-new-string/src/Main.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2016 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. + */ + +public class Main implements Runnable { + static final int numberOfThreads = 2; + static final int totalOperations = 40000; + static boolean sFlag = false; + static volatile boolean done = false; + int threadIndex; + + public static native void deoptimizeAll(); + public static native void undeoptimizeAll(); + + Main(int index) { + threadIndex = index; + } + + public static void main(String[] args) throws Exception { + System.loadLibrary(args[0]); + + final Thread[] threads = new Thread[numberOfThreads]; + for (int t = 0; t < threads.length; t++) { + threads[t] = new Thread(new Main(t)); + threads[t].start(); + } + for (Thread t : threads) { + t.join(); + } + System.out.println("Finishing"); + } + + public String $noinline$run0() { + // Prevent inlining. + if (sFlag) { + throw new Error(); + } + char[] arr = {'a', 'b', 'c'}; + String str = new String(arr, 0, arr.length); + if (!str.equals("abc")) { + System.out.println("Failure 1! " + str); + System.exit(0); + } + return str; + } + + public void run() { + if (threadIndex == 0) { + // This thread keeps doing deoptimization of all threads. + // Hopefully that will trigger one deoptimization when returning from + // StringFactory.newEmptyString() in one of the other threads. + for (int i = 0; i < totalOperations; ++i) { + if (i % 50 == 0) { + deoptimizeAll(); + } + if (i % 50 == 25) { + undeoptimizeAll(); + } + } + done = true; + } else { + // This thread keeps doing new String() from a char array. + while (!done) { + String str = $noinline$run0(); + if (!str.equals("abc")) { + System.out.println("Failure 2! " + str); + System.exit(0); + } + } + } + } +} diff --git a/test/598-checker-irreducible-dominance/expected.txt b/test/598-checker-irreducible-dominance/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/598-checker-irreducible-dominance/expected.txt diff --git a/test/598-checker-irreducible-dominance/info.txt b/test/598-checker-irreducible-dominance/info.txt new file mode 100644 index 0000000000..8ca4e63be9 --- /dev/null +++ b/test/598-checker-irreducible-dominance/info.txt @@ -0,0 +1,2 @@ +Regression test for HGraphBuilder which would compute wrong dominance information +in the presence of irreducible loops.
\ No newline at end of file diff --git a/test/598-checker-irreducible-dominance/smali/IrreducibleLoop.smali b/test/598-checker-irreducible-dominance/smali/IrreducibleLoop.smali new file mode 100644 index 0000000000..4d8b5152fd --- /dev/null +++ b/test/598-checker-irreducible-dominance/smali/IrreducibleLoop.smali @@ -0,0 +1,52 @@ +# Copyright (C) 2016 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. + +.class public LIrreducibleLoop; +.super Ljava/lang/Object; + +# Test case in which `inner_back_edge` is not dominated by `inner_header` and +# causes `outer_back_edge` to not be dominated by `outer_header`. HGraphBuilder +# not do a fix-point iteration and would miss the path to `outer_back_edge` +# through `inner_back_edge` and incorrectly label the outer loop non-irreducible. + +## CHECK-START: int IrreducibleLoop.dominance(int) builder (after) +## CHECK: Add irreducible:true + +.method public static dominance(I)I + .registers 2 + + if-eqz p0, :outer_header + goto :inner_back_edge + + :outer_header + if-eqz p0, :inner_header + + :outer_branch_exit + if-eqz p0, :outer_merge + return p0 + + :inner_header + goto :outer_merge + + :inner_back_edge + goto :inner_header + + :outer_merge + if-eqz p0, :inner_back_edge + + :outer_back_edge + add-int/2addr p0, p0 + goto :outer_header + +.end method diff --git a/test/598-checker-irreducible-dominance/src/Main.java b/test/598-checker-irreducible-dominance/src/Main.java new file mode 100644 index 0000000000..38b2ab4384 --- /dev/null +++ b/test/598-checker-irreducible-dominance/src/Main.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2016 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. + */ + +public class Main { + // Workaround for b/18051191. + class InnerClass {} + + public static void main(String[] args) { + // Nothing to run. This regression test merely makes sure the smali test + // case successfully compiles. + } +} diff --git a/test/599-checker-irreducible-loop/expected.txt b/test/599-checker-irreducible-loop/expected.txt new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/test/599-checker-irreducible-loop/expected.txt @@ -0,0 +1 @@ +0 diff --git a/test/599-checker-irreducible-loop/info.txt b/test/599-checker-irreducible-loop/info.txt new file mode 100644 index 0000000000..1e0dd02284 --- /dev/null +++ b/test/599-checker-irreducible-loop/info.txt @@ -0,0 +1,2 @@ +Regression test for optimizing in the presence of +an irreducible loop. diff --git a/test/599-checker-irreducible-loop/smali/IrreducibleLoop.smali b/test/599-checker-irreducible-loop/smali/IrreducibleLoop.smali new file mode 100644 index 0000000000..5331fd6a31 --- /dev/null +++ b/test/599-checker-irreducible-loop/smali/IrreducibleLoop.smali @@ -0,0 +1,56 @@ +# Copyright (C) 2016 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. + +.class public LIrreducibleLoop; + +.super Ljava/lang/Object; + +## CHECK-START: int IrreducibleLoop.test(int) GVN (before) +## CHECK-DAG: LoadClass loop:none +## CHECK-DAG: LoadClass loop:{{B\d+}} outer_loop:none + +## CHECK-START: int IrreducibleLoop.test(int) GVN (after) +## CHECK-DAG: LoadClass loop:none +## CHECK-DAG: LoadClass loop:{{B\d+}} outer_loop:none +.method public static test(I)I + .registers 2 + + sget v0, LIrreducibleLoop;->field1:I + sput v0, LIrreducibleLoop;->field2:I + + if-eqz p0, :loop_entry + goto :exit + + :loop_entry + if-eqz p0, :irreducible_loop_entry + sget v0, LIrreducibleLoop;->field2:I + sput v0, LIrreducibleLoop;->field1:I + if-eqz v0, :exit + goto :irreducible_other_loop_entry + + :irreducible_loop_entry + if-eqz p0, :loop_back_edge + :irreducible_other_loop_entry + if-eqz v0, :loop_back_edge + goto :irreducible_loop_entry + + :loop_back_edge + goto :loop_entry + + :exit + return v0 +.end method + +.field public static field1:I +.field public static field2:I diff --git a/test/599-checker-irreducible-loop/src/Main.java b/test/599-checker-irreducible-loop/src/Main.java new file mode 100644 index 0000000000..b47721f721 --- /dev/null +++ b/test/599-checker-irreducible-loop/src/Main.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2016 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. + */ + +import java.lang.reflect.Method; + +public class Main { + // Workaround for b/18051191. + class InnerClass {} + + public static void main(String[] args) throws Exception { + Class<?> c = Class.forName("IrreducibleLoop"); + Method m = c.getMethod("test", int.class); + Object[] arguments = { 42 }; + // Invoke the code just for sanity checking. + System.out.println(m.invoke(null, arguments)); + } +} diff --git a/test/600-verifier-fails/expected.txt b/test/600-verifier-fails/expected.txt new file mode 100644 index 0000000000..010f1b74b2 --- /dev/null +++ b/test/600-verifier-fails/expected.txt @@ -0,0 +1,3 @@ +passed A +passed B +passed C diff --git a/test/600-verifier-fails/info.txt b/test/600-verifier-fails/info.txt new file mode 100644 index 0000000000..677b4775d5 --- /dev/null +++ b/test/600-verifier-fails/info.txt @@ -0,0 +1,14 @@ +The situations in these tests were discovered by running the mutating +dexfuzz on the DEX files of fuzzingly random generated Java test. + +(1) b/28908555: + soft verification fail (on the final field modification) should + not hide the hard verification fail (on the type mismatch) to + avoid compiler crash later on +(2) b/29070461: + hard failure (not calling super in constructor) should bail + immediately and not allow soft fails to pile up behind it to + avoid fatal message later on +(3) b/29068831: + access validation should occur prior to null reference check + diff --git a/test/600-verifier-fails/smali/construct.smali b/test/600-verifier-fails/smali/construct.smali new file mode 100644 index 0000000000..417ced94fa --- /dev/null +++ b/test/600-verifier-fails/smali/construct.smali @@ -0,0 +1,25 @@ +# +# Copyright (C) 2016 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. + +.class public LB; +.super Ljava/lang/Object; + +.method public constructor <init>()V + .registers 1 + if-eqz v0, :bail + invoke-direct {v0}, LB;->append(Ljava/lang/String;)V +:bail + return-void +.end method diff --git a/test/600-verifier-fails/smali/iput.smali b/test/600-verifier-fails/smali/iput.smali new file mode 100644 index 0000000000..bd8b9280c0 --- /dev/null +++ b/test/600-verifier-fails/smali/iput.smali @@ -0,0 +1,25 @@ +# +# Copyright (C) 2016 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. + +.class public LC; +.super Ljava/lang/Object; + +.method public constructor <init>()V + .registers 2 + invoke-direct {v1}, Ljava/lang/Object;-><init>()V + const v0, 0 + iput-object v0, v0, LMain;->staticPrivateField:Ljava/lang/String; + return-void +.end method diff --git a/test/600-verifier-fails/smali/sput.smali b/test/600-verifier-fails/smali/sput.smali new file mode 100644 index 0000000000..e8e56acf13 --- /dev/null +++ b/test/600-verifier-fails/smali/sput.smali @@ -0,0 +1,23 @@ +# +# Copyright (C) 2016 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. + +.class public LA; +.super Ljava/lang/Object; + +.method public foo(I)V +.registers 2 + sput v1, LMain;->staticFinalField:Ljava/lang/String; + return-void +.end method diff --git a/test/600-verifier-fails/src/Main.java b/test/600-verifier-fails/src/Main.java new file mode 100644 index 0000000000..0a8c5a179d --- /dev/null +++ b/test/600-verifier-fails/src/Main.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2016 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. + */ + +public class Main { + + public static final String staticFinalField = null; + + private static String staticPrivateField = null; + + private static void test(String name) throws Exception { + try { + Class<?> a = Class.forName(name); + a.newInstance(); + } catch (java.lang.LinkageError e) { + System.out.println("passed " + name); + } + } + + public static void main(String[] args) throws Exception { + test("A"); + test("B"); + test("C"); + } +} diff --git a/test/601-method-access/expected.txt b/test/601-method-access/expected.txt new file mode 100644 index 0000000000..90fbab87af --- /dev/null +++ b/test/601-method-access/expected.txt @@ -0,0 +1 @@ +Got expected failure diff --git a/test/601-method-access/info.txt b/test/601-method-access/info.txt new file mode 100644 index 0000000000..e38a336179 --- /dev/null +++ b/test/601-method-access/info.txt @@ -0,0 +1 @@ +Regression test for method access checks. diff --git a/test/601-method-access/smali/SubClassUsingInaccessibleMethod.smali b/test/601-method-access/smali/SubClassUsingInaccessibleMethod.smali new file mode 100644 index 0000000000..7a896a2b8d --- /dev/null +++ b/test/601-method-access/smali/SubClassUsingInaccessibleMethod.smali @@ -0,0 +1,33 @@ +# Copyright (C) 2016 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. + +.class public LSubClassUsingInaccessibleMethod; + +.super Lother/PublicClass; + +.method public constructor <init>()V + .registers 1 + invoke-direct {p0}, Lother/PublicClass;-><init>()V + return-void +.end method + +# Regression test for compiler DCHECK() failure (bogus check) when referencing +# a package-private method from an indirectly inherited package-private class, +# using this very class as the declaring class in the MethodId, bug: 28771056. +.method public test()I + .registers 2 + invoke-virtual {p0}, LSubClassUsingInaccessibleMethod;->otherProtectedClassPackageIntInstanceMethod()I + move-result v0 + return v0 +.end method diff --git a/test/601-method-access/src/Main.java b/test/601-method-access/src/Main.java new file mode 100644 index 0000000000..838080a506 --- /dev/null +++ b/test/601-method-access/src/Main.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2016 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. + */ + +import java.lang.reflect.InvocationTargetException; + +/* + * Test method access through reflection. + */ +public class Main { + public static void main(String[] args) { + try { + Class c = Class.forName("SubClassUsingInaccessibleMethod"); + Object o = c.newInstance(); + c.getMethod("test").invoke(o, null); + } catch (InvocationTargetException ite) { + if (ite.getCause() instanceof IllegalAccessError) { + System.out.println("Got expected failure"); + } else { + System.out.println("Got unexpected failure " + ite.getCause()); + } + } catch (Exception e) { + System.out.println("Got unexpected failure " + e); + } + } +} diff --git a/test/601-method-access/src/other/ProtectedClass.java b/test/601-method-access/src/other/ProtectedClass.java new file mode 100644 index 0000000000..9426884c07 --- /dev/null +++ b/test/601-method-access/src/other/ProtectedClass.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2016 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. + */ + +package other; + +// Class that cannot be accessed outside of this package. +class ProtectedClass { + /* package */ int otherProtectedClassPackageIntInstanceMethod() { + return 28; + } +} diff --git a/test/601-method-access/src/other/PublicClass.java b/test/601-method-access/src/other/PublicClass.java new file mode 100644 index 0000000000..d9f79610c5 --- /dev/null +++ b/test/601-method-access/src/other/PublicClass.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2016 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. + */ + +package other; + +// Class that makes the ProtectedClass sub-classable by classes outside of package other. +public class PublicClass extends ProtectedClass { +} diff --git a/test/602-deoptimizeable/expected.txt b/test/602-deoptimizeable/expected.txt new file mode 100644 index 0000000000..f993efcdad --- /dev/null +++ b/test/602-deoptimizeable/expected.txt @@ -0,0 +1,2 @@ +JNI_OnLoad called +Finishing diff --git a/test/602-deoptimizeable/info.txt b/test/602-deoptimizeable/info.txt new file mode 100644 index 0000000000..d0952f903b --- /dev/null +++ b/test/602-deoptimizeable/info.txt @@ -0,0 +1 @@ +Test various cases for full/partial-fragment deoptimization. diff --git a/test/602-deoptimizeable/src/Main.java b/test/602-deoptimizeable/src/Main.java new file mode 100644 index 0000000000..743a5796c9 --- /dev/null +++ b/test/602-deoptimizeable/src/Main.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2016 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. + */ + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; + +class DummyObject { + public static boolean sHashCodeInvoked = false; + private int i; + + public DummyObject(int i) { + this.i = i; + } + + public boolean equals(Object obj) { + return (obj instanceof DummyObject) && (i == ((DummyObject)obj).i); + } + + public int hashCode() { + sHashCodeInvoked = true; + Main.assertIsManaged(); + Main.deoptimizeAll(); + Main.assertIsInterpreted(); + Main.assertCallerIsManaged(); // Caller is from framework code HashMap. + return i % 64; + } +} + +public class Main { + static boolean sFlag = false; + + public static native void deoptimizeAll(); + public static native void undeoptimizeAll(); + public static native void assertIsInterpreted(); + public static native void assertIsManaged(); + public static native void assertCallerIsInterpreted(); + public static native void assertCallerIsManaged(); + public static native void disableStackFrameAsserts(); + public static native boolean hasOatFile(); + public static native boolean isInterpreted(); + + public static void execute(Runnable runnable) throws Exception { + Thread t = new Thread(runnable); + t.start(); + t.join(); + } + + public static void main(String[] args) throws Exception { + System.loadLibrary(args[0]); + // Only test stack frames in compiled mode. + if (!hasOatFile() || isInterpreted()) { + disableStackFrameAsserts(); + } + final HashMap<DummyObject, Long> map = new HashMap<DummyObject, Long>(); + + // Single-frame deoptimization that covers partial fragment. + execute(new Runnable() { + public void run() { + int[] arr = new int[3]; + assertIsManaged(); + int res = $noinline$run1(arr); + assertIsManaged(); // Only single frame is deoptimized. + if (res != 79) { + System.out.println("Failure 1!"); + System.exit(0); + } + } + }); + + // Single-frame deoptimization that covers a full fragment. + execute(new Runnable() { + public void run() { + try { + int[] arr = new int[3]; + assertIsManaged(); + // Use reflection to call $noinline$run2 so that it does + // full-fragment deoptimization since that is an upcall. + Class<?> cls = Class.forName("Main"); + Method method = cls.getDeclaredMethod("$noinline$run2", int[].class); + double res = (double)method.invoke(Main.class, arr); + assertIsManaged(); // Only single frame is deoptimized. + if (res != 79.3d) { + System.out.println("Failure 2!"); + System.exit(0); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + + // Full-fragment deoptimization. + execute(new Runnable() { + public void run() { + assertIsManaged(); + float res = $noinline$run3B(); + assertIsInterpreted(); // Every deoptimizeable method is deoptimized. + if (res != 0.034f) { + System.out.println("Failure 3!"); + System.exit(0); + } + } + }); + + undeoptimizeAll(); // Make compiled code useable again. + + // Partial-fragment deoptimization. + execute(new Runnable() { + public void run() { + try { + assertIsManaged(); + map.put(new DummyObject(10), Long.valueOf(100)); + assertIsInterpreted(); // Every deoptimizeable method is deoptimized. + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + + undeoptimizeAll(); // Make compiled code useable again. + + if (!DummyObject.sHashCodeInvoked) { + System.out.println("hashCode() method not invoked!"); + } + if (map.get(new DummyObject(10)) != 100) { + System.out.println("Wrong hashmap value!"); + } + System.out.println("Finishing"); + } + + public static int $noinline$run1(int[] arr) { + assertIsManaged(); + // Prevent inlining. + if (sFlag) { + throw new Error(); + } + boolean caught = false; + // BCE will use deoptimization for the code below. + try { + arr[0] = 1; + arr[1] = 1; + arr[2] = 1; + // This causes AIOOBE and triggers deoptimization from compiled code. + arr[3] = 1; + } catch (ArrayIndexOutOfBoundsException e) { + assertIsInterpreted(); // Single-frame deoptimization triggered. + caught = true; + } + if (!caught) { + System.out.println("Expected exception"); + } + assertIsInterpreted(); + return 79; + } + + public static double $noinline$run2(int[] arr) { + assertIsManaged(); + // Prevent inlining. + if (sFlag) { + throw new Error(); + } + boolean caught = false; + // BCE will use deoptimization for the code below. + try { + arr[0] = 1; + arr[1] = 1; + arr[2] = 1; + // This causes AIOOBE and triggers deoptimization from compiled code. + arr[3] = 1; + } catch (ArrayIndexOutOfBoundsException e) { + assertIsInterpreted(); // Single-frame deoptimization triggered. + caught = true; + } + if (!caught) { + System.out.println("Expected exception"); + } + assertIsInterpreted(); + return 79.3d; + } + + public static float $noinline$run3A() { + assertIsManaged(); + // Prevent inlining. + if (sFlag) { + throw new Error(); + } + // Deoptimize callers. + deoptimizeAll(); + assertIsInterpreted(); + assertCallerIsInterpreted(); // $noinline$run3B is deoptimizeable. + return 0.034f; + } + + public static float $noinline$run3B() { + assertIsManaged(); + // Prevent inlining. + if (sFlag) { + throw new Error(); + } + float res = $noinline$run3A(); + assertIsInterpreted(); + return res; + } +} diff --git a/test/603-checker-instanceof/expected.txt b/test/603-checker-instanceof/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/603-checker-instanceof/expected.txt diff --git a/test/603-checker-instanceof/info.txt b/test/603-checker-instanceof/info.txt new file mode 100644 index 0000000000..5907abc855 --- /dev/null +++ b/test/603-checker-instanceof/info.txt @@ -0,0 +1,2 @@ +Regression test for the compiler that used to wrongly optimize +an instanceof. diff --git a/test/603-checker-instanceof/src/Main.java b/test/603-checker-instanceof/src/Main.java new file mode 100644 index 0000000000..ddf4b92fba --- /dev/null +++ b/test/603-checker-instanceof/src/Main.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2016 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. + */ + +class SuperClass { +} + +class ChildClass extends SuperClass { +} + +public class Main { + + /// CHECK-START: void Main.main(java.lang.String[]) builder (after) + /// CHECK: BoundType klass:SuperClass can_be_null:false exact:false + + /// CHECK-START: void Main.main(java.lang.String[]) builder (after) + /// CHECK-NOT: BoundType klass:SuperClass can_be_null:false exact:true + public static void main(String[] args) { + Object obj = new ChildClass(); + + // We need a fixed point iteration to hit the bogus type update + // of 'obj' below, so create a loop that updates the type of 'obj'. + for (int i = 1; i < 1; i++) { + obj = new Object(); + } + + if (obj instanceof SuperClass) { + // We used to wrongly type obj as an exact SuperClass from this point, + // meaning we were statically determining that the following instanceof + // would always fail. + if (!(obj instanceof ChildClass)) { + throw new Error("Expected a ChildClass, got " + obj.getClass()); + } + } + } +} diff --git a/test/800-smali/expected.txt b/test/800-smali/expected.txt index c2a9a31aeb..11150c296d 100644 --- a/test/800-smali/expected.txt +++ b/test/800-smali/expected.txt @@ -66,4 +66,5 @@ b/27799205 (3) b/27799205 (4) b/27799205 (5) b/27799205 (6) +b/28187158 Done! diff --git a/test/800-smali/smali/b_28187158.smali b/test/800-smali/smali/b_28187158.smali new file mode 100644 index 0000000000..47e5ef64fd --- /dev/null +++ b/test/800-smali/smali/b_28187158.smali @@ -0,0 +1,11 @@ +.class public LB28187158; + +# Regression test for iget with wrong classes. + +.super Ljava/lang/Object; + +.method public static run(Ljava/lang/Integer;)V + .registers 2 + iget v0, p0, Ljava/lang/System;->in:Ljava/io/InputStream; + return-void +.end method diff --git a/test/800-smali/src/Main.java b/test/800-smali/src/Main.java index 2001cb4abb..b2fc005620 100644 --- a/test/800-smali/src/Main.java +++ b/test/800-smali/src/Main.java @@ -174,6 +174,8 @@ public class Main { testCases.add(new TestCase("b/27799205 (5)", "B27799205Helper", "run5", null, new VerifyError(), null)); testCases.add(new TestCase("b/27799205 (6)", "B27799205Helper", "run6", null, null, null)); + testCases.add(new TestCase("b/28187158", "B28187158", "run", new Object[] { null }, + new VerifyError(), null)); } public void runTests() { diff --git a/test/803-no-super/expected.txt b/test/803-no-super/expected.txt new file mode 100644 index 0000000000..5036991397 --- /dev/null +++ b/test/803-no-super/expected.txt @@ -0,0 +1,2 @@ +java.lang.ClassNotFoundException: NoSuper1 +Done! diff --git a/test/803-no-super/info.txt b/test/803-no-super/info.txt new file mode 100644 index 0000000000..0178a446e1 --- /dev/null +++ b/test/803-no-super/info.txt @@ -0,0 +1,3 @@ +Regression test that temp (erroneous) classes don't get conflict tables created. + +Obviously needs to run under Dalvik or ART. diff --git a/test/803-no-super/smali/nosuper1.smali b/test/803-no-super/smali/nosuper1.smali new file mode 100644 index 0000000000..df2eaa5ca8 --- /dev/null +++ b/test/803-no-super/smali/nosuper1.smali @@ -0,0 +1,3 @@ +.class public LNoSuper1; + +.super LNoClass; diff --git a/test/803-no-super/src/Main.java b/test/803-no-super/src/Main.java new file mode 100644 index 0000000000..a07e042c32 --- /dev/null +++ b/test/803-no-super/src/Main.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2016 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. + */ + +/** + * Attempt to load class with no superclass. + */ +public class Main { + public static void main(String[] args) throws Exception { + try { + Class<?> c = Class.forName("NoSuper1"); + } catch (Exception e) { + System.out.println(e); + } + System.out.println("Done!"); + } +} diff --git a/test/804-class-extends-itself/expected.txt b/test/804-class-extends-itself/expected.txt new file mode 100644 index 0000000000..b98f963ce7 --- /dev/null +++ b/test/804-class-extends-itself/expected.txt @@ -0,0 +1,2 @@ +Caught ClassCircularityError +Done! diff --git a/test/804-class-extends-itself/info.txt b/test/804-class-extends-itself/info.txt new file mode 100644 index 0000000000..c48934c21b --- /dev/null +++ b/test/804-class-extends-itself/info.txt @@ -0,0 +1 @@ +Exercise class linker check for classes extending themselves (b/28685551). diff --git a/test/804-class-extends-itself/smali/Main.smali b/test/804-class-extends-itself/smali/Main.smali new file mode 100644 index 0000000000..5c349edcc9 --- /dev/null +++ b/test/804-class-extends-itself/smali/Main.smali @@ -0,0 +1,57 @@ +# Copyright (C) 2016 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. + +# We cannot implement Main in Java, as this would require to run +# dexmerger (to merge the Dex file produced from Smali code and the +# Dex file produced from Java code), which loops indefinitely when +# processing class B28685551, as this class inherits from itself. As +# a workaround, implement Main using Smali (we could also have used +# multidex, but this requires a custom build script). + +.class public LMain; +.super Ljava/lang/Object; + +.method public static main([Ljava/lang/String;)V + .registers 3 + .param p0, "args" + + invoke-static {}, LMain;->test()V + sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream; + const-string v1, "Done!" + invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V + return-void +.end method + +.method static test()V + .registers 4 + + :try_start + const-string v2, "B28685551" + invoke-static {v2}, Ljava/lang/Class;->forName(Ljava/lang/String;)Ljava/lang/Class; + :try_end + .catch Ljava/lang/ClassCircularityError; {:try_start .. :try_end} :catch + + move-result-object v0 + + :goto_7 + return-void + + :catch + move-exception v1 + .local v1, "e":Ljava/lang/ClassCircularityError; + sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream; + const-string v3, "Caught ClassCircularityError" + invoke-virtual {v2, v3}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V + goto :goto_7 +.end method diff --git a/test/804-class-extends-itself/smali/b_28685551.smali b/test/804-class-extends-itself/smali/b_28685551.smali new file mode 100644 index 0000000000..d98c6e3b32 --- /dev/null +++ b/test/804-class-extends-itself/smali/b_28685551.smali @@ -0,0 +1,18 @@ +# Copyright (C) 2016 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. + +# Regression test for a class inheriting from itself. + +.class public LB28685551; +.super LB28685551; diff --git a/test/955-lambda-smali/build b/test/955-lambda-smali/build new file mode 100755 index 0000000000..14230c2e1d --- /dev/null +++ b/test/955-lambda-smali/build @@ -0,0 +1,20 @@ +#!/bin/bash +# +# Copyright 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. + +# make us exit on a failure +set -e + +./default-build "$@" --experimental default-methods diff --git a/test/960-default-smali/expected.txt b/test/960-default-smali/expected.txt index 7671eed5de..f3db93f87f 100644 --- a/test/960-default-smali/expected.txt +++ b/test/960-default-smali/expected.txt @@ -82,3 +82,19 @@ J-virtual A.SayHiTwice()='Hi Hi ' J-interface Greeter.SayHiTwice()='Hi Hi ' J-virtual J.SayHiTwice()='Hi Hi ' End testing for type J +Testing for type K +K-interface Foo.bar()='foobar' +K-virtual K.bar()='foobar' +End testing for type K +Testing for type L +L-interface Foo.bar()='foobar' +L-virtual K.bar()='foobar' +L-virtual L.bar()='foobar' +End testing for type L +Testing for type M +M-interface Foo.bar()='BAZ!' +M-interface Fooer.bar()='BAZ!' +M-virtual K.bar()='BAZ!' +M-virtual L.bar()='BAZ!' +M-virtual M.bar()='BAZ!' +End testing for type M diff --git a/test/960-default-smali/src/Foo.java b/test/960-default-smali/src/Foo.java new file mode 100644 index 0000000000..ed5b35f47b --- /dev/null +++ b/test/960-default-smali/src/Foo.java @@ -0,0 +1,20 @@ +/* + * Copyright 2016 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. + */ +interface Foo { + public default String bar() { + return "foobar"; + } +} diff --git a/test/960-default-smali/src/Fooer.java b/test/960-default-smali/src/Fooer.java new file mode 100644 index 0000000000..d8a5f61636 --- /dev/null +++ b/test/960-default-smali/src/Fooer.java @@ -0,0 +1,19 @@ +/* + * Copyright 2016 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. + */ + +interface Fooer extends Foo { + public String bar(); +} diff --git a/test/960-default-smali/src/K.java b/test/960-default-smali/src/K.java new file mode 100644 index 0000000000..4426be7192 --- /dev/null +++ b/test/960-default-smali/src/K.java @@ -0,0 +1,17 @@ +/* + * Copyright 2016 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. + */ + +class K implements Foo { } diff --git a/test/960-default-smali/src/L.java b/test/960-default-smali/src/L.java new file mode 100644 index 0000000000..c08ab72a99 --- /dev/null +++ b/test/960-default-smali/src/L.java @@ -0,0 +1,17 @@ +/* + * Copyright 2016 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. + */ + +class L extends K { } diff --git a/test/960-default-smali/src/M.java b/test/960-default-smali/src/M.java new file mode 100644 index 0000000000..affe7e9c9e --- /dev/null +++ b/test/960-default-smali/src/M.java @@ -0,0 +1,21 @@ +/* + * Copyright 2016 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. + */ + +class M extends L implements Fooer { + public String bar() { + return "BAZ!"; + } +} diff --git a/test/960-default-smali/src/classes.xml b/test/960-default-smali/src/classes.xml index 0aa41f7fb6..f3e50c570b 100644 --- a/test/960-default-smali/src/classes.xml +++ b/test/960-default-smali/src/classes.xml @@ -81,6 +81,27 @@ <implements> </implements> <methods> </methods> </class> + + <class name="K" super="java/lang/Object"> + <implements> + <item>Foo</item> + </implements> + <methods> </methods> + </class> + + <class name="L" super="K"> + <implements> </implements> + <methods> </methods> + </class> + + <class name="M" super="L"> + <implements> + <item>Fooer</item> + </implements> + <methods> + <method>bar</method> + </methods> + </class> </classes> <interfaces> @@ -123,5 +144,22 @@ <method type="abstract">GetPlace</method> </methods> </interface> + + <interface name="Foo" super="java/lang/Object"> + <implements> + </implements> + <methods> + <method type="default">bar</method> + </methods> + </interface> + + <interface name="Fooer" super="java/lang/Object"> + <implements> + <item>Foo</item> + </implements> + <methods> + <method type="abstract">bar</method> + </methods> + </interface> </interfaces> </data> diff --git a/test/975-iface-private/build b/test/975-iface-private/build new file mode 100755 index 0000000000..14230c2e1d --- /dev/null +++ b/test/975-iface-private/build @@ -0,0 +1,20 @@ +#!/bin/bash +# +# Copyright 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. + +# make us exit on a failure +set -e + +./default-build "$@" --experimental default-methods diff --git a/test/975-iface-private/expected.txt b/test/975-iface-private/expected.txt new file mode 100644 index 0000000000..908a8f2131 --- /dev/null +++ b/test/975-iface-private/expected.txt @@ -0,0 +1,4 @@ +Saying hi from class +HELLO! +Saying hi from interface +HELLO! diff --git a/test/975-iface-private/info.txt b/test/975-iface-private/info.txt new file mode 100644 index 0000000000..d5a8d3f6d5 --- /dev/null +++ b/test/975-iface-private/info.txt @@ -0,0 +1,5 @@ +Smali-based tests for experimental interface private methods. + +This test cannot be run with --jvm. + +This test checks that synthetic private methods in interfaces work correctly. diff --git a/test/975-iface-private/smali/Iface.smali b/test/975-iface-private/smali/Iface.smali new file mode 100644 index 0000000000..a9a44d1bf4 --- /dev/null +++ b/test/975-iface-private/smali/Iface.smali @@ -0,0 +1,45 @@ + +# /* +# * 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. +# */ +# +# public interface Iface { +# public default void sayHi() { +# System.out.println(getHiWords()); +# } +# +# // Synthetic method +# private String getHiWords() { +# return "HELLO!"; +# } +# } + +.class public abstract interface LIface; +.super Ljava/lang/Object; + +.method public sayHi()V + .locals 2 + sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream; + invoke-direct {p0}, LIface;->getHiWords()Ljava/lang/String; + move-result-object v1 + invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + return-void +.end method + +.method private synthetic getHiWords()Ljava/lang/String; + .locals 1 + const-string v0, "HELLO!" + return-object v0 +.end method diff --git a/test/975-iface-private/smali/Main.smali b/test/975-iface-private/smali/Main.smali new file mode 100644 index 0000000000..dbde20362a --- /dev/null +++ b/test/975-iface-private/smali/Main.smali @@ -0,0 +1,71 @@ +# /* +# * 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. +# */ +# +# class Main implements Iface { +# public static void main(String[] args) { +# Main m = new Main(); +# sayHiMain(m); +# sayHiIface(m); +# } +# public static void sayHiMain(Main m) { +# System.out.println("Saying hi from class"); +# m.sayHi(); +# } +# public static void sayHiIface(Iface m) { +# System.out.println("Saying hi from interface"); +# m.sayHi(); +# } +# } +.class public LMain; +.super Ljava/lang/Object; +.implements LIface; + +.method public constructor <init>()V + .registers 1 + invoke-direct {p0}, Ljava/lang/Object;-><init>()V + return-void +.end method + +.method public static main([Ljava/lang/String;)V + .locals 2 + new-instance v0, LMain; + invoke-direct {v0}, LMain;-><init>()V + + invoke-static {v0}, LMain;->sayHiMain(LMain;)V + invoke-static {v0}, LMain;->sayHiIface(LIface;)V + + return-void +.end method + +.method public static sayHiMain(LMain;)V + .locals 2 + sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream; + const-string v1, "Saying hi from class" + invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + invoke-virtual {p0}, LMain;->sayHi()V + return-void +.end method + +.method public static sayHiIface(LIface;)V + .locals 2 + sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream; + const-string v1, "Saying hi from interface" + invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + invoke-interface {p0}, LIface;->sayHi()V + return-void +.end method diff --git a/test/976-conflict-no-methods/expected.txt b/test/976-conflict-no-methods/expected.txt new file mode 100644 index 0000000000..656dfc57d5 --- /dev/null +++ b/test/976-conflict-no-methods/expected.txt @@ -0,0 +1 @@ +Pass diff --git a/test/976-conflict-no-methods/info.txt b/test/976-conflict-no-methods/info.txt new file mode 100644 index 0000000000..cdc314903c --- /dev/null +++ b/test/976-conflict-no-methods/info.txt @@ -0,0 +1 @@ +Regression test for classes that have conflict tables but no methods. b/28707801
\ No newline at end of file diff --git a/test/976-conflict-no-methods/smali/Iface.smali b/test/976-conflict-no-methods/smali/Iface.smali new file mode 100644 index 0000000000..aa4ec37687 --- /dev/null +++ b/test/976-conflict-no-methods/smali/Iface.smali @@ -0,0 +1,281 @@ +# /* +# * Copyright (C) 2016 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. +# */ +# +# public interface Iface2 { +# public void abstractMethod0(); +# public void abstractMethod1(); +# public void abstractMethod2(); +# public void abstractMethod3(); +# public void abstractMethod4(); +# public void abstractMethod5(); +# public void abstractMethod6(); +# public void abstractMethod7(); +# public void abstractMethod8(); +# public void abstractMethod9(); +# public void abstractMethod10(); +# public void abstractMethod11(); +# public void abstractMethod12(); +# public void abstractMethod13(); +# public void abstractMethod14(); +# public void abstractMethod15(); +# public void abstractMethod16(); +# public void abstractMethod17(); +# public void abstractMethod18(); +# public void abstractMethod19(); +# public void abstractMethod20(); +# public void abstractMethod21(); +# public void abstractMethod22(); +# public void abstractMethod23(); +# public void abstractMethod24(); +# public void abstractMethod25(); +# public void abstractMethod26(); +# public void abstractMethod27(); +# public void abstractMethod28(); +# public void abstractMethod29(); +# public void abstractMethod30(); +# public void abstractMethod31(); +# public void abstractMethod32(); +# public void abstractMethod33(); +# public void abstractMethod34(); +# public void abstractMethod35(); +# public void abstractMethod36(); +# public void abstractMethod37(); +# public void abstractMethod38(); +# public void abstractMethod39(); +# public void abstractMethod40(); +# public void abstractMethod41(); +# public void abstractMethod42(); +# public void abstractMethod43(); +# public void abstractMethod44(); +# public void abstractMethod45(); +# public void abstractMethod46(); +# public void abstractMethod47(); +# public void abstractMethod48(); +# public void abstractMethod49(); +# public void abstractMethod50(); +# public void abstractMethod51(); +# public void abstractMethod52(); +# public void abstractMethod53(); +# public void abstractMethod54(); +# public void abstractMethod55(); +# public void abstractMethod56(); +# public void abstractMethod57(); +# public void abstractMethod58(); +# public void abstractMethod59(); +# public void abstractMethod60(); +# public void abstractMethod61(); +# public void abstractMethod62(); +# public void abstractMethod63(); +# public void abstractMethod64(); +# } + +.class public abstract interface LIface; +.super Ljava/lang/Object; + +.method public abstract abstractMethod0()V +.end method + +.method public abstract abstractMethod1()V +.end method + +.method public abstract abstractMethod2()V +.end method + +.method public abstract abstractMethod3()V +.end method + +.method public abstract abstractMethod4()V +.end method + +.method public abstract abstractMethod5()V +.end method + +.method public abstract abstractMethod6()V +.end method + +.method public abstract abstractMethod7()V +.end method + +.method public abstract abstractMethod8()V +.end method + +.method public abstract abstractMethod9()V +.end method + +.method public abstract abstractMethod10()V +.end method + +.method public abstract abstractMethod11()V +.end method + +.method public abstract abstractMethod12()V +.end method + +.method public abstract abstractMethod13()V +.end method + +.method public abstract abstractMethod14()V +.end method + +.method public abstract abstractMethod15()V +.end method + +.method public abstract abstractMethod16()V +.end method + +.method public abstract abstractMethod17()V +.end method + +.method public abstract abstractMethod18()V +.end method + +.method public abstract abstractMethod19()V +.end method + +.method public abstract abstractMethod20()V +.end method + +.method public abstract abstractMethod21()V +.end method + +.method public abstract abstractMethod22()V +.end method + +.method public abstract abstractMethod23()V +.end method + +.method public abstract abstractMethod24()V +.end method + +.method public abstract abstractMethod25()V +.end method + +.method public abstract abstractMethod26()V +.end method + +.method public abstract abstractMethod27()V +.end method + +.method public abstract abstractMethod28()V +.end method + +.method public abstract abstractMethod29()V +.end method + +.method public abstract abstractMethod30()V +.end method + +.method public abstract abstractMethod31()V +.end method + +.method public abstract abstractMethod32()V +.end method + +.method public abstract abstractMethod33()V +.end method + +.method public abstract abstractMethod34()V +.end method + +.method public abstract abstractMethod35()V +.end method + +.method public abstract abstractMethod36()V +.end method + +.method public abstract abstractMethod37()V +.end method + +.method public abstract abstractMethod38()V +.end method + +.method public abstract abstractMethod39()V +.end method + +.method public abstract abstractMethod40()V +.end method + +.method public abstract abstractMethod41()V +.end method + +.method public abstract abstractMethod42()V +.end method + +.method public abstract abstractMethod43()V +.end method + +.method public abstract abstractMethod44()V +.end method + +.method public abstract abstractMethod45()V +.end method + +.method public abstract abstractMethod46()V +.end method + +.method public abstract abstractMethod47()V +.end method + +.method public abstract abstractMethod48()V +.end method + +.method public abstract abstractMethod49()V +.end method + +.method public abstract abstractMethod50()V +.end method + +.method public abstract abstractMethod51()V +.end method + +.method public abstract abstractMethod52()V +.end method + +.method public abstract abstractMethod53()V +.end method + +.method public abstract abstractMethod54()V +.end method + +.method public abstract abstractMethod55()V +.end method + +.method public abstract abstractMethod56()V +.end method + +.method public abstract abstractMethod57()V +.end method + +.method public abstract abstractMethod58()V +.end method + +.method public abstract abstractMethod59()V +.end method + +.method public abstract abstractMethod60()V +.end method + +.method public abstract abstractMethod61()V +.end method + +.method public abstract abstractMethod62()V +.end method + +.method public abstract abstractMethod63()V +.end method + +.method public abstract abstractMethod64()V +.end method diff --git a/test/976-conflict-no-methods/smali/Main.smali b/test/976-conflict-no-methods/smali/Main.smali new file mode 100644 index 0000000000..7dd11605b1 --- /dev/null +++ b/test/976-conflict-no-methods/smali/Main.smali @@ -0,0 +1,358 @@ +# /* +# * Copyright (C) 2016 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. +# */ +# +.class public LMain; +.super Ljava/lang/Object; +.implements LIface; + +.method public constructor <init>()V + .registers 1 + invoke-direct {p0}, Ljava/lang/Object;-><init>()V + return-void +.end method + +.method public static main([Ljava/lang/String;)V + .locals 2 + sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream; + const-string v1, "Pass" + invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + return-void +.end method + +.method public abstractMethod0()V + .locals 0 + return-void +.end method + +.method public abstractMethod1()V + .locals 0 + return-void +.end method + +.method public abstractMethod2()V + .locals 0 + return-void +.end method + +.method public abstractMethod3()V + .locals 0 + return-void +.end method + +.method public abstractMethod4()V + .locals 0 + return-void +.end method + +.method public abstractMethod5()V + .locals 0 + return-void +.end method + +.method public abstractMethod6()V + .locals 0 + return-void +.end method + +.method public abstractMethod7()V + .locals 0 + return-void +.end method + +.method public abstractMethod8()V + .locals 0 + return-void +.end method + +.method public abstractMethod9()V + .locals 0 + return-void +.end method + +.method public abstractMethod10()V + .locals 0 + return-void +.end method + +.method public abstractMethod11()V + .locals 0 + return-void +.end method + +.method public abstractMethod12()V + .locals 0 + return-void +.end method + +.method public abstractMethod13()V + .locals 0 + return-void +.end method + +.method public abstractMethod14()V + .locals 0 + return-void +.end method + +.method public abstractMethod15()V + .locals 0 + return-void +.end method + +.method public abstractMethod16()V + .locals 0 + return-void +.end method + +.method public abstractMethod17()V + .locals 0 + return-void +.end method + +.method public abstractMethod18()V + .locals 0 + return-void +.end method + +.method public abstractMethod19()V + .locals 0 + return-void +.end method + +.method public abstractMethod20()V + .locals 0 + return-void +.end method + +.method public abstractMethod21()V + .locals 0 + return-void +.end method + +.method public abstractMethod22()V + .locals 0 + return-void +.end method + +.method public abstractMethod23()V + .locals 0 + return-void +.end method + +.method public abstractMethod24()V + .locals 0 + return-void +.end method + +.method public abstractMethod25()V + .locals 0 + return-void +.end method + +.method public abstractMethod26()V + .locals 0 + return-void +.end method + +.method public abstractMethod27()V + .locals 0 + return-void +.end method + +.method public abstractMethod28()V + .locals 0 + return-void +.end method + +.method public abstractMethod29()V + .locals 0 + return-void +.end method + +.method public abstractMethod30()V + .locals 0 + return-void +.end method + +.method public abstractMethod31()V + .locals 0 + return-void +.end method + +.method public abstractMethod32()V + .locals 0 + return-void +.end method + +.method public abstractMethod33()V + .locals 0 + return-void +.end method + +.method public abstractMethod34()V + .locals 0 + return-void +.end method + +.method public abstractMethod35()V + .locals 0 + return-void +.end method + +.method public abstractMethod36()V + .locals 0 + return-void +.end method + +.method public abstractMethod37()V + .locals 0 + return-void +.end method + +.method public abstractMethod38()V + .locals 0 + return-void +.end method + +.method public abstractMethod39()V + .locals 0 + return-void +.end method + +.method public abstractMethod40()V + .locals 0 + return-void +.end method + +.method public abstractMethod41()V + .locals 0 + return-void +.end method + +.method public abstractMethod42()V + .locals 0 + return-void +.end method + +.method public abstractMethod43()V + .locals 0 + return-void +.end method + +.method public abstractMethod44()V + .locals 0 + return-void +.end method + +.method public abstractMethod45()V + .locals 0 + return-void +.end method + +.method public abstractMethod46()V + .locals 0 + return-void +.end method + +.method public abstractMethod47()V + .locals 0 + return-void +.end method + +.method public abstractMethod48()V + .locals 0 + return-void +.end method + +.method public abstractMethod49()V + .locals 0 + return-void +.end method + +.method public abstractMethod50()V + .locals 0 + return-void +.end method + +.method public abstractMethod51()V + .locals 0 + return-void +.end method + +.method public abstractMethod52()V + .locals 0 + return-void +.end method + +.method public abstractMethod53()V + .locals 0 + return-void +.end method + +.method public abstractMethod54()V + .locals 0 + return-void +.end method + +.method public abstractMethod55()V + .locals 0 + return-void +.end method + +.method public abstractMethod56()V + .locals 0 + return-void +.end method + +.method public abstractMethod57()V + .locals 0 + return-void +.end method + +.method public abstractMethod58()V + .locals 0 + return-void +.end method + +.method public abstractMethod59()V + .locals 0 + return-void +.end method + +.method public abstractMethod60()V + .locals 0 + return-void +.end method + +.method public abstractMethod61()V + .locals 0 + return-void +.end method + +.method public abstractMethod62()V + .locals 0 + return-void +.end method + +.method public abstractMethod63()V + .locals 0 + return-void +.end method + +.method public abstractMethod64()V + .locals 0 + return-void +.end method diff --git a/test/976-conflict-no-methods/smali/NoMethods.smali b/test/976-conflict-no-methods/smali/NoMethods.smali new file mode 100644 index 0000000000..787e34a423 --- /dev/null +++ b/test/976-conflict-no-methods/smali/NoMethods.smali @@ -0,0 +1,19 @@ +# /* +# * Copyright (C) 2016 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. +# */ +# + +.class public LNoMethods; +.super LMain; diff --git a/test/Android.libarttest.mk b/test/Android.libarttest.mk index e547c72c0e..97204d34c4 100644 --- a/test/Android.libarttest.mk +++ b/test/Android.libarttest.mk @@ -34,6 +34,7 @@ LIBARTTEST_COMMON_SRC_FILES := \ 137-cfi/cfi.cc \ 139-register-natives/regnative.cc \ 141-class-unload/jni_unload.cc \ + 148-multithread-gc-annotations/gc_coverage.cc \ 454-get-vreg/get_vreg_jni.cc \ 457-regs/regs_jni.cc \ 461-get-reference-vreg/get_reference_vreg_jni.cc \ @@ -41,7 +42,10 @@ LIBARTTEST_COMMON_SRC_FILES := \ 497-inlining-and-class-loader/clear_dex_cache.cc \ 543-env-long-ref/env_long_ref.cc \ 566-polymorphic-inlining/polymorphic_inline.cc \ - 570-checker-osr/osr.cc + 570-checker-osr/osr.cc \ + 595-profile-saving/profile-saving.cc \ + 596-app-images/app_images.cc \ + 597-deopt-new-string/deopt.cc ART_TARGET_LIBARTTEST_$(ART_PHONY_TEST_TARGET_SUFFIX) += $(ART_TARGET_TEST_OUT)/$(TARGET_ARCH)/libarttest.so ART_TARGET_LIBARTTEST_$(ART_PHONY_TEST_TARGET_SUFFIX) += $(ART_TARGET_TEST_OUT)/$(TARGET_ARCH)/libarttestd.so @@ -90,7 +94,12 @@ define build-libarttest include $(BUILD_SHARED_LIBRARY) else # host LOCAL_CLANG := $(ART_HOST_CLANG) - LOCAL_CFLAGS := $(ART_HOST_CFLAGS) $(ART_HOST_DEBUG_CFLAGS) + LOCAL_CFLAGS := $(ART_HOST_CFLAGS) + ifeq ($$(suffix),d) + LOCAL_CFLAGS += $(ART_HOST_DEBUG_CFLAGS) + else + LOCAL_CFLAGS += $(ART_HOST_NON_DEBUG_CFLAGS) + endif LOCAL_ASFLAGS := $(ART_HOST_ASFLAGS) LOCAL_LDLIBS := $(ART_HOST_LDLIBS) -ldl -lpthread LOCAL_IS_HOST_MODULE := true diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk index 1edc5993eb..ee651b5494 100644 --- a/test/Android.run-test.mk +++ b/test/Android.run-test.mk @@ -235,8 +235,11 @@ ifdef dist_goal $(IMAGE_TYPES), $(PICTEST_TYPES), $(DEBUGGABLE_TYPES), $(TEST_ART_TIMING_SENSITIVE_RUN_TESTS), $(ALL_ADDRESS_SIZES)) endif +# 147-stripped-dex-fallback isn't supported on device because --strip-dex +# requires the zip command. # 569-checker-pattern-replacement tests behaviour present only on host. TEST_ART_BROKEN_TARGET_TESTS := \ + 147-stripped-dex-fallback \ 569-checker-pattern-replacement ifneq (,$(filter target,$(TARGET_TYPES))) @@ -287,6 +290,7 @@ TEST_ART_BROKEN_PREBUILD_RUN_TESTS := # 529 and 555: b/27784033 TEST_ART_BROKEN_NO_PREBUILD_TESTS := \ 117-nopatchoat \ + 147-stripped-dex-fallback \ 554-jit-profile-file \ 529-checker-unresolved \ 555-checker-regression-x86const @@ -375,6 +379,7 @@ ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),ndebug,$(PREB # All these tests check that we have sane behavior if we don't have a patchoat or dex2oat. # Therefore we shouldn't run them in situations where we actually don't have these since they # explicitly test for them. These all also assume we have an image. +# 147-stripped-dex-fallback is disabled because it requires --prebuild. # 554-jit-profile-file is disabled because it needs a primary oat file to know what it should save. TEST_ART_BROKEN_FALLBACK_RUN_TESTS := \ 116-nodex2oat \ @@ -383,6 +388,7 @@ TEST_ART_BROKEN_FALLBACK_RUN_TESTS := \ 119-noimage-patchoat \ 137-cfi \ 138-duplicate-classes-check2 \ + 147-stripped-dex-fallback \ 554-jit-profile-file # This test fails without an image. @@ -557,6 +563,13 @@ endif TEST_ART_BROKEN_OPTIMIZING_READ_BARRIER_RUN_TESTS := TEST_ART_BROKEN_JIT_READ_BARRIER_RUN_TESTS := +TEST_ART_BROKEN_NPIC_RUN_TESTS := 596-app-images +ifneq (,$(filter npictest,$(PICTEST_TYPES))) + ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(PREBUILD_TYPES), \ + ${COMPILER_TYPES},$(RELOCATE_TYPES),$(TRACE_TYPES),$(GC_TYPES),$(JNI_TYPES), \ + $(IMAGE_TYPES),npictest,$(DEBUGGABLE_TYPES),$(TEST_ART_BROKEN_NPIC_RUN_TESTS),$(ALL_ADDRESS_SIZES)) +endif + # Tests that should fail in the heap poisoning configuration with the Optimizing compiler. # 055: Exceeds run time limits due to heap poisoning instrumentation (on ARM and ARM64 devices). TEST_ART_BROKEN_OPTIMIZING_HEAP_POISONING_RUN_TESTS := \ diff --git a/test/common/stack_inspect.cc b/test/common/stack_inspect.cc index 922eae61e2..85ea1c8dd1 100644 --- a/test/common/stack_inspect.cc +++ b/test/common/stack_inspect.cc @@ -37,17 +37,20 @@ extern "C" JNIEXPORT void JNICALL Java_Main_disableStackFrameAsserts(JNIEnv* env asserts_enabled = false; } - -// public static native boolean isInterpreted(); - -extern "C" JNIEXPORT jboolean JNICALL Java_Main_isInterpreted(JNIEnv* env, jclass) { +static jboolean IsInterpreted(JNIEnv* env, jclass, size_t level) { ScopedObjectAccess soa(env); - NthCallerVisitor caller(soa.Self(), 1, false); + NthCallerVisitor caller(soa.Self(), level, false); caller.WalkStack(); CHECK(caller.caller != nullptr); return caller.GetCurrentShadowFrame() != nullptr ? JNI_TRUE : JNI_FALSE; } +// public static native boolean isInterpreted(); + +extern "C" JNIEXPORT jboolean JNICALL Java_Main_isInterpreted(JNIEnv* env, jclass klass) { + return IsInterpreted(env, klass, 1); +} + // public static native void assertIsInterpreted(); extern "C" JNIEXPORT void JNICALL Java_Main_assertIsInterpreted(JNIEnv* env, jclass klass) { @@ -56,10 +59,7 @@ extern "C" JNIEXPORT void JNICALL Java_Main_assertIsInterpreted(JNIEnv* env, jcl } } - -// public static native boolean isManaged(); - -extern "C" JNIEXPORT jboolean JNICALL Java_Main_isManaged(JNIEnv* env, jclass cls) { +static jboolean IsManaged(JNIEnv* env, jclass cls, size_t level) { ScopedObjectAccess soa(env); mirror::Class* klass = soa.Decode<mirror::Class*>(cls); @@ -71,13 +71,19 @@ extern "C" JNIEXPORT jboolean JNICALL Java_Main_isManaged(JNIEnv* env, jclass cl return JNI_FALSE; } - NthCallerVisitor caller(soa.Self(), 1, false); + NthCallerVisitor caller(soa.Self(), level, false); caller.WalkStack(); CHECK(caller.caller != nullptr); return caller.GetCurrentShadowFrame() != nullptr ? JNI_FALSE : JNI_TRUE; } +// public static native boolean isManaged(); + +extern "C" JNIEXPORT jboolean JNICALL Java_Main_isManaged(JNIEnv* env, jclass cls) { + return IsManaged(env, cls, 1); +} + // public static native void assertIsManaged(); extern "C" JNIEXPORT void JNICALL Java_Main_assertIsManaged(JNIEnv* env, jclass cls) { @@ -86,4 +92,32 @@ extern "C" JNIEXPORT void JNICALL Java_Main_assertIsManaged(JNIEnv* env, jclass } } +// public static native boolean isCallerInterpreted(); + +extern "C" JNIEXPORT jboolean JNICALL Java_Main_isCallerInterpreted(JNIEnv* env, jclass klass) { + return IsInterpreted(env, klass, 2); +} + +// public static native void assertCallerIsInterpreted(); + +extern "C" JNIEXPORT void JNICALL Java_Main_assertCallerIsInterpreted(JNIEnv* env, jclass klass) { + if (asserts_enabled) { + CHECK(Java_Main_isCallerInterpreted(env, klass)); + } +} + +// public static native boolean isCallerManaged(); + +extern "C" JNIEXPORT jboolean JNICALL Java_Main_isCallerManaged(JNIEnv* env, jclass cls) { + return IsManaged(env, cls, 2); +} + +// public static native void assertCallerIsManaged(); + +extern "C" JNIEXPORT void JNICALL Java_Main_assertCallerIsManaged(JNIEnv* env, jclass cls) { + if (asserts_enabled) { + CHECK(Java_Main_isCallerManaged(env, cls)); + } +} + } // namespace art diff --git a/test/etc/default-build b/test/etc/default-build index 3d84821bf0..962ae38041 100755 --- a/test/etc/default-build +++ b/test/etc/default-build @@ -69,10 +69,13 @@ declare -A JACK_EXPERIMENTAL_ARGS JACK_EXPERIMENTAL_ARGS["default-methods"]="-D jack.java.source.version=1.8 -D jack.android.min-api-level=24" JACK_EXPERIMENTAL_ARGS["lambdas"]="-D jack.java.source.version=1.8 -D jack.android.min-api-level=24" +declare -A SMALI_EXPERIMENTAL_ARGS +SMALI_EXPERIMENTAL_ARGS["default-methods"]="--api-level 24" + while true; do if [ "x$1" = "x--dx-option" ]; then shift - option="$1" + on="$1" DX_FLAGS="${DX_FLAGS} $option" shift elif [ "x$1" = "x--jvm" ]; then @@ -110,6 +113,7 @@ done # Add args from the experimental mappings. for experiment in ${EXPERIMENTAL}; do JACK_ARGS="${JACK_ARGS} ${JACK_EXPERIMENTAL_ARGS[${experiment}]}" + SMALI_ARGS="${SMALI_ARGS} ${SMALI_EXPERIMENTAL_ARGS[${experiment}]}" done if [ -e classes.dex ]; then diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar index 28a99de099..64bf4f3046 100755 --- a/test/etc/run-test-jar +++ b/test/etc/run-test-jar @@ -37,13 +37,14 @@ PATCHOAT="" PREBUILD="y" QUIET="n" RELOCATE="y" +STRIP_DEX="n" SECONDARY_DEX="" TIME_OUT="gdb" # "n" (disabled), "timeout" (use timeout), "gdb" (use gdb) # Value in seconds if [ "$ART_USE_READ_BARRIER" = "true" ]; then - TIME_OUT_VALUE=900 # 15 minutes. + TIME_OUT_VALUE=1800 # 30 minutes. else - TIME_OUT_VALUE=600 # 10 minutes. + TIME_OUT_VALUE=1200 # 20 minutes. fi USE_GDB="n" USE_JVM="n" @@ -118,6 +119,9 @@ while true; do elif [ "x$1" = "x--prebuild" ]; then PREBUILD="y" shift + elif [ "x$1" = "x--strip-dex" ]; then + STRIP_DEX="y" + shift elif [ "x$1" = "x--host" ]; then HOST="y" ANDROID_ROOT="$ANDROID_HOST_OUT" @@ -195,6 +199,10 @@ while true; do shift INSTRUCTION_SET_FEATURES="$1" shift + elif [ "x$1" = "x--timeout" ]; then + shift + TIME_OUT_VALUE="$1" + shift elif [ "x$1" = "x--" ]; then shift break @@ -319,11 +327,14 @@ fi if [ "$INTERPRETER" = "y" ]; then INT_OPTS="-Xint" if [ "$VERIFY" = "y" ] ; then + INT_OPTS="${INT_OPTS} -Xcompiler-option --compiler-filter=interpret-only" COMPILE_FLAGS="${COMPILE_FLAGS} --compiler-filter=interpret-only" elif [ "$VERIFY" = "s" ]; then + INT_OPTS="${INT_OPTS} -Xcompiler-option --compiler-filter=verify-at-runtime" COMPILE_FLAGS="${COMPILE_FLAGS} --compiler-filter=verify-at-runtime" DEX_VERIFY="${DEX_VERIFY} -Xverify:softfail" else # VERIFY = "n" + INT_OPTS="${INT_OPTS} -Xcompiler-option --compiler-filter=verify-none" COMPILE_FLAGS="${COMPILE_FLAGS} --compiler-filter=verify-none" DEX_VERIFY="${DEX_VERIFY} -Xverify:none" fi @@ -332,18 +343,12 @@ fi if [ "$JIT" = "y" ]; then INT_OPTS="-Xusejit:true" if [ "$VERIFY" = "y" ] ; then + INT_OPTS="${INT_OPTS} -Xcompiler-option --compiler-filter=verify-at-runtime" COMPILE_FLAGS="${COMPILE_FLAGS} --compiler-filter=verify-at-runtime" - if [ "$PREBUILD" = "n" ]; then - # Make sure that if we have noprebuild we still JIT as DexClassLoader will - # try to compile the dex file. - INT_OPTS="${INT_OPTS} -Xcompiler-option --compiler-filter=verify-at-runtime" - fi else + INT_OPTS="${INT_OPTS} -Xcompiler-option --compiler-filter=verify-none" COMPILE_FLAGS="${COMPILE_FLAGS} --compiler-filter=verify-none" DEX_VERIFY="${DEX_VERIFY} -Xverify:none" - if [ "$PREBUILD" = "n" ]; then - INT_OPTS="${INT_OPTS} -Xcompiler-option --compiler-filter=verify-none" - fi fi fi @@ -379,13 +384,14 @@ if [ "$HOST" = "n" ]; then fi dex2oat_cmdline="true" -mkdir_cmdline="mkdir -p ${DEX_LOCATION}/dalvik-cache/$ISA" +mkdir_locations="${DEX_LOCATION}/dalvik-cache/$ISA" +strip_cmdline="true" # Pick a base that will force the app image to get relocated. app_image="--base=0x4000 --app-image-file=$DEX_LOCATION/oat/$ISA/$TEST_NAME.art" if [ "$PREBUILD" = "y" ]; then - mkdir_cmdline="${mkdir_cmdline} && mkdir -p ${DEX_LOCATION}/oat/$ISA" + mkdir_locations="${mkdir_locations} ${DEX_LOCATION}/oat/$ISA" dex2oat_cmdline="$INVOKE_WITH $ANDROID_ROOT/bin/dex2oatd \ $COMPILE_FLAGS \ --boot-image=${BOOT_IMAGE} \ @@ -409,6 +415,10 @@ if [ "$PREBUILD" = "y" ]; then fi fi +if [ "$STRIP_DEX" = "y" ]; then + strip_cmdline="zip --quiet --delete $DEX_LOCATION/$TEST_NAME.jar classes.dex" +fi + DALVIKVM_ISA_FEATURES_ARGS="" if [ "x$INSTRUCTION_SET_FEATURES" != "x" ] ; then DALVIKVM_ISA_FEATURES_ARGS="-Xcompiler-option --instruction-set-features=${INSTRUCTION_SET_FEATURES}" @@ -467,17 +477,21 @@ if [ "$HOST" = "n" ]; then LD_LIBRARY_PATH=$ANDROID_ROOT/$LIBRARY_DIRECTORY fi + PUBLIC_LIBS=libart.so:libartd.so + # Create a script with the command. The command can get longer than the longest # allowed adb command and there is no way to get the exit status from a adb shell # command. cmdline="cd $DEX_LOCATION && \ export ANDROID_DATA=$DEX_LOCATION && \ + export ANDROID_ADDITIONAL_PUBLIC_LIBRARIES=$PUBLIC_LIBS && \ export DEX_LOCATION=$DEX_LOCATION && \ export ANDROID_ROOT=$ANDROID_ROOT && \ - $mkdir_cmdline && \ + mkdir -p ${mkdir_locations} && \ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH && \ export PATH=$ANDROID_ROOT/bin:$PATH && \ $dex2oat_cmdline && \ + $strip_cmdline && \ $dalvikvm_cmdline" cmdfile=$(tempfile -p "cmd-" -s "-$TEST_NAME") @@ -548,20 +562,15 @@ else fi if [ "$DEV_MODE" = "y" ]; then - if [ "$PREBUILD" = "y" ]; then - echo "$mkdir_cmdline && $dex2oat_cmdline && $cmdline" - elif [ "$RELOCATE" = "y" ]; then - echo "$mkdir_cmdline && $cmdline" - else - echo $cmdline - fi + echo "mkdir -p ${mkdir_locations} && $dex2oat_cmdline && $strip_cmdline && $cmdline" fi cd $ANDROID_BUILD_TOP rm -rf ${DEX_LOCATION}/dalvik-cache/ - $mkdir_cmdline || exit 1 + mkdir -p ${mkdir_locations} || exit 1 $dex2oat_cmdline || { echo "Dex2oat failed." >&2 ; exit 2; } + $strip_cmdline || { echo "Strip failed." >&2 ; exit 3; } # For running, we must turn off logging when dex2oat or patchoat are missing. Otherwise we use # the same defaults as for prebuilt: everything when --dev, otherwise errors and above only. diff --git a/test/run-test b/test/run-test index 01464cd6b6..3ae063abca 100755 --- a/test/run-test +++ b/test/run-test @@ -46,7 +46,7 @@ export RUN="${progdir}/etc/run-test-jar" export DEX_LOCATION=/data/run-test/${test_dir} export NEED_DEX="true" export USE_JACK="true" -export SMALI_ARGS="--experimental --api-level 23" +export SMALI_ARGS="--experimental" # If dx was not set by the environment variable, assume it is in the path. if [ -z "$DX" ]; then @@ -122,10 +122,12 @@ never_clean="no" have_dex2oat="yes" have_patchoat="yes" have_image="yes" -image_suffix="" pic_image_suffix="" multi_image_suffix="" android_root="/system" +# By default we will use optimizing. +image_args="" +image_suffix="-optimizing" while true; do if [ "x$1" = "x--host" ]; then @@ -148,6 +150,7 @@ while true; do elif [ "x$1" = "x--jvm" ]; then target_mode="no" runtime="jvm" + image_args="" prebuild_mode="no" NEED_DEX="false" USE_JACK="false" @@ -190,6 +193,9 @@ while true; do run_args="${run_args} --prebuild" prebuild_mode="yes" shift; + elif [ "x$1" = "x--strip-dex" ]; then + run_args="${run_args} --strip-dex" + shift; elif [ "x$1" = "x--debuggable" ]; then run_args="${run_args} -Xcompiler-option --debuggable" debuggable="yes" @@ -235,28 +241,28 @@ while true; do shift elif [ "x$1" = "x--strace" ]; then strace="yes" - run_args="${run_args} --invoke-with strace --invoke-with -o --invoke-with $tmp_dir/$strace_output" + run_args="${run_args} --timeout 1800 --invoke-with strace --invoke-with -o --invoke-with $tmp_dir/$strace_output" shift elif [ "x$1" = "x--zygote" ]; then run_args="${run_args} --zygote" shift elif [ "x$1" = "x--interpreter" ]; then - run_args="${run_args} --interpreter --runtime-option -XOatFileManagerCompilerFilter:verify-at-runtime" + run_args="${run_args} --interpreter" image_suffix="-interpreter" shift elif [ "x$1" = "x--jit" ]; then - run_args="${run_args} --jit --runtime-option -XOatFileManagerCompilerFilter:verify-at-runtime" + image_args="--jit" image_suffix="-jit" shift elif [ "x$1" = "x--optimizing" ]; then - run_args="${run_args} -Xcompiler-option --compiler-backend=Optimizing" + image_args="-Xcompiler-option --compiler-backend=Optimizing" image_suffix="-optimizing" shift elif [ "x$1" = "x--no-verify" ]; then - run_args="${run_args} --no-verify --runtime-option -XOatFileManagerCompilerFilter:verify-none" + run_args="${run_args} --no-verify" shift elif [ "x$1" = "x--verify-soft-fail" ]; then - run_args="${run_args} --verify-soft-fail --runtime-option -XOatFileManagerCompilerFilter:verify-at-runtime" + image_args="--verify-soft-fail" image_suffix="-interp-ac" shift elif [ "x$1" = "x--no-optimize" ]; then @@ -345,6 +351,7 @@ while true; do fi done +run_args="${run_args} ${image_args}" # Allocate file descriptor real_stderr and redirect it to the shell's error # output (fd 2). if [ ${BASH_VERSINFO[1]} -ge 4 ] && [ ${BASH_VERSINFO[2]} -ge 1 ]; then @@ -449,7 +456,7 @@ if [ "$runtime" = "dalvik" ]; then if [ "$target_mode" = "no" ]; then framework="${ANDROID_PRODUCT_OUT}/system/framework" bpath="${framework}/core-libart.jar:${framework}/core-oj.jar:${framework}/conscrypt.jar:${framework}/okhttp.jar:${framework}/bouncycastle.jar:${framework}/ext.jar" - run_args="${run_args} --boot -Xbootclasspath:${bpath}" + run_args="${run_args} --boot --runtime-option -Xbootclasspath:${bpath}" else true # defaults to using target BOOTCLASSPATH fi @@ -464,7 +471,7 @@ elif [ "$runtime" = "art" ]; then run_args="${run_args} --runtime-option -Djava.library.path=${ANDROID_HOST_OUT}/lib${suffix64}" else guess_target_arch_name - run_args="${run_args} --runtime-option -Djava.library.path=/data/art-test/${target_arch_name}" + run_args="${run_args} --runtime-option -Djava.library.path=/data/art-test/${target_arch_name}:/system/lib${suffix64}" run_args="${run_args} --boot /data/art-test/core${image_suffix}${pic_image_suffix}${multi_image_suffix}.art" fi if [ "$relocate" = "yes" ]; then @@ -571,6 +578,7 @@ if [ "$usage" = "yes" ]; then echo " --prebuild Run dex2oat on the files before starting test. (default)" echo " --no-prebuild Do not run dex2oat on the files before starting" echo " the test." + echo " --strip-dex Strip the dex files before starting test." echo " --relocate Force the use of relocating in the test, making" echo " the image and oat files be relocated to a random" echo " address before running. (default)" @@ -900,4 +908,8 @@ fi ) 2>&${real_stderr} 1>&2 -exit 1 +if [ "$never_clean" = "yes" ] && [ "$good" = "yes" ]; then + exit 0 +else + exit 1 +fi diff --git a/tools/ahat/README.txt b/tools/ahat/README.txt index d9b26bcf58..0cd77ab69d 100644 --- a/tools/ahat/README.txt +++ b/tools/ahat/README.txt @@ -77,7 +77,12 @@ Things to move to perflib: * Instance.isRoot and Instance.getRootTypes. Release History: - 0.4 Pending + 0.6 Pending + + 0.5 Apr 19, 2016 + Update perflib to perflib-25.0.0 to improve processing performance. + + 0.4 Feb 23, 2016 Annotate char[] objects with their string values. Show registered native allocations for heap dumps that support it. diff --git a/tools/ahat/src/AhatSnapshot.java b/tools/ahat/src/AhatSnapshot.java index 2adec6f17b..d088e8c43f 100644 --- a/tools/ahat/src/AhatSnapshot.java +++ b/tools/ahat/src/AhatSnapshot.java @@ -25,8 +25,8 @@ import com.android.tools.perflib.heap.Snapshot; import com.android.tools.perflib.heap.StackFrame; import com.android.tools.perflib.heap.StackTrace; import com.android.tools.perflib.captures.MemoryMappedFileBuffer; -import com.google.common.collect.Iterables; import com.google.common.collect.Lists; +import gnu.trove.TObjectProcedure; import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -85,49 +85,59 @@ class AhatSnapshot { ClassObj javaLangClass = mSnapshot.findClass("java.lang.Class"); for (Heap heap : mHeaps) { - long total = 0; - for (Instance inst : Iterables.concat(heap.getClasses(), heap.getInstances())) { - Instance dominator = inst.getImmediateDominator(); - if (dominator != null) { - total += inst.getSize(); - - if (dominator == Snapshot.SENTINEL_ROOT) { - mRooted.add(inst); - } + // Use a single element array for the total to act as a reference to a + // long. + final long[] total = new long[]{0}; + TObjectProcedure<Instance> processInstance = new TObjectProcedure<Instance>() { + @Override + public boolean execute(Instance inst) { + Instance dominator = inst.getImmediateDominator(); + if (dominator != null) { + total[0] += inst.getSize(); + + if (dominator == Snapshot.SENTINEL_ROOT) { + mRooted.add(inst); + } - // Properly label the class of a class object. - if (inst instanceof ClassObj && javaLangClass != null && inst.getClassObj() == null) { - inst.setClassId(javaLangClass.getId()); - } + // Properly label the class of a class object. + if (inst instanceof ClassObj && javaLangClass != null && inst.getClassObj() == null) { + inst.setClassId(javaLangClass.getId()); + } - // Update dominated instances. - List<Instance> instances = mDominated.get(dominator); - if (instances == null) { - instances = new ArrayList<Instance>(); - mDominated.put(dominator, instances); - } - instances.add(inst); - - // Update sites. - List<StackFrame> path = Collections.emptyList(); - StackTrace stack = getStack(inst); - int stackId = getStackTraceSerialNumber(stack); - if (stack != null) { - StackFrame[] frames = getStackFrames(stack); - if (frames != null && frames.length > 0) { - path = Lists.reverse(Arrays.asList(frames)); + // Update dominated instances. + List<Instance> instances = mDominated.get(dominator); + if (instances == null) { + instances = new ArrayList<Instance>(); + mDominated.put(dominator, instances); } - } - mRootSite.add(stackId, 0, path.iterator(), inst); + instances.add(inst); + + // Update sites. + List<StackFrame> path = Collections.emptyList(); + StackTrace stack = getStack(inst); + int stackId = getStackTraceSerialNumber(stack); + if (stack != null) { + StackFrame[] frames = getStackFrames(stack); + if (frames != null && frames.length > 0) { + path = Lists.reverse(Arrays.asList(frames)); + } + } + mRootSite.add(stackId, 0, path.iterator(), inst); - // Update native allocations. - InstanceUtils.NativeAllocation alloc = InstanceUtils.getNativeAllocation(inst); - if (alloc != null) { - mNativeAllocations.add(alloc); + // Update native allocations. + InstanceUtils.NativeAllocation alloc = InstanceUtils.getNativeAllocation(inst); + if (alloc != null) { + mNativeAllocations.add(alloc); + } } + return true; } + }; + for (Instance instance : heap.getClasses()) { + processInstance.execute(instance); } - mHeapSizes.put(heap, total); + heap.forEachInstance(processInstance); + mHeapSizes.put(heap, total[0]); } // Record the roots and their types. diff --git a/tools/ahat/src/InstanceUtils.java b/tools/ahat/src/InstanceUtils.java index d7b64e2afd..8defba2647 100644 --- a/tools/ahat/src/InstanceUtils.java +++ b/tools/ahat/src/InstanceUtils.java @@ -244,8 +244,8 @@ class InstanceUtils { if (inst instanceof ArrayInstance) { ArrayInstance array = (ArrayInstance)inst; - if (array.getArrayType() == Type.BYTE && inst.getHardReferences().size() == 1) { - Instance ref = inst.getHardReferences().get(0); + if (array.getArrayType() == Type.BYTE && inst.getHardReverseReferences().size() == 1) { + Instance ref = inst.getHardReverseReferences().get(0); ClassObj clsref = ref.getClassObj(); if (clsref != null && "android.graphics.Bitmap".equals(clsref.getClassName())) { return ref; @@ -344,7 +344,7 @@ class InstanceUtils { } Instance referent = null; - for (Instance ref : inst.getHardReferences()) { + for (Instance ref : inst.getHardReverseReferences()) { if (isInstanceOfClass(ref, "sun.misc.Cleaner")) { referent = InstanceUtils.getReferent(ref); if (referent != null) { diff --git a/tools/ahat/src/ObjectHandler.java b/tools/ahat/src/ObjectHandler.java index 06023dab7f..4df1be5ac2 100644 --- a/tools/ahat/src/ObjectHandler.java +++ b/tools/ahat/src/ObjectHandler.java @@ -160,11 +160,11 @@ class ObjectHandler implements AhatHandler { private static void printReferences( Doc doc, Query query, AhatSnapshot snapshot, Instance inst) { doc.section("Objects with References to this Object"); - if (inst.getHardReferences().isEmpty()) { + if (inst.getHardReverseReferences().isEmpty()) { doc.println(DocString.text("(none)")); } else { doc.table(new Column("Object")); - List<Instance> references = inst.getHardReferences(); + List<Instance> references = inst.getHardReverseReferences(); SubsetSelector<Instance> selector = new SubsetSelector(query, HARD_REFS_ID, references); for (Instance ref : selector.selected()) { doc.row(Value.render(snapshot, ref)); @@ -173,10 +173,10 @@ class ObjectHandler implements AhatHandler { selector.render(doc); } - if (inst.getSoftReferences() != null) { + if (inst.getSoftReverseReferences() != null) { doc.section("Objects with Soft References to this Object"); doc.table(new Column("Object")); - List<Instance> references = inst.getSoftReferences(); + List<Instance> references = inst.getSoftReverseReferences(); SubsetSelector<Instance> selector = new SubsetSelector(query, SOFT_REFS_ID, references); for (Instance ref : selector.selected()) { doc.row(Value.render(snapshot, ref)); diff --git a/tools/ahat/test-dump/Main.java b/tools/ahat/test-dump/Main.java index d61a98da54..3936f296d3 100644 --- a/tools/ahat/test-dump/Main.java +++ b/tools/ahat/test-dump/Main.java @@ -50,7 +50,8 @@ public class Main { bigArray[i] = (byte)((i*i) & 0xFF); } - NativeAllocationRegistry registry = new NativeAllocationRegistry(0x12345, 42); + NativeAllocationRegistry registry = new NativeAllocationRegistry( + Main.class.getClassLoader(), 0x12345, 42); registry.registerNativeAllocation(anObject, 0xABCDABCD); } } diff --git a/tools/buildbot-build.sh b/tools/buildbot-build.sh index 2eb52bcad9..304c2a9398 100755 --- a/tools/buildbot-build.sh +++ b/tools/buildbot-build.sh @@ -46,9 +46,14 @@ while true; do done if [[ $mode == "host" ]]; then - make_command="make $j_arg $showcommands build-art-host-tests $common_targets ${out_dir}/host/linux-x86/lib/libjavacoretests.so ${out_dir}/host/linux-x86/lib64/libjavacoretests.so" + make_command="make $j_arg $showcommands build-art-host-tests $common_targets" + make_command+=" ${out_dir}/host/linux-x86/lib/libjavacoretests.so " + make_command+=" ${out_dir}/host/linux-x86/lib64/libjavacoretests.so" elif [[ $mode == "target" ]]; then - make_command="make $j_arg $showcommands build-art-target-tests $common_targets libjavacrypto libjavacoretests linker toybox toolbox sh ${out_dir}/host/linux-x86/bin/adb libstdc++" + make_command="make $j_arg $showcommands build-art-target-tests $common_targets" + make_command+=" libjavacrypto libjavacoretests linker toybox toolbox sh" + make_command+=" ${out_dir}/host/linux-x86/bin/adb libstdc++ " + make_command+=" ${out_dir}/target/product/${TARGET_PRODUCT}/system/etc/public.libraries.txt" fi echo "Executing $make_command" diff --git a/tools/dexfuzz/src/dexfuzz/executors/Device.java b/tools/dexfuzz/src/dexfuzz/executors/Device.java index 4a53957782..45538fe7de 100644 --- a/tools/dexfuzz/src/dexfuzz/executors/Device.java +++ b/tools/dexfuzz/src/dexfuzz/executors/Device.java @@ -68,7 +68,13 @@ public class Device { return envVars.get(key); } - private String getHostCoreImagePath() { + private String getHostCoreImagePathWithArch() { + // TODO: Using host currently implies x86 (see Options.java), change this when generalized. + assert(Options.useArchX86); + return androidHostOut + "/framework/x86/core.art"; + } + + private String getHostCoreImagePathNoArch() { return androidHostOut + "/framework/core.art"; } @@ -80,7 +86,7 @@ public class Device { androidHostOut = checkForEnvVar(envVars, "ANDROID_HOST_OUT"); if (Options.executeOnHost) { - File coreImage = new File(getHostCoreImagePath()); + File coreImage = new File(getHostCoreImagePathWithArch()); if (!coreImage.exists()) { Log.errorAndQuit("Host core image not found at " + coreImage.getPath() + ". Did you forget to build it?"); @@ -156,7 +162,7 @@ public class Device { * Get any extra flags required to execute ART on the host. */ public String getHostExecutionFlags() { - return String.format("-Xnorelocate -Ximage:%s", getHostCoreImagePath()); + return String.format("-Xnorelocate -Ximage:%s", getHostCoreImagePathNoArch()); } public String getAndroidHostOut() { diff --git a/tools/dmtracedump/tracedump.cc b/tools/dmtracedump/tracedump.cc index f70e2c2207..3afee6fdcb 100644 --- a/tools/dmtracedump/tracedump.cc +++ b/tools/dmtracedump/tracedump.cc @@ -512,10 +512,10 @@ int32_t compareUniqueExclusive(const void* a, const void* b) { void freeDataKeys(DataKeys* pKeys) { if (pKeys == nullptr) return; - free(pKeys->fileData); - free(pKeys->threads); - free(pKeys->methods); - free(pKeys); + delete[] pKeys->fileData; + delete[] pKeys->threads; + delete[] pKeys->methods; + delete pKeys; } /* @@ -822,8 +822,8 @@ void sortMethodList(DataKeys* pKeys) { DataKeys* parseKeys(FILE* fp, int32_t verbose) { int64_t offset; DataKeys* pKeys = new DataKeys(); - memset(pKeys, 0, sizeof(DataKeys)); if (pKeys == nullptr) return nullptr; + memset(pKeys, 0, sizeof(DataKeys)); /* * We load the entire file into memory. We do this, rather than memory- @@ -865,9 +865,13 @@ DataKeys* parseKeys(FILE* fp, int32_t verbose) { return nullptr; } - /* Reduce our allocation now that we know where the end of the key section is. */ - pKeys->fileData = reinterpret_cast<char*>(realloc(pKeys->fileData, offset)); - pKeys->fileLen = offset; + /* + * Although it is tempting to reduce our allocation now that we know where the + * end of the key section is, there is a pitfall. The method names and + * signatures in the method list contain pointers into the fileData area. + * Realloc or free will result in corruption. + */ + /* Leave fp pointing to the beginning of the data section. */ fseek(fp, offset, SEEK_SET); @@ -2607,7 +2611,7 @@ int32_t main(int32_t argc, char** argv) { if (gOptions.graphFileName != nullptr) { createInclusiveProfileGraphNew(dataKeys); } - free(methods); + delete[] methods; } freeDataKeys(dataKeys); diff --git a/tools/libcore_failures.txt b/tools/libcore_failures.txt index 38b6ea60f0..f25fb98c4d 100644 --- a/tools/libcore_failures.txt +++ b/tools/libcore_failures.txt @@ -243,48 +243,6 @@ "org.apache.harmony.tests.java.util.prefs.FilePreferencesImplTest#testPutGet"] }, { - description: "libnativehelper_compat_libc++ loading issue", - result: EXEC_FAILED, - modes: [device], - names: ["dalvik.system.JniTest#testGetSuperclass", - "dalvik.system.JniTest#testPassingBooleans", - "dalvik.system.JniTest#testPassingBytes", - "dalvik.system.JniTest#testPassingChars", - "dalvik.system.JniTest#testPassingClass", - "dalvik.system.JniTest#testPassingDoubles", - "dalvik.system.JniTest#testPassingFloats", - "dalvik.system.JniTest#testPassingInts", - "dalvik.system.JniTest#testPassingLongs", - "dalvik.system.JniTest#testPassingObjectReferences", - "dalvik.system.JniTest#testPassingShorts", - "dalvik.system.JniTest#testPassingThis", - "libcore.util.NativeAllocationRegistryTest#testBadSize", - "libcore.util.NativeAllocationRegistryTest#testEarlyFree", - "libcore.util.NativeAllocationRegistryTest#testNativeAllocationAllocatorAndNoSharedRegistry", - "libcore.util.NativeAllocationRegistryTest#testNativeAllocationAllocatorAndSharedRegistry", - "libcore.util.NativeAllocationRegistryTest#testNativeAllocationNoAllocatorAndNoSharedRegistry", - "libcore.util.NativeAllocationRegistryTest#testNativeAllocationNoAllocatorAndSharedRegistry", - "libcore.util.NativeAllocationRegistryTest#testNullArguments"] -}, -{ - description: "libnativehelper_compat_libc++.so not found by dlopen on ARM64", - result: EXEC_FAILED, - modes: [device], - bug: 28082914, - names: ["libcore.java.lang.ThreadTest#testContextClassLoaderIsInherited", - "libcore.java.lang.ThreadTest#testContextClassLoaderIsNotNull", - "libcore.java.lang.ThreadTest#testGetAllStackTracesIncludesAllGroups", - "libcore.java.lang.ThreadTest#testGetStackTrace", - "libcore.java.lang.ThreadTest#testJavaContextClassLoader", - "libcore.java.lang.ThreadTest#testLeakingStartedThreads", - "libcore.java.lang.ThreadTest#testLeakingUnstartedThreads", - "libcore.java.lang.ThreadTest#testNativeThreadNames", - "libcore.java.lang.ThreadTest#testThreadInterrupted", - "libcore.java.lang.ThreadTest#testThreadSleep", - "libcore.java.lang.ThreadTest#testThreadSleepIllegalArguments", - "libcore.java.lang.ThreadTest#testThreadWakeup"] -}, -{ description: "Only work with --mode=activity", result: EXEC_FAILED, names: [ "libcore.java.io.FileTest#testJavaIoTmpdirMutable" ] diff --git a/tools/public.libraries.buildbot.txt b/tools/public.libraries.buildbot.txt new file mode 100644 index 0000000000..4b01796a0a --- /dev/null +++ b/tools/public.libraries.buildbot.txt @@ -0,0 +1,8 @@ +libart.so +libartd.so +libbacktrace.so +libc.so +libc++.so +libdl.so +libm.so +libnativehelper.so diff --git a/tools/run-jdwp-tests.sh b/tools/run-jdwp-tests.sh index 8422e20823..976e1d8817 100755 --- a/tools/run-jdwp-tests.sh +++ b/tools/run-jdwp-tests.sh @@ -24,22 +24,10 @@ test_jack=${OUT_DIR-out}/host/common/obj/JAVA_LIBRARIES/apache-harmony-jdwp-test if [ ! -f $test_jack ]; then echo "Before running, you must build jdwp tests and vogar:" \ - "make apache-harmony-jdwp-tests-hostdex vogar vogar.jar" + "make apache-harmony-jdwp-tests-hostdex vogar" exit 1 fi -if [ "x$ART_USE_READ_BARRIER" = xtrue ]; then - # For the moment, skip JDWP tests when read barriers are enabled, as - # they sometimes exhibit a deadlock issue with the concurrent - # copying collector in the read barrier configuration, between the - # HeapTaskDeamon and the JDWP thread (b/25800335). - # - # TODO: Re-enable the JDWP tests when this deadlock issue is fixed. - echo "JDWP tests are temporarily disabled in the read barrier configuration because of" - echo "a deadlock issue (b/25800335)." - exit 0 -fi - art="/data/local/tmp/system/bin/art" art_debugee="sh /data/local/tmp/system/bin/art" args=$@ @@ -56,6 +44,8 @@ vm_args="" # By default, we run the whole JDWP test suite. test="org.apache.harmony.jpda.tests.share.AllTests" host="no" +# Use JIT compiling by default. +use_jit=true while true; do if [[ "$1" == "--mode=host" ]]; then @@ -74,6 +64,11 @@ while true; do elif [[ $1 == -Ximage:* ]]; then image="$1" shift + elif [[ "$1" == "--no-jit" ]]; then + use_jit=false + # Remove the --no-jit from the arguments. + args=${args/$1} + shift elif [[ $1 == "--debug" ]]; then debug="yes" # Remove the --debug from the arguments. @@ -102,8 +97,12 @@ done if [[ "$image" != "" ]]; then vm_args="--vm-arg $image" fi -vm_args="$vm_args --vm-arg -Xusejit:true" -debuggee_args="$debuggee_args -Xusejit:true" +if $use_jit; then + vm_args="$vm_args --vm-arg -Xcompiler-option --vm-arg --compiler-filter=interpret-only" + debuggee_args="$debuggee_args -Xcompiler-option --compiler-filter=interpret-only" +fi +vm_args="$vm_args --vm-arg -Xusejit:$use_jit" +debuggee_args="$debuggee_args -Xusejit:$use_jit" if [[ $debug == "yes" ]]; then art="$art -d" art_debugee="$art_debugee -d" @@ -114,9 +113,6 @@ if [[ $verbose == "yes" ]]; then art_debugee="$art_debugee -verbose:jdwp" fi -# Use Jack with "1.8" configuration. -export JACK_VERSION=`basename prebuilts/sdk/tools/jacks/*ALPHA* | sed 's/^jack-//' | sed 's/.jar$//'` - # Run the tests using vogar. vogar $vm_command \ $vm_args \ @@ -126,7 +122,6 @@ vogar $vm_command \ $image_compiler_option \ --timeout 800 \ --vm-arg -Djpda.settings.verbose=true \ - --vm-arg -Djpda.settings.syncPort=34016 \ --vm-arg -Djpda.settings.transportAddress=127.0.0.1:55107 \ --vm-arg -Djpda.settings.debuggeeJavaPath="$art_debugee $image $debuggee_args" \ --classpath $test_jack \ diff --git a/tools/run-libcore-tests.sh b/tools/run-libcore-tests.sh index 45fb4b4dec..3e2a512355 100755 --- a/tools/run-libcore-tests.sh +++ b/tools/run-libcore-tests.sh @@ -28,7 +28,7 @@ test_jack=${OUT_DIR-out}/target/common/obj/JAVA_LIBRARIES/core-tests_intermediat if [ ! -f $test_jack ]; then echo "Before running, you must build core-tests, jsr166-tests and vogar: \ - make core-tests jsr166-tests vogar vogar.jar" + make core-tests jsr166-tests vogar" exit 1 fi @@ -43,6 +43,9 @@ if [ "$ANDROID_SERIAL" = "emulator-5554" ]; then emulator="yes" fi +# Use JIT compiling by default. +use_jit=true + # Packages that currently work correctly with the expectation files. working_packages=("dalvik.system" "libcore.icu" @@ -91,6 +94,11 @@ while true; do # classpath/resources differences when compiling the boot image. vogar_args="$vogar_args --vm-arg -Ximage:/non/existent/vogar.art" shift + elif [[ "$1" == "--no-jit" ]]; then + # Remove the --no-jit from the arguments. + vogar_args=${vogar_args/$1} + use_jit=false + shift elif [[ "$1" == "--debug" ]]; then # Remove the --debug from the arguments. vogar_args=${vogar_args/$1} @@ -109,10 +117,15 @@ done vogar_args="$vogar_args --timeout 480" # Use Jack with "1.8" configuration. -export JACK_VERSION=`basename prebuilts/sdk/tools/jacks/*ALPHA* | sed 's/^jack-//' | sed 's/.jar$//'` vogar_args="$vogar_args --toolchain jack --language JN" +# JIT settings. +if $use_jit; then + vogar_args="$vogar_args --vm-arg -Xcompiler-option --vm-arg --compiler-filter=interpret-only" +fi +vogar_args="$vogar_args --vm-arg -Xusejit:$use_jit" + # Run the tests using vogar. echo "Running tests for the following test packages:" echo ${working_packages[@]} | tr " " "\n" -vogar $vogar_args --vm-arg -Xusejit:true $expectations --classpath $jsr166_test_jack --classpath $test_jack ${working_packages[@]} +vogar $vogar_args $expectations --classpath $jsr166_test_jack --classpath $test_jack ${working_packages[@]} |