diff options
435 files changed, 15159 insertions, 5229 deletions
diff --git a/Android.bp b/Android.bp index b9f1db5f46..d0e22fb873 100644 --- a/Android.bp +++ b/Android.bp @@ -27,6 +27,7 @@ subdirs = [ "dexdump", "dexlayout", "dexlist", + "dexoptanalyzer", "disassembler", "imgdiag", "oatdump", diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg new file mode 100644 index 0000000000..cf1832beb4 --- /dev/null +++ b/PREUPLOAD.cfg @@ -0,0 +1,2 @@ +[Hook Scripts] +check_generated_files_up_to_date = tools/cpp-define-generator/presubmit-check-files-up-to-date diff --git a/build/Android.bp b/build/Android.bp index cd9d74a934..b1553c759c 100644 --- a/build/Android.bp +++ b/build/Android.bp @@ -70,6 +70,8 @@ art_global_defaults { "-DART_STACK_OVERFLOW_GAP_mips64=16384", "-DART_STACK_OVERFLOW_GAP_x86=8192", "-DART_STACK_OVERFLOW_GAP_x86_64=8192", + // Enable thread annotations for std::mutex, etc. + "-D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS", ], target: { diff --git a/build/Android.common_path.mk b/build/Android.common_path.mk index e568ce283e..6de5aefc0b 100644 --- a/build/Android.common_path.mk +++ b/build/Android.common_path.mk @@ -109,6 +109,7 @@ endif ART_CORE_DEBUGGABLE_EXECUTABLES := \ dex2oat \ + dexoptanalyzer \ imgdiag \ oatdump \ patchoat \ diff --git a/build/Android.common_test.mk b/build/Android.common_test.mk index 291db8b531..b7a2379dc9 100644 --- a/build/Android.common_test.mk +++ b/build/Android.common_test.mk @@ -87,8 +87,8 @@ ART_TEST_GC_STRESS ?= $(ART_TEST_FULL) # Do you want tests with the JNI forcecopy mode enabled run? ART_TEST_JNI_FORCECOPY ?= $(ART_TEST_FULL) -# Do you want run-tests with relocation disabled run? -ART_TEST_RUN_TEST_NO_RELOCATE ?= $(ART_TEST_FULL) +# Do you want run-tests with relocation enabled run? +ART_TEST_RUN_TEST_RELOCATE ?= $(ART_TEST_FULL) # Do you want run-tests with prebuilding? ART_TEST_RUN_TEST_PREBUILD ?= true @@ -96,6 +96,9 @@ ART_TEST_RUN_TEST_PREBUILD ?= true # Do you want run-tests with no prebuilding enabled run? ART_TEST_RUN_TEST_NO_PREBUILD ?= $(ART_TEST_FULL) +# Do you want run-tests with a pregenerated core.art? +ART_TEST_RUN_TEST_IMAGE ?= true + # Do you want run-tests without a pregenerated core.art? ART_TEST_RUN_TEST_NO_IMAGE ?= $(ART_TEST_FULL) diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk index c87075f51c..e5258087db 100644 --- a/build/Android.gtest.mk +++ b/build/Android.gtest.mk @@ -28,6 +28,7 @@ GTEST_DEX_DIRECTORIES := \ DexToDexDecompiler \ ErroneousA \ ErroneousB \ + ErroneousInit \ ExceptionHandle \ GetMethodSignature \ ImageLayoutA \ @@ -87,7 +88,7 @@ $(ART_TEST_TARGET_GTEST_VerifierDeps_DEX): $(ART_TEST_GTEST_VerifierDeps_SRC) $( ART_GTEST_dex2oat_environment_tests_DEX_DEPS := Main MainStripped MultiDex MultiDexModifiedSecondary Nested ART_GTEST_atomic_method_ref_map_test_DEX_DEPS := Interfaces -ART_GTEST_class_linker_test_DEX_DEPS := AllFields ErroneousA ErroneousB Interfaces MethodTypes MultiDex MyClass Nested Statics StaticsFromCode +ART_GTEST_class_linker_test_DEX_DEPS := AllFields ErroneousA ErroneousB ErroneousInit Interfaces MethodTypes MultiDex MyClass Nested Statics StaticsFromCode ART_GTEST_class_table_test_DEX_DEPS := XandY ART_GTEST_compiler_driver_test_DEX_DEPS := AbstractMethod StaticLeafMethods ProfileTestMultiDex ART_GTEST_dex_cache_test_DEX_DEPS := Main Packages MethodTypes @@ -100,6 +101,7 @@ ART_GTEST_instrumentation_test_DEX_DEPS := Instrumentation ART_GTEST_jni_compiler_test_DEX_DEPS := MyClassNatives ART_GTEST_jni_internal_test_DEX_DEPS := AllFields StaticLeafMethods ART_GTEST_oat_file_assistant_test_DEX_DEPS := $(ART_GTEST_dex2oat_environment_tests_DEX_DEPS) +ART_GTEST_dexoptanalyzer_test_DEX_DEPS := $(ART_GTEST_dex2oat_environment_tests_DEX_DEPS) ART_GTEST_oat_file_test_DEX_DEPS := Main MultiDex ART_GTEST_oat_test_DEX_DEPS := Main ART_GTEST_object_test_DEX_DEPS := ProtoCompare ProtoCompare2 StaticsFromCode XandY @@ -111,6 +113,7 @@ ART_GTEST_runtime_callbacks_test_DEX_DEPS := XandY ART_GTEST_stub_test_DEX_DEPS := AllFields ART_GTEST_transaction_test_DEX_DEPS := Transaction ART_GTEST_type_lookup_table_test_DEX_DEPS := Lookup +ART_GTEST_unstarted_runtime_test_DEX_DEPS := Nested ART_GTEST_verifier_deps_test_DEX_DEPS := VerifierDeps MultiDex ART_GTEST_dex_to_dex_decompiler_test_DEX_DEPS := VerifierDeps DexToDexDecompiler @@ -136,6 +139,12 @@ ART_GTEST_oat_file_assistant_test_HOST_DEPS := \ ART_GTEST_oat_file_assistant_test_TARGET_DEPS := \ $(ART_GTEST_dex2oat_environment_tests_TARGET_DEPS) +ART_GTEST_dexoptanalyzer_test_HOST_DEPS := \ + $(ART_GTEST_dex2oat_environment_tests_HOST_DEPS) \ + $(HOST_OUT_EXECUTABLES)/dexoptanalyzerd +ART_GTEST_dexoptanalyzer_test_TARGET_DEPS := \ + $(ART_GTEST_dex2oat_environment_tests_TARGET_DEPS) \ + dexoptanalyzerd ART_GTEST_dex2oat_test_HOST_DEPS := \ $(ART_GTEST_dex2oat_environment_tests_HOST_DEPS) @@ -219,6 +228,7 @@ ART_TEST_MODULES := \ art_dexdump_tests \ art_dexlayout_tests \ art_dexlist_tests \ + art_dexoptanalyzer_tests \ art_imgdiag_tests \ art_oatdump_tests \ art_profman_tests \ @@ -614,6 +624,9 @@ ART_GTEST_jni_internal_test_DEX_DEPS := ART_GTEST_oat_file_assistant_test_DEX_DEPS := ART_GTEST_oat_file_assistant_test_HOST_DEPS := ART_GTEST_oat_file_assistant_test_TARGET_DEPS := +ART_GTEST_dexoptanalyzer_test_DEX_DEPS := +ART_GTEST_dexoptanalyzer_test_HOST_DEPS := +ART_GTEST_dexoptanalyzer_test_TARGET_DEPS := ART_GTEST_dex2oat_test_DEX_DEPS := ART_GTEST_dex2oat_test_HOST_DEPS := ART_GTEST_dex2oat_test_TARGET_DEPS := diff --git a/build/art.go b/build/art.go index 999ece6de6..e7f7e2121e 100644 --- a/build/art.go +++ b/build/art.go @@ -58,7 +58,7 @@ func globalFlags(ctx android.BaseContext) ([]string, []string) { asflags = append(asflags, "-DART_HEAP_POISONING=1") } - if !envFalse(ctx, "ART_USE_READ_BARRIER") || ctx.AConfig().ArtUseReadBarrier() { + if !envFalse(ctx, "ART_USE_READ_BARRIER") && ctx.AConfig().ArtUseReadBarrier() { // Used to change the read barrier type. Valid values are BAKER, BROOKS, TABLELOOKUP. // The default is BAKER. barrierType := envDefault(ctx, "ART_READ_BARRIER_TYPE", "BAKER") diff --git a/cmdline/cmdline_types.h b/cmdline/cmdline_types.h index 28c009edf0..f1123eb692 100644 --- a/cmdline/cmdline_types.h +++ b/cmdline/cmdline_types.h @@ -766,10 +766,6 @@ struct CmdlineType<ExperimentalFlags> : CmdlineTypeParser<ExperimentalFlags> { Result ParseAndAppend(const std::string& option, ExperimentalFlags& existing) { if (option == "none") { existing = ExperimentalFlags::kNone; - } else if (option == "agents") { - existing = existing | ExperimentalFlags::kAgents; - } else if (option == "runtime-plugins") { - existing = existing | ExperimentalFlags::kRuntimePlugins; } else { return Result::Failure(std::string("Unknown option '") + option + "'"); } diff --git a/compiler/Android.bp b/compiler/Android.bp index 46f3358af1..f6a4db49fb 100644 --- a/compiler/Android.bp +++ b/compiler/Android.bp @@ -80,6 +80,7 @@ art_cc_defaults { "optimizing/register_allocator_graph_color.cc", "optimizing/register_allocator_linear_scan.cc", "optimizing/select_generator.cc", + "optimizing/scheduler.cc", "optimizing/sharpening.cc", "optimizing/side_effects_analysis.cc", "optimizing/ssa_builder.cc", @@ -123,6 +124,7 @@ art_cc_defaults { "jni/quick/arm64/calling_convention_arm64.cc", "linker/arm64/relative_patcher_arm64.cc", "optimizing/code_generator_arm64.cc", + "optimizing/scheduler_arm64.cc", "optimizing/instruction_simplifier_arm64.cc", "optimizing/intrinsics_arm64.cc", "optimizing/nodes_arm64.cc", @@ -362,6 +364,7 @@ art_cc_test { "jni/jni_cfi_test.cc", "optimizing/codegen_test.cc", "optimizing/optimizing_cfi_test.cc", + "optimizing/scheduler_test.cc", ], codegen: { diff --git a/compiler/driver/compiler_driver-inl.h b/compiler/driver/compiler_driver-inl.h index f056dd3c00..f296851ebf 100644 --- a/compiler/driver/compiler_driver-inl.h +++ b/compiler/driver/compiler_driver-inl.h @@ -135,65 +135,6 @@ inline bool CompilerDriver::CanAccessResolvedMember<ArtMethod>( return referrer_class->CanAccessResolvedMethod(access_to, method, dex_cache, field_idx); } -template <typename ArtMember> -inline std::pair<bool, bool> CompilerDriver::IsClassOfStaticMemberAvailableToReferrer( - mirror::DexCache* dex_cache, - mirror::Class* referrer_class, - ArtMember* resolved_member, - uint16_t member_idx, - dex::TypeIndex* storage_index) { - DCHECK(resolved_member->IsStatic()); - if (LIKELY(referrer_class != nullptr)) { - ObjPtr<mirror::Class> members_class = resolved_member->GetDeclaringClass(); - if (members_class == referrer_class) { - *storage_index = members_class->GetDexTypeIndex(); - return std::make_pair(true, true); - } - if (CanAccessResolvedMember<ArtMember>( - referrer_class, members_class.Ptr(), resolved_member, dex_cache, member_idx)) { - // We have the resolved member, we must make it into a index for the referrer - // in its static storage (which may fail if it doesn't have a slot for it) - // TODO: for images we can elide the static storage base null check - // if we know there's a non-null entry in the image - const DexFile* dex_file = dex_cache->GetDexFile(); - dex::TypeIndex storage_idx(DexFile::kDexNoIndex16); - if (LIKELY(members_class->GetDexCache() == dex_cache)) { - // common case where the dex cache of both the referrer and the member are the same, - // no need to search the dex file - storage_idx = members_class->GetDexTypeIndex(); - } else { - // Search dex file for localized ssb index, may fail if member's class is a parent - // of the class mentioned in the dex file and there is no dex cache entry. - storage_idx = resolved_member->GetDeclaringClass()->FindTypeIndexInOtherDexFile(*dex_file); - } - if (storage_idx.IsValid()) { - *storage_index = storage_idx; - return std::make_pair(true, !resolved_member->IsFinal()); - } - } - } - // Conservative defaults. - *storage_index = dex::TypeIndex(DexFile::kDexNoIndex16); - return std::make_pair(false, false); -} - -inline std::pair<bool, bool> CompilerDriver::IsFastStaticField( - mirror::DexCache* dex_cache, mirror::Class* referrer_class, - ArtField* resolved_field, uint16_t field_idx, dex::TypeIndex* storage_index) { - return IsClassOfStaticMemberAvailableToReferrer( - dex_cache, referrer_class, resolved_field, field_idx, storage_index); -} - -inline bool CompilerDriver::IsClassOfStaticMethodAvailableToReferrer( - mirror::DexCache* dex_cache, mirror::Class* referrer_class, - ArtMethod* resolved_method, uint16_t method_idx, dex::TypeIndex* storage_index) { - std::pair<bool, bool> result = IsClassOfStaticMemberAvailableToReferrer( - dex_cache, referrer_class, resolved_method, method_idx, storage_index); - // Only the first member of `result` is meaningful, as there is no - // "write access" to a method. - return result.first; -} - inline ArtMethod* CompilerDriver::ResolveMethod( ScopedObjectAccess& soa, Handle<mirror::DexCache> dex_cache, Handle<mirror::ClassLoader> class_loader, const DexCompilationUnit* mUnit, diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc index c03ffcaa31..1d4eaf8c5a 100644 --- a/compiler/driver/compiler_driver.cc +++ b/compiler/driver/compiler_driver.cc @@ -529,9 +529,15 @@ static optimizer::DexToDexCompilationLevel GetDexToDexCompilationLevel( // We store the verification information in the class status in the oat file, which the linker // can validate (checksums) and use to skip load-time verification. It is thus safe to // optimize when a class has been fully verified before. + optimizer::DexToDexCompilationLevel max_level = optimizer::DexToDexCompilationLevel::kOptimize; + if (driver.GetCompilerOptions().GetDebuggable()) { + // We are debuggable so definitions of classes might be changed. We don't want to do any + // optimizations that could break that. + max_level = optimizer::DexToDexCompilationLevel::kRequired; + } if (klass->IsVerified()) { // Class is verified so we can enable DEX-to-DEX compilation for performance. - return optimizer::DexToDexCompilationLevel::kOptimize; + return max_level; } else if (klass->IsCompileTimeVerified()) { // Class verification has soft-failed. Anyway, ensure at least correctness. DCHECK_EQ(klass->GetStatus(), mirror::Class::kStatusRetryVerificationAtRuntime); @@ -940,6 +946,31 @@ inline void CompilerDriver::CheckThreadPools() { DCHECK(single_thread_pool_ != nullptr); } +static void EnsureVerifiedOrVerifyAtRuntime(jobject jclass_loader, + const std::vector<const DexFile*>& dex_files) { + ScopedObjectAccess soa(Thread::Current()); + StackHandleScope<2> hs(soa.Self()); + Handle<mirror::ClassLoader> class_loader( + hs.NewHandle(soa.Decode<mirror::ClassLoader>(jclass_loader))); + MutableHandle<mirror::Class> cls(hs.NewHandle<mirror::Class>(nullptr)); + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + + for (const DexFile* dex_file : dex_files) { + for (uint32_t i = 0; i < dex_file->NumClassDefs(); ++i) { + const DexFile::ClassDef& class_def = dex_file->GetClassDef(i); + const char* descriptor = dex_file->GetClassDescriptor(class_def); + cls.Assign(class_linker->FindClass(soa.Self(), descriptor, class_loader)); + if (cls.Get() == nullptr) { + soa.Self()->ClearException(); + } else if (&cls->GetDexFile() == dex_file) { + DCHECK(cls->IsErroneous() || cls->IsVerified() || cls->IsCompileTimeVerified()) + << cls->PrettyClass() + << " " << cls->GetStatus(); + } + } + } +} + void CompilerDriver::PreCompile(jobject class_loader, const std::vector<const DexFile*>& dex_files, TimingLogger* timings) { @@ -984,6 +1015,9 @@ void CompilerDriver::PreCompile(jobject class_loader, } if (compiler_options_->IsAnyMethodCompilationEnabled()) { + if (kIsDebugBuild) { + EnsureVerifiedOrVerifyAtRuntime(class_loader, dex_files); + } InitializeClasses(class_loader, dex_files, timings); VLOG(compiler) << "InitializeClasses: " << GetMemoryUsageString(false); } @@ -1034,23 +1068,6 @@ bool CompilerDriver::ShouldCompileBasedOnProfile(const MethodReference& method_r return result; } -bool CompilerDriver::ShouldVerifyClassBasedOnProfile(const DexFile& dex_file, - uint16_t class_idx) const { - if (!compiler_options_->VerifyOnlyProfile()) { - // No profile, verify everything. - return true; - } - DCHECK(profile_compilation_info_ != nullptr); - const DexFile::ClassDef& class_def = dex_file.GetClassDef(class_idx); - dex::TypeIndex type_idx = class_def.class_idx_; - bool result = profile_compilation_info_->ContainsClass(dex_file, type_idx); - if (kDebugProfileGuidedCompilation) { - LOG(INFO) << "[ProfileGuidedCompilation] " << (result ? "Verified" : "Skipped") << " method:" - << dex_file.GetClassDescriptor(class_def); - } - return result; -} - class ResolveCatchBlockExceptionsClassVisitor : public ClassVisitor { public: explicit ResolveCatchBlockExceptionsClassVisitor( @@ -1949,6 +1966,31 @@ static void PopulateVerifiedMethods(const DexFile& dex_file, DCHECK(!it.HasNext()); } +static void LoadAndUpdateStatus(const DexFile& dex_file, + const DexFile::ClassDef& class_def, + mirror::Class::Status status, + Handle<mirror::ClassLoader> class_loader, + Thread* self) + REQUIRES_SHARED(Locks::mutator_lock_) { + StackHandleScope<1> hs(self); + const char* descriptor = dex_file.GetClassDescriptor(class_def); + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + Handle<mirror::Class> cls(hs.NewHandle<mirror::Class>( + class_linker->FindClass(self, descriptor, class_loader))); + if (cls.Get() != nullptr) { + // Check that the class is resolved with the current dex file. We might get + // a boot image class, or a class in a different dex file for multidex, and + // we should not update the status in that case. + if (&cls->GetDexFile() == &dex_file) { + ObjectLock<mirror::Class> lock(self, cls); + mirror::Class::SetStatus(cls, status, self); + } + } else { + DCHECK(self->IsExceptionPending()); + self->ClearException(); + } +} + bool CompilerDriver::FastVerify(jobject jclass_loader, const std::vector<const DexFile*>& dex_files, TimingLogger* timings) { @@ -1963,12 +2005,12 @@ bool CompilerDriver::FastVerify(jobject jclass_loader, StackHandleScope<2> hs(soa.Self()); Handle<mirror::ClassLoader> class_loader( hs.NewHandle(soa.Decode<mirror::ClassLoader>(jclass_loader))); - MutableHandle<mirror::Class> cls(hs.NewHandle<mirror::Class>(nullptr)); - ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); if (!verifier_deps->ValidateDependencies(class_loader, soa.Self())) { return false; } + bool compiler_only_verifies = !GetCompilerOptions().IsAnyMethodCompilationEnabled(); + // We successfully validated the dependencies, now update class status // of verified classes. Note that the dependencies also record which classes // could not be fully verified; we could try again, but that would hurt verification @@ -1983,28 +2025,16 @@ bool CompilerDriver::FastVerify(jobject jclass_loader, for (uint32_t i = 0; i < dex_file->NumClassDefs(); ++i) { const DexFile::ClassDef& class_def = dex_file->GetClassDef(i); if (set.find(class_def.class_idx_) == set.end()) { - if (!GetCompilerOptions().IsAnyMethodCompilationEnabled()) { + if (compiler_only_verifies) { // Just update the compiled_classes_ map. The compiler doesn't need to resolve // the type. compiled_classes_.Overwrite( ClassReference(dex_file, i), new CompiledClass(mirror::Class::kStatusVerified)); } else { - // Resolve the type, so later compilation stages know they don't need to verify + // Update the class status, so later compilation stages know they don't need to verify // the class. - const char* descriptor = dex_file->GetClassDescriptor(class_def); - cls.Assign(class_linker->FindClass(soa.Self(), descriptor, class_loader)); - if (cls.Get() != nullptr) { - // Check that the class is resolved with the current dex file. We might get - // a boot image class, or a class in a different dex file for multidex, and - // we should not update the status in that case. - if (&cls->GetDexFile() == dex_file) { - ObjectLock<mirror::Class> lock(soa.Self(), cls); - mirror::Class::SetStatus(cls, mirror::Class::kStatusVerified, soa.Self()); - } - } else { - DCHECK(soa.Self()->IsExceptionPending()); - soa.Self()->ClearException(); - } + LoadAndUpdateStatus( + *dex_file, class_def, mirror::Class::kStatusVerified, class_loader, soa.Self()); // Create `VerifiedMethod`s for each methods, the compiler expects one for // quickening or compiling. // Note that this means: @@ -2013,6 +2043,14 @@ bool CompilerDriver::FastVerify(jobject jclass_loader, // TODO(ngeoffray): Reconsider this once we refactor compiler filters. PopulateVerifiedMethods(*dex_file, i, verification_results_); } + } else if (!compiler_only_verifies) { + // Make sure later compilation stages know they should not try to verify + // this class again. + LoadAndUpdateStatus(*dex_file, + class_def, + mirror::Class::kStatusRetryVerificationAtRuntime, + class_loader, + soa.Self()); } } } @@ -2077,13 +2115,6 @@ class VerifyClassVisitor : public CompilationVisitor { ATRACE_CALL(); ScopedObjectAccess soa(Thread::Current()); const DexFile& dex_file = *manager_->GetDexFile(); - if (!manager_->GetCompiler()->ShouldVerifyClassBasedOnProfile(dex_file, class_def_index)) { - // Skip verification since the class is not in the profile, and let the VerifierDeps know - // that the class will need to be verified at runtime. - verifier::VerifierDeps::MaybeRecordVerificationStatus( - dex_file, dex::TypeIndex(class_def_index), verifier::MethodVerifier::kSoftFailure); - return; - } const DexFile::ClassDef& class_def = dex_file.GetClassDef(class_def_index); const char* descriptor = dex_file.GetClassDescriptor(class_def); ClassLinker* class_linker = manager_->GetClassLinker(); @@ -2199,7 +2230,7 @@ class SetVerifiedClassVisitor : public CompilationVisitor { if (klass.Get() != nullptr) { // Only do this if the class is resolved. If even resolution fails, quickening will go very, // very wrong. - if (klass->IsResolved()) { + if (klass->IsResolved() && !klass->IsErroneousResolved()) { if (klass->GetStatus() < mirror::Class::kStatusVerified) { ObjectLock<mirror::Class> lock(soa.Self(), klass); // Set class status to verified. @@ -2626,7 +2657,8 @@ CompiledClass* CompilerDriver::GetCompiledClass(ClassReference ref) const { void CompilerDriver::RecordClassStatus(ClassReference ref, mirror::Class::Status status) { switch (status) { case mirror::Class::kStatusNotReady: - case mirror::Class::kStatusError: + case mirror::Class::kStatusErrorResolved: + case mirror::Class::kStatusErrorUnresolved: case mirror::Class::kStatusRetryVerificationAtRuntime: case mirror::Class::kStatusVerified: case mirror::Class::kStatusInitialized: diff --git a/compiler/driver/compiler_driver.h b/compiler/driver/compiler_driver.h index 503fe3adfc..5b4c751c4a 100644 --- a/compiler/driver/compiler_driver.h +++ b/compiler/driver/compiler_driver.h @@ -233,27 +233,6 @@ class CompilerDriver { ArtField* resolved_field, uint16_t field_idx) REQUIRES_SHARED(Locks::mutator_lock_); - // Can we fast-path an SGET/SPUT access to a static field? If yes, compute the type index - // of the declaring class in the referrer's dex file. - std::pair<bool, bool> IsFastStaticField(mirror::DexCache* dex_cache, - mirror::Class* referrer_class, - ArtField* resolved_field, - uint16_t field_idx, - dex::TypeIndex* storage_index) - REQUIRES_SHARED(Locks::mutator_lock_); - - // Return whether the declaring class of `resolved_method` is - // available to `referrer_class`. If this is true, compute the type - // index of the declaring class in the referrer's dex file and - // return it through the out argument `storage_index`; otherwise - // return DexFile::kDexNoIndex through `storage_index`. - bool IsClassOfStaticMethodAvailableToReferrer(mirror::DexCache* dex_cache, - mirror::Class* referrer_class, - ArtMethod* resolved_method, - uint16_t method_idx, - dex::TypeIndex* storage_index) - REQUIRES_SHARED(Locks::mutator_lock_); - // Resolve a method. Returns null on failure, including incompatible class change. ArtMethod* ResolveMethod( ScopedObjectAccess& soa, Handle<mirror::DexCache> dex_cache, @@ -379,21 +358,6 @@ class CompilerDriver { } private: - // Return whether the declaring class of `resolved_member` is - // available to `referrer_class` for read or write access using two - // Boolean values returned as a pair. If is true at least for read - // access, compute the type index of the declaring class in the - // referrer's dex file and return it through the out argument - // `storage_index`; otherwise return DexFile::kDexNoIndex through - // `storage_index`. - template <typename ArtMember> - std::pair<bool, bool> IsClassOfStaticMemberAvailableToReferrer(mirror::DexCache* dex_cache, - mirror::Class* referrer_class, - ArtMember* resolved_member, - uint16_t member_idx, - dex::TypeIndex* storage_index) - REQUIRES_SHARED(Locks::mutator_lock_); - // Can `referrer_class` access the resolved `member`? // Dispatch call to mirror::Class::CanAccessResolvedField or // mirror::Class::CanAccessResolvedMember depending on the value of diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc index 459aca3b38..c72edb18a3 100644 --- a/compiler/image_writer.cc +++ b/compiler/image_writer.cc @@ -756,7 +756,7 @@ bool ImageWriter::PruneAppImageClassInternal( bool my_early_exit = false; // Only for ourselves, ignore caller. // Remove classes that failed to verify since we don't want to have java.lang.VerifyError in the // app image. - if (klass->GetStatus() == mirror::Class::kStatusError) { + if (klass->IsErroneous()) { result = true; } else { ObjPtr<mirror::ClassExt> ext(klass->GetExtData()); @@ -777,8 +777,8 @@ bool ImageWriter::PruneAppImageClassInternal( visited); } // Check static fields and their classes. - size_t num_static_fields = klass->NumReferenceStaticFields(); - if (num_static_fields != 0 && klass->IsResolved()) { + if (klass->IsResolved() && klass->NumReferenceStaticFields() != 0) { + size_t num_static_fields = klass->NumReferenceStaticFields(); // Presumably GC can happen when we are cross compiling, it should not cause performance // problems to do pointer size logic. MemberOffset field_offset = klass->GetFirstReferenceStaticFieldOffset( @@ -1154,7 +1154,7 @@ mirror::Object* ImageWriter::TryAssignBinSlot(WorkStack& work_stack, // Visit and assign offsets for fields and field arrays. mirror::Class* as_klass = obj->AsClass(); mirror::DexCache* dex_cache = as_klass->GetDexCache(); - DCHECK_NE(as_klass->GetStatus(), mirror::Class::kStatusError); + DCHECK(!as_klass->IsErroneous()) << as_klass->GetStatus(); if (compile_app_image_) { // Extra sanity, no boot loader classes should be left! CHECK(!IsBootClassLoaderClass(as_klass)) << as_klass->PrettyClass(); @@ -2348,6 +2348,16 @@ const uint8_t* ImageWriter::GetQuickCode(ArtMethod* method, void ImageWriter::CopyAndFixupMethod(ArtMethod* orig, ArtMethod* copy, const ImageInfo& image_info) { + if (orig->IsAbstract()) { + // Ignore the single-implementation info for abstract method. + // Do this on orig instead of copy, otherwise there is a crash due to methods + // are copied before classes. + // TODO: handle fixup of single-implementation method for abstract method. + orig->SetHasSingleImplementation(false); + orig->SetSingleImplementation( + nullptr, Runtime::Current()->GetClassLinker()->GetImagePointerSize()); + } + memcpy(copy, orig, ArtMethod::Size(target_ptr_size_)); copy->SetDeclaringClass(GetImageAddress(orig->GetDeclaringClassUnchecked())); diff --git a/compiler/jit/jit_compiler.cc b/compiler/jit/jit_compiler.cc index 148ce4f9ee..cbd831a60f 100644 --- a/compiler/jit/jit_compiler.cc +++ b/compiler/jit/jit_compiler.cc @@ -102,7 +102,7 @@ JitCompiler::JitCompiler() { /* no_inline_from */ nullptr, /* include_patch_information */ false, CompilerOptions::kDefaultTopKProfileThreshold, - Runtime::Current()->IsDebuggable(), + Runtime::Current()->IsJavaDebuggable(), CompilerOptions::kDefaultGenerateDebugInfo, /* implicit_null_checks */ true, /* implicit_so_checks */ true, @@ -211,7 +211,7 @@ bool JitCompiler::CompileMethod(Thread* self, ArtMethod* method, bool osr) { JitCodeCache* const code_cache = runtime->GetJit()->GetCodeCache(); success = compiler_driver_->GetCompiler()->JitCompile(self, code_cache, method, osr); if (success && (jit_logger_ != nullptr)) { - jit_logger_->WriteLog(code_cache, method); + jit_logger_->WriteLog(code_cache, method, osr); } } diff --git a/compiler/jit/jit_logger.cc b/compiler/jit/jit_logger.cc index 9ce3b0cfe8..aa4f66773a 100644 --- a/compiler/jit/jit_logger.cc +++ b/compiler/jit/jit_logger.cc @@ -23,6 +23,7 @@ #include "driver/compiler_driver.h" #include "jit/jit.h" #include "jit/jit_code_cache.h" +#include "oat_file-inl.h" namespace art { namespace jit { @@ -49,9 +50,10 @@ void JitLogger::OpenPerfMapLog() { } } -void JitLogger::WritePerfMapLog(JitCodeCache* code_cache, ArtMethod* method) { +void JitLogger::WritePerfMapLog(JitCodeCache* code_cache, ArtMethod* method, bool osr) { if (perf_file_ != nullptr) { - const void* ptr = method->GetEntryPointFromQuickCompiledCode(); + const void* ptr = osr ? code_cache->LookupOsrMethodHeader(method)->GetCode() + : method->GetEntryPointFromQuickCompiledCode(); size_t code_size = code_cache->GetMemorySizeOfCodePointer(ptr); std::string method_name = method->PrettyMethod(); @@ -268,9 +270,10 @@ void JitLogger::OpenJitDumpLog() { WriteJitDumpHeader(); } -void JitLogger::WriteJitDumpLog(JitCodeCache* code_cache, ArtMethod* method) { +void JitLogger::WriteJitDumpLog(JitCodeCache* code_cache, ArtMethod* method, bool osr) { if (jit_dump_file_ != nullptr) { - const void* code = method->GetEntryPointFromQuickCompiledCode(); + const void* code = osr ? code_cache->LookupOsrMethodHeader(method)->GetCode() + : method->GetEntryPointFromQuickCompiledCode(); size_t code_size = code_cache->GetMemorySizeOfCodePointer(code); std::string method_name = method->PrettyMethod(); diff --git a/compiler/jit/jit_logger.h b/compiler/jit/jit_logger.h index 0f8cfe4e2f..460864e8a9 100644 --- a/compiler/jit/jit_logger.h +++ b/compiler/jit/jit_logger.h @@ -94,10 +94,10 @@ class JitLogger { OpenJitDumpLog(); } - void WriteLog(JitCodeCache* code_cache, ArtMethod* method) + void WriteLog(JitCodeCache* code_cache, ArtMethod* method, bool osr) REQUIRES_SHARED(Locks::mutator_lock_) { - WritePerfMapLog(code_cache, method); - WriteJitDumpLog(code_cache, method); + WritePerfMapLog(code_cache, method, osr); + WriteJitDumpLog(code_cache, method, osr); } void CloseLog() { @@ -108,13 +108,13 @@ class JitLogger { private: // For perf-map profiling void OpenPerfMapLog(); - void WritePerfMapLog(JitCodeCache* code_cache, ArtMethod* method) + void WritePerfMapLog(JitCodeCache* code_cache, ArtMethod* method, bool osr) REQUIRES_SHARED(Locks::mutator_lock_); void ClosePerfMapLog(); // For perf-inject profiling void OpenJitDumpLog(); - void WriteJitDumpLog(JitCodeCache* code_cache, ArtMethod* method) + void WriteJitDumpLog(JitCodeCache* code_cache, ArtMethod* method, bool osr) REQUIRES_SHARED(Locks::mutator_lock_); void CloseJitDumpLog(); diff --git a/compiler/linker/mips/relative_patcher_mips.cc b/compiler/linker/mips/relative_patcher_mips.cc index c09950cd5d..fe5f9a948a 100644 --- a/compiler/linker/mips/relative_patcher_mips.cc +++ b/compiler/linker/mips/relative_patcher_mips.cc @@ -49,9 +49,12 @@ void MipsRelativePatcher::PatchPcRelativeReference(std::vector<uint8_t>* code, uint32_t target_offset) { uint32_t anchor_literal_offset = patch.PcInsnOffset(); uint32_t literal_offset = patch.LiteralOffset(); + uint32_t literal_low_offset; bool dex_cache_array = (patch.GetType() == LinkerPatch::Type::kDexCacheArray); - // Basic sanity checks. + // Perform basic sanity checks and initialize `literal_low_offset` to point + // to the instruction containing the 16 least significant bits of the + // relative address. if (is_r6) { DCHECK_GE(code->size(), 8u); DCHECK_LE(literal_offset, code->size() - 8u); @@ -61,10 +64,10 @@ void MipsRelativePatcher::PatchPcRelativeReference(std::vector<uint8_t>* code, DCHECK_EQ((*code)[literal_offset + 1], 0x12); DCHECK_EQ(((*code)[literal_offset + 2] & 0x1F), 0x1E); DCHECK_EQ(((*code)[literal_offset + 3] & 0xFC), 0xEC); - // ADDIU reg, reg, offset_low + // instr reg(s), offset_low DCHECK_EQ((*code)[literal_offset + 4], 0x78); DCHECK_EQ((*code)[literal_offset + 5], 0x56); - DCHECK_EQ(((*code)[literal_offset + 7] & 0xFC), 0x24); + literal_low_offset = literal_offset + 4; } else { DCHECK_GE(code->size(), 16u); DCHECK_LE(literal_offset, code->size() - 12u); @@ -84,36 +87,34 @@ void MipsRelativePatcher::PatchPcRelativeReference(std::vector<uint8_t>* code, DCHECK_EQ((*code)[literal_offset + 1], 0x12); DCHECK_EQ(((*code)[literal_offset + 2] & 0xE0), 0x00); DCHECK_EQ((*code)[literal_offset + 3], 0x3C); - // ORI reg, reg, offset_low - DCHECK_EQ((*code)[literal_offset + 4], 0x78); - DCHECK_EQ((*code)[literal_offset + 5], 0x56); - DCHECK_EQ(((*code)[literal_offset + 7] & 0xFC), 0x34); // ADDU reg, reg, reg2 - DCHECK_EQ((*code)[literal_offset + 8], 0x21); - DCHECK_EQ(((*code)[literal_offset + 9] & 0x07), 0x00); + DCHECK_EQ((*code)[literal_offset + 4], 0x21); + DCHECK_EQ(((*code)[literal_offset + 5] & 0x07), 0x00); if (dex_cache_array) { // reg2 is either RA or from HMipsComputeBaseMethodAddress. - DCHECK_EQ(((*code)[literal_offset + 10] & 0x1F), 0x1F); + DCHECK_EQ(((*code)[literal_offset + 6] & 0x1F), 0x1F); } - DCHECK_EQ(((*code)[literal_offset + 11] & 0xFC), 0x00); + DCHECK_EQ(((*code)[literal_offset + 7] & 0xFC), 0x00); + // instr reg(s), offset_low + DCHECK_EQ((*code)[literal_offset + 8], 0x78); + DCHECK_EQ((*code)[literal_offset + 9], 0x56); + literal_low_offset = literal_offset + 8; } // Apply patch. uint32_t anchor_offset = patch_offset - literal_offset + anchor_literal_offset; uint32_t diff = target_offset - anchor_offset; - if (dex_cache_array) { + if (dex_cache_array && !is_r6) { diff += kDexCacheArrayLwOffset; } - if (is_r6) { - diff += (diff & 0x8000) << 1; // Account for sign extension in ADDIU. - } + diff += (diff & 0x8000) << 1; // Account for sign extension in "instr reg(s), offset_low". // LUI reg, offset_high / AUIPC reg, offset_high (*code)[literal_offset + 0] = static_cast<uint8_t>(diff >> 16); (*code)[literal_offset + 1] = static_cast<uint8_t>(diff >> 24); - // ORI reg, reg, offset_low / ADDIU reg, reg, offset_low - (*code)[literal_offset + 4] = static_cast<uint8_t>(diff >> 0); - (*code)[literal_offset + 5] = static_cast<uint8_t>(diff >> 8); + // instr reg(s), offset_low + (*code)[literal_low_offset + 0] = static_cast<uint8_t>(diff >> 0); + (*code)[literal_low_offset + 1] = static_cast<uint8_t>(diff >> 8); } } // namespace linker diff --git a/compiler/linker/mips/relative_patcher_mips32r6_test.cc b/compiler/linker/mips/relative_patcher_mips32r6_test.cc index 4f9a3a050a..474eb73e08 100644 --- a/compiler/linker/mips/relative_patcher_mips32r6_test.cc +++ b/compiler/linker/mips/relative_patcher_mips32r6_test.cc @@ -20,10 +20,6 @@ namespace art { namespace linker { -// We'll maximize the range of a single load instruction for dex cache array accesses -// by aligning offset -32768 with the offset of the first used element. -static constexpr uint32_t kDexCacheArrayLwOffset = 0x8000; - class Mips32r6RelativePatcherTest : public RelativePatcherTest { public: Mips32r6RelativePatcherTest() : RelativePatcherTest(kMips, "mips32r6") {} @@ -64,9 +60,6 @@ void Mips32r6RelativePatcherTest::CheckPcRelativePatch(const ArrayRef<const Link ASSERT_TRUE(result.first); uint32_t diff = target_offset - (result.second + kAnchorOffset); - if (patches[0].GetType() == LinkerPatch::Type::kDexCacheArray) { - diff += kDexCacheArrayLwOffset; - } diff += (diff & 0x8000) << 1; // Account for sign extension in addiu. const uint8_t expected_code[] = { diff --git a/compiler/linker/mips/relative_patcher_mips_test.cc b/compiler/linker/mips/relative_patcher_mips_test.cc index faeb92a315..b0d1294cf4 100644 --- a/compiler/linker/mips/relative_patcher_mips_test.cc +++ b/compiler/linker/mips/relative_patcher_mips_test.cc @@ -47,12 +47,12 @@ class MipsRelativePatcherTest : public RelativePatcherTest { const uint8_t MipsRelativePatcherTest::kUnpatchedPcRelativeRawCode[] = { 0x00, 0x00, 0x10, 0x04, // nal - 0x34, 0x12, 0x12, 0x3C, // lui s2, high(diff); placeholder = 0x1234 - 0x78, 0x56, 0x52, 0x36, // ori s2, s2, low(diff); placeholder = 0x5678 - 0x21, 0x90, 0x5F, 0x02, // addu s2, s2, ra + 0x34, 0x12, 0x12, 0x3C, // lui s2, high(diff); placeholder = 0x1234 + 0x21, 0x90, 0x5F, 0x02, // addu s2, s2, ra + 0x78, 0x56, 0x52, 0x26, // addiu s2, s2, low(diff); placeholder = 0x5678 }; const uint32_t MipsRelativePatcherTest::kLiteralOffset = 4; // At lui (where patching starts). -const uint32_t MipsRelativePatcherTest::kAnchorOffset = 8; // At ori (where PC+0 points). +const uint32_t MipsRelativePatcherTest::kAnchorOffset = 8; // At addu (where PC+0 points). const ArrayRef<const uint8_t> MipsRelativePatcherTest::kUnpatchedPcRelativeCode( kUnpatchedPcRelativeRawCode); @@ -68,12 +68,13 @@ void MipsRelativePatcherTest::CheckPcRelativePatch(const ArrayRef<const LinkerPa if (patches[0].GetType() == LinkerPatch::Type::kDexCacheArray) { diff += kDexCacheArrayLwOffset; } + diff += (diff & 0x8000) << 1; // Account for sign extension in addiu. const uint8_t expected_code[] = { 0x00, 0x00, 0x10, 0x04, static_cast<uint8_t>(diff >> 16), static_cast<uint8_t>(diff >> 24), 0x12, 0x3C, - static_cast<uint8_t>(diff), static_cast<uint8_t>(diff >> 8), 0x52, 0x36, 0x21, 0x90, 0x5F, 0x02, + static_cast<uint8_t>(diff), static_cast<uint8_t>(diff >> 8), 0x52, 0x26, }; EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code))); } diff --git a/compiler/oat_test.cc b/compiler/oat_test.cc index 34b33a13a3..d5842a8c9d 100644 --- a/compiler/oat_test.cc +++ b/compiler/oat_test.cc @@ -487,7 +487,7 @@ TEST_F(OatTest, OatHeaderSizeCheck) { EXPECT_EQ(72U, sizeof(OatHeader)); EXPECT_EQ(4U, sizeof(OatMethodOffsets)); EXPECT_EQ(20U, sizeof(OatQuickMethodHeader)); - EXPECT_EQ(157 * static_cast<size_t>(GetInstructionSetPointerSize(kRuntimeISA)), + EXPECT_EQ(161 * static_cast<size_t>(GetInstructionSetPointerSize(kRuntimeISA)), sizeof(QuickEntryPoints)); } diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc index de5af97b9a..a16a34b299 100644 --- a/compiler/oat_writer.cc +++ b/compiler/oat_writer.cc @@ -716,7 +716,10 @@ class OatWriter::InitOatClassesMethodVisitor : public DexMethodVisitor { if (compiled_class != nullptr) { status = compiled_class->GetStatus(); } else if (writer_->compiler_driver_->GetVerificationResults()->IsClassRejected(class_ref)) { - status = mirror::Class::kStatusError; + // The oat class status is used only for verification of resolved classes, + // so use kStatusErrorResolved whether the class was resolved or unresolved + // during compile-time verification. + status = mirror::Class::kStatusErrorResolved; } else { status = mirror::Class::kStatusNotReady; } @@ -2263,6 +2266,10 @@ bool OatWriter::LayoutAndWriteDexFile(OutputStream* out, OatDexFile* oat_dex_fil File* raw_file = oat_dex_file->source_.GetRawFile(); dex_file = DexFile::OpenDex(raw_file->Fd(), location, /* verify_checksum */ true, &error_msg); } + if (dex_file == nullptr) { + LOG(ERROR) << "Failed to open dex file for layout:" << error_msg; + return false; + } Options options; options.output_to_memmap_ = true; DexLayout dex_layout(options, profile_compilation_info_, nullptr); diff --git a/compiler/optimizing/builder.h b/compiler/optimizing/builder.h index 8cf4089eba..e4ad4222fb 100644 --- a/compiler/optimizing/builder.h +++ b/compiler/optimizing/builder.h @@ -32,6 +32,8 @@ namespace art { +class CodeGenerator; + class HGraphBuilder : public ValueObject { public: HGraphBuilder(HGraph* graph, @@ -40,6 +42,7 @@ class HGraphBuilder : public ValueObject { const DexFile* dex_file, const DexFile::CodeItem& code_item, CompilerDriver* driver, + CodeGenerator* code_generator, OptimizingCompilerStats* compiler_stats, const uint8_t* interpreter_metadata, Handle<mirror::DexCache> dex_cache, @@ -61,6 +64,7 @@ class HGraphBuilder : public ValueObject { dex_compilation_unit, outer_compilation_unit, driver, + code_generator, interpreter_metadata, compiler_stats, dex_cache, @@ -89,6 +93,7 @@ class HGraphBuilder : public ValueObject { /* dex_compilation_unit */ nullptr, /* outer_compilation_unit */ nullptr, /* compiler_driver */ nullptr, + /* code_generator */ nullptr, /* interpreter_metadata */ nullptr, /* compiler_stats */ nullptr, null_dex_cache_, diff --git a/compiler/optimizing/code_generator.cc b/compiler/optimizing/code_generator.cc index 99427f05da..d68aa51b1b 100644 --- a/compiler/optimizing/code_generator.cc +++ b/compiler/optimizing/code_generator.cc @@ -1417,4 +1417,22 @@ void CodeGenerator::EmitJitRoots(uint8_t* code, EmitJitRootPatches(code, roots_data); } +QuickEntrypointEnum CodeGenerator::GetArrayAllocationEntrypoint(Handle<mirror::Class> array_klass) { + ScopedObjectAccess soa(Thread::Current()); + if (array_klass.Get() == nullptr) { + // This can only happen for non-primitive arrays, as primitive arrays can always + // be resolved. + return kQuickAllocArrayResolved32; + } + + switch (array_klass->GetComponentSize()) { + case 1: return kQuickAllocArrayResolved8; + case 2: return kQuickAllocArrayResolved16; + case 4: return kQuickAllocArrayResolved32; + case 8: return kQuickAllocArrayResolved64; + } + LOG(FATAL) << "Unreachable"; + return kQuickAllocArrayResolved; +} + } // namespace art diff --git a/compiler/optimizing/code_generator.h b/compiler/optimizing/code_generator.h index 2d129aff22..b912672792 100644 --- a/compiler/optimizing/code_generator.h +++ b/compiler/optimizing/code_generator.h @@ -573,6 +573,8 @@ class CodeGenerator : public DeletableArenaObject<kArenaAllocCodeGenerator> { uint32_t GetReferenceSlowFlagOffset() const; uint32_t GetReferenceDisableFlagOffset() const; + static QuickEntrypointEnum GetArrayAllocationEntrypoint(Handle<mirror::Class> array_klass); + protected: // Patch info used for recording locations of required linker patches and their targets, // i.e. target method, string, type or code identified by their dex file and index. diff --git a/compiler/optimizing/code_generator_arm.cc b/compiler/optimizing/code_generator_arm.cc index f5b6ebef9c..20cdae3619 100644 --- a/compiler/optimizing/code_generator_arm.cc +++ b/compiler/optimizing/code_generator_arm.cc @@ -3993,8 +3993,11 @@ void LocationsBuilderARM::VisitNewArray(HNewArray* instruction) { void InstructionCodeGeneratorARM::VisitNewArray(HNewArray* instruction) { // Note: if heap poisoning is enabled, the entry point takes cares // of poisoning the reference. - codegen_->InvokeRuntime(kQuickAllocArrayResolved, instruction, instruction->GetDexPc()); + QuickEntrypointEnum entrypoint = + CodeGenerator::GetArrayAllocationEntrypoint(instruction->GetLoadClass()->GetClass()); + codegen_->InvokeRuntime(entrypoint, instruction, instruction->GetDexPc()); CheckEntrypointTypes<kQuickAllocArrayResolved, void*, mirror::Class*, int32_t>(); + DCHECK(!codegen_->IsLeafMethod()); } void LocationsBuilderARM::VisitParameterValue(HParameterValue* instruction) { @@ -5719,6 +5722,9 @@ void ParallelMoveResolverARM::RestoreScratch(int reg) { HLoadClass::LoadKind CodeGeneratorARM::GetSupportedLoadClassKind( HLoadClass::LoadKind desired_class_load_kind) { switch (desired_class_load_kind) { + case HLoadClass::LoadKind::kInvalid: + LOG(FATAL) << "UNREACHABLE"; + UNREACHABLE(); case HLoadClass::LoadKind::kReferrersClass: break; case HLoadClass::LoadKind::kBootImageLinkTimeAddress: @@ -5849,6 +5855,7 @@ void InstructionCodeGeneratorARM::VisitLoadClass(HLoadClass* cls) NO_THREAD_SAFE break; } case HLoadClass::LoadKind::kDexCacheViaMethod: + case HLoadClass::LoadKind::kInvalid: LOG(FATAL) << "UNREACHABLE"; UNREACHABLE(); } diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc index 9762ee81b1..598be4715b 100644 --- a/compiler/optimizing/code_generator_arm64.cc +++ b/compiler/optimizing/code_generator_arm64.cc @@ -1452,6 +1452,19 @@ static bool CoherentConstantAndType(Location constant, Primitive::Type type) { (cst->IsDoubleConstant() && type == Primitive::kPrimDouble); } +// Allocate a scratch register from the VIXL pool, querying first into +// the floating-point register pool, and then the the core register +// pool. This is essentially a reimplementation of +// vixl::aarch64::UseScratchRegisterScope::AcquireCPURegisterOfSize +// using a different allocation strategy. +static CPURegister AcquireFPOrCoreCPURegisterOfSize(vixl::aarch64::MacroAssembler* masm, + vixl::aarch64::UseScratchRegisterScope* temps, + int size_in_bits) { + return masm->GetScratchFPRegisterList()->IsEmpty() + ? CPURegister(temps->AcquireRegisterOfSize(size_in_bits)) + : CPURegister(temps->AcquireVRegisterOfSize(size_in_bits)); +} + void CodeGeneratorARM64::MoveLocation(Location destination, Location source, Primitive::Type dst_type) { @@ -1533,7 +1546,9 @@ void CodeGeneratorARM64::MoveLocation(Location destination, HConstant* src_cst = source.GetConstant(); CPURegister temp; if (src_cst->IsZeroBitPattern()) { - temp = (src_cst->IsLongConstant() || src_cst->IsDoubleConstant()) ? xzr : wzr; + temp = (src_cst->IsLongConstant() || src_cst->IsDoubleConstant()) + ? Register(xzr) + : Register(wzr); } else { if (src_cst->IsIntConstant()) { temp = temps.AcquireW(); @@ -1561,8 +1576,16 @@ void CodeGeneratorARM64::MoveLocation(Location destination, // a move is blocked by a another move requiring a scratch FP // register, which would reserve D31). To prevent this issue, we // ask for a scratch register of any type (core or FP). - CPURegister temp = - temps.AcquireCPURegisterOfSize(destination.IsDoubleStackSlot() ? kXRegSize : kWRegSize); + // + // Also, we start by asking for a FP scratch register first, as the + // demand of scratch core registers is higher. This is why we + // use AcquireFPOrCoreCPURegisterOfSize instead of + // UseScratchRegisterScope::AcquireCPURegisterOfSize, which + // allocates core scratch registers first. + CPURegister temp = AcquireFPOrCoreCPURegisterOfSize( + GetVIXLAssembler(), + &temps, + (destination.IsDoubleStackSlot() ? kXRegSize : kWRegSize)); __ Ldr(temp, StackOperandFrom(source)); __ Str(temp, StackOperandFrom(destination)); } @@ -1903,6 +1926,9 @@ void LocationsBuilderARM64::HandleFieldGet(HInstruction* instruction) { LocationSummary::kNoCall); if (object_field_get_with_read_barrier && kUseBakerReadBarrier) { locations->SetCustomSlowPathCallerSaves(RegisterSet::Empty()); // No caller-save registers. + // We need a temporary register for the read barrier marking slow + // path in CodeGeneratorARM64::GenerateFieldLoadWithBakerReadBarrier. + locations->AddTemp(Location::RequiresRegister()); } locations->SetInAt(0, Location::RequiresRegister()); if (Primitive::IsFloatingPointType(instruction->GetType())) { @@ -1930,11 +1956,9 @@ void InstructionCodeGeneratorARM64::HandleFieldGet(HInstruction* instruction, if (field_type == Primitive::kPrimNot && kEmitCompilerReadBarrier && kUseBakerReadBarrier) { // Object FieldGet with Baker's read barrier case. - MacroAssembler* masm = GetVIXLAssembler(); - UseScratchRegisterScope temps(masm); // /* HeapReference<Object> */ out = *(base + offset) Register base = RegisterFrom(base_loc, Primitive::kPrimNot); - Register temp = temps.AcquireW(); + Register temp = WRegisterFrom(locations->GetTemp(0)); // Note that potential implicit null checks are handled in this // CodeGeneratorARM64::GenerateFieldLoadWithBakerReadBarrier call. codegen_->GenerateFieldLoadWithBakerReadBarrier( @@ -4336,6 +4360,9 @@ void InstructionCodeGeneratorARM64::VisitInvokeVirtual(HInvokeVirtual* invoke) { HLoadClass::LoadKind CodeGeneratorARM64::GetSupportedLoadClassKind( HLoadClass::LoadKind desired_class_load_kind) { switch (desired_class_load_kind) { + case HLoadClass::LoadKind::kInvalid: + LOG(FATAL) << "UNREACHABLE"; + UNREACHABLE(); case HLoadClass::LoadKind::kReferrersClass: break; case HLoadClass::LoadKind::kBootImageLinkTimeAddress: @@ -4474,6 +4501,7 @@ void InstructionCodeGeneratorARM64::VisitLoadClass(HLoadClass* cls) NO_THREAD_SA break; } case HLoadClass::LoadKind::kDexCacheViaMethod: + case HLoadClass::LoadKind::kInvalid: LOG(FATAL) << "UNREACHABLE"; UNREACHABLE(); } @@ -4762,7 +4790,9 @@ void LocationsBuilderARM64::VisitNewArray(HNewArray* instruction) { void InstructionCodeGeneratorARM64::VisitNewArray(HNewArray* instruction) { // Note: if heap poisoning is enabled, the entry point takes cares // of poisoning the reference. - codegen_->InvokeRuntime(kQuickAllocArrayResolved, instruction, instruction->GetDexPc()); + QuickEntrypointEnum entrypoint = + CodeGenerator::GetArrayAllocationEntrypoint(instruction->GetLoadClass()->GetClass()); + codegen_->InvokeRuntime(entrypoint, instruction, instruction->GetDexPc()); CheckEntrypointTypes<kQuickAllocArrayResolved, void*, mirror::Class*, int32_t>(); } diff --git a/compiler/optimizing/code_generator_arm64.h b/compiler/optimizing/code_generator_arm64.h index 7d3c655b27..f6cb90a63a 100644 --- a/compiler/optimizing/code_generator_arm64.h +++ b/compiler/optimizing/code_generator_arm64.h @@ -210,12 +210,11 @@ class FieldAccessCallingConventionARM64 : public FieldAccessCallingConvention { Location GetReturnLocation(Primitive::Type type ATTRIBUTE_UNUSED) const OVERRIDE { return helpers::LocationFrom(vixl::aarch64::x0); } - Location GetSetValueLocation(Primitive::Type type, bool is_instance) const OVERRIDE { - return Primitive::Is64BitType(type) + Location GetSetValueLocation(Primitive::Type type ATTRIBUTE_UNUSED, + bool is_instance) const OVERRIDE { + return is_instance ? helpers::LocationFrom(vixl::aarch64::x2) - : (is_instance - ? helpers::LocationFrom(vixl::aarch64::x2) - : helpers::LocationFrom(vixl::aarch64::x1)); + : helpers::LocationFrom(vixl::aarch64::x1); } Location GetFpuLocation(Primitive::Type type ATTRIBUTE_UNUSED) const OVERRIDE { return helpers::LocationFrom(vixl::aarch64::d0); diff --git a/compiler/optimizing/code_generator_arm_vixl.cc b/compiler/optimizing/code_generator_arm_vixl.cc index ffaf18fb4d..e18960872e 100644 --- a/compiler/optimizing/code_generator_arm_vixl.cc +++ b/compiler/optimizing/code_generator_arm_vixl.cc @@ -3998,15 +3998,18 @@ void LocationsBuilderARMVIXL::VisitNewArray(HNewArray* instruction) { new (GetGraph()->GetArena()) LocationSummary(instruction, LocationSummary::kCallOnMainOnly); InvokeRuntimeCallingConventionARMVIXL calling_convention; locations->SetOut(LocationFrom(r0)); - locations->SetInAt(0, LocationFrom(calling_convention.GetRegisterAt(1))); - locations->SetInAt(1, LocationFrom(calling_convention.GetRegisterAt(2))); + locations->SetInAt(0, LocationFrom(calling_convention.GetRegisterAt(0))); + locations->SetInAt(1, LocationFrom(calling_convention.GetRegisterAt(1))); } void InstructionCodeGeneratorARMVIXL::VisitNewArray(HNewArray* instruction) { // Note: if heap poisoning is enabled, the entry point takes cares // of poisoning the reference. - codegen_->InvokeRuntime(kQuickAllocArrayResolved, instruction, instruction->GetDexPc()); + QuickEntrypointEnum entrypoint = + CodeGenerator::GetArrayAllocationEntrypoint(instruction->GetLoadClass()->GetClass()); + codegen_->InvokeRuntime(entrypoint, instruction, instruction->GetDexPc()); CheckEntrypointTypes<kQuickAllocArrayResolved, void*, mirror::Class*, int32_t>(); + DCHECK(!codegen_->IsLeafMethod()); } void LocationsBuilderARMVIXL::VisitParameterValue(HParameterValue* instruction) { @@ -5796,6 +5799,9 @@ void ParallelMoveResolverARMVIXL::RestoreScratch(int reg ATTRIBUTE_UNUSED) { HLoadClass::LoadKind CodeGeneratorARMVIXL::GetSupportedLoadClassKind( HLoadClass::LoadKind desired_class_load_kind) { switch (desired_class_load_kind) { + case HLoadClass::LoadKind::kInvalid: + LOG(FATAL) << "UNREACHABLE"; + UNREACHABLE(); case HLoadClass::LoadKind::kReferrersClass: break; case HLoadClass::LoadKind::kBootImageLinkTimeAddress: @@ -5916,6 +5922,7 @@ void InstructionCodeGeneratorARMVIXL::VisitLoadClass(HLoadClass* cls) NO_THREAD_ break; } case HLoadClass::LoadKind::kDexCacheViaMethod: + case HLoadClass::LoadKind::kInvalid: LOG(FATAL) << "UNREACHABLE"; UNREACHABLE(); } @@ -7253,8 +7260,7 @@ vixl32::Register CodeGeneratorARMVIXL::GetInvokeStaticOrDirectExtraParameter( // 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(RegisterFrom(location).GetCode())) { + if (slow_path != nullptr && slow_path->IsCoreRegisterSaved(RegisterFrom(location).GetCode())) { int stack_offset = slow_path->GetStackOffsetOfCoreRegister(RegisterFrom(location).GetCode()); GetAssembler()->LoadFromOffset(kLoadWord, temp, sp, stack_offset); return temp; @@ -7679,15 +7685,21 @@ void InstructionCodeGeneratorARMVIXL::VisitPackedSwitch(HPackedSwitch* switch_in vixl32::Register jump_offset = temps.Acquire(); // Load jump offset from the table. - __ Adr(table_base, jump_table->GetTableStartLabel()); - __ Ldr(jump_offset, MemOperand(table_base, key_reg, vixl32::LSL, 2)); + { + const size_t jump_size = switch_instr->GetNumEntries() * sizeof(int32_t); + ExactAssemblyScope aas(GetVIXLAssembler(), + (vixl32::kMaxInstructionSizeInBytes * 4) + jump_size, + CodeBufferCheckScope::kMaximumSize); + __ adr(table_base, jump_table->GetTableStartLabel()); + __ ldr(jump_offset, MemOperand(table_base, key_reg, vixl32::LSL, 2)); - // Jump to target block by branching to table_base(pc related) + offset. - vixl32::Register target_address = table_base; - __ Add(target_address, table_base, jump_offset); - __ Bx(target_address); + // Jump to target block by branching to table_base(pc related) + offset. + vixl32::Register target_address = table_base; + __ add(target_address, table_base, jump_offset); + __ bx(target_address); - jump_table->EmitTable(codegen_); + jump_table->EmitTable(codegen_); + } } } void LocationsBuilderARMVIXL::VisitArmDexCacheArraysBase(HArmDexCacheArraysBase* base) { diff --git a/compiler/optimizing/code_generator_mips.cc b/compiler/optimizing/code_generator_mips.cc index 76be74e921..0677dad078 100644 --- a/compiler/optimizing/code_generator_mips.cc +++ b/compiler/optimizing/code_generator_mips.cc @@ -258,8 +258,10 @@ class LoadClassSlowPathMIPS : public SlowPathCodeMIPS { DCHECK_NE(out.AsRegister<Register>(), AT); CodeGeneratorMIPS::PcRelativePatchInfo* info = mips_codegen->NewTypeBssEntryPatch(cls_->GetDexFile(), type_index); - mips_codegen->EmitPcRelativeAddressPlaceholder(info, TMP, base); - __ StoreToOffset(kStoreWord, out.AsRegister<Register>(), TMP, 0); + bool reordering = __ SetReorder(false); + mips_codegen->EmitPcRelativeAddressPlaceholderHigh(info, TMP, base); + __ StoreToOffset(kStoreWord, out.AsRegister<Register>(), TMP, /* placeholder */ 0x5678); + __ SetReorder(reordering); } __ B(GetExitLabel()); } @@ -313,8 +315,10 @@ class LoadStringSlowPathMIPS : public SlowPathCodeMIPS { DCHECK_NE(out, AT); CodeGeneratorMIPS::PcRelativePatchInfo* info = mips_codegen->NewPcRelativeStringPatch(load->GetDexFile(), string_index); - mips_codegen->EmitPcRelativeAddressPlaceholder(info, TMP, base); - __ StoreToOffset(kStoreWord, out, TMP, 0); + bool reordering = __ SetReorder(false); + mips_codegen->EmitPcRelativeAddressPlaceholderHigh(info, TMP, base); + __ StoreToOffset(kStoreWord, out, TMP, /* placeholder */ 0x5678); + __ SetReorder(reordering); __ B(GetExitLabel()); } @@ -480,6 +484,8 @@ CodeGeneratorMIPS::CodeGeneratorMIPS(HGraph* graph, type_bss_entry_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)), boot_image_address_patches_(std::less<uint32_t>(), graph->GetArena()->Adapter(kArenaAllocCodeGenerator)), + jit_string_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)), + jit_class_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)), clobbered_ra_(false) { // Save RA (containing the return address) to mimic Quick. AddAllocatedRegister(Location::RegisterLocation(RA)); @@ -700,9 +706,6 @@ bool CodeGeneratorMIPS::HasAllocatedCalleeSaveRegisters() const { // (this can happen in leaf methods), force CodeGenerator::InitializeCodeGeneration() // into the path that creates a stack frame so that RA can be explicitly saved and restored. // RA can't otherwise be saved/restored when it's the only spilled register. - // TODO: Can this be improved? It causes creation of a stack frame (while RA might be - // saved in an unused temporary register) and saving of RA and the current method pointer - // in the frame. return CodeGenerator::HasAllocatedCalleeSaveRegisters() || clobbered_ra_; } @@ -1127,16 +1130,15 @@ Literal* CodeGeneratorMIPS::DeduplicateBootImageAddressLiteral(uint32_t address) return DeduplicateUint32Literal(dchecked_integral_cast<uint32_t>(address), map); } -void CodeGeneratorMIPS::EmitPcRelativeAddressPlaceholder( - PcRelativePatchInfo* info, Register out, Register base) { - bool reordering = __ SetReorder(false); +void CodeGeneratorMIPS::EmitPcRelativeAddressPlaceholderHigh(PcRelativePatchInfo* info, + Register out, + Register base) { if (GetInstructionSetFeatures().IsR6()) { DCHECK_EQ(base, ZERO); __ Bind(&info->high_label); __ Bind(&info->pc_rel_label); - // Add a 32-bit offset to PC. + // Add the high half of a 32-bit offset to PC. __ Auipc(out, /* placeholder */ 0x1234); - __ Addiu(out, out, /* placeholder */ 0x5678); } else { // If base is ZERO, emit NAL to obtain the actual base. if (base == ZERO) { @@ -1150,11 +1152,72 @@ void CodeGeneratorMIPS::EmitPcRelativeAddressPlaceholder( if (base == ZERO) { __ Bind(&info->pc_rel_label); } - __ Ori(out, out, /* placeholder */ 0x5678); - // Add a 32-bit offset to PC. + // Add the high half of a 32-bit offset to PC. __ Addu(out, out, (base == ZERO) ? RA : base); } - __ SetReorder(reordering); + // The immediately following instruction will add the sign-extended low half of the 32-bit + // offset to `out` (e.g. lw, jialc, addiu). +} + +CodeGeneratorMIPS::JitPatchInfo* CodeGeneratorMIPS::NewJitRootStringPatch( + const DexFile& dex_file, + dex::StringIndex dex_index, + Handle<mirror::String> handle) { + jit_string_roots_.Overwrite(StringReference(&dex_file, dex_index), + reinterpret_cast64<uint64_t>(handle.GetReference())); + jit_string_patches_.emplace_back(dex_file, dex_index.index_); + return &jit_string_patches_.back(); +} + +CodeGeneratorMIPS::JitPatchInfo* CodeGeneratorMIPS::NewJitRootClassPatch( + const DexFile& dex_file, + dex::TypeIndex dex_index, + Handle<mirror::Class> handle) { + jit_class_roots_.Overwrite(TypeReference(&dex_file, dex_index), + reinterpret_cast64<uint64_t>(handle.GetReference())); + jit_class_patches_.emplace_back(dex_file, dex_index.index_); + return &jit_class_patches_.back(); +} + +void CodeGeneratorMIPS::PatchJitRootUse(uint8_t* code, + const uint8_t* roots_data, + const CodeGeneratorMIPS::JitPatchInfo& info, + uint64_t index_in_table) const { + uint32_t literal_offset = GetAssembler().GetLabelLocation(&info.high_label); + uintptr_t address = + reinterpret_cast<uintptr_t>(roots_data) + index_in_table * sizeof(GcRoot<mirror::Object>); + uint32_t addr32 = dchecked_integral_cast<uint32_t>(address); + // lui reg, addr32_high + DCHECK_EQ(code[literal_offset + 0], 0x34); + DCHECK_EQ(code[literal_offset + 1], 0x12); + DCHECK_EQ((code[literal_offset + 2] & 0xE0), 0x00); + DCHECK_EQ(code[literal_offset + 3], 0x3C); + // lw reg, reg, addr32_low + DCHECK_EQ(code[literal_offset + 4], 0x78); + DCHECK_EQ(code[literal_offset + 5], 0x56); + DCHECK_EQ((code[literal_offset + 7] & 0xFC), 0x8C); + addr32 += (addr32 & 0x8000) << 1; // Account for sign extension in "lw reg, reg, addr32_low". + // lui reg, addr32_high + code[literal_offset + 0] = static_cast<uint8_t>(addr32 >> 16); + code[literal_offset + 1] = static_cast<uint8_t>(addr32 >> 24); + // lw reg, reg, addr32_low + code[literal_offset + 4] = static_cast<uint8_t>(addr32 >> 0); + code[literal_offset + 5] = static_cast<uint8_t>(addr32 >> 8); +} + +void CodeGeneratorMIPS::EmitJitRootPatches(uint8_t* code, const uint8_t* roots_data) { + for (const JitPatchInfo& info : jit_string_patches_) { + const auto& it = jit_string_roots_.find(StringReference(&info.target_dex_file, + dex::StringIndex(info.index))); + DCHECK(it != jit_string_roots_.end()); + PatchJitRootUse(code, roots_data, info, it->second); + } + for (const JitPatchInfo& info : jit_class_patches_) { + const auto& it = jit_class_roots_.find(TypeReference(&info.target_dex_file, + dex::TypeIndex(info.index))); + DCHECK(it != jit_class_roots_.end()); + PatchJitRootUse(code, roots_data, info, it->second); + } } void CodeGeneratorMIPS::MarkGCCard(Register object, @@ -5159,7 +5222,8 @@ void LocationsBuilderMIPS::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invo // art::PrepareForRegisterAllocation. DCHECK(!invoke->IsStaticWithExplicitClinitCheck()); - bool has_extra_input = invoke->HasPcRelativeDexCache(); + bool is_r6 = codegen_->GetInstructionSetFeatures().IsR6(); + bool has_extra_input = invoke->HasPcRelativeDexCache() && !is_r6; IntrinsicLocationsBuilderMIPS intrinsic(codegen_); if (intrinsic.TryDispatch(invoke)) { @@ -5200,12 +5264,13 @@ HLoadString::LoadKind CodeGeneratorMIPS::GetSupportedLoadStringKind( if (kEmitCompilerReadBarrier) { UNIMPLEMENTED(FATAL) << "for read barrier"; } - // We disable PC-relative load when there is an irreducible loop, as the optimization + // We disable PC-relative load on pre-R6 when there is an irreducible loop, as the optimization // is incompatible with it. // TODO: Create as many MipsDexCacheArraysBase instructions as needed for methods // with irreducible loops. bool has_irreducible_loops = GetGraph()->HasIrreducibleLoops(); - bool fallback_load = has_irreducible_loops; + bool is_r6 = GetInstructionSetFeatures().IsR6(); + bool fallback_load = has_irreducible_loops && !is_r6; switch (desired_string_load_kind) { case HLoadString::LoadKind::kBootImageLinkTimeAddress: DCHECK(!GetCompilerOptions().GetCompilePic()); @@ -5220,8 +5285,7 @@ HLoadString::LoadKind CodeGeneratorMIPS::GetSupportedLoadStringKind( break; case HLoadString::LoadKind::kJitTableAddress: DCHECK(Runtime::Current()->UseJitCompilation()); - // TODO: implement. - fallback_load = true; + fallback_load = false; break; case HLoadString::LoadKind::kDexCacheViaMethod: fallback_load = false; @@ -5238,11 +5302,15 @@ HLoadClass::LoadKind CodeGeneratorMIPS::GetSupportedLoadClassKind( if (kEmitCompilerReadBarrier) { UNIMPLEMENTED(FATAL) << "for read barrier"; } - // We disable pc-relative load when there is an irreducible loop, as the optimization + // We disable PC-relative load on pre-R6 when there is an irreducible loop, as the optimization // is incompatible with it. bool has_irreducible_loops = GetGraph()->HasIrreducibleLoops(); - bool fallback_load = has_irreducible_loops; + bool is_r6 = GetInstructionSetFeatures().IsR6(); + bool fallback_load = has_irreducible_loops && !is_r6; switch (desired_class_load_kind) { + case HLoadClass::LoadKind::kInvalid: + LOG(FATAL) << "UNREACHABLE"; + UNREACHABLE(); case HLoadClass::LoadKind::kReferrersClass: fallback_load = false; break; @@ -5259,7 +5327,7 @@ HLoadClass::LoadKind CodeGeneratorMIPS::GetSupportedLoadClassKind( break; case HLoadClass::LoadKind::kJitTableAddress: DCHECK(Runtime::Current()->UseJitCompilation()); - fallback_load = true; + fallback_load = false; break; case HLoadClass::LoadKind::kDexCacheViaMethod: fallback_load = false; @@ -5273,6 +5341,7 @@ HLoadClass::LoadKind CodeGeneratorMIPS::GetSupportedLoadClassKind( Register CodeGeneratorMIPS::GetInvokeStaticOrDirectExtraParameter(HInvokeStaticOrDirect* invoke, Register temp) { + CHECK(!GetInstructionSetFeatures().IsR6()); CHECK_EQ(invoke->InputCount(), invoke->GetNumberOfArguments() + 1u); Location location = invoke->GetLocations()->InAt(invoke->GetSpecialInputIndex()); if (!invoke->GetLocations()->Intrinsified()) { @@ -5301,13 +5370,13 @@ HInvokeStaticOrDirect::DispatchInfo CodeGeneratorMIPS::GetSupportedInvokeStaticO const HInvokeStaticOrDirect::DispatchInfo& desired_dispatch_info, HInvokeStaticOrDirect* invoke ATTRIBUTE_UNUSED) { HInvokeStaticOrDirect::DispatchInfo dispatch_info = desired_dispatch_info; - // We disable PC-relative load when there is an irreducible loop, as the optimization + // We disable PC-relative load on pre-R6 when there is an irreducible loop, as the optimization // is incompatible with it. bool has_irreducible_loops = GetGraph()->HasIrreducibleLoops(); - bool fallback_load = true; + bool is_r6 = GetInstructionSetFeatures().IsR6(); + bool fallback_load = has_irreducible_loops && !is_r6; switch (dispatch_info.method_load_kind) { case HInvokeStaticOrDirect::MethodLoadKind::kDexCachePcRelative: - fallback_load = has_irreducible_loops; break; default: fallback_load = false; @@ -5325,7 +5394,8 @@ void CodeGeneratorMIPS::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke Location callee_method = temp; // For all kinds except kRecursive, callee will be in temp. HInvokeStaticOrDirect::MethodLoadKind method_load_kind = invoke->GetMethodLoadKind(); HInvokeStaticOrDirect::CodePtrLocation code_ptr_location = invoke->GetCodePtrLocation(); - Register base_reg = invoke->HasPcRelativeDexCache() + bool is_r6 = GetInstructionSetFeatures().IsR6(); + Register base_reg = (invoke->HasPcRelativeDexCache() && !is_r6) ? GetInvokeStaticOrDirectExtraParameter(invoke, temp.AsRegister<Register>()) : ZERO; @@ -5346,14 +5416,23 @@ void CodeGeneratorMIPS::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke case HInvokeStaticOrDirect::MethodLoadKind::kDirectAddress: __ LoadConst32(temp.AsRegister<Register>(), invoke->GetMethodAddress()); break; - case HInvokeStaticOrDirect::MethodLoadKind::kDexCachePcRelative: { - HMipsDexCacheArraysBase* base = - invoke->InputAt(invoke->GetSpecialInputIndex())->AsMipsDexCacheArraysBase(); - int32_t offset = - invoke->GetDexCacheArrayOffset() - base->GetElementOffset() - kDexCacheArrayLwOffset; - __ LoadFromOffset(kLoadWord, temp.AsRegister<Register>(), base_reg, offset); + case HInvokeStaticOrDirect::MethodLoadKind::kDexCachePcRelative: + if (is_r6) { + uint32_t offset = invoke->GetDexCacheArrayOffset(); + CodeGeneratorMIPS::PcRelativePatchInfo* info = + NewPcRelativeDexCacheArrayPatch(invoke->GetDexFileForPcRelativeDexCache(), offset); + bool reordering = __ SetReorder(false); + EmitPcRelativeAddressPlaceholderHigh(info, TMP, ZERO); + __ Lw(temp.AsRegister<Register>(), TMP, /* placeholder */ 0x5678); + __ SetReorder(reordering); + } else { + HMipsDexCacheArraysBase* base = + invoke->InputAt(invoke->GetSpecialInputIndex())->AsMipsDexCacheArraysBase(); + int32_t offset = + invoke->GetDexCacheArrayOffset() - base->GetElementOffset() - kDexCacheArrayLwOffset; + __ LoadFromOffset(kLoadWord, temp.AsRegister<Register>(), base_reg, offset); + } break; - } case HInvokeStaticOrDirect::MethodLoadKind::kDexCacheViaMethod: { Location current_method = invoke->GetLocations()->InAt(invoke->GetSpecialInputIndex()); Register reg = temp.AsRegister<Register>(); @@ -5546,7 +5625,10 @@ void InstructionCodeGeneratorMIPS::VisitLoadClass(HLoadClass* cls) NO_THREAD_SAF DCHECK(codegen_->GetCompilerOptions().IsBootImage()); CodeGeneratorMIPS::PcRelativePatchInfo* info = codegen_->NewPcRelativeTypePatch(cls->GetDexFile(), cls->GetTypeIndex()); - codegen_->EmitPcRelativeAddressPlaceholder(info, out, base_or_current_method_reg); + bool reordering = __ SetReorder(false); + codegen_->EmitPcRelativeAddressPlaceholderHigh(info, out, base_or_current_method_reg); + __ Addiu(out, out, /* placeholder */ 0x5678); + __ SetReorder(reordering); break; } case HLoadClass::LoadKind::kBootImageAddress: { @@ -5562,16 +5644,26 @@ void InstructionCodeGeneratorMIPS::VisitLoadClass(HLoadClass* cls) NO_THREAD_SAF case HLoadClass::LoadKind::kBssEntry: { CodeGeneratorMIPS::PcRelativePatchInfo* info = codegen_->NewTypeBssEntryPatch(cls->GetDexFile(), cls->GetTypeIndex()); - codegen_->EmitPcRelativeAddressPlaceholder(info, out, base_or_current_method_reg); - __ LoadFromOffset(kLoadWord, out, out, 0); + bool reordering = __ SetReorder(false); + codegen_->EmitPcRelativeAddressPlaceholderHigh(info, out, base_or_current_method_reg); + __ LoadFromOffset(kLoadWord, out, out, /* placeholder */ 0x5678); + __ SetReorder(reordering); generate_null_check = true; break; } case HLoadClass::LoadKind::kJitTableAddress: { - LOG(FATAL) << "Unimplemented"; + CodeGeneratorMIPS::JitPatchInfo* info = codegen_->NewJitRootClassPatch(cls->GetDexFile(), + cls->GetTypeIndex(), + cls->GetClass()); + bool reordering = __ SetReorder(false); + __ Bind(&info->high_label); + __ Lui(out, /* placeholder */ 0x1234); + GenerateGcRootFieldLoad(cls, out_loc, out, /* placeholder */ 0x5678); + __ SetReorder(reordering); break; } case HLoadClass::LoadKind::kDexCacheViaMethod: + case HLoadClass::LoadKind::kInvalid: LOG(FATAL) << "UNREACHABLE"; UNREACHABLE(); } @@ -5678,7 +5770,10 @@ void InstructionCodeGeneratorMIPS::VisitLoadString(HLoadString* load) NO_THREAD_ DCHECK(codegen_->GetCompilerOptions().IsBootImage()); CodeGeneratorMIPS::PcRelativePatchInfo* info = codegen_->NewPcRelativeStringPatch(load->GetDexFile(), load->GetStringIndex()); - codegen_->EmitPcRelativeAddressPlaceholder(info, out, base_or_current_method_reg); + bool reordering = __ SetReorder(false); + codegen_->EmitPcRelativeAddressPlaceholderHigh(info, out, base_or_current_method_reg); + __ Addiu(out, out, /* placeholder */ 0x5678); + __ SetReorder(reordering); return; // No dex cache slow path. } case HLoadString::LoadKind::kBootImageAddress: { @@ -5694,14 +5789,28 @@ void InstructionCodeGeneratorMIPS::VisitLoadString(HLoadString* load) NO_THREAD_ DCHECK(!codegen_->GetCompilerOptions().IsBootImage()); CodeGeneratorMIPS::PcRelativePatchInfo* info = codegen_->NewPcRelativeStringPatch(load->GetDexFile(), load->GetStringIndex()); - codegen_->EmitPcRelativeAddressPlaceholder(info, out, base_or_current_method_reg); - __ LoadFromOffset(kLoadWord, out, out, 0); + bool reordering = __ SetReorder(false); + codegen_->EmitPcRelativeAddressPlaceholderHigh(info, out, base_or_current_method_reg); + __ LoadFromOffset(kLoadWord, out, out, /* placeholder */ 0x5678); + __ SetReorder(reordering); SlowPathCodeMIPS* slow_path = new (GetGraph()->GetArena()) LoadStringSlowPathMIPS(load); codegen_->AddSlowPath(slow_path); __ Beqz(out, slow_path->GetEntryLabel()); __ Bind(slow_path->GetExitLabel()); return; } + case HLoadString::LoadKind::kJitTableAddress: { + CodeGeneratorMIPS::JitPatchInfo* info = + codegen_->NewJitRootStringPatch(load->GetDexFile(), + load->GetStringIndex(), + load->GetString()); + bool reordering = __ SetReorder(false); + __ Bind(&info->high_label); + __ Lui(out, /* placeholder */ 0x1234); + GenerateGcRootFieldLoad(load, out_loc, out, /* placeholder */ 0x5678); + __ SetReorder(reordering); + return; + } default: break; } @@ -6894,8 +7003,12 @@ void InstructionCodeGeneratorMIPS::VisitMipsDexCacheArraysBase(HMipsDexCacheArra Register reg = base->GetLocations()->Out().AsRegister<Register>(); CodeGeneratorMIPS::PcRelativePatchInfo* info = codegen_->NewPcRelativeDexCacheArrayPatch(base->GetDexFile(), base->GetElementOffset()); + CHECK(!codegen_->GetInstructionSetFeatures().IsR6()); + bool reordering = __ SetReorder(false); // TODO: Reuse MipsComputeBaseMethodAddress on R2 instead of passing ZERO to force emitting NAL. - codegen_->EmitPcRelativeAddressPlaceholder(info, reg, ZERO); + codegen_->EmitPcRelativeAddressPlaceholderHigh(info, reg, ZERO); + __ Addiu(reg, reg, /* placeholder */ 0x5678); + __ SetReorder(reordering); } void LocationsBuilderMIPS::VisitInvokeUnresolved(HInvokeUnresolved* invoke) { diff --git a/compiler/optimizing/code_generator_mips.h b/compiler/optimizing/code_generator_mips.h index c8fd325999..47eba50248 100644 --- a/compiler/optimizing/code_generator_mips.h +++ b/compiler/optimizing/code_generator_mips.h @@ -352,6 +352,7 @@ class CodeGeneratorMIPS : public CodeGenerator { // Emit linker patches. void EmitLinkerPatches(ArenaVector<LinkerPatch>* linker_patches) OVERRIDE; + void EmitJitRootPatches(uint8_t* code, const uint8_t* roots_data) OVERRIDE; void MarkGCCard(Register object, Register value, bool value_can_be_null); @@ -463,7 +464,32 @@ class CodeGeneratorMIPS : public CodeGenerator { Literal* DeduplicateBootImageTypeLiteral(const DexFile& dex_file, dex::TypeIndex type_index); Literal* DeduplicateBootImageAddressLiteral(uint32_t address); - void EmitPcRelativeAddressPlaceholder(PcRelativePatchInfo* info, Register out, Register base); + void EmitPcRelativeAddressPlaceholderHigh(PcRelativePatchInfo* info, Register out, Register base); + + // The JitPatchInfo is used for JIT string and class loads. + struct JitPatchInfo { + JitPatchInfo(const DexFile& dex_file, uint64_t idx) + : target_dex_file(dex_file), index(idx) { } + JitPatchInfo(JitPatchInfo&& other) = default; + + const DexFile& target_dex_file; + // String/type index. + uint64_t index; + // Label for the instruction loading the most significant half of the address. + // The least significant half is loaded with the instruction that follows immediately. + MipsLabel high_label; + }; + + void PatchJitRootUse(uint8_t* code, + const uint8_t* roots_data, + const JitPatchInfo& info, + uint64_t index_in_table) const; + JitPatchInfo* NewJitRootStringPatch(const DexFile& dex_file, + dex::StringIndex dex_index, + Handle<mirror::String> handle); + JitPatchInfo* NewJitRootClassPatch(const DexFile& dex_file, + dex::TypeIndex dex_index, + Handle<mirror::Class> handle); private: Register GetInvokeStaticOrDirectExtraParameter(HInvokeStaticOrDirect* invoke, Register temp); @@ -512,6 +538,10 @@ class CodeGeneratorMIPS : public CodeGenerator { ArenaDeque<PcRelativePatchInfo> type_bss_entry_patches_; // Deduplication map for patchable boot image addresses. Uint32ToLiteralMap boot_image_address_patches_; + // Patches for string root accesses in JIT compiled code. + ArenaDeque<JitPatchInfo> jit_string_patches_; + // Patches for class root accesses in JIT compiled code. + ArenaDeque<JitPatchInfo> jit_class_patches_; // PC-relative loads on R2 clobber RA, which may need to be preserved explicitly in leaf methods. // This is a flag set by pc_relative_fixups_mips and dex_cache_array_fixups_mips optimizations. diff --git a/compiler/optimizing/code_generator_mips64.cc b/compiler/optimizing/code_generator_mips64.cc index 192b4a5050..4c8dabfede 100644 --- a/compiler/optimizing/code_generator_mips64.cc +++ b/compiler/optimizing/code_generator_mips64.cc @@ -91,9 +91,6 @@ Location InvokeDexCallingConventionVisitorMIPS64::GetNextLocation(Primitive::Typ // Space on the stack is reserved for all arguments. stack_index_ += Primitive::Is64BitType(type) ? 2 : 1; - // TODO: shouldn't we use a whole machine word per argument on the stack? - // Implicit 4-byte method pointer (and such) will cause misalignment. - return next_location; } @@ -434,7 +431,11 @@ CodeGeneratorMIPS64::CodeGeneratorMIPS64(HGraph* graph, pc_relative_type_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)), type_bss_entry_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)), boot_image_address_patches_(std::less<uint32_t>(), - graph->GetArena()->Adapter(kArenaAllocCodeGenerator)) { + graph->GetArena()->Adapter(kArenaAllocCodeGenerator)), + jit_string_patches_(StringReferenceValueComparator(), + graph->GetArena()->Adapter(kArenaAllocCodeGenerator)), + jit_class_patches_(TypeReferenceValueComparator(), + graph->GetArena()->Adapter(kArenaAllocCodeGenerator)) { // Save RA (containing the return address) to mimic Quick. AddAllocatedRegister(Location::RegisterLocation(RA)); } @@ -1055,6 +1056,49 @@ void CodeGeneratorMIPS64::EmitPcRelativeAddressPlaceholderHigh(PcRelativePatchIn // offset to `out` (e.g. ld, jialc, daddiu). } +Literal* CodeGeneratorMIPS64::DeduplicateJitStringLiteral(const DexFile& dex_file, + dex::StringIndex string_index, + Handle<mirror::String> handle) { + jit_string_roots_.Overwrite(StringReference(&dex_file, string_index), + reinterpret_cast64<uint64_t>(handle.GetReference())); + return jit_string_patches_.GetOrCreate( + StringReference(&dex_file, string_index), + [this]() { return __ NewLiteral<uint32_t>(/* placeholder */ 0u); }); +} + +Literal* CodeGeneratorMIPS64::DeduplicateJitClassLiteral(const DexFile& dex_file, + dex::TypeIndex type_index, + Handle<mirror::Class> handle) { + jit_class_roots_.Overwrite(TypeReference(&dex_file, type_index), + reinterpret_cast64<uint64_t>(handle.GetReference())); + return jit_class_patches_.GetOrCreate( + TypeReference(&dex_file, type_index), + [this]() { return __ NewLiteral<uint32_t>(/* placeholder */ 0u); }); +} + +void CodeGeneratorMIPS64::PatchJitRootUse(uint8_t* code, + const uint8_t* roots_data, + const Literal* literal, + uint64_t index_in_table) const { + uint32_t literal_offset = GetAssembler().GetLabelLocation(literal->GetLabel()); + uintptr_t address = + reinterpret_cast<uintptr_t>(roots_data) + index_in_table * sizeof(GcRoot<mirror::Object>); + reinterpret_cast<uint32_t*>(code + literal_offset)[0] = dchecked_integral_cast<uint32_t>(address); +} + +void CodeGeneratorMIPS64::EmitJitRootPatches(uint8_t* code, const uint8_t* roots_data) { + for (const auto& entry : jit_string_patches_) { + const auto& it = jit_string_roots_.find(entry.first); + DCHECK(it != jit_string_roots_.end()); + PatchJitRootUse(code, roots_data, entry.second, it->second); + } + for (const auto& entry : jit_class_patches_) { + const auto& it = jit_class_roots_.find(entry.first); + DCHECK(it != jit_class_roots_.end()); + PatchJitRootUse(code, roots_data, entry.second, it->second); + } +} + void CodeGeneratorMIPS64::SetupBlockedRegisters() const { // ZERO, K0, K1, GP, SP, RA are always reserved and can't be allocated. blocked_core_registers_[ZERO] = true; @@ -3117,14 +3161,6 @@ void InstructionCodeGeneratorMIPS64::GenerateGcRootFieldLoad( Location root, GpuRegister obj, uint32_t offset) { - // When handling PC-relative loads, the caller calls - // EmitPcRelativeAddressPlaceholderHigh() and then GenerateGcRootFieldLoad(). - // The relative patcher expects the two methods to emit the following patchable - // sequence of instructions in this case: - // auipc reg1, 0x1234 // 0x1234 is a placeholder for offset_high. - // lwu reg2, 0x5678(reg1) // 0x5678 is a placeholder for offset_low. - // TODO: Adjust GenerateGcRootFieldLoad() and its caller when this method is - // extended (e.g. for read barriers) so as not to break the relative patcher. GpuRegister root_reg = root.AsRegister<GpuRegister>(); if (kEmitCompilerReadBarrier) { UNIMPLEMENTED(FATAL) << "for read barrier"; @@ -3317,8 +3353,6 @@ HLoadString::LoadKind CodeGeneratorMIPS64::GetSupportedLoadStringKind( break; case HLoadString::LoadKind::kJitTableAddress: DCHECK(Runtime::Current()->UseJitCompilation()); - // TODO: implement. - fallback_load = true; break; } if (fallback_load) { @@ -3334,6 +3368,9 @@ HLoadClass::LoadKind CodeGeneratorMIPS64::GetSupportedLoadClassKind( } bool fallback_load = false; switch (desired_class_load_kind) { + case HLoadClass::LoadKind::kInvalid: + LOG(FATAL) << "UNREACHABLE"; + UNREACHABLE(); case HLoadClass::LoadKind::kReferrersClass: break; case HLoadClass::LoadKind::kBootImageLinkTimeAddress: @@ -3349,8 +3386,6 @@ HLoadClass::LoadKind CodeGeneratorMIPS64::GetSupportedLoadClassKind( break; case HLoadClass::LoadKind::kJitTableAddress: DCHECK(Runtime::Current()->UseJitCompilation()); - // TODO: implement. - fallback_load = true; break; case HLoadClass::LoadKind::kDexCacheViaMethod: break; @@ -3588,11 +3623,16 @@ void InstructionCodeGeneratorMIPS64::VisitLoadClass(HLoadClass* cls) NO_THREAD_S generate_null_check = true; break; } - case HLoadClass::LoadKind::kJitTableAddress: { - LOG(FATAL) << "Unimplemented"; + case HLoadClass::LoadKind::kJitTableAddress: + __ LoadLiteral(out, + kLoadUnsignedWord, + codegen_->DeduplicateJitClassLiteral(cls->GetDexFile(), + cls->GetTypeIndex(), + cls->GetClass())); + GenerateGcRootFieldLoad(cls, out_loc, out, 0); break; - } case HLoadClass::LoadKind::kDexCacheViaMethod: + case HLoadClass::LoadKind::kInvalid: LOG(FATAL) << "UNREACHABLE"; UNREACHABLE(); } @@ -3693,6 +3733,14 @@ void InstructionCodeGeneratorMIPS64::VisitLoadString(HLoadString* load) NO_THREA __ Bind(slow_path->GetExitLabel()); return; } + case HLoadString::LoadKind::kJitTableAddress: + __ LoadLiteral(out, + kLoadUnsignedWord, + codegen_->DeduplicateJitStringLiteral(load->GetDexFile(), + load->GetStringIndex(), + load->GetString())); + GenerateGcRootFieldLoad(load, out_loc, out, 0); + return; default: break; } diff --git a/compiler/optimizing/code_generator_mips64.h b/compiler/optimizing/code_generator_mips64.h index 52b780c106..26cc7dc788 100644 --- a/compiler/optimizing/code_generator_mips64.h +++ b/compiler/optimizing/code_generator_mips64.h @@ -52,7 +52,7 @@ static constexpr size_t kRuntimeParameterFpuRegistersLength = static constexpr GpuRegister kCoreCalleeSaves[] = - { S0, S1, S2, S3, S4, S5, S6, S7, GP, S8, RA }; // TODO: review + { S0, S1, S2, S3, S4, S5, S6, S7, GP, S8, RA }; static constexpr FpuRegister kFpuCalleeSaves[] = { F24, F25, F26, F27, F28, F29, F30, F31 }; @@ -115,12 +115,11 @@ class FieldAccessCallingConventionMIPS64 : public FieldAccessCallingConvention { Location GetReturnLocation(Primitive::Type type ATTRIBUTE_UNUSED) const OVERRIDE { return Location::RegisterLocation(V0); } - Location GetSetValueLocation(Primitive::Type type, bool is_instance) const OVERRIDE { - return Primitive::Is64BitType(type) + Location GetSetValueLocation(Primitive::Type type ATTRIBUTE_UNUSED, + bool is_instance) const OVERRIDE { + return is_instance ? Location::RegisterLocation(A2) - : (is_instance - ? Location::RegisterLocation(A2) - : Location::RegisterLocation(A1)); + : Location::RegisterLocation(A1); } Location GetFpuLocation(Primitive::Type type ATTRIBUTE_UNUSED) const OVERRIDE { return Location::FpuRegisterLocation(F0); @@ -313,6 +312,7 @@ class CodeGeneratorMIPS64 : public CodeGenerator { // Emit linker patches. void EmitLinkerPatches(ArenaVector<LinkerPatch>* linker_patches) OVERRIDE; + void EmitJitRootPatches(uint8_t* code, const uint8_t* roots_data) OVERRIDE; void MarkGCCard(GpuRegister object, GpuRegister value, bool value_can_be_null); @@ -426,10 +426,27 @@ class CodeGeneratorMIPS64 : public CodeGenerator { void EmitPcRelativeAddressPlaceholderHigh(PcRelativePatchInfo* info, GpuRegister out); + void PatchJitRootUse(uint8_t* code, + const uint8_t* roots_data, + const Literal* literal, + uint64_t index_in_table) const; + Literal* DeduplicateJitStringLiteral(const DexFile& dex_file, + dex::StringIndex string_index, + Handle<mirror::String> handle); + Literal* DeduplicateJitClassLiteral(const DexFile& dex_file, + dex::TypeIndex type_index, + Handle<mirror::Class> handle); + private: using Uint32ToLiteralMap = ArenaSafeMap<uint32_t, Literal*>; using Uint64ToLiteralMap = ArenaSafeMap<uint64_t, Literal*>; using MethodToLiteralMap = ArenaSafeMap<MethodReference, Literal*, MethodReferenceComparator>; + using StringToLiteralMap = ArenaSafeMap<StringReference, + Literal*, + StringReferenceValueComparator>; + using TypeToLiteralMap = ArenaSafeMap<TypeReference, + Literal*, + TypeReferenceValueComparator>; using BootStringToLiteralMap = ArenaSafeMap<StringReference, Literal*, StringReferenceValueComparator>; @@ -477,6 +494,10 @@ class CodeGeneratorMIPS64 : public CodeGenerator { ArenaDeque<PcRelativePatchInfo> type_bss_entry_patches_; // Deduplication map for patchable boot image addresses. Uint32ToLiteralMap boot_image_address_patches_; + // Patches for string root accesses in JIT compiled code. + StringToLiteralMap jit_string_patches_; + // Patches for class root accesses in JIT compiled code. + TypeToLiteralMap jit_class_patches_; DISALLOW_COPY_AND_ASSIGN(CodeGeneratorMIPS64); }; diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc index 1b7431612d..137b55423b 100644 --- a/compiler/optimizing/code_generator_x86.cc +++ b/compiler/optimizing/code_generator_x86.cc @@ -4214,7 +4214,9 @@ void LocationsBuilderX86::VisitNewArray(HNewArray* instruction) { void InstructionCodeGeneratorX86::VisitNewArray(HNewArray* instruction) { // Note: if heap poisoning is enabled, the entry point takes cares // of poisoning the reference. - codegen_->InvokeRuntime(kQuickAllocArrayResolved, instruction, instruction->GetDexPc()); + QuickEntrypointEnum entrypoint = + CodeGenerator::GetArrayAllocationEntrypoint(instruction->GetLoadClass()->GetClass()); + codegen_->InvokeRuntime(entrypoint, instruction, instruction->GetDexPc()); CheckEntrypointTypes<kQuickAllocArrayResolved, void*, mirror::Class*, int32_t>(); DCHECK(!codegen_->IsLeafMethod()); } @@ -6022,6 +6024,9 @@ void ParallelMoveResolverX86::RestoreScratch(int reg) { HLoadClass::LoadKind CodeGeneratorX86::GetSupportedLoadClassKind( HLoadClass::LoadKind desired_class_load_kind) { switch (desired_class_load_kind) { + case HLoadClass::LoadKind::kInvalid: + LOG(FATAL) << "UNREACHABLE"; + UNREACHABLE(); case HLoadClass::LoadKind::kReferrersClass: break; case HLoadClass::LoadKind::kBootImageLinkTimeAddress: @@ -6157,6 +6162,7 @@ void InstructionCodeGeneratorX86::VisitLoadClass(HLoadClass* cls) NO_THREAD_SAFE break; } case HLoadClass::LoadKind::kDexCacheViaMethod: + case HLoadClass::LoadKind::kInvalid: LOG(FATAL) << "UNREACHABLE"; UNREACHABLE(); } diff --git a/compiler/optimizing/code_generator_x86.h b/compiler/optimizing/code_generator_x86.h index 7350fcc48a..5360dc9209 100644 --- a/compiler/optimizing/code_generator_x86.h +++ b/compiler/optimizing/code_generator_x86.h @@ -110,7 +110,9 @@ class FieldAccessCallingConventionX86 : public FieldAccessCallingConvention { } Location GetSetValueLocation(Primitive::Type type, bool is_instance) const OVERRIDE { return Primitive::Is64BitType(type) - ? Location::RegisterPairLocation(EDX, EBX) + ? (is_instance + ? Location::RegisterPairLocation(EDX, EBX) + : Location::RegisterPairLocation(ECX, EDX)) : (is_instance ? Location::RegisterLocation(EDX) : Location::RegisterLocation(ECX)); diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc index c4caf4bf9d..c5367ce86e 100644 --- a/compiler/optimizing/code_generator_x86_64.cc +++ b/compiler/optimizing/code_generator_x86_64.cc @@ -4096,7 +4096,9 @@ void LocationsBuilderX86_64::VisitNewArray(HNewArray* instruction) { void InstructionCodeGeneratorX86_64::VisitNewArray(HNewArray* instruction) { // Note: if heap poisoning is enabled, the entry point takes cares // of poisoning the reference. - codegen_->InvokeRuntime(kQuickAllocArrayResolved, instruction, instruction->GetDexPc()); + QuickEntrypointEnum entrypoint = + CodeGenerator::GetArrayAllocationEntrypoint(instruction->GetLoadClass()->GetClass()); + codegen_->InvokeRuntime(entrypoint, instruction, instruction->GetDexPc()); CheckEntrypointTypes<kQuickAllocArrayResolved, void*, mirror::Class*, int32_t>(); DCHECK(!codegen_->IsLeafMethod()); } @@ -5425,6 +5427,9 @@ void InstructionCodeGeneratorX86_64::GenerateClassInitializationCheck( HLoadClass::LoadKind CodeGeneratorX86_64::GetSupportedLoadClassKind( HLoadClass::LoadKind desired_class_load_kind) { switch (desired_class_load_kind) { + case HLoadClass::LoadKind::kInvalid: + LOG(FATAL) << "UNREACHABLE"; + UNREACHABLE(); case HLoadClass::LoadKind::kReferrersClass: break; case HLoadClass::LoadKind::kBootImageLinkTimeAddress: diff --git a/compiler/optimizing/code_generator_x86_64.h b/compiler/optimizing/code_generator_x86_64.h index 3438b8159f..3a83731b3f 100644 --- a/compiler/optimizing/code_generator_x86_64.h +++ b/compiler/optimizing/code_generator_x86_64.h @@ -92,12 +92,11 @@ class FieldAccessCallingConventionX86_64 : public FieldAccessCallingConvention { Location GetReturnLocation(Primitive::Type type ATTRIBUTE_UNUSED) const OVERRIDE { return Location::RegisterLocation(RAX); } - Location GetSetValueLocation(Primitive::Type type, bool is_instance) const OVERRIDE { - return Primitive::Is64BitType(type) + Location GetSetValueLocation(Primitive::Type type ATTRIBUTE_UNUSED, bool is_instance) + const OVERRIDE { + return is_instance ? Location::RegisterLocation(RDX) - : (is_instance - ? Location::RegisterLocation(RDX) - : Location::RegisterLocation(RSI)); + : Location::RegisterLocation(RSI); } Location GetFpuLocation(Primitive::Type type ATTRIBUTE_UNUSED) const OVERRIDE { return Location::FpuRegisterLocation(XMM0); diff --git a/compiler/optimizing/codegen_test.cc b/compiler/optimizing/codegen_test.cc index e3f3df0ff5..f8bbf68c1c 100644 --- a/compiler/optimizing/codegen_test.cc +++ b/compiler/optimizing/codegen_test.cc @@ -17,30 +17,15 @@ #include <functional> #include <memory> -#include "arch/instruction_set.h" -#include "arch/arm/instruction_set_features_arm.h" -#include "arch/arm/registers_arm.h" -#include "arch/arm64/instruction_set_features_arm64.h" -#include "arch/mips/instruction_set_features_mips.h" -#include "arch/mips/registers_mips.h" -#include "arch/mips64/instruction_set_features_mips64.h" -#include "arch/mips64/registers_mips64.h" -#include "arch/x86/instruction_set_features_x86.h" -#include "arch/x86/registers_x86.h" -#include "arch/x86_64/instruction_set_features_x86_64.h" #include "base/macros.h" #include "builder.h" -#include "code_simulator_container.h" -#include "common_compiler_test.h" +#include "codegen_test_utils.h" #include "dex_file.h" #include "dex_instruction.h" #include "driver/compiler_options.h" -#include "graph_checker.h" #include "nodes.h" #include "optimizing_unit_test.h" -#include "prepare_for_register_allocation.h" #include "register_allocator_linear_scan.h" -#include "ssa_liveness_analysis.h" #include "utils.h" #include "utils/arm/assembler_arm_vixl.h" #include "utils/arm/managed_register_arm.h" @@ -48,324 +33,10 @@ #include "utils/mips64/managed_register_mips64.h" #include "utils/x86/managed_register_x86.h" -#ifdef ART_ENABLE_CODEGEN_arm -#include "code_generator_arm.h" -#include "code_generator_arm_vixl.h" -#endif - -#ifdef ART_ENABLE_CODEGEN_arm64 -#include "code_generator_arm64.h" -#endif - -#ifdef ART_ENABLE_CODEGEN_x86 -#include "code_generator_x86.h" -#endif - -#ifdef ART_ENABLE_CODEGEN_x86_64 -#include "code_generator_x86_64.h" -#endif - -#ifdef ART_ENABLE_CODEGEN_mips -#include "code_generator_mips.h" -#endif - -#ifdef ART_ENABLE_CODEGEN_mips64 -#include "code_generator_mips64.h" -#endif - #include "gtest/gtest.h" namespace art { -typedef CodeGenerator* (*CreateCodegenFn)(HGraph*, const CompilerOptions&); - -class CodegenTargetConfig { - public: - CodegenTargetConfig(InstructionSet isa, CreateCodegenFn create_codegen) - : isa_(isa), create_codegen_(create_codegen) { - } - InstructionSet GetInstructionSet() const { return isa_; } - CodeGenerator* CreateCodeGenerator(HGraph* graph, const CompilerOptions& compiler_options) { - return create_codegen_(graph, compiler_options); - } - - private: - CodegenTargetConfig() {} - InstructionSet isa_; - CreateCodegenFn create_codegen_; -}; - -#ifdef ART_ENABLE_CODEGEN_arm -// Provide our own codegen, that ensures the C calling conventions -// are preserved. Currently, ART and C do not match as R4 is caller-save -// in ART, and callee-save in C. Alternatively, we could use or write -// the stub that saves and restores all registers, but it is easier -// to just overwrite the code generator. -class TestCodeGeneratorARM : public arm::CodeGeneratorARM { - public: - TestCodeGeneratorARM(HGraph* graph, - const ArmInstructionSetFeatures& isa_features, - const CompilerOptions& compiler_options) - : arm::CodeGeneratorARM(graph, isa_features, compiler_options) { - AddAllocatedRegister(Location::RegisterLocation(arm::R6)); - AddAllocatedRegister(Location::RegisterLocation(arm::R7)); - } - - void SetupBlockedRegisters() const OVERRIDE { - arm::CodeGeneratorARM::SetupBlockedRegisters(); - blocked_core_registers_[arm::R4] = true; - blocked_core_registers_[arm::R6] = false; - blocked_core_registers_[arm::R7] = false; - } -}; - -// A way to test the VIXL32-based code generator on ARM. This will replace -// TestCodeGeneratorARM when the VIXL32-based backend replaces the existing one. -class TestCodeGeneratorARMVIXL : public arm::CodeGeneratorARMVIXL { - public: - TestCodeGeneratorARMVIXL(HGraph* graph, - const ArmInstructionSetFeatures& isa_features, - const CompilerOptions& compiler_options) - : arm::CodeGeneratorARMVIXL(graph, isa_features, compiler_options) { - AddAllocatedRegister(Location::RegisterLocation(arm::R6)); - AddAllocatedRegister(Location::RegisterLocation(arm::R7)); - } - - void SetupBlockedRegisters() const OVERRIDE { - arm::CodeGeneratorARMVIXL::SetupBlockedRegisters(); - blocked_core_registers_[arm::R4] = true; - blocked_core_registers_[arm::R6] = false; - blocked_core_registers_[arm::R7] = false; - } -}; -#endif - -#ifdef ART_ENABLE_CODEGEN_x86 -class TestCodeGeneratorX86 : public x86::CodeGeneratorX86 { - public: - TestCodeGeneratorX86(HGraph* graph, - const X86InstructionSetFeatures& isa_features, - const CompilerOptions& compiler_options) - : x86::CodeGeneratorX86(graph, isa_features, compiler_options) { - // Save edi, we need it for getting enough registers for long multiplication. - AddAllocatedRegister(Location::RegisterLocation(x86::EDI)); - } - - void SetupBlockedRegisters() const OVERRIDE { - x86::CodeGeneratorX86::SetupBlockedRegisters(); - // ebx is a callee-save register in C, but caller-save for ART. - blocked_core_registers_[x86::EBX] = true; - - // Make edi available. - blocked_core_registers_[x86::EDI] = false; - } -}; -#endif - -class InternalCodeAllocator : public CodeAllocator { - public: - InternalCodeAllocator() : size_(0) { } - - virtual uint8_t* Allocate(size_t size) { - size_ = size; - memory_.reset(new uint8_t[size]); - return memory_.get(); - } - - size_t GetSize() const { return size_; } - uint8_t* GetMemory() const { return memory_.get(); } - - private: - size_t size_; - std::unique_ptr<uint8_t[]> memory_; - - DISALLOW_COPY_AND_ASSIGN(InternalCodeAllocator); -}; - -static bool CanExecuteOnHardware(InstructionSet target_isa) { - return (target_isa == kRuntimeISA) - // Handle the special case of ARM, with two instructions sets (ARM32 and Thumb-2). - || (kRuntimeISA == kArm && target_isa == kThumb2); -} - -static bool CanExecute(InstructionSet target_isa) { - CodeSimulatorContainer simulator(target_isa); - return CanExecuteOnHardware(target_isa) || simulator.CanSimulate(); -} - -template <typename Expected> -static Expected SimulatorExecute(CodeSimulator* simulator, Expected (*f)()); - -template <> -bool SimulatorExecute<bool>(CodeSimulator* simulator, bool (*f)()) { - simulator->RunFrom(reinterpret_cast<intptr_t>(f)); - return simulator->GetCReturnBool(); -} - -template <> -int32_t SimulatorExecute<int32_t>(CodeSimulator* simulator, int32_t (*f)()) { - simulator->RunFrom(reinterpret_cast<intptr_t>(f)); - return simulator->GetCReturnInt32(); -} - -template <> -int64_t SimulatorExecute<int64_t>(CodeSimulator* simulator, int64_t (*f)()) { - simulator->RunFrom(reinterpret_cast<intptr_t>(f)); - return simulator->GetCReturnInt64(); -} - -template <typename Expected> -static void VerifyGeneratedCode(InstructionSet target_isa, - Expected (*f)(), - bool has_result, - Expected expected) { - ASSERT_TRUE(CanExecute(target_isa)) << "Target isa is not executable."; - - // Verify on simulator. - CodeSimulatorContainer simulator(target_isa); - if (simulator.CanSimulate()) { - Expected result = SimulatorExecute<Expected>(simulator.Get(), f); - if (has_result) { - ASSERT_EQ(expected, result); - } - } - - // Verify on hardware. - if (CanExecuteOnHardware(target_isa)) { - Expected result = f(); - if (has_result) { - ASSERT_EQ(expected, result); - } - } -} - -template <typename Expected> -static void Run(const InternalCodeAllocator& allocator, - const CodeGenerator& codegen, - bool has_result, - Expected expected) { - InstructionSet target_isa = codegen.GetInstructionSet(); - - typedef Expected (*fptr)(); - CommonCompilerTest::MakeExecutable(allocator.GetMemory(), allocator.GetSize()); - fptr f = reinterpret_cast<fptr>(allocator.GetMemory()); - if (target_isa == kThumb2) { - // For thumb we need the bottom bit set. - f = reinterpret_cast<fptr>(reinterpret_cast<uintptr_t>(f) + 1); - } - VerifyGeneratedCode(target_isa, f, has_result, expected); -} - -static void ValidateGraph(HGraph* graph) { - GraphChecker graph_checker(graph); - graph_checker.Run(); - if (!graph_checker.IsValid()) { - for (const auto& error : graph_checker.GetErrors()) { - std::cout << error << std::endl; - } - } - ASSERT_TRUE(graph_checker.IsValid()); -} - -template <typename Expected> -static void RunCodeNoCheck(CodeGenerator* codegen, - HGraph* graph, - const std::function<void(HGraph*)>& hook_before_codegen, - bool has_result, - Expected expected) { - SsaLivenessAnalysis liveness(graph, codegen); - PrepareForRegisterAllocation(graph).Run(); - liveness.Analyze(); - RegisterAllocator::Create(graph->GetArena(), codegen, liveness)->AllocateRegisters(); - hook_before_codegen(graph); - InternalCodeAllocator allocator; - codegen->Compile(&allocator); - Run(allocator, *codegen, has_result, expected); -} - -template <typename Expected> -static void RunCode(CodeGenerator* codegen, - HGraph* graph, - std::function<void(HGraph*)> hook_before_codegen, - bool has_result, - Expected expected) { - ValidateGraph(graph); - RunCodeNoCheck(codegen, graph, hook_before_codegen, has_result, expected); -} - -template <typename Expected> -static void RunCode(CodegenTargetConfig target_config, - HGraph* graph, - std::function<void(HGraph*)> hook_before_codegen, - bool has_result, - Expected expected) { - CompilerOptions compiler_options; - std::unique_ptr<CodeGenerator> codegen(target_config.CreateCodeGenerator(graph, compiler_options)); - RunCode(codegen.get(), graph, hook_before_codegen, has_result, expected); -} - -#ifdef ART_ENABLE_CODEGEN_arm -CodeGenerator* create_codegen_arm(HGraph* graph, const CompilerOptions& compiler_options) { - std::unique_ptr<const ArmInstructionSetFeatures> features_arm( - ArmInstructionSetFeatures::FromCppDefines()); - return new (graph->GetArena()) TestCodeGeneratorARM(graph, - *features_arm.get(), - compiler_options); -} - -CodeGenerator* create_codegen_arm_vixl32(HGraph* graph, const CompilerOptions& compiler_options) { - std::unique_ptr<const ArmInstructionSetFeatures> features_arm( - ArmInstructionSetFeatures::FromCppDefines()); - return new (graph->GetArena()) - TestCodeGeneratorARMVIXL(graph, *features_arm.get(), compiler_options); -} -#endif - -#ifdef ART_ENABLE_CODEGEN_arm64 -CodeGenerator* create_codegen_arm64(HGraph* graph, const CompilerOptions& compiler_options) { - std::unique_ptr<const Arm64InstructionSetFeatures> features_arm64( - Arm64InstructionSetFeatures::FromCppDefines()); - return new (graph->GetArena()) arm64::CodeGeneratorARM64(graph, - *features_arm64.get(), - compiler_options); -} -#endif - -#ifdef ART_ENABLE_CODEGEN_x86 -CodeGenerator* create_codegen_x86(HGraph* graph, const CompilerOptions& compiler_options) { - std::unique_ptr<const X86InstructionSetFeatures> features_x86( - X86InstructionSetFeatures::FromCppDefines()); - return new (graph->GetArena()) TestCodeGeneratorX86(graph, *features_x86.get(), compiler_options); -} -#endif - -#ifdef ART_ENABLE_CODEGEN_x86_64 -CodeGenerator* create_codegen_x86_64(HGraph* graph, const CompilerOptions& compiler_options) { - std::unique_ptr<const X86_64InstructionSetFeatures> features_x86_64( - X86_64InstructionSetFeatures::FromCppDefines()); - return new (graph->GetArena()) - x86_64::CodeGeneratorX86_64(graph, *features_x86_64.get(), compiler_options); -} -#endif - -#ifdef ART_ENABLE_CODEGEN_mips -CodeGenerator* create_codegen_mips(HGraph* graph, const CompilerOptions& compiler_options) { - std::unique_ptr<const MipsInstructionSetFeatures> features_mips( - MipsInstructionSetFeatures::FromCppDefines()); - return new (graph->GetArena()) - mips::CodeGeneratorMIPS(graph, *features_mips.get(), compiler_options); -} -#endif - -#ifdef ART_ENABLE_CODEGEN_mips64 -CodeGenerator* create_codegen_mips64(HGraph* graph, const CompilerOptions& compiler_options) { - std::unique_ptr<const Mips64InstructionSetFeatures> features_mips64( - Mips64InstructionSetFeatures::FromCppDefines()); - return new (graph->GetArena()) - mips64::CodeGeneratorMIPS64(graph, *features_mips64.get(), compiler_options); -} -#endif - // Return all combinations of ISA and code generator that are executable on // hardware, or on simulator, and that we'd like to test. static ::std::vector<CodegenTargetConfig> GetTargetConfigs() { @@ -1067,6 +738,39 @@ TEST_F(CodegenTest, ARMVIXLParallelMoveResolver) { } #endif +#ifdef ART_ENABLE_CODEGEN_arm64 +// Regression test for b/34760542. +TEST_F(CodegenTest, ARM64ParallelMoveResolverB34760542) { + std::unique_ptr<const Arm64InstructionSetFeatures> features( + Arm64InstructionSetFeatures::FromCppDefines()); + ArenaPool pool; + ArenaAllocator allocator(&pool); + HGraph* graph = CreateGraph(&allocator); + arm64::CodeGeneratorARM64 codegen(graph, *features.get(), CompilerOptions()); + + codegen.Initialize(); + + // The following ParallelMove used to fail this assertion: + // + // Assertion failed (!available->IsEmpty()) + // + // in vixl::aarch64::UseScratchRegisterScope::AcquireNextAvailable. + HParallelMove* move = new (graph->GetArena()) HParallelMove(graph->GetArena()); + move->AddMove(Location::DoubleStackSlot(0), + Location::DoubleStackSlot(257), + Primitive::kPrimDouble, + nullptr); + move->AddMove(Location::DoubleStackSlot(257), + Location::DoubleStackSlot(0), + Primitive::kPrimDouble, + nullptr); + codegen.GetMoveResolver()->EmitNativeCode(move); + + InternalCodeAllocator code_allocator; + codegen.Finalize(&code_allocator); +} +#endif + #ifdef ART_ENABLE_CODEGEN_mips TEST_F(CodegenTest, MipsClobberRA) { std::unique_ptr<const MipsInstructionSetFeatures> features_mips( diff --git a/compiler/optimizing/codegen_test_utils.h b/compiler/optimizing/codegen_test_utils.h new file mode 100644 index 0000000000..cd954043f5 --- /dev/null +++ b/compiler/optimizing/codegen_test_utils.h @@ -0,0 +1,355 @@ +/* + * 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_OPTIMIZING_CODEGEN_TEST_UTILS_H_ +#define ART_COMPILER_OPTIMIZING_CODEGEN_TEST_UTILS_H_ + +#include "arch/arm/instruction_set_features_arm.h" +#include "arch/arm/registers_arm.h" +#include "arch/arm64/instruction_set_features_arm64.h" +#include "arch/instruction_set.h" +#include "arch/mips/instruction_set_features_mips.h" +#include "arch/mips/registers_mips.h" +#include "arch/mips64/instruction_set_features_mips64.h" +#include "arch/mips64/registers_mips64.h" +#include "arch/x86/instruction_set_features_x86.h" +#include "arch/x86/registers_x86.h" +#include "arch/x86_64/instruction_set_features_x86_64.h" +#include "code_simulator_container.h" +#include "common_compiler_test.h" +#include "graph_checker.h" +#include "prepare_for_register_allocation.h" +#include "ssa_liveness_analysis.h" + +#ifdef ART_ENABLE_CODEGEN_arm +#include "code_generator_arm.h" +#include "code_generator_arm_vixl.h" +#endif + +#ifdef ART_ENABLE_CODEGEN_arm64 +#include "code_generator_arm64.h" +#endif + +#ifdef ART_ENABLE_CODEGEN_x86 +#include "code_generator_x86.h" +#endif + +#ifdef ART_ENABLE_CODEGEN_x86_64 +#include "code_generator_x86_64.h" +#endif + +#ifdef ART_ENABLE_CODEGEN_mips +#include "code_generator_mips.h" +#endif + +#ifdef ART_ENABLE_CODEGEN_mips64 +#include "code_generator_mips64.h" +#endif + +namespace art { + +typedef CodeGenerator* (*CreateCodegenFn)(HGraph*, const CompilerOptions&); + +class CodegenTargetConfig { + public: + CodegenTargetConfig(InstructionSet isa, CreateCodegenFn create_codegen) + : isa_(isa), create_codegen_(create_codegen) { + } + InstructionSet GetInstructionSet() const { return isa_; } + CodeGenerator* CreateCodeGenerator(HGraph* graph, const CompilerOptions& compiler_options) { + return create_codegen_(graph, compiler_options); + } + + private: + CodegenTargetConfig() {} + InstructionSet isa_; + CreateCodegenFn create_codegen_; +}; + +#ifdef ART_ENABLE_CODEGEN_arm +// Provide our own codegen, that ensures the C calling conventions +// are preserved. Currently, ART and C do not match as R4 is caller-save +// in ART, and callee-save in C. Alternatively, we could use or write +// the stub that saves and restores all registers, but it is easier +// to just overwrite the code generator. +class TestCodeGeneratorARM : public arm::CodeGeneratorARM { + public: + TestCodeGeneratorARM(HGraph* graph, + const ArmInstructionSetFeatures& isa_features, + const CompilerOptions& compiler_options) + : arm::CodeGeneratorARM(graph, isa_features, compiler_options) { + AddAllocatedRegister(Location::RegisterLocation(arm::R6)); + AddAllocatedRegister(Location::RegisterLocation(arm::R7)); + } + + void SetupBlockedRegisters() const OVERRIDE { + arm::CodeGeneratorARM::SetupBlockedRegisters(); + blocked_core_registers_[arm::R4] = true; + blocked_core_registers_[arm::R6] = false; + blocked_core_registers_[arm::R7] = false; + } +}; + +// A way to test the VIXL32-based code generator on ARM. This will replace +// TestCodeGeneratorARM when the VIXL32-based backend replaces the existing one. +class TestCodeGeneratorARMVIXL : public arm::CodeGeneratorARMVIXL { + public: + TestCodeGeneratorARMVIXL(HGraph* graph, + const ArmInstructionSetFeatures& isa_features, + const CompilerOptions& compiler_options) + : arm::CodeGeneratorARMVIXL(graph, isa_features, compiler_options) { + AddAllocatedRegister(Location::RegisterLocation(arm::R6)); + AddAllocatedRegister(Location::RegisterLocation(arm::R7)); + } + + void SetupBlockedRegisters() const OVERRIDE { + arm::CodeGeneratorARMVIXL::SetupBlockedRegisters(); + blocked_core_registers_[arm::R4] = true; + blocked_core_registers_[arm::R6] = false; + blocked_core_registers_[arm::R7] = false; + } +}; +#endif + +#ifdef ART_ENABLE_CODEGEN_x86 +class TestCodeGeneratorX86 : public x86::CodeGeneratorX86 { + public: + TestCodeGeneratorX86(HGraph* graph, + const X86InstructionSetFeatures& isa_features, + const CompilerOptions& compiler_options) + : x86::CodeGeneratorX86(graph, isa_features, compiler_options) { + // Save edi, we need it for getting enough registers for long multiplication. + AddAllocatedRegister(Location::RegisterLocation(x86::EDI)); + } + + void SetupBlockedRegisters() const OVERRIDE { + x86::CodeGeneratorX86::SetupBlockedRegisters(); + // ebx is a callee-save register in C, but caller-save for ART. + blocked_core_registers_[x86::EBX] = true; + + // Make edi available. + blocked_core_registers_[x86::EDI] = false; + } +}; +#endif + +class InternalCodeAllocator : public CodeAllocator { + public: + InternalCodeAllocator() : size_(0) { } + + virtual uint8_t* Allocate(size_t size) { + size_ = size; + memory_.reset(new uint8_t[size]); + return memory_.get(); + } + + size_t GetSize() const { return size_; } + uint8_t* GetMemory() const { return memory_.get(); } + + private: + size_t size_; + std::unique_ptr<uint8_t[]> memory_; + + DISALLOW_COPY_AND_ASSIGN(InternalCodeAllocator); +}; + +static bool CanExecuteOnHardware(InstructionSet target_isa) { + return (target_isa == kRuntimeISA) + // Handle the special case of ARM, with two instructions sets (ARM32 and Thumb-2). + || (kRuntimeISA == kArm && target_isa == kThumb2); +} + +static bool CanExecute(InstructionSet target_isa) { + CodeSimulatorContainer simulator(target_isa); + return CanExecuteOnHardware(target_isa) || simulator.CanSimulate(); +} + +template <typename Expected> +inline static Expected SimulatorExecute(CodeSimulator* simulator, Expected (*f)()); + +template <> +inline bool SimulatorExecute<bool>(CodeSimulator* simulator, bool (*f)()) { + simulator->RunFrom(reinterpret_cast<intptr_t>(f)); + return simulator->GetCReturnBool(); +} + +template <> +inline int32_t SimulatorExecute<int32_t>(CodeSimulator* simulator, int32_t (*f)()) { + simulator->RunFrom(reinterpret_cast<intptr_t>(f)); + return simulator->GetCReturnInt32(); +} + +template <> +inline int64_t SimulatorExecute<int64_t>(CodeSimulator* simulator, int64_t (*f)()) { + simulator->RunFrom(reinterpret_cast<intptr_t>(f)); + return simulator->GetCReturnInt64(); +} + +template <typename Expected> +static void VerifyGeneratedCode(InstructionSet target_isa, + Expected (*f)(), + bool has_result, + Expected expected) { + ASSERT_TRUE(CanExecute(target_isa)) << "Target isa is not executable."; + + // Verify on simulator. + CodeSimulatorContainer simulator(target_isa); + if (simulator.CanSimulate()) { + Expected result = SimulatorExecute<Expected>(simulator.Get(), f); + if (has_result) { + ASSERT_EQ(expected, result); + } + } + + // Verify on hardware. + if (CanExecuteOnHardware(target_isa)) { + Expected result = f(); + if (has_result) { + ASSERT_EQ(expected, result); + } + } +} + +template <typename Expected> +static void Run(const InternalCodeAllocator& allocator, + const CodeGenerator& codegen, + bool has_result, + Expected expected) { + InstructionSet target_isa = codegen.GetInstructionSet(); + + typedef Expected (*fptr)(); + CommonCompilerTest::MakeExecutable(allocator.GetMemory(), allocator.GetSize()); + fptr f = reinterpret_cast<fptr>(allocator.GetMemory()); + if (target_isa == kThumb2) { + // For thumb we need the bottom bit set. + f = reinterpret_cast<fptr>(reinterpret_cast<uintptr_t>(f) + 1); + } + VerifyGeneratedCode(target_isa, f, has_result, expected); +} + +static void ValidateGraph(HGraph* graph) { + GraphChecker graph_checker(graph); + graph_checker.Run(); + if (!graph_checker.IsValid()) { + for (const auto& error : graph_checker.GetErrors()) { + std::cout << error << std::endl; + } + } + ASSERT_TRUE(graph_checker.IsValid()); +} + +template <typename Expected> +static void RunCodeNoCheck(CodeGenerator* codegen, + HGraph* graph, + const std::function<void(HGraph*)>& hook_before_codegen, + bool has_result, + Expected expected) { + SsaLivenessAnalysis liveness(graph, codegen); + PrepareForRegisterAllocation(graph).Run(); + liveness.Analyze(); + RegisterAllocator::Create(graph->GetArena(), codegen, liveness)->AllocateRegisters(); + hook_before_codegen(graph); + InternalCodeAllocator allocator; + codegen->Compile(&allocator); + Run(allocator, *codegen, has_result, expected); +} + +template <typename Expected> +static void RunCode(CodeGenerator* codegen, + HGraph* graph, + std::function<void(HGraph*)> hook_before_codegen, + bool has_result, + Expected expected) { + ValidateGraph(graph); + RunCodeNoCheck(codegen, graph, hook_before_codegen, has_result, expected); +} + +template <typename Expected> +static void RunCode(CodegenTargetConfig target_config, + HGraph* graph, + std::function<void(HGraph*)> hook_before_codegen, + bool has_result, + Expected expected) { + CompilerOptions compiler_options; + std::unique_ptr<CodeGenerator> codegen(target_config.CreateCodeGenerator(graph, compiler_options)); + RunCode(codegen.get(), graph, hook_before_codegen, has_result, expected); +} + +#ifdef ART_ENABLE_CODEGEN_arm +CodeGenerator* create_codegen_arm(HGraph* graph, const CompilerOptions& compiler_options) { + std::unique_ptr<const ArmInstructionSetFeatures> features_arm( + ArmInstructionSetFeatures::FromCppDefines()); + return new (graph->GetArena()) TestCodeGeneratorARM(graph, + *features_arm.get(), + compiler_options); +} + +CodeGenerator* create_codegen_arm_vixl32(HGraph* graph, const CompilerOptions& compiler_options) { + std::unique_ptr<const ArmInstructionSetFeatures> features_arm( + ArmInstructionSetFeatures::FromCppDefines()); + return new (graph->GetArena()) + TestCodeGeneratorARMVIXL(graph, *features_arm.get(), compiler_options); +} +#endif + +#ifdef ART_ENABLE_CODEGEN_arm64 +CodeGenerator* create_codegen_arm64(HGraph* graph, const CompilerOptions& compiler_options) { + std::unique_ptr<const Arm64InstructionSetFeatures> features_arm64( + Arm64InstructionSetFeatures::FromCppDefines()); + return new (graph->GetArena()) arm64::CodeGeneratorARM64(graph, + *features_arm64.get(), + compiler_options); +} +#endif + +#ifdef ART_ENABLE_CODEGEN_x86 +CodeGenerator* create_codegen_x86(HGraph* graph, const CompilerOptions& compiler_options) { + std::unique_ptr<const X86InstructionSetFeatures> features_x86( + X86InstructionSetFeatures::FromCppDefines()); + return new (graph->GetArena()) TestCodeGeneratorX86(graph, *features_x86.get(), compiler_options); +} +#endif + +#ifdef ART_ENABLE_CODEGEN_x86_64 +CodeGenerator* create_codegen_x86_64(HGraph* graph, const CompilerOptions& compiler_options) { + std::unique_ptr<const X86_64InstructionSetFeatures> features_x86_64( + X86_64InstructionSetFeatures::FromCppDefines()); + return new (graph->GetArena()) + x86_64::CodeGeneratorX86_64(graph, *features_x86_64.get(), compiler_options); +} +#endif + +#ifdef ART_ENABLE_CODEGEN_mips +CodeGenerator* create_codegen_mips(HGraph* graph, const CompilerOptions& compiler_options) { + std::unique_ptr<const MipsInstructionSetFeatures> features_mips( + MipsInstructionSetFeatures::FromCppDefines()); + return new (graph->GetArena()) + mips::CodeGeneratorMIPS(graph, *features_mips.get(), compiler_options); +} +#endif + +#ifdef ART_ENABLE_CODEGEN_mips64 +CodeGenerator* create_codegen_mips64(HGraph* graph, const CompilerOptions& compiler_options) { + std::unique_ptr<const Mips64InstructionSetFeatures> features_mips64( + Mips64InstructionSetFeatures::FromCppDefines()); + return new (graph->GetArena()) + mips64::CodeGeneratorMIPS64(graph, *features_mips64.get(), compiler_options); +} +#endif + +} // namespace art + +#endif // ART_COMPILER_OPTIMIZING_CODEGEN_TEST_UTILS_H_ diff --git a/compiler/optimizing/common_arm.h b/compiler/optimizing/common_arm.h index 21c3ae628a..ecb86875d6 100644 --- a/compiler/optimizing/common_arm.h +++ b/compiler/optimizing/common_arm.h @@ -146,6 +146,12 @@ inline vixl::aarch32::Register InputRegister(HInstruction* instr) { return InputRegisterAt(instr, 0); } +inline vixl::aarch32::DRegister DRegisterFromS(vixl::aarch32::SRegister s) { + vixl::aarch32::DRegister d = vixl::aarch32::DRegister(s.GetCode() / 2); + DCHECK(s.Is(d.GetLane(0)) || s.Is(d.GetLane(1))); + return d; +} + inline int32_t Int32ConstantFrom(HInstruction* instr) { if (instr->IsIntConstant()) { return instr->AsIntConstant()->GetValue(); diff --git a/compiler/optimizing/common_arm64.h b/compiler/optimizing/common_arm64.h index 776a483d43..93ea090583 100644 --- a/compiler/optimizing/common_arm64.h +++ b/compiler/optimizing/common_arm64.h @@ -130,8 +130,8 @@ inline vixl::aarch64::CPURegister InputCPURegisterOrZeroRegAt(HInstruction* inst Primitive::Type input_type = input->GetType(); if (input->IsConstant() && input->AsConstant()->IsZeroBitPattern()) { return (Primitive::ComponentSize(input_type) >= vixl::aarch64::kXRegSizeInBytes) - ? vixl::aarch64::xzr - : vixl::aarch64::wzr; + ? vixl::aarch64::Register(vixl::aarch64::xzr) + : vixl::aarch64::Register(vixl::aarch64::wzr); } return InputCPURegisterAt(instr, index); } diff --git a/compiler/optimizing/dex_cache_array_fixups_mips.cc b/compiler/optimizing/dex_cache_array_fixups_mips.cc index 04a4294c48..7734f9197d 100644 --- a/compiler/optimizing/dex_cache_array_fixups_mips.cc +++ b/compiler/optimizing/dex_cache_array_fixups_mips.cc @@ -47,7 +47,7 @@ class DexCacheArrayFixupsVisitor : public HGraphVisitor { // Computing the dex cache base for PC-relative accesses will clobber RA with // the NAL instruction on R2. Take a note of this before generating the method // entry. - if (!dex_cache_array_bases_.empty() && !codegen_->GetInstructionSetFeatures().IsR6()) { + if (!dex_cache_array_bases_.empty()) { codegen_->ClobberRA(); } } @@ -92,6 +92,11 @@ class DexCacheArrayFixupsVisitor : public HGraphVisitor { }; void DexCacheArrayFixups::Run() { + CodeGeneratorMIPS* mips_codegen = down_cast<CodeGeneratorMIPS*>(codegen_); + if (mips_codegen->GetInstructionSetFeatures().IsR6()) { + // Do nothing for R6 because it has PC-relative addressing. + return; + } if (graph_->HasIrreducibleLoops()) { // Do not run this optimization, as irreducible loops do not work with an instruction // that can be live-in at the irreducible loop header. diff --git a/compiler/optimizing/induction_var_range.cc b/compiler/optimizing/induction_var_range.cc index 3973985338..5539413aad 100644 --- a/compiler/optimizing/induction_var_range.cc +++ b/compiler/optimizing/induction_var_range.cc @@ -57,14 +57,18 @@ static bool IsIntAndGet(HInstruction* instruction, int64_t* value) { return false; } -/** Returns b^e for b,e >= 1. */ -static int64_t IntPow(int64_t b, int64_t e) { +/** Returns b^e for b,e >= 1. Sets overflow if arithmetic wrap-around occurred. */ +static int64_t IntPow(int64_t b, int64_t e, /*out*/ bool* overflow) { DCHECK_GE(b, 1); DCHECK_GE(e, 1); int64_t pow = 1; while (e) { if (e & 1) { + int64_t oldpow = pow; pow *= b; + if (pow < oldpow) { + *overflow = true; + } } e >>= 1; b *= b; @@ -1020,20 +1024,27 @@ bool InductionVarRange::GenerateLastValueGeometric(HInductionVarAnalysis::Induct HInstruction* opb = nullptr; if (GenerateCode(info->op_a, nullptr, graph, block, &opa, false, false) && GenerateCode(info->op_b, nullptr, graph, block, &opb, false, false)) { - // Compute f ^ m for known maximum index value m. - int64_t fpow = IntPow(f, m); if (graph != nullptr) { - DCHECK(info->operation == HInductionVarAnalysis::kMul || - info->operation == HInductionVarAnalysis::kDiv); Primitive::Type type = info->type; + // Compute f ^ m for known maximum index value m. + bool overflow = false; + int64_t fpow = IntPow(f, m, &overflow); + if (info->operation == HInductionVarAnalysis::kDiv) { + // For division, any overflow truncates to zero. + if (overflow || (type != Primitive::kPrimLong && !CanLongValueFitIntoInt(fpow))) { + fpow = 0; + } + } else if (type != Primitive::kPrimLong) { + // For multiplication, okay to truncate to required precision. + DCHECK(info->operation == HInductionVarAnalysis::kMul); + fpow = static_cast<int32_t>(fpow); + } + // Generate code. if (fpow == 0) { // Special case: repeated mul/div always yields zero. *result = graph->GetConstant(type, 0); } else { // Last value: a * f ^ m + b or a * f ^ -m + b. - if (type != Primitive::kPrimLong) { - fpow = static_cast<int32_t>(fpow); // okay to truncate - } HInstruction* e = nullptr; if (info->operation == HInductionVarAnalysis::kMul) { e = new (graph->GetArena()) HMul(type, opa, graph->GetConstant(type, fpow)); diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc index 5d40f75618..f0afccb782 100644 --- a/compiler/optimizing/inliner.cc +++ b/compiler/optimizing/inliner.cc @@ -304,7 +304,8 @@ ArtMethod* HInliner::TryCHADevirtualization(ArtMethod* resolved_method) { // We do not support HDeoptimize in OSR methods. return nullptr; } - return resolved_method->GetSingleImplementation(); + PointerSize pointer_size = caller_compilation_unit_.GetClassLinker()->GetImagePointerSize(); + return resolved_method->GetSingleImplementation(pointer_size); } bool HInliner::TryInline(HInvoke* invoke_instruction) { @@ -557,9 +558,13 @@ HInstruction* HInliner::AddTypeGuard(HInstruction* receiver, is_referrer, invoke_instruction->GetDexPc(), /* needs_access_check */ false); + HLoadClass::LoadKind kind = HSharpening::SharpenClass( + load_class, codegen_, compiler_driver_, caller_compilation_unit_); + DCHECK(kind != HLoadClass::LoadKind::kInvalid) + << "We should always be able to reference a class for inline caches"; + // Insert before setting the kind, as setting the kind affects the inputs. bb_cursor->InsertInstructionAfter(load_class, receiver_class); - // Sharpen after adding the instruction, as the sharpening may remove inputs. - HSharpening::SharpenClass(load_class, codegen_, compiler_driver_); + load_class->SetLoadKind(kind); // TODO: Extend reference type propagation to understand the guard. HNotEqual* compare = new (graph_->GetArena()) HNotEqual(load_class, receiver_class); @@ -1285,6 +1290,7 @@ bool HInliner::TryBuildAndInlineHelper(HInvoke* invoke_instruction, resolved_method->GetDexFile(), *code_item, compiler_driver_, + codegen_, inline_stats.get(), resolved_method->GetQuickenedInfo(class_linker->GetImagePointerSize()), dex_cache, @@ -1415,10 +1421,13 @@ bool HInliner::TryBuildAndInlineHelper(HInvoke* invoke_instruction, return false; } - if (!same_dex_file && current->NeedsEnvironment()) { + if (current->NeedsEnvironment() && + !CanEncodeInlinedMethodInStackMap(*caller_compilation_unit_.GetDexFile(), + resolved_method)) { VLOG(compiler) << "Method " << callee_dex_file.PrettyMethod(method_index) << " could not be inlined because " << current->DebugName() - << " needs an environment and is in a different dex file"; + << " needs an environment, is in a different dex file" + << ", and cannot be encoded in the stack maps."; return false; } diff --git a/compiler/optimizing/instruction_builder.cc b/compiler/optimizing/instruction_builder.cc index cac385ce3c..a1c391f455 100644 --- a/compiler/optimizing/instruction_builder.cc +++ b/compiler/optimizing/instruction_builder.cc @@ -22,6 +22,7 @@ #include "dex_instruction-inl.h" #include "driver/compiler_options.h" #include "imtable-inl.h" +#include "sharpening.h" #include "scoped_thread_state_change-inl.h" namespace art { @@ -847,7 +848,7 @@ bool HInstructionBuilder::BuildInvoke(const Instruction& instruction, ScopedObjectAccess soa(Thread::Current()); if (invoke_type == kStatic) { clinit_check = ProcessClinitCheckForInvoke( - dex_pc, resolved_method, method_idx, &clinit_check_requirement); + dex_pc, resolved_method, &clinit_check_requirement); } else if (invoke_type == kSuper) { if (IsSameDexFile(*resolved_method->GetDexFile(), *dex_compilation_unit_->GetDexFile())) { // Update the method index to the one resolved. Note that this may be a no-op if @@ -933,15 +934,8 @@ bool HInstructionBuilder::BuildInvokePolymorphic(const Instruction& instruction bool HInstructionBuilder::BuildNewInstance(dex::TypeIndex type_index, uint32_t dex_pc) { ScopedObjectAccess soa(Thread::Current()); - Handle<mirror::DexCache> dex_cache = dex_compilation_unit_->GetDexCache(); - Handle<mirror::DexCache> outer_dex_cache = outer_compilation_unit_->GetDexCache(); - - if (outer_dex_cache.Get() != dex_cache.Get()) { - // We currently do not support inlining allocations across dex files. - return false; - } - HLoadClass* load_class = BuildLoadClass(type_index, dex_pc, /* check_access */ true); + HLoadClass* load_class = BuildLoadClass(type_index, dex_pc); HInstruction* cls = load_class; Handle<mirror::Class> klass = load_class->GetClass(); @@ -1005,39 +999,23 @@ bool HInstructionBuilder::IsInitialized(Handle<mirror::Class> cls) const { HClinitCheck* HInstructionBuilder::ProcessClinitCheckForInvoke( uint32_t dex_pc, ArtMethod* resolved_method, - uint32_t method_idx, HInvokeStaticOrDirect::ClinitCheckRequirement* clinit_check_requirement) { - Thread* self = Thread::Current(); - 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())); - - // The index at which the method's class is stored in the DexCache's type array. - dex::TypeIndex storage_index; - bool is_outer_class = (resolved_method->GetDeclaringClass() == outer_class.Get()); - if (is_outer_class) { - storage_index = outer_class->GetDexTypeIndex(); - } else if (outer_dex_cache.Get() == dex_cache.Get()) { - // Get `storage_index` from IsClassOfStaticMethodAvailableToReferrer. - compiler_driver_->IsClassOfStaticMethodAvailableToReferrer(outer_dex_cache.Get(), - GetCompilingClass(), - resolved_method, - method_idx, - &storage_index); - } + Handle<mirror::Class> klass = handles_->NewHandle(resolved_method->GetDeclaringClass()); HClinitCheck* clinit_check = nullptr; - - if (IsInitialized(resolved_method_class)) { + if (IsInitialized(klass)) { *clinit_check_requirement = HInvokeStaticOrDirect::ClinitCheckRequirement::kNone; - } else if (storage_index.IsValid()) { - *clinit_check_requirement = HInvokeStaticOrDirect::ClinitCheckRequirement::kExplicit; - HLoadClass* cls = BuildLoadClass( - storage_index, dex_pc, /* check_access */ false, /* outer */ true); - clinit_check = new (arena_) HClinitCheck(cls, dex_pc); - AppendInstruction(clinit_check); + } else { + HLoadClass* cls = BuildLoadClass(klass->GetDexTypeIndex(), + klass->GetDexFile(), + klass, + dex_pc, + /* needs_access_check */ false); + if (cls != nullptr) { + *clinit_check_requirement = HInvokeStaticOrDirect::ClinitCheckRequirement::kExplicit; + clinit_check = new (arena_) HClinitCheck(cls, dex_pc); + AppendInstruction(clinit_check); + } } return clinit_check; } @@ -1216,9 +1194,7 @@ bool HInstructionBuilder::BuildInstanceFieldAccess(const Instruction& instructio } ScopedObjectAccess soa(Thread::Current()); - ArtField* resolved_field = - compiler_driver_->ComputeInstanceFieldInfo(field_index, dex_compilation_unit_, is_put, soa); - + ArtField* resolved_field = ResolveField(field_index, /* is_static */ false, is_put); // 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 @@ -1336,6 +1312,56 @@ void HInstructionBuilder::BuildUnresolvedStaticFieldAccess(const Instruction& in } } +ArtField* HInstructionBuilder::ResolveField(uint16_t field_idx, bool is_static, bool is_put) { + ScopedObjectAccess soa(Thread::Current()); + StackHandleScope<2> hs(soa.Self()); + + ClassLinker* class_linker = dex_compilation_unit_->GetClassLinker(); + Handle<mirror::ClassLoader> class_loader(hs.NewHandle( + soa.Decode<mirror::ClassLoader>(dex_compilation_unit_->GetClassLoader()))); + Handle<mirror::Class> compiling_class(hs.NewHandle(GetCompilingClass())); + + ArtField* resolved_field = class_linker->ResolveField(*dex_compilation_unit_->GetDexFile(), + field_idx, + dex_compilation_unit_->GetDexCache(), + class_loader, + is_static); + + if (UNLIKELY(resolved_field == nullptr)) { + // Clean up any exception left by type resolution. + soa.Self()->ClearException(); + return nullptr; + } + + // Check static/instance. The class linker has a fast path for looking into the dex cache + // and does not check static/instance if it hits it. + if (UNLIKELY(resolved_field->IsStatic() != is_static)) { + return nullptr; + } + + // Check access. + if (compiling_class.Get() == nullptr) { + if (!resolved_field->IsPublic()) { + return nullptr; + } + } else if (!compiling_class->CanAccessResolvedField(resolved_field->GetDeclaringClass(), + resolved_field, + dex_compilation_unit_->GetDexCache().Get(), + field_idx)) { + return nullptr; + } + + if (is_put && + resolved_field->IsFinal() && + (compiling_class.Get() != resolved_field->GetDeclaringClass())) { + // Final fields can only be updated within their own class. + // TODO: Only allow it in constructors. b/34966607. + return nullptr; + } + + return resolved_field; +} + bool HInstructionBuilder::BuildStaticFieldAccess(const Instruction& instruction, uint32_t dex_pc, bool is_put) { @@ -1343,12 +1369,7 @@ bool HInstructionBuilder::BuildStaticFieldAccess(const Instruction& instruction, uint16_t field_index = instruction.VRegB_21c(); ScopedObjectAccess soa(Thread::Current()); - 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( - soa, dex_cache, class_loader, dex_compilation_unit_, field_index, true); + ArtField* resolved_field = ResolveField(field_index, /* is_static */ true, is_put); if (resolved_field == nullptr) { MaybeRecordStat(MethodCompilationStat::kUnresolvedField); @@ -1358,38 +1379,23 @@ bool HInstructionBuilder::BuildStaticFieldAccess(const Instruction& instruction, } Primitive::Type field_type = resolved_field->GetTypeAsPrimitiveType(); - 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. - dex::TypeIndex storage_index; - bool is_outer_class = (outer_class.Get() == resolved_field->GetDeclaringClass()); - if (is_outer_class) { - storage_index = outer_class->GetDexTypeIndex(); - } else if (outer_dex_cache.Get() != dex_cache.Get()) { - // The compiler driver cannot currently understand multiple dex caches involved. Just bailout. - return false; - } else { - // TODO: This is rather expensive. Perf it and cache the results if needed. - std::pair<bool, bool> pair = compiler_driver_->IsFastStaticField( - outer_dex_cache.Get(), - GetCompilingClass(), - resolved_field, - field_index, - &storage_index); - bool can_easily_access = is_put ? pair.second : pair.first; - if (!can_easily_access) { - MaybeRecordStat(MethodCompilationStat::kUnresolvedFieldNotAFastAccess); - BuildUnresolvedStaticFieldAccess(instruction, dex_pc, is_put, field_type); - return true; - } + Handle<mirror::Class> klass = handles_->NewHandle(resolved_field->GetDeclaringClass()); + HLoadClass* constant = BuildLoadClass(klass->GetDexTypeIndex(), + klass->GetDexFile(), + klass, + dex_pc, + /* needs_access_check */ false); + + if (constant == nullptr) { + // The class cannot be referenced from this compiled code. Generate + // an unresolved access. + MaybeRecordStat(MethodCompilationStat::kUnresolvedFieldNotAFastAccess); + BuildUnresolvedStaticFieldAccess(instruction, dex_pc, is_put, field_type); + return true; } - HLoadClass* constant = BuildLoadClass( - storage_index, dex_pc, /* check_access */ false, /* outer */ true); - HInstruction* cls = constant; - Handle<mirror::Class> klass(hs.NewHandle(resolved_field->GetDeclaringClass())); if (!IsInitialized(klass)) { cls = new (arena_) HClinitCheck(constant, dex_pc); AppendInstruction(cls); @@ -1497,7 +1503,7 @@ void HInstructionBuilder::BuildFilledNewArray(uint32_t dex_pc, uint32_t* args, uint32_t register_index) { HInstruction* length = graph_->GetIntConstant(number_of_vreg_arguments, dex_pc); - HLoadClass* cls = BuildLoadClass(type_index, dex_pc, /* check_access */ true); + HLoadClass* cls = BuildLoadClass(type_index, dex_pc); HInstruction* object = new (arena_) HNewArray(cls, length, dex_pc); AppendInstruction(object); @@ -1627,44 +1633,68 @@ static TypeCheckKind ComputeTypeCheckKind(Handle<mirror::Class> cls) } } -HLoadClass* HInstructionBuilder::BuildLoadClass(dex::TypeIndex type_index, - uint32_t dex_pc, - bool check_access, - bool outer) { +HLoadClass* HInstructionBuilder::BuildLoadClass(dex::TypeIndex type_index, uint32_t dex_pc) { ScopedObjectAccess soa(Thread::Current()); - const DexCompilationUnit* compilation_unit = - outer ? outer_compilation_unit_ : dex_compilation_unit_; - const DexFile& dex_file = *compilation_unit->GetDexFile(); - StackHandleScope<1> hs(soa.Self()); + StackHandleScope<2> hs(soa.Self()); + const DexFile& dex_file = *dex_compilation_unit_->GetDexFile(); Handle<mirror::ClassLoader> class_loader(hs.NewHandle( soa.Decode<mirror::ClassLoader>(dex_compilation_unit_->GetClassLoader()))); Handle<mirror::Class> klass = handles_->NewHandle(compiler_driver_->ResolveClass( - soa, compilation_unit->GetDexCache(), class_loader, type_index, compilation_unit)); + soa, dex_compilation_unit_->GetDexCache(), class_loader, type_index, dex_compilation_unit_)); - bool is_accessible = false; - if (!check_access) { - is_accessible = true; - } else if (klass.Get() != nullptr) { + bool needs_access_check = true; + if (klass.Get() != nullptr) { if (klass->IsPublic()) { - is_accessible = true; + needs_access_check = false; } else { mirror::Class* compiling_class = GetCompilingClass(); if (compiling_class != nullptr && compiling_class->CanAccess(klass.Get())) { - is_accessible = true; + needs_access_check = false; } } } + return BuildLoadClass(type_index, dex_file, klass, dex_pc, needs_access_check); +} + +HLoadClass* HInstructionBuilder::BuildLoadClass(dex::TypeIndex type_index, + const DexFile& dex_file, + Handle<mirror::Class> klass, + uint32_t dex_pc, + bool needs_access_check) { + // Try to find a reference in the compiling dex file. + const DexFile* actual_dex_file = &dex_file; + if (!IsSameDexFile(dex_file, *dex_compilation_unit_->GetDexFile())) { + dex::TypeIndex local_type_index = + klass->FindTypeIndexInOtherDexFile(*dex_compilation_unit_->GetDexFile()); + if (local_type_index.IsValid()) { + type_index = local_type_index; + actual_dex_file = dex_compilation_unit_->GetDexFile(); + } + } + + // Note: `klass` must be from `handles_`. HLoadClass* load_class = new (arena_) HLoadClass( graph_->GetCurrentMethod(), type_index, - dex_file, + *actual_dex_file, klass, klass.Get() != nullptr && (klass.Get() == GetOutermostCompilingClass()), dex_pc, - !is_accessible); + needs_access_check); + HLoadClass::LoadKind load_kind = HSharpening::SharpenClass(load_class, + code_generator_, + compiler_driver_, + *dex_compilation_unit_); + + if (load_kind == HLoadClass::LoadKind::kInvalid) { + // We actually cannot reference this class, we're forced to bail. + return nullptr; + } + // Append the instruction first, as setting the load kind affects the inputs. AppendInstruction(load_class); + load_class->SetLoadKind(load_kind); return load_class; } @@ -1674,7 +1704,7 @@ void HInstructionBuilder::BuildTypeCheck(const Instruction& instruction, dex::TypeIndex type_index, uint32_t dex_pc) { HInstruction* object = LoadLocal(reference, Primitive::kPrimNot); - HLoadClass* cls = BuildLoadClass(type_index, dex_pc, /* check_access */ true); + HLoadClass* cls = BuildLoadClass(type_index, dex_pc); ScopedObjectAccess soa(Thread::Current()); TypeCheckKind check_kind = ComputeTypeCheckKind(cls->GetClass()); @@ -2498,7 +2528,7 @@ bool HInstructionBuilder::ProcessDexInstruction(const Instruction& instruction, case Instruction::NEW_ARRAY: { dex::TypeIndex type_index(instruction.VRegC_22c()); HInstruction* length = LoadLocal(instruction.VRegB_22c(), Primitive::kPrimInt); - HLoadClass* cls = BuildLoadClass(type_index, dex_pc, /* check_access */ true); + HLoadClass* cls = BuildLoadClass(type_index, dex_pc); AppendInstruction(new (arena_) HNewArray(cls, length, dex_pc)); UpdateLocal(instruction.VRegA_22c(), current_block_->GetLastInstruction()); break; @@ -2673,7 +2703,7 @@ bool HInstructionBuilder::ProcessDexInstruction(const Instruction& instruction, case Instruction::CONST_CLASS: { dex::TypeIndex type_index(instruction.VRegB_21c()); - BuildLoadClass(type_index, dex_pc, /* check_access */ true); + BuildLoadClass(type_index, dex_pc); UpdateLocal(instruction.VRegA_21c(), current_block_->GetLastInstruction()); break; } diff --git a/compiler/optimizing/instruction_builder.h b/compiler/optimizing/instruction_builder.h index 5efe95094c..3bb680ce44 100644 --- a/compiler/optimizing/instruction_builder.h +++ b/compiler/optimizing/instruction_builder.h @@ -31,6 +31,7 @@ namespace art { +class CodeGenerator; class Instruction; class HInstructionBuilder : public ValueObject { @@ -44,6 +45,7 @@ class HInstructionBuilder : public ValueObject { DexCompilationUnit* dex_compilation_unit, const DexCompilationUnit* const outer_compilation_unit, CompilerDriver* driver, + CodeGenerator* code_generator, const uint8_t* interpreter_metadata, OptimizingCompilerStats* compiler_stats, Handle<mirror::DexCache> dex_cache, @@ -61,6 +63,7 @@ class HInstructionBuilder : public ValueObject { current_locals_(nullptr), latest_result_(nullptr), compiler_driver_(driver), + code_generator_(code_generator), dex_compilation_unit_(dex_compilation_unit), outer_compilation_unit_(outer_compilation_unit), interpreter_metadata_(interpreter_metadata), @@ -228,10 +231,14 @@ class HInstructionBuilder : public ValueObject { // Builds a `HLoadClass` loading the given `type_index`. If `outer` is true, // this method will use the outer class's dex file to lookup the type at // `type_index`. + HLoadClass* BuildLoadClass(dex::TypeIndex type_index, uint32_t dex_pc); + HLoadClass* BuildLoadClass(dex::TypeIndex type_index, + const DexFile& dex_file, + Handle<mirror::Class> klass, uint32_t dex_pc, - bool check_access, - bool outer = false); + bool needs_access_check) + REQUIRES_SHARED(Locks::mutator_lock_); // Returns the outer-most compiling method's class. mirror::Class* GetOutermostCompilingClass() const; @@ -275,7 +282,6 @@ class HInstructionBuilder : public ValueObject { HClinitCheck* ProcessClinitCheckForInvoke( uint32_t dex_pc, ArtMethod* method, - uint32_t method_idx, HInvokeStaticOrDirect::ClinitCheckRequirement* clinit_check_requirement) REQUIRES_SHARED(Locks::mutator_lock_); @@ -290,6 +296,10 @@ class HInstructionBuilder : public ValueObject { // not be resolved. ArtMethod* ResolveMethod(uint16_t method_idx, InvokeType invoke_type); + // Try to resolve a field using the class linker. Return null if it could not + // be found. + ArtField* ResolveField(uint16_t field_idx, bool is_static, bool is_put); + ArenaAllocator* const arena_; HGraph* const graph_; VariableSizedHandleScope* handles_; @@ -311,6 +321,8 @@ class HInstructionBuilder : public ValueObject { CompilerDriver* const compiler_driver_; + CodeGenerator* const code_generator_; + // The compilation unit of the current method being compiled. Note that // it can be an inlined method. DexCompilationUnit* const dex_compilation_unit_; diff --git a/compiler/optimizing/intrinsics.h b/compiler/optimizing/intrinsics.h index 1e73cf67df..6425e1313f 100644 --- a/compiler/optimizing/intrinsics.h +++ b/compiler/optimizing/intrinsics.h @@ -31,6 +31,9 @@ class DexFile; static constexpr uint32_t kPositiveInfinityFloat = 0x7f800000U; static constexpr uint64_t kPositiveInfinityDouble = UINT64_C(0x7ff0000000000000); +static constexpr uint32_t kNanFloat = 0x7fc00000U; +static constexpr uint64_t kNanDouble = 0x7ff8000000000000; + // Recognize intrinsics from HInvoke nodes. class IntrinsicsRecognizer : public HOptimization { public: diff --git a/compiler/optimizing/intrinsics_arm_vixl.cc b/compiler/optimizing/intrinsics_arm_vixl.cc index 68c2d2e36e..70a3d38c13 100644 --- a/compiler/optimizing/intrinsics_arm_vixl.cc +++ b/compiler/optimizing/intrinsics_arm_vixl.cc @@ -40,10 +40,12 @@ using helpers::LocationFrom; using helpers::LowRegisterFrom; using helpers::LowSRegisterFrom; using helpers::OutputDRegister; +using helpers::OutputSRegister; using helpers::OutputRegister; using helpers::OutputVRegister; using helpers::RegisterFrom; using helpers::SRegisterFrom; +using helpers::DRegisterFromS; using namespace vixl::aarch32; // NOLINT(build/namespaces) @@ -462,6 +464,214 @@ void IntrinsicCodeGeneratorARMVIXL::VisitMathAbsLong(HInvoke* invoke) { GenAbsInteger(invoke->GetLocations(), /* is64bit */ true, GetAssembler()); } +static void GenMinMaxFloat(HInvoke* invoke, bool is_min, ArmVIXLAssembler* assembler) { + Location op1_loc = invoke->GetLocations()->InAt(0); + Location op2_loc = invoke->GetLocations()->InAt(1); + Location out_loc = invoke->GetLocations()->Out(); + + // Optimization: don't generate any code if inputs are the same. + if (op1_loc.Equals(op2_loc)) { + DCHECK(out_loc.Equals(op1_loc)); // out_loc is set as SameAsFirstInput() in location builder. + return; + } + + vixl32::SRegister op1 = SRegisterFrom(op1_loc); + vixl32::SRegister op2 = SRegisterFrom(op2_loc); + vixl32::SRegister out = OutputSRegister(invoke); + UseScratchRegisterScope temps(assembler->GetVIXLAssembler()); + const vixl32::Register temp1 = temps.Acquire(); + vixl32::Register temp2 = RegisterFrom(invoke->GetLocations()->GetTemp(0)); + vixl32::Label nan, done; + + DCHECK(op1.Is(out)); + + __ Vcmp(op1, op2); + __ Vmrs(RegisterOrAPSR_nzcv(kPcCode), FPSCR); + __ B(vs, &nan, /* far_target */ false); // if un-ordered, go to NaN handling. + + // op1 <> op2 + vixl32::ConditionType cond = is_min ? gt : lt; + { + ExactAssemblyScope it_scope(assembler->GetVIXLAssembler(), + 2 * kMaxInstructionSizeInBytes, + CodeBufferCheckScope::kMaximumSize); + __ it(cond); + __ vmov(cond, F32, out, op2); + } + __ B(ne, &done, /* far_target */ false); // for <>(not equal), we've done min/max calculation. + + // handle op1 == op2, max(+0.0,-0.0), min(+0.0,-0.0). + __ Vmov(temp1, op1); + __ Vmov(temp2, op2); + if (is_min) { + __ Orr(temp1, temp1, temp2); + } else { + __ And(temp1, temp1, temp2); + } + __ Vmov(out, temp1); + __ B(&done); + + // handle NaN input. + __ Bind(&nan); + __ Movt(temp1, High16Bits(kNanFloat)); // 0x7FC0xxxx is a NaN. + __ Vmov(out, temp1); + + __ Bind(&done); +} + +static void CreateFPFPToFPLocations(ArenaAllocator* arena, HInvoke* invoke) { + LocationSummary* locations = new (arena) LocationSummary(invoke, + LocationSummary::kNoCall, + kIntrinsified); + locations->SetInAt(0, Location::RequiresFpuRegister()); + locations->SetInAt(1, Location::RequiresFpuRegister()); + locations->SetOut(Location::SameAsFirstInput()); +} + +void IntrinsicLocationsBuilderARMVIXL::VisitMathMinFloatFloat(HInvoke* invoke) { + CreateFPFPToFPLocations(arena_, invoke); + invoke->GetLocations()->AddTemp(Location::RequiresRegister()); +} + +void IntrinsicCodeGeneratorARMVIXL::VisitMathMinFloatFloat(HInvoke* invoke) { + GenMinMaxFloat(invoke, /* is_min */ true, GetAssembler()); +} + +void IntrinsicLocationsBuilderARMVIXL::VisitMathMaxFloatFloat(HInvoke* invoke) { + CreateFPFPToFPLocations(arena_, invoke); + invoke->GetLocations()->AddTemp(Location::RequiresRegister()); +} + +void IntrinsicCodeGeneratorARMVIXL::VisitMathMaxFloatFloat(HInvoke* invoke) { + GenMinMaxFloat(invoke, /* is_min */ false, GetAssembler()); +} + +static void GenMinMaxDouble(HInvoke* invoke, bool is_min, ArmVIXLAssembler* assembler) { + Location op1_loc = invoke->GetLocations()->InAt(0); + Location op2_loc = invoke->GetLocations()->InAt(1); + Location out_loc = invoke->GetLocations()->Out(); + + // Optimization: don't generate any code if inputs are the same. + if (op1_loc.Equals(op2_loc)) { + DCHECK(out_loc.Equals(op1_loc)); // out_loc is set as SameAsFirstInput() in. + return; + } + + vixl32::DRegister op1 = DRegisterFrom(op1_loc); + vixl32::DRegister op2 = DRegisterFrom(op2_loc); + vixl32::DRegister out = OutputDRegister(invoke); + vixl32::Label handle_nan_eq, done; + + DCHECK(op1.Is(out)); + + __ Vcmp(op1, op2); + __ Vmrs(RegisterOrAPSR_nzcv(kPcCode), FPSCR); + __ B(vs, &handle_nan_eq, /* far_target */ false); // if un-ordered, go to NaN handling. + + // op1 <> op2 + vixl32::ConditionType cond = is_min ? gt : lt; + { + ExactAssemblyScope it_scope(assembler->GetVIXLAssembler(), + 2 * kMaxInstructionSizeInBytes, + CodeBufferCheckScope::kMaximumSize); + __ it(cond); + __ vmov(cond, F64, out, op2); + } + __ B(ne, &done, /* far_target */ false); // for <>(not equal), we've done min/max calculation. + + // handle op1 == op2, max(+0.0,-0.0). + if (!is_min) { + __ Vand(F64, out, op1, op2); + __ B(&done); + } + + // handle op1 == op2, min(+0.0,-0.0), NaN input. + __ Bind(&handle_nan_eq); + __ Vorr(F64, out, op1, op2); // assemble op1/-0.0/NaN. + + __ Bind(&done); +} + +void IntrinsicLocationsBuilderARMVIXL::VisitMathMinDoubleDouble(HInvoke* invoke) { + CreateFPFPToFPLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorARMVIXL::VisitMathMinDoubleDouble(HInvoke* invoke) { + GenMinMaxDouble(invoke, /* is_min */ true , GetAssembler()); +} + +void IntrinsicLocationsBuilderARMVIXL::VisitMathMaxDoubleDouble(HInvoke* invoke) { + CreateFPFPToFPLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorARMVIXL::VisitMathMaxDoubleDouble(HInvoke* invoke) { + GenMinMaxDouble(invoke, /* is_min */ false, GetAssembler()); +} + +static void GenMinMaxLong(HInvoke* invoke, bool is_min, ArmVIXLAssembler* assembler) { + Location op1_loc = invoke->GetLocations()->InAt(0); + Location op2_loc = invoke->GetLocations()->InAt(1); + Location out_loc = invoke->GetLocations()->Out(); + + // Optimization: don't generate any code if inputs are the same. + if (op1_loc.Equals(op2_loc)) { + DCHECK(out_loc.Equals(op1_loc)); // out_loc is set as SameAsFirstInput() in location builder. + return; + } + + vixl32::Register op1_lo = LowRegisterFrom(op1_loc); + vixl32::Register op1_hi = HighRegisterFrom(op1_loc); + vixl32::Register op2_lo = LowRegisterFrom(op2_loc); + vixl32::Register op2_hi = HighRegisterFrom(op2_loc); + vixl32::Register out_lo = LowRegisterFrom(out_loc); + vixl32::Register out_hi = HighRegisterFrom(out_loc); + UseScratchRegisterScope temps(assembler->GetVIXLAssembler()); + const vixl32::Register temp = temps.Acquire(); + + DCHECK(op1_lo.Is(out_lo)); + DCHECK(op1_hi.Is(out_hi)); + + // Compare op1 >= op2, or op1 < op2. + __ Cmp(out_lo, op2_lo); + __ Sbcs(temp, out_hi, op2_hi); + + // Now GE/LT condition code is correct for the long comparison. + { + vixl32::ConditionType cond = is_min ? ge : lt; + ExactAssemblyScope it_scope(assembler->GetVIXLAssembler(), + 3 * kMaxInstructionSizeInBytes, + CodeBufferCheckScope::kMaximumSize); + __ itt(cond); + __ mov(cond, out_lo, op2_lo); + __ mov(cond, out_hi, op2_hi); + } +} + +static void CreateLongLongToLongLocations(ArenaAllocator* arena, HInvoke* invoke) { + LocationSummary* locations = new (arena) LocationSummary(invoke, + LocationSummary::kNoCall, + kIntrinsified); + locations->SetInAt(0, Location::RequiresRegister()); + locations->SetInAt(1, Location::RequiresRegister()); + locations->SetOut(Location::SameAsFirstInput()); +} + +void IntrinsicLocationsBuilderARMVIXL::VisitMathMinLongLong(HInvoke* invoke) { + CreateLongLongToLongLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorARMVIXL::VisitMathMinLongLong(HInvoke* invoke) { + GenMinMaxLong(invoke, /* is_min */ true, GetAssembler()); +} + +void IntrinsicLocationsBuilderARMVIXL::VisitMathMaxLongLong(HInvoke* invoke) { + CreateLongLongToLongLocations(arena_, invoke); +} + +void IntrinsicCodeGeneratorARMVIXL::VisitMathMaxLongLong(HInvoke* invoke) { + GenMinMaxLong(invoke, /* is_min */ false, GetAssembler()); +} + static void GenMinMax(HInvoke* invoke, bool is_min, ArmVIXLAssembler* assembler) { vixl32::Register op1 = InputRegisterAt(invoke, 0); vixl32::Register op2 = InputRegisterAt(invoke, 1); @@ -514,6 +724,18 @@ void IntrinsicCodeGeneratorARMVIXL::VisitMathSqrt(HInvoke* invoke) { __ Vsqrt(OutputDRegister(invoke), InputDRegisterAt(invoke, 0)); } +void IntrinsicLocationsBuilderARMVIXL::VisitMathRint(HInvoke* invoke) { + if (features_.HasARMv8AInstructions()) { + CreateFPToFPLocations(arena_, invoke); + } +} + +void IntrinsicCodeGeneratorARMVIXL::VisitMathRint(HInvoke* invoke) { + DCHECK(codegen_->GetInstructionSetFeatures().HasARMv8AInstructions()); + ArmVIXLAssembler* assembler = GetAssembler(); + __ Vrintn(F64, F64, OutputDRegister(invoke), InputDRegisterAt(invoke, 0)); +} + void IntrinsicLocationsBuilderARMVIXL::VisitMemoryPeekByte(HInvoke* invoke) { CreateIntToIntLocations(arena_, invoke); } @@ -2742,15 +2964,30 @@ void IntrinsicCodeGeneratorARMVIXL::VisitReferenceGetReferent(HInvoke* invoke) { __ Bind(slow_path->GetExitLabel()); } -UNIMPLEMENTED_INTRINSIC(ARMVIXL, MathMinDoubleDouble) -UNIMPLEMENTED_INTRINSIC(ARMVIXL, MathMinFloatFloat) -UNIMPLEMENTED_INTRINSIC(ARMVIXL, MathMaxDoubleDouble) -UNIMPLEMENTED_INTRINSIC(ARMVIXL, MathMaxFloatFloat) -UNIMPLEMENTED_INTRINSIC(ARMVIXL, MathMinLongLong) -UNIMPLEMENTED_INTRINSIC(ARMVIXL, MathMaxLongLong) -UNIMPLEMENTED_INTRINSIC(ARMVIXL, MathCeil) // Could be done by changing rounding mode, maybe? -UNIMPLEMENTED_INTRINSIC(ARMVIXL, MathFloor) // Could be done by changing rounding mode, maybe? -UNIMPLEMENTED_INTRINSIC(ARMVIXL, MathRint) +void IntrinsicLocationsBuilderARMVIXL::VisitMathCeil(HInvoke* invoke) { + if (features_.HasARMv8AInstructions()) { + CreateFPToFPLocations(arena_, invoke); + } +} + +void IntrinsicCodeGeneratorARMVIXL::VisitMathCeil(HInvoke* invoke) { + ArmVIXLAssembler* assembler = GetAssembler(); + DCHECK(codegen_->GetInstructionSetFeatures().HasARMv8AInstructions()); + __ Vrintp(F64, F64, OutputDRegister(invoke), InputDRegisterAt(invoke, 0)); +} + +void IntrinsicLocationsBuilderARMVIXL::VisitMathFloor(HInvoke* invoke) { + if (features_.HasARMv8AInstructions()) { + CreateFPToFPLocations(arena_, invoke); + } +} + +void IntrinsicCodeGeneratorARMVIXL::VisitMathFloor(HInvoke* invoke) { + ArmVIXLAssembler* assembler = GetAssembler(); + DCHECK(codegen_->GetInstructionSetFeatures().HasARMv8AInstructions()); + __ Vrintm(F64, F64, OutputDRegister(invoke), InputDRegisterAt(invoke, 0)); +} + UNIMPLEMENTED_INTRINSIC(ARMVIXL, MathRoundDouble) // Could be done by changing rounding mode, maybe? UNIMPLEMENTED_INTRINSIC(ARMVIXL, MathRoundFloat) // Could be done by changing rounding mode, maybe? UNIMPLEMENTED_INTRINSIC(ARMVIXL, UnsafeCASLong) // High register pressure. diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc index d15145e673..abbb91a1a9 100644 --- a/compiler/optimizing/nodes.cc +++ b/compiler/optimizing/nodes.cc @@ -1354,13 +1354,15 @@ std::ostream& operator<<(std::ostream& os, const HInstruction::InstructionKind& return os; } -void HInstruction::MoveBefore(HInstruction* cursor) { - DCHECK(!IsPhi()); - DCHECK(!IsControlFlow()); - DCHECK(CanBeMoved() || - // HShouldDeoptimizeFlag can only be moved by CHAGuardOptimization. - IsShouldDeoptimizeFlag()); - DCHECK(!cursor->IsPhi()); +void HInstruction::MoveBefore(HInstruction* cursor, bool do_checks) { + if (do_checks) { + DCHECK(!IsPhi()); + DCHECK(!IsControlFlow()); + DCHECK(CanBeMoved() || + // HShouldDeoptimizeFlag can only be moved by CHAGuardOptimization. + IsShouldDeoptimizeFlag()); + DCHECK(!cursor->IsPhi()); + } next_->previous_ = previous_; if (previous_ != nullptr) { @@ -2462,16 +2464,15 @@ bool HLoadClass::InstructionDataEquals(const HInstruction* other) const { } } -void HLoadClass::SetLoadKindInternal(LoadKind load_kind) { - // Once sharpened, the load kind should not be changed again. - // Also, kReferrersClass should never be overwritten. - DCHECK_EQ(GetLoadKind(), LoadKind::kDexCacheViaMethod); +void HLoadClass::SetLoadKind(LoadKind load_kind) { SetPackedField<LoadKindField>(load_kind); - if (load_kind != LoadKind::kDexCacheViaMethod) { + if (load_kind != LoadKind::kDexCacheViaMethod && + load_kind != LoadKind::kReferrersClass) { RemoveAsUserOfInput(0u); SetRawInputAt(0u, nullptr); } + if (!NeedsEnvironment()) { RemoveEnvironment(); SetSideEffects(SideEffects::None()); diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index f0ea9e20e6..96f9abafbf 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -2065,8 +2065,8 @@ class HInstruction : public ArenaObject<kArenaAllocInstruction> { other->ReplaceInput(this, use_index); } - // Move `this` instruction before `cursor`. - void MoveBefore(HInstruction* cursor); + // Move `this` instruction before `cursor` + void MoveBefore(HInstruction* cursor, bool do_checks = true); // Move `this` before its first user and out of any loops. If there is no // out-of-loop user that dominates all other users, move the instruction @@ -4322,6 +4322,11 @@ class HInvokeInterface FINAL : public HInvoke { return (obj == InputAt(0)) && !GetLocations()->Intrinsified(); } + bool NeedsDexCacheOfDeclaringClass() const OVERRIDE { + // The assembly stub currently needs it. + return true; + } + uint32_t GetImtIndex() const { return imt_index_; } uint32_t GetDexMethodIndex() const { return dex_method_index_; } @@ -5508,6 +5513,9 @@ class HLoadClass FINAL : public HInstruction { public: // Determines how to load the Class. enum class LoadKind { + // We cannot load this class. See HSharpening::SharpenLoadClass. + kInvalid = -1, + // Use the Class* from the method's own ArtMethod*. kReferrersClass, @@ -5564,18 +5572,7 @@ class HLoadClass FINAL : public HInstruction { SetPackedFlag<kFlagGenerateClInitCheck>(false); } - void SetLoadKind(LoadKind load_kind) { - SetLoadKindInternal(load_kind); - } - - void SetLoadKindWithTypeReference(LoadKind load_kind, - const DexFile& dex_file, - dex::TypeIndex type_index) { - DCHECK(HasTypeReference(load_kind)); - DCHECK(IsSameDexFile(dex_file_, dex_file)); - DCHECK_EQ(type_index_, type_index); - SetLoadKindInternal(load_kind); - } + void SetLoadKind(LoadKind load_kind); LoadKind GetLoadKind() const { return GetPackedField<LoadKindField>(); @@ -5694,6 +5691,11 @@ class HLoadClass FINAL : public HInstruction { // for PC-relative loads, i.e. kBssEntry or kBootImageLinkTimePcRelative. HUserRecord<HInstruction*> special_input_; + // A type index and dex file where the class can be accessed. The dex file can be: + // - The compiling method's dex file if the class is defined there too. + // - The compiling method's dex file if the class is referenced there. + // - The dex file where the class is defined. When the load kind can only be + // kBssEntry or kDexCacheViaMethod, we cannot emit code for this `HLoadClass`. const dex::TypeIndex type_index_; const DexFile& dex_file_; diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc index 297500b12f..727ca7d893 100644 --- a/compiler/optimizing/optimizing_compiler.cc +++ b/compiler/optimizing/optimizing_compiler.cc @@ -90,6 +90,7 @@ #include "reference_type_propagation.h" #include "register_allocator_linear_scan.h" #include "select_generator.h" +#include "scheduler.h" #include "sharpening.h" #include "side_effects_analysis.h" #include "ssa_builder.h" @@ -658,10 +659,13 @@ void OptimizingCompiler::RunArchOptimizations(InstructionSet instruction_set, new (arena) arm64::InstructionSimplifierArm64(graph, stats); SideEffectsAnalysis* side_effects = new (arena) SideEffectsAnalysis(graph); GVNOptimization* gvn = new (arena) GVNOptimization(graph, *side_effects, "GVN$after_arch"); + HInstructionScheduling* scheduling = + new (arena) HInstructionScheduling(graph, instruction_set); HOptimization* arm64_optimizations[] = { simplifier, side_effects, - gvn + gvn, + scheduling, }; RunOptimizations(arm64_optimizations, arraysize(arm64_optimizations), pass_observer); break; @@ -995,6 +999,7 @@ CodeGenerator* OptimizingCompiler::TryCompile(ArenaAllocator* arena, &dex_file, *code_item, compiler_driver, + codegen.get(), compilation_stats_.get(), interpreter_metadata, dex_cache, @@ -1129,6 +1134,25 @@ bool IsCompilingWithCoreImage() { return false; } +bool EncodeArtMethodInInlineInfo(ArtMethod* method ATTRIBUTE_UNUSED) { + // Note: the runtime is null only for unit testing. + return Runtime::Current() == nullptr || !Runtime::Current()->IsAotCompiler(); +} + +bool CanEncodeInlinedMethodInStackMap(const DexFile& caller_dex_file, ArtMethod* callee) { + if (!Runtime::Current()->IsAotCompiler()) { + // JIT can always encode methods in stack maps. + return true; + } + if (IsSameDexFile(caller_dex_file, *callee->GetDexFile())) { + return true; + } + // TODO(ngeoffray): Support more AOT cases for inlining: + // - methods in multidex + // - methods in boot image for on-device non-PIC compilation. + return false; +} + bool OptimizingCompiler::JitCompile(Thread* self, jit::JitCodeCache* code_cache, ArtMethod* method, diff --git a/compiler/optimizing/optimizing_compiler.h b/compiler/optimizing/optimizing_compiler.h index 0c89da12e8..d8cea30a6b 100644 --- a/compiler/optimizing/optimizing_compiler.h +++ b/compiler/optimizing/optimizing_compiler.h @@ -17,10 +17,15 @@ #ifndef ART_COMPILER_OPTIMIZING_OPTIMIZING_COMPILER_H_ #define ART_COMPILER_OPTIMIZING_OPTIMIZING_COMPILER_H_ +#include "base/mutex.h" +#include "globals.h" + namespace art { +class ArtMethod; class Compiler; class CompilerDriver; +class DexFile; Compiler* CreateOptimizingCompiler(CompilerDriver* driver); @@ -29,6 +34,10 @@ Compiler* CreateOptimizingCompiler(CompilerDriver* driver); // information for checking invariants. bool IsCompilingWithCoreImage(); +bool EncodeArtMethodInInlineInfo(ArtMethod* method); +bool CanEncodeInlinedMethodInStackMap(const DexFile& caller_dex_file, ArtMethod* callee) + REQUIRES_SHARED(Locks::mutator_lock_); + } // namespace art #endif // ART_COMPILER_OPTIMIZING_OPTIMIZING_COMPILER_H_ diff --git a/compiler/optimizing/optimizing_unit_test.h b/compiler/optimizing/optimizing_unit_test.h index 58d90176cd..bf963b8996 100644 --- a/compiler/optimizing/optimizing_unit_test.h +++ b/compiler/optimizing/optimizing_unit_test.h @@ -64,6 +64,9 @@ LiveInterval* BuildInterval(const size_t ranges[][2], void RemoveSuspendChecks(HGraph* graph) { for (HBasicBlock* block : graph->GetBlocks()) { if (block != nullptr) { + if (block->GetLoopInformation() != nullptr) { + block->GetLoopInformation()->SetSuspendCheck(nullptr); + } for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) { HInstruction* current = it.Current(); if (current->IsSuspendCheck()) { diff --git a/compiler/optimizing/scheduler.cc b/compiler/optimizing/scheduler.cc new file mode 100644 index 0000000000..d65d20cf43 --- /dev/null +++ b/compiler/optimizing/scheduler.cc @@ -0,0 +1,610 @@ +/* + * 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 <string> + +#include "prepare_for_register_allocation.h" +#include "scheduler.h" + +#ifdef ART_ENABLE_CODEGEN_arm64 +#include "scheduler_arm64.h" +#endif + +namespace art { + +void SchedulingGraph::AddDependency(SchedulingNode* node, + SchedulingNode* dependency, + bool is_data_dependency) { + if (node == nullptr || dependency == nullptr) { + // A `nullptr` node indicates an instruction out of scheduling range (eg. in + // an other block), so we do not need to add a dependency edge to the graph. + return; + } + + if (is_data_dependency) { + if (!HasImmediateDataDependency(node, dependency)) { + node->AddDataPredecessor(dependency); + } + } else if (!HasImmediateOtherDependency(node, dependency)) { + node->AddOtherPredecessor(dependency); + } +} + +static bool MayHaveReorderingDependency(SideEffects node, SideEffects other) { + // Read after write. + if (node.MayDependOn(other)) { + return true; + } + + // Write after read. + if (other.MayDependOn(node)) { + return true; + } + + // Memory write after write. + if (node.DoesAnyWrite() && other.DoesAnyWrite()) { + return true; + } + + return false; +} + + +// Check whether `node` depends on `other`, taking into account `SideEffect` +// information and `CanThrow` information. +static bool HasSideEffectDependency(const HInstruction* node, const HInstruction* other) { + if (MayHaveReorderingDependency(node->GetSideEffects(), other->GetSideEffects())) { + return true; + } + + if (other->CanThrow() && node->GetSideEffects().DoesAnyWrite()) { + return true; + } + + if (other->GetSideEffects().DoesAnyWrite() && node->CanThrow()) { + return true; + } + + if (other->CanThrow() && node->CanThrow()) { + return true; + } + + // Check side-effect dependency between ArrayGet and BoundsCheck. + if (node->IsArrayGet() && other->IsBoundsCheck() && node->InputAt(1) == other) { + return true; + } + + return false; +} + +void SchedulingGraph::AddDependencies(HInstruction* instruction, bool is_scheduling_barrier) { + SchedulingNode* instruction_node = GetNode(instruction); + + // Define-use dependencies. + for (const HUseListNode<HInstruction*>& use : instruction->GetUses()) { + AddDataDependency(GetNode(use.GetUser()), instruction_node); + } + + // Scheduling barrier dependencies. + DCHECK(!is_scheduling_barrier || contains_scheduling_barrier_); + if (contains_scheduling_barrier_) { + // A barrier depends on instructions after it. And instructions before the + // barrier depend on it. + for (HInstruction* other = instruction->GetNext(); other != nullptr; other = other->GetNext()) { + SchedulingNode* other_node = GetNode(other); + bool other_is_barrier = other_node->IsSchedulingBarrier(); + if (is_scheduling_barrier || other_is_barrier) { + AddOtherDependency(other_node, instruction_node); + } + if (other_is_barrier) { + // This other scheduling barrier guarantees ordering of instructions after + // it, so avoid creating additional useless dependencies in the graph. + // For example if we have + // instr_1 + // barrier_2 + // instr_3 + // barrier_4 + // instr_5 + // we only create the following non-data dependencies + // 1 -> 2 + // 2 -> 3 + // 2 -> 4 + // 3 -> 4 + // 4 -> 5 + // and do not create + // 1 -> 4 + // 2 -> 5 + // Note that in this example we could also avoid creating the dependency + // `2 -> 4`. But if we remove `instr_3` that dependency is required to + // order the barriers. So we generate it to avoid a special case. + break; + } + } + } + + // Side effect dependencies. + if (!instruction->GetSideEffects().DoesNothing() || instruction->CanThrow()) { + for (HInstruction* other = instruction->GetNext(); other != nullptr; other = other->GetNext()) { + SchedulingNode* other_node = GetNode(other); + if (other_node->IsSchedulingBarrier()) { + // We have reached a scheduling barrier so we can stop further + // processing. + DCHECK(HasImmediateOtherDependency(other_node, instruction_node)); + break; + } + if (HasSideEffectDependency(other, instruction)) { + AddOtherDependency(other_node, instruction_node); + } + } + } + + // Environment dependencies. + // We do not need to process those if the instruction is a scheduling barrier, + // since the barrier already has non-data dependencies on all following + // instructions. + if (!is_scheduling_barrier) { + for (const HUseListNode<HEnvironment*>& use : instruction->GetEnvUses()) { + // Note that here we could stop processing if the environment holder is + // across a scheduling barrier. But checking this would likely require + // more work than simply iterating through environment uses. + AddOtherDependency(GetNode(use.GetUser()->GetHolder()), instruction_node); + } + } +} + +bool SchedulingGraph::HasImmediateDataDependency(const SchedulingNode* node, + const SchedulingNode* other) const { + return ContainsElement(node->GetDataPredecessors(), other); +} + +bool SchedulingGraph::HasImmediateDataDependency(const HInstruction* instruction, + const HInstruction* other_instruction) const { + const SchedulingNode* node = GetNode(instruction); + const SchedulingNode* other = GetNode(other_instruction); + if (node == nullptr || other == nullptr) { + // Both instructions must be in current basic block, i.e. the SchedulingGraph can see their + // corresponding SchedulingNode in the graph, and tell whether there is a dependency. + // Otherwise there is no dependency from SchedulingGraph's perspective, for example, + // instruction and other_instruction are in different basic blocks. + return false; + } + return HasImmediateDataDependency(node, other); +} + +bool SchedulingGraph::HasImmediateOtherDependency(const SchedulingNode* node, + const SchedulingNode* other) const { + return ContainsElement(node->GetOtherPredecessors(), other); +} + +bool SchedulingGraph::HasImmediateOtherDependency(const HInstruction* instruction, + const HInstruction* other_instruction) const { + const SchedulingNode* node = GetNode(instruction); + const SchedulingNode* other = GetNode(other_instruction); + if (node == nullptr || other == nullptr) { + // Both instructions must be in current basic block, i.e. the SchedulingGraph can see their + // corresponding SchedulingNode in the graph, and tell whether there is a dependency. + // Otherwise there is no dependency from SchedulingGraph's perspective, for example, + // instruction and other_instruction are in different basic blocks. + return false; + } + return HasImmediateOtherDependency(node, other); +} + +static const std::string InstructionTypeId(const HInstruction* instruction) { + std::string id; + Primitive::Type type = instruction->GetType(); + if (type == Primitive::kPrimNot) { + id.append("l"); + } else { + id.append(Primitive::Descriptor(instruction->GetType())); + } + // Use lower-case to be closer to the `HGraphVisualizer` output. + id[0] = std::tolower(id[0]); + id.append(std::to_string(instruction->GetId())); + return id; +} + +// Ideally we would reuse the graph visualizer code, but it is not available +// from here and it is not worth moving all that code only for our use. +static void DumpAsDotNode(std::ostream& output, const SchedulingNode* node) { + const HInstruction* instruction = node->GetInstruction(); + // Use the instruction typed id as the node identifier. + std::string instruction_id = InstructionTypeId(instruction); + output << instruction_id << "[shape=record, label=\"" + << instruction_id << ' ' << instruction->DebugName() << " ["; + // List the instruction's inputs in its description. When visualizing the + // graph this helps differentiating data inputs from other dependencies. + const char* seperator = ""; + for (const HInstruction* input : instruction->GetInputs()) { + output << seperator << InstructionTypeId(input); + seperator = ","; + } + output << "]"; + // Other properties of the node. + output << "\\ninternal_latency: " << node->GetInternalLatency(); + output << "\\ncritical_path: " << node->GetCriticalPath(); + if (node->IsSchedulingBarrier()) { + output << "\\n(barrier)"; + } + output << "\"];\n"; + // We want program order to go from top to bottom in the graph output, so we + // reverse the edges and specify `dir=back`. + for (const SchedulingNode* predecessor : node->GetDataPredecessors()) { + const HInstruction* predecessor_instruction = predecessor->GetInstruction(); + output << InstructionTypeId(predecessor_instruction) << ":s -> " << instruction_id << ":n " + << "[label=\"" << predecessor->GetLatency() << "\",dir=back]\n"; + } + for (const SchedulingNode* predecessor : node->GetOtherPredecessors()) { + const HInstruction* predecessor_instruction = predecessor->GetInstruction(); + output << InstructionTypeId(predecessor_instruction) << ":s -> " << instruction_id << ":n " + << "[dir=back,color=blue]\n"; + } +} + +void SchedulingGraph::DumpAsDotGraph(const std::string& description, + const ArenaVector<SchedulingNode*>& initial_candidates) { + // TODO(xueliang): ideally we should move scheduling information into HInstruction, after that + // we should move this dotty graph dump feature to visualizer, and have a compiler option for it. + std::ofstream output("scheduling_graphs.dot", std::ofstream::out | std::ofstream::app); + // Description of this graph, as a comment. + output << "// " << description << "\n"; + // Start the dot graph. Use an increasing index for easier differentiation. + output << "digraph G {\n"; + for (const auto& entry : nodes_map_) { + DumpAsDotNode(output, entry.second); + } + // Create a fake 'end_of_scheduling' node to help visualization of critical_paths. + for (auto node : initial_candidates) { + const HInstruction* instruction = node->GetInstruction(); + output << InstructionTypeId(instruction) << ":s -> end_of_scheduling:n " + << "[label=\"" << node->GetLatency() << "\",dir=back]\n"; + } + // End of the dot graph. + output << "}\n"; + output.close(); +} + +SchedulingNode* CriticalPathSchedulingNodeSelector::SelectMaterializedCondition( + ArenaVector<SchedulingNode*>* nodes, const SchedulingGraph& graph) const { + // Schedule condition inputs that can be materialized immediately before their use. + // In following example, after we've scheduled HSelect, we want LessThan to be scheduled + // immediately, because it is a materialized condition, and will be emitted right before HSelect + // in codegen phase. + // + // i20 HLessThan [...] HLessThan HAdd HAdd + // i21 HAdd [...] ===> | | | + // i22 HAdd [...] +----------+---------+ + // i23 HSelect [i21, i22, i20] HSelect + + if (prev_select_ == nullptr) { + return nullptr; + } + + const HInstruction* instruction = prev_select_->GetInstruction(); + const HCondition* condition = nullptr; + DCHECK(instruction != nullptr); + + if (instruction->IsIf()) { + condition = instruction->AsIf()->InputAt(0)->AsCondition(); + } else if (instruction->IsSelect()) { + condition = instruction->AsSelect()->GetCondition()->AsCondition(); + } + + SchedulingNode* condition_node = (condition != nullptr) ? graph.GetNode(condition) : nullptr; + + if ((condition_node != nullptr) && + condition->HasOnlyOneNonEnvironmentUse() && + ContainsElement(*nodes, condition_node)) { + DCHECK(!condition_node->HasUnscheduledSuccessors()); + // Remove the condition from the list of candidates and schedule it. + RemoveElement(*nodes, condition_node); + return condition_node; + } + + return nullptr; +} + +SchedulingNode* CriticalPathSchedulingNodeSelector::PopHighestPriorityNode( + ArenaVector<SchedulingNode*>* nodes, const SchedulingGraph& graph) { + DCHECK(!nodes->empty()); + SchedulingNode* select_node = nullptr; + + // Optimize for materialized condition and its emit before use scenario. + select_node = SelectMaterializedCondition(nodes, graph); + + if (select_node == nullptr) { + // Get highest priority node based on critical path information. + select_node = (*nodes)[0]; + size_t select = 0; + for (size_t i = 1, e = nodes->size(); i < e; i++) { + SchedulingNode* check = (*nodes)[i]; + SchedulingNode* candidate = (*nodes)[select]; + select_node = GetHigherPrioritySchedulingNode(candidate, check); + if (select_node == check) { + select = i; + } + } + DeleteNodeAtIndex(nodes, select); + } + + prev_select_ = select_node; + return select_node; +} + +SchedulingNode* CriticalPathSchedulingNodeSelector::GetHigherPrioritySchedulingNode( + SchedulingNode* candidate, SchedulingNode* check) const { + uint32_t candidate_path = candidate->GetCriticalPath(); + uint32_t check_path = check->GetCriticalPath(); + // First look at the critical_path. + if (check_path != candidate_path) { + return check_path < candidate_path ? check : candidate; + } + // If both critical paths are equal, schedule instructions with a higher latency + // first in program order. + return check->GetLatency() < candidate->GetLatency() ? check : candidate; +} + +void HScheduler::Schedule(HGraph* graph) { + for (HBasicBlock* block : graph->GetReversePostOrder()) { + if (IsSchedulable(block)) { + Schedule(block); + } + } +} + +void HScheduler::Schedule(HBasicBlock* block) { + ArenaVector<SchedulingNode*> scheduling_nodes(arena_->Adapter(kArenaAllocScheduler)); + + // Build the scheduling graph. + scheduling_graph_.Clear(); + for (HBackwardInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) { + HInstruction* instruction = it.Current(); + SchedulingNode* node = scheduling_graph_.AddNode(instruction, IsSchedulingBarrier(instruction)); + CalculateLatency(node); + scheduling_nodes.push_back(node); + } + + if (scheduling_graph_.Size() <= 1) { + scheduling_graph_.Clear(); + return; + } + + cursor_ = block->GetLastInstruction(); + + // Find the initial candidates for scheduling. + candidates_.clear(); + for (SchedulingNode* node : scheduling_nodes) { + if (!node->HasUnscheduledSuccessors()) { + node->MaybeUpdateCriticalPath(node->GetLatency()); + candidates_.push_back(node); + } + } + + ArenaVector<SchedulingNode*> initial_candidates(arena_->Adapter(kArenaAllocScheduler)); + if (kDumpDotSchedulingGraphs) { + // Remember the list of initial candidates for debug output purposes. + initial_candidates.assign(candidates_.begin(), candidates_.end()); + } + + // Schedule all nodes. + while (!candidates_.empty()) { + Schedule(selector_->PopHighestPriorityNode(&candidates_, scheduling_graph_)); + } + + if (kDumpDotSchedulingGraphs) { + // Dump the graph in `dot` format. + HGraph* graph = block->GetGraph(); + std::stringstream description; + description << graph->GetDexFile().PrettyMethod(graph->GetMethodIdx()) + << " B" << block->GetBlockId(); + scheduling_graph_.DumpAsDotGraph(description.str(), initial_candidates); + } +} + +void HScheduler::Schedule(SchedulingNode* scheduling_node) { + // Check whether any of the node's predecessors will be valid candidates after + // this node is scheduled. + uint32_t path_to_node = scheduling_node->GetCriticalPath(); + for (SchedulingNode* predecessor : scheduling_node->GetDataPredecessors()) { + predecessor->MaybeUpdateCriticalPath( + path_to_node + predecessor->GetInternalLatency() + predecessor->GetLatency()); + predecessor->DecrementNumberOfUnscheduledSuccessors(); + if (!predecessor->HasUnscheduledSuccessors()) { + candidates_.push_back(predecessor); + } + } + for (SchedulingNode* predecessor : scheduling_node->GetOtherPredecessors()) { + // Do not update the critical path. + // The 'other' (so 'non-data') dependencies (usually) do not represent a + // 'material' dependency of nodes on others. They exist for program + // correctness. So we do not use them to compute the critical path. + predecessor->DecrementNumberOfUnscheduledSuccessors(); + if (!predecessor->HasUnscheduledSuccessors()) { + candidates_.push_back(predecessor); + } + } + + Schedule(scheduling_node->GetInstruction()); +} + +// Move an instruction after cursor instruction inside one basic block. +static void MoveAfterInBlock(HInstruction* instruction, HInstruction* cursor) { + DCHECK_EQ(instruction->GetBlock(), cursor->GetBlock()); + DCHECK_NE(cursor, cursor->GetBlock()->GetLastInstruction()); + DCHECK(!instruction->IsControlFlow()); + DCHECK(!cursor->IsControlFlow()); + instruction->MoveBefore(cursor->GetNext(), /* do_checks */ false); +} + +void HScheduler::Schedule(HInstruction* instruction) { + if (instruction == cursor_) { + cursor_ = cursor_->GetPrevious(); + } else { + MoveAfterInBlock(instruction, cursor_); + } +} + +bool HScheduler::IsSchedulable(const HInstruction* instruction) const { + // We want to avoid exhaustively listing all instructions, so we first check + // for instruction categories that we know are safe. + if (instruction->IsControlFlow() || + instruction->IsConstant()) { + return true; + } + // Currently all unary and binary operations are safe to schedule, so avoid + // checking for each of them individually. + // Since nothing prevents a new scheduling-unsafe HInstruction to subclass + // HUnaryOperation (or HBinaryOperation), check in debug mode that we have + // the exhaustive lists here. + if (instruction->IsUnaryOperation()) { + DCHECK(instruction->IsBooleanNot() || + instruction->IsNot() || + instruction->IsNeg()) << "unexpected instruction " << instruction->DebugName(); + return true; + } + if (instruction->IsBinaryOperation()) { + DCHECK(instruction->IsAdd() || + instruction->IsAnd() || + instruction->IsCompare() || + instruction->IsCondition() || + instruction->IsDiv() || + instruction->IsMul() || + instruction->IsOr() || + instruction->IsRem() || + instruction->IsRor() || + instruction->IsShl() || + instruction->IsShr() || + instruction->IsSub() || + instruction->IsUShr() || + instruction->IsXor()) << "unexpected instruction " << instruction->DebugName(); + return true; + } + // The scheduler should not see any of these. + DCHECK(!instruction->IsParallelMove()) << "unexpected instruction " << instruction->DebugName(); + // List of instructions explicitly excluded: + // HClearException + // HClinitCheck + // HDeoptimize + // HLoadClass + // HLoadException + // HMemoryBarrier + // HMonitorOperation + // HNativeDebugInfo + // HThrow + // HTryBoundary + // TODO: Some of the instructions above may be safe to schedule (maybe as + // scheduling barriers). + return instruction->IsArrayGet() || + instruction->IsArraySet() || + instruction->IsArrayLength() || + instruction->IsBoundType() || + instruction->IsBoundsCheck() || + instruction->IsCheckCast() || + instruction->IsClassTableGet() || + instruction->IsCurrentMethod() || + instruction->IsDivZeroCheck() || + instruction->IsInstanceFieldGet() || + instruction->IsInstanceFieldSet() || + instruction->IsInstanceOf() || + instruction->IsInvokeInterface() || + instruction->IsInvokeStaticOrDirect() || + instruction->IsInvokeUnresolved() || + instruction->IsInvokeVirtual() || + instruction->IsLoadString() || + instruction->IsNewArray() || + instruction->IsNewInstance() || + instruction->IsNullCheck() || + instruction->IsPackedSwitch() || + instruction->IsParameterValue() || + instruction->IsPhi() || + instruction->IsReturn() || + instruction->IsReturnVoid() || + instruction->IsSelect() || + instruction->IsStaticFieldGet() || + instruction->IsStaticFieldSet() || + instruction->IsSuspendCheck() || + instruction->IsTypeConversion() || + instruction->IsUnresolvedInstanceFieldGet() || + instruction->IsUnresolvedInstanceFieldSet() || + instruction->IsUnresolvedStaticFieldGet() || + instruction->IsUnresolvedStaticFieldSet(); +} + +bool HScheduler::IsSchedulable(const HBasicBlock* block) const { + // We may be only interested in loop blocks. + if (only_optimize_loop_blocks_ && !block->IsInLoop()) { + return false; + } + if (block->GetTryCatchInformation() != nullptr) { + // Do not schedule blocks that are part of try-catch. + // Because scheduler cannot see if catch block has assumptions on the instruction order in + // the try block. In following example, if we enable scheduler for the try block, + // MulitiplyAccumulate may be scheduled before DivZeroCheck, + // which can result in an incorrect value in the catch block. + // try { + // a = a/b; // DivZeroCheck + // // Div + // c = c*d+e; // MulitiplyAccumulate + // } catch {System.out.print(c); } + return false; + } + // Check whether all instructions in this block are schedulable. + for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) { + if (!IsSchedulable(it.Current())) { + return false; + } + } + return true; +} + +bool HScheduler::IsSchedulingBarrier(const HInstruction* instr) const { + return instr->IsControlFlow() || + // Don't break calling convention. + instr->IsParameterValue() || + // Code generation of goto relies on SuspendCheck's position. + instr->IsSuspendCheck(); +} + +void HInstructionScheduling::Run(bool only_optimize_loop_blocks, + bool schedule_randomly) { + // Avoid compilation error when compiling for unsupported instruction set. + UNUSED(only_optimize_loop_blocks); + UNUSED(schedule_randomly); + switch (instruction_set_) { +#ifdef ART_ENABLE_CODEGEN_arm64 + case kArm64: { + // Phase-local allocator that allocates scheduler internal data structures like + // scheduling nodes, internel nodes map, dependencies, etc. + ArenaAllocator arena_allocator(graph_->GetArena()->GetArenaPool()); + + CriticalPathSchedulingNodeSelector critical_path_selector; + RandomSchedulingNodeSelector random_selector; + SchedulingNodeSelector* selector = schedule_randomly + ? static_cast<SchedulingNodeSelector*>(&random_selector) + : static_cast<SchedulingNodeSelector*>(&critical_path_selector); + + arm64::HSchedulerARM64 scheduler(&arena_allocator, selector); + scheduler.SetOnlyOptimizeLoopBlocks(only_optimize_loop_blocks); + scheduler.Schedule(graph_); + break; + } +#endif + default: + break; + } +} + +} // namespace art diff --git a/compiler/optimizing/scheduler.h b/compiler/optimizing/scheduler.h new file mode 100644 index 0000000000..ab0dad4300 --- /dev/null +++ b/compiler/optimizing/scheduler.h @@ -0,0 +1,487 @@ +/* + * 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_OPTIMIZING_SCHEDULER_H_ +#define ART_COMPILER_OPTIMIZING_SCHEDULER_H_ + +#include <fstream> + +#include "base/time_utils.h" +#include "driver/compiler_driver.h" +#include "nodes.h" +#include "optimization.h" + +namespace art { + +// General description of instruction scheduling. +// +// This pass tries to improve the quality of the generated code by reordering +// instructions in the graph to avoid execution delays caused by execution +// dependencies. +// Currently, scheduling is performed at the block level, so no `HInstruction` +// ever leaves its block in this pass. +// +// The scheduling process iterates through blocks in the graph. For blocks that +// we can and want to schedule: +// 1) Build a dependency graph for instructions. +// It includes data dependencies (inputs/uses), but also environment +// dependencies and side-effect dependencies. +// 2) Schedule the dependency graph. +// This is a topological sort of the dependency graph, using heuristics to +// decide what node to scheduler first when there are multiple candidates. +// +// A few factors impacting the quality of the scheduling are: +// - The heuristics used to decide what node to schedule in the topological sort +// when there are multiple valid candidates. There is a wide range of +// complexity possible here, going from a simple model only considering +// latencies, to a super detailed CPU pipeline model. +// - Fewer dependencies in the dependency graph give more freedom for the +// scheduling heuristics. For example de-aliasing can allow possibilities for +// reordering of memory accesses. +// - The level of abstraction of the IR. It is easier to evaluate scheduling for +// IRs that translate to a single assembly instruction than for IRs +// that generate multiple assembly instructions or generate different code +// depending on properties of the IR. +// - Scheduling is performed before register allocation, it is not aware of the +// impact of moving instructions on register allocation. +// +// +// The scheduling code uses the terms predecessors, successors, and dependencies. +// This can be confusing at times, so here are clarifications. +// These terms are used from the point of view of the program dependency graph. So +// the inputs of an instruction are part of its dependencies, and hence part its +// predecessors. So the uses of an instruction are (part of) its successors. +// (Side-effect dependencies can yield predecessors or successors that are not +// inputs or uses.) +// +// Here is a trivial example. For the Java code: +// +// int a = 1 + 2; +// +// we would have the instructions +// +// i1 HIntConstant 1 +// i2 HIntConstant 2 +// i3 HAdd [i1,i2] +// +// `i1` and `i2` are predecessors of `i3`. +// `i3` is a successor of `i1` and a successor of `i2`. +// In a scheduling graph for this code we would have three nodes `n1`, `n2`, +// and `n3` (respectively for instructions `i1`, `i1`, and `i3`). +// Conceptually the program dependency graph for this would contain two edges +// +// n1 -> n3 +// n2 -> n3 +// +// Since we schedule backwards (starting from the last instruction in each basic +// block), the implementation of nodes keeps a list of pointers their +// predecessors. So `n3` would keep pointers to its predecessors `n1` and `n2`. +// +// Node dependencies are also referred to from the program dependency graph +// point of view: we say that node `B` immediately depends on `A` if there is an +// edge from `A` to `B` in the program dependency graph. `A` is a predecessor of +// `B`, `B` is a successor of `A`. In the example above `n3` depends on `n1` and +// `n2`. +// Since nodes in the scheduling graph keep a list of their predecessors, node +// `B` will have a pointer to its predecessor `A`. +// As we schedule backwards, `B` will be selected for scheduling before `A` is. +// +// So the scheduling for the example above could happen as follow +// +// |---------------------------+------------------------| +// | candidates for scheduling | instructions scheduled | +// | --------------------------+------------------------| +// +// The only node without successors is `n3`, so it is the only initial +// candidate. +// +// | n3 | (none) | +// +// We schedule `n3` as the last (and only) instruction. All its predecessors +// that do not have any unscheduled successors become candidate. That is, `n1` +// and `n2` become candidates. +// +// | n1, n2 | n3 | +// +// One of the candidates is selected. In practice this is where scheduling +// heuristics kick in, to decide which of the candidates should be selected. +// In this example, let it be `n1`. It is scheduled before previously scheduled +// nodes (in program order). There are no other nodes to add to the list of +// candidates. +// +// | n2 | n1 | +// | | n3 | +// +// The only candidate available for scheduling is `n2`. Schedule it before +// (in program order) the previously scheduled nodes. +// +// | (none) | n2 | +// | | n1 | +// | | n3 | +// |---------------------------+------------------------| +// +// So finally the instructions will be executed in the order `i2`, `i1`, and `i3`. +// In this trivial example, it does not matter which of `i1` and `i2` is +// scheduled first since they are constants. However the same process would +// apply if `i1` and `i2` were actual operations (for example `HMul` and `HDiv`). + +// Set to true to have instruction scheduling dump scheduling graphs to the file +// `scheduling_graphs.dot`. See `SchedulingGraph::DumpAsDotGraph()`. +static constexpr bool kDumpDotSchedulingGraphs = false; + +// Typically used as a default instruction latency. +static constexpr uint32_t kGenericInstructionLatency = 1; + +class HScheduler; + +/** + * A node representing an `HInstruction` in the `SchedulingGraph`. + */ +class SchedulingNode : public ArenaObject<kArenaAllocScheduler> { + public: + SchedulingNode(HInstruction* instr, ArenaAllocator* arena, bool is_scheduling_barrier) + : latency_(0), + internal_latency_(0), + critical_path_(0), + instruction_(instr), + is_scheduling_barrier_(is_scheduling_barrier), + data_predecessors_(arena->Adapter(kArenaAllocScheduler)), + other_predecessors_(arena->Adapter(kArenaAllocScheduler)), + num_unscheduled_successors_(0) { + data_predecessors_.reserve(kPreallocatedPredecessors); + } + + void AddDataPredecessor(SchedulingNode* predecessor) { + data_predecessors_.push_back(predecessor); + predecessor->num_unscheduled_successors_++; + } + + void AddOtherPredecessor(SchedulingNode* predecessor) { + other_predecessors_.push_back(predecessor); + predecessor->num_unscheduled_successors_++; + } + + void DecrementNumberOfUnscheduledSuccessors() { + num_unscheduled_successors_--; + } + + void MaybeUpdateCriticalPath(uint32_t other_critical_path) { + critical_path_ = std::max(critical_path_, other_critical_path); + } + + bool HasUnscheduledSuccessors() const { + return num_unscheduled_successors_ != 0; + } + + HInstruction* GetInstruction() const { return instruction_; } + uint32_t GetLatency() const { return latency_; } + void SetLatency(uint32_t latency) { latency_ = latency; } + uint32_t GetInternalLatency() const { return internal_latency_; } + void SetInternalLatency(uint32_t internal_latency) { internal_latency_ = internal_latency; } + uint32_t GetCriticalPath() const { return critical_path_; } + bool IsSchedulingBarrier() const { return is_scheduling_barrier_; } + const ArenaVector<SchedulingNode*>& GetDataPredecessors() const { return data_predecessors_; } + const ArenaVector<SchedulingNode*>& GetOtherPredecessors() const { return other_predecessors_; } + + private: + // The latency of this node. It represents the latency between the moment the + // last instruction for this node has executed to the moment the result + // produced by this node is available to users. + uint32_t latency_; + // This represents the time spent *within* the generated code for this node. + // It should be zero for nodes that only generate a single instruction. + uint32_t internal_latency_; + + // The critical path from this instruction to the end of scheduling. It is + // used by the scheduling heuristics to measure the priority of this instruction. + // It is defined as + // critical_path_ = latency_ + max((use.internal_latency_ + use.critical_path_) for all uses) + // (Note that here 'uses' is equivalent to 'data successors'. Also see comments in + // `HScheduler::Schedule(SchedulingNode* scheduling_node)`). + uint32_t critical_path_; + + // The instruction that this node represents. + HInstruction* const instruction_; + + // If a node is scheduling barrier, other nodes cannot be scheduled before it. + const bool is_scheduling_barrier_; + + // The lists of predecessors. They cannot be scheduled before this node. Once + // this node is scheduled, we check whether any of its predecessors has become a + // valid candidate for scheduling. + // Predecessors in `data_predecessors_` are data dependencies. Those in + // `other_predecessors_` contain side-effect dependencies, environment + // dependencies, and scheduling barrier dependencies. + ArenaVector<SchedulingNode*> data_predecessors_; + ArenaVector<SchedulingNode*> other_predecessors_; + + // The number of unscheduled successors for this node. This number is + // decremented as successors are scheduled. When it reaches zero this node + // becomes a valid candidate to schedule. + uint32_t num_unscheduled_successors_; + + static constexpr size_t kPreallocatedPredecessors = 4; +}; + +/* + * Directed acyclic graph for scheduling. + */ +class SchedulingGraph : public ValueObject { + public: + SchedulingGraph(const HScheduler* scheduler, ArenaAllocator* arena) + : scheduler_(scheduler), + arena_(arena), + contains_scheduling_barrier_(false), + nodes_map_(arena_->Adapter(kArenaAllocScheduler)) {} + + SchedulingNode* AddNode(HInstruction* instr, bool is_scheduling_barrier = false) { + SchedulingNode* node = new (arena_) SchedulingNode(instr, arena_, is_scheduling_barrier); + nodes_map_.Insert(std::make_pair(instr, node)); + contains_scheduling_barrier_ |= is_scheduling_barrier; + AddDependencies(instr, is_scheduling_barrier); + return node; + } + + void Clear() { + nodes_map_.Clear(); + contains_scheduling_barrier_ = false; + } + + SchedulingNode* GetNode(const HInstruction* instr) const { + auto it = nodes_map_.Find(instr); + if (it == nodes_map_.end()) { + return nullptr; + } else { + return it->second; + } + } + + bool IsSchedulingBarrier(const HInstruction* instruction) const; + + bool HasImmediateDataDependency(const SchedulingNode* node, const SchedulingNode* other) const; + bool HasImmediateDataDependency(const HInstruction* node, const HInstruction* other) const; + bool HasImmediateOtherDependency(const SchedulingNode* node, const SchedulingNode* other) const; + bool HasImmediateOtherDependency(const HInstruction* node, const HInstruction* other) const; + + size_t Size() const { + return nodes_map_.Size(); + } + + // Dump the scheduling graph, in dot file format, appending it to the file + // `scheduling_graphs.dot`. + void DumpAsDotGraph(const std::string& description, + const ArenaVector<SchedulingNode*>& initial_candidates); + + protected: + void AddDependency(SchedulingNode* node, SchedulingNode* dependency, bool is_data_dependency); + void AddDataDependency(SchedulingNode* node, SchedulingNode* dependency) { + AddDependency(node, dependency, /*is_data_dependency*/true); + } + void AddOtherDependency(SchedulingNode* node, SchedulingNode* dependency) { + AddDependency(node, dependency, /*is_data_dependency*/false); + } + + // Add dependencies nodes for the given `HInstruction`: inputs, environments, and side-effects. + void AddDependencies(HInstruction* instruction, bool is_scheduling_barrier = false); + + const HScheduler* const scheduler_; + + ArenaAllocator* const arena_; + + bool contains_scheduling_barrier_; + + ArenaHashMap<const HInstruction*, SchedulingNode*> nodes_map_; +}; + +/* + * The visitors derived from this base class are used by schedulers to evaluate + * the latencies of `HInstruction`s. + */ +class SchedulingLatencyVisitor : public HGraphDelegateVisitor { + public: + // This class and its sub-classes will never be used to drive a visit of an + // `HGraph` but only to visit `HInstructions` one at a time, so we do not need + // to pass a valid graph to `HGraphDelegateVisitor()`. + SchedulingLatencyVisitor() : HGraphDelegateVisitor(nullptr) {} + + void VisitInstruction(HInstruction* instruction) OVERRIDE { + LOG(FATAL) << "Error visiting " << instruction->DebugName() << ". " + "Architecture-specific scheduling latency visitors must handle all instructions" + " (potentially by overriding the generic `VisitInstruction()`."; + UNREACHABLE(); + } + + void Visit(HInstruction* instruction) { + instruction->Accept(this); + } + + void CalculateLatency(SchedulingNode* node) { + // By default nodes have no internal latency. + last_visited_internal_latency_ = 0; + Visit(node->GetInstruction()); + } + + uint32_t GetLastVisitedLatency() const { return last_visited_latency_; } + uint32_t GetLastVisitedInternalLatency() const { return last_visited_internal_latency_; } + + protected: + // The latency of the most recent visited SchedulingNode. + // This is for reporting the latency value to the user of this visitor. + uint32_t last_visited_latency_; + // This represents the time spent *within* the generated code for the most recent visited + // SchedulingNode. This is for reporting the internal latency value to the user of this visitor. + uint32_t last_visited_internal_latency_; +}; + +class SchedulingNodeSelector : public ArenaObject<kArenaAllocScheduler> { + public: + virtual SchedulingNode* PopHighestPriorityNode(ArenaVector<SchedulingNode*>* nodes, + const SchedulingGraph& graph) = 0; + virtual ~SchedulingNodeSelector() {} + protected: + static void DeleteNodeAtIndex(ArenaVector<SchedulingNode*>* nodes, size_t index) { + (*nodes)[index] = nodes->back(); + nodes->pop_back(); + } +}; + +/* + * Select a `SchedulingNode` at random within the candidates. + */ +class RandomSchedulingNodeSelector : public SchedulingNodeSelector { + public: + explicit RandomSchedulingNodeSelector() : seed_(0) { + seed_ = static_cast<uint32_t>(NanoTime()); + srand(seed_); + } + + SchedulingNode* PopHighestPriorityNode(ArenaVector<SchedulingNode*>* nodes, + const SchedulingGraph& graph) OVERRIDE { + UNUSED(graph); + DCHECK(!nodes->empty()); + size_t select = rand_r(&seed_) % nodes->size(); + SchedulingNode* select_node = (*nodes)[select]; + DeleteNodeAtIndex(nodes, select); + return select_node; + } + + uint32_t seed_; +}; + +/* + * Select a `SchedulingNode` according to critical path information, + * with heuristics to favor certain instruction patterns like materialized condition. + */ +class CriticalPathSchedulingNodeSelector : public SchedulingNodeSelector { + public: + CriticalPathSchedulingNodeSelector() : prev_select_(nullptr) {} + + SchedulingNode* PopHighestPriorityNode(ArenaVector<SchedulingNode*>* nodes, + const SchedulingGraph& graph) OVERRIDE; + + protected: + SchedulingNode* GetHigherPrioritySchedulingNode(SchedulingNode* candidate, + SchedulingNode* check) const; + + SchedulingNode* SelectMaterializedCondition(ArenaVector<SchedulingNode*>* nodes, + const SchedulingGraph& graph) const; + + private: + const SchedulingNode* prev_select_; +}; + +class HScheduler { + public: + HScheduler(ArenaAllocator* arena, + SchedulingLatencyVisitor* latency_visitor, + SchedulingNodeSelector* selector) + : arena_(arena), + latency_visitor_(latency_visitor), + selector_(selector), + only_optimize_loop_blocks_(true), + scheduling_graph_(this, arena), + candidates_(arena_->Adapter(kArenaAllocScheduler)) {} + virtual ~HScheduler() {} + + void Schedule(HGraph* graph); + + void SetOnlyOptimizeLoopBlocks(bool loop_only) { only_optimize_loop_blocks_ = loop_only; } + + // Instructions can not be rescheduled across a scheduling barrier. + virtual bool IsSchedulingBarrier(const HInstruction* instruction) const; + + protected: + void Schedule(HBasicBlock* block); + void Schedule(SchedulingNode* scheduling_node); + void Schedule(HInstruction* instruction); + + // Any instruction returning `false` via this method will prevent its + // containing basic block from being scheduled. + // This method is used to restrict scheduling to instructions that we know are + // safe to handle. + virtual bool IsSchedulable(const HInstruction* instruction) const; + bool IsSchedulable(const HBasicBlock* block) const; + + void CalculateLatency(SchedulingNode* node) { + latency_visitor_->CalculateLatency(node); + node->SetLatency(latency_visitor_->GetLastVisitedLatency()); + node->SetInternalLatency(latency_visitor_->GetLastVisitedInternalLatency()); + } + + ArenaAllocator* const arena_; + SchedulingLatencyVisitor* const latency_visitor_; + SchedulingNodeSelector* const selector_; + bool only_optimize_loop_blocks_; + + // We instantiate the members below as part of this class to avoid + // instantiating them locally for every chunk scheduled. + SchedulingGraph scheduling_graph_; + // A pointer indicating where the next instruction to be scheduled will be inserted. + HInstruction* cursor_; + // The list of candidates for scheduling. A node becomes a candidate when all + // its predecessors have been scheduled. + ArenaVector<SchedulingNode*> candidates_; + + private: + DISALLOW_COPY_AND_ASSIGN(HScheduler); +}; + +inline bool SchedulingGraph::IsSchedulingBarrier(const HInstruction* instruction) const { + return scheduler_->IsSchedulingBarrier(instruction); +} + +class HInstructionScheduling : public HOptimization { + public: + HInstructionScheduling(HGraph* graph, InstructionSet instruction_set) + : HOptimization(graph, kInstructionScheduling), + instruction_set_(instruction_set) {} + + void Run() { + Run(/*only_optimize_loop_blocks*/ true, /*schedule_randomly*/ false); + } + void Run(bool only_optimize_loop_blocks, bool schedule_randomly); + + static constexpr const char* kInstructionScheduling = "scheduler"; + + const InstructionSet instruction_set_; + + private: + DISALLOW_COPY_AND_ASSIGN(HInstructionScheduling); +}; + +} // namespace art + +#endif // ART_COMPILER_OPTIMIZING_SCHEDULER_H_ diff --git a/compiler/optimizing/scheduler_arm64.cc b/compiler/optimizing/scheduler_arm64.cc new file mode 100644 index 0000000000..e3701fbcb1 --- /dev/null +++ b/compiler/optimizing/scheduler_arm64.cc @@ -0,0 +1,196 @@ +/* + * 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 "scheduler_arm64.h" +#include "code_generator_utils.h" + +namespace art { +namespace arm64 { + +void SchedulingLatencyVisitorARM64::VisitBinaryOperation(HBinaryOperation* instr) { + last_visited_latency_ = Primitive::IsFloatingPointType(instr->GetResultType()) + ? kArm64FloatingPointOpLatency + : kArm64IntegerOpLatency; +} + +void SchedulingLatencyVisitorARM64::VisitBitwiseNegatedRight( + HBitwiseNegatedRight* ATTRIBUTE_UNUSED) { + last_visited_latency_ = kArm64IntegerOpLatency; +} + +void SchedulingLatencyVisitorARM64::VisitArm64DataProcWithShifterOp( + HArm64DataProcWithShifterOp* ATTRIBUTE_UNUSED) { + last_visited_latency_ = kArm64DataProcWithShifterOpLatency; +} + +void SchedulingLatencyVisitorARM64::VisitIntermediateAddress( + HIntermediateAddress* ATTRIBUTE_UNUSED) { + // Although the code generated is a simple `add` instruction, we found through empirical results + // that spacing it from its use in memory accesses was beneficial. + last_visited_latency_ = kArm64IntegerOpLatency + 2; +} + +void SchedulingLatencyVisitorARM64::VisitMultiplyAccumulate(HMultiplyAccumulate* ATTRIBUTE_UNUSED) { + last_visited_latency_ = kArm64MulIntegerLatency; +} + +void SchedulingLatencyVisitorARM64::VisitArrayGet(HArrayGet* instruction) { + if (!instruction->GetArray()->IsIntermediateAddress()) { + // Take the intermediate address computation into account. + last_visited_internal_latency_ = kArm64IntegerOpLatency; + } + last_visited_latency_ = kArm64MemoryLoadLatency; +} + +void SchedulingLatencyVisitorARM64::VisitArrayLength(HArrayLength* ATTRIBUTE_UNUSED) { + last_visited_latency_ = kArm64MemoryLoadLatency; +} + +void SchedulingLatencyVisitorARM64::VisitArraySet(HArraySet* ATTRIBUTE_UNUSED) { + last_visited_latency_ = kArm64MemoryStoreLatency; +} + +void SchedulingLatencyVisitorARM64::VisitBoundsCheck(HBoundsCheck* ATTRIBUTE_UNUSED) { + last_visited_internal_latency_ = kArm64IntegerOpLatency; + // Users do not use any data results. + last_visited_latency_ = 0; +} + +void SchedulingLatencyVisitorARM64::VisitDiv(HDiv* instr) { + Primitive::Type type = instr->GetResultType(); + switch (type) { + case Primitive::kPrimFloat: + last_visited_latency_ = kArm64DivFloatLatency; + break; + case Primitive::kPrimDouble: + last_visited_latency_ = kArm64DivDoubleLatency; + break; + default: + // Follow the code path used by code generation. + if (instr->GetRight()->IsConstant()) { + int64_t imm = Int64FromConstant(instr->GetRight()->AsConstant()); + if (imm == 0) { + last_visited_internal_latency_ = 0; + last_visited_latency_ = 0; + } else if (imm == 1 || imm == -1) { + last_visited_internal_latency_ = 0; + last_visited_latency_ = kArm64IntegerOpLatency; + } else if (IsPowerOfTwo(AbsOrMin(imm))) { + last_visited_internal_latency_ = 4 * kArm64IntegerOpLatency; + last_visited_latency_ = kArm64IntegerOpLatency; + } else { + DCHECK(imm <= -2 || imm >= 2); + last_visited_internal_latency_ = 4 * kArm64IntegerOpLatency; + last_visited_latency_ = kArm64MulIntegerLatency; + } + } else { + last_visited_latency_ = kArm64DivIntegerLatency; + } + break; + } +} + +void SchedulingLatencyVisitorARM64::VisitInstanceFieldGet(HInstanceFieldGet* ATTRIBUTE_UNUSED) { + last_visited_latency_ = kArm64MemoryLoadLatency; +} + +void SchedulingLatencyVisitorARM64::VisitInstanceOf(HInstanceOf* ATTRIBUTE_UNUSED) { + last_visited_internal_latency_ = kArm64CallInternalLatency; + last_visited_latency_ = kArm64IntegerOpLatency; +} + +void SchedulingLatencyVisitorARM64::VisitInvoke(HInvoke* ATTRIBUTE_UNUSED) { + last_visited_internal_latency_ = kArm64CallInternalLatency; + last_visited_latency_ = kArm64CallLatency; +} + +void SchedulingLatencyVisitorARM64::VisitLoadString(HLoadString* ATTRIBUTE_UNUSED) { + last_visited_internal_latency_ = kArm64LoadStringInternalLatency; + last_visited_latency_ = kArm64MemoryLoadLatency; +} + +void SchedulingLatencyVisitorARM64::VisitMul(HMul* instr) { + last_visited_latency_ = Primitive::IsFloatingPointType(instr->GetResultType()) + ? kArm64MulFloatingPointLatency + : kArm64MulIntegerLatency; +} + +void SchedulingLatencyVisitorARM64::VisitNewArray(HNewArray* ATTRIBUTE_UNUSED) { + last_visited_internal_latency_ = kArm64IntegerOpLatency + kArm64CallInternalLatency; + last_visited_latency_ = kArm64CallLatency; +} + +void SchedulingLatencyVisitorARM64::VisitNewInstance(HNewInstance* instruction) { + if (instruction->IsStringAlloc()) { + last_visited_internal_latency_ = 2 + kArm64MemoryLoadLatency + kArm64CallInternalLatency; + } else { + last_visited_internal_latency_ = kArm64CallInternalLatency; + } + last_visited_latency_ = kArm64CallLatency; +} + +void SchedulingLatencyVisitorARM64::VisitRem(HRem* instruction) { + if (Primitive::IsFloatingPointType(instruction->GetResultType())) { + last_visited_internal_latency_ = kArm64CallInternalLatency; + last_visited_latency_ = kArm64CallLatency; + } else { + // Follow the code path used by code generation. + if (instruction->GetRight()->IsConstant()) { + int64_t imm = Int64FromConstant(instruction->GetRight()->AsConstant()); + if (imm == 0) { + last_visited_internal_latency_ = 0; + last_visited_latency_ = 0; + } else if (imm == 1 || imm == -1) { + last_visited_internal_latency_ = 0; + last_visited_latency_ = kArm64IntegerOpLatency; + } else if (IsPowerOfTwo(AbsOrMin(imm))) { + last_visited_internal_latency_ = 4 * kArm64IntegerOpLatency; + last_visited_latency_ = kArm64IntegerOpLatency; + } else { + DCHECK(imm <= -2 || imm >= 2); + last_visited_internal_latency_ = 4 * kArm64IntegerOpLatency; + last_visited_latency_ = kArm64MulIntegerLatency; + } + } else { + last_visited_internal_latency_ = kArm64DivIntegerLatency; + last_visited_latency_ = kArm64MulIntegerLatency; + } + } +} + +void SchedulingLatencyVisitorARM64::VisitStaticFieldGet(HStaticFieldGet* ATTRIBUTE_UNUSED) { + last_visited_latency_ = kArm64MemoryLoadLatency; +} + +void SchedulingLatencyVisitorARM64::VisitSuspendCheck(HSuspendCheck* instruction) { + HBasicBlock* block = instruction->GetBlock(); + DCHECK((block->GetLoopInformation() != nullptr) || + (block->IsEntryBlock() && instruction->GetNext()->IsGoto())); + // Users do not use any data results. + last_visited_latency_ = 0; +} + +void SchedulingLatencyVisitorARM64::VisitTypeConversion(HTypeConversion* instr) { + if (Primitive::IsFloatingPointType(instr->GetResultType()) || + Primitive::IsFloatingPointType(instr->GetInputType())) { + last_visited_latency_ = kArm64TypeConversionFloatingPointIntegerLatency; + } else { + last_visited_latency_ = kArm64IntegerOpLatency; + } +} + +} // namespace arm64 +} // namespace art diff --git a/compiler/optimizing/scheduler_arm64.h b/compiler/optimizing/scheduler_arm64.h new file mode 100644 index 0000000000..702027c535 --- /dev/null +++ b/compiler/optimizing/scheduler_arm64.h @@ -0,0 +1,117 @@ +/* + * 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_OPTIMIZING_SCHEDULER_ARM64_H_ +#define ART_COMPILER_OPTIMIZING_SCHEDULER_ARM64_H_ + +#include "scheduler.h" + +namespace art { +namespace arm64 { + +static constexpr uint32_t kArm64MemoryLoadLatency = 5; +static constexpr uint32_t kArm64MemoryStoreLatency = 3; + +static constexpr uint32_t kArm64CallInternalLatency = 10; +static constexpr uint32_t kArm64CallLatency = 5; + +// AArch64 instruction latency. +// We currently assume that all arm64 CPUs share the same instruction latency list. +static constexpr uint32_t kArm64IntegerOpLatency = 2; +static constexpr uint32_t kArm64FloatingPointOpLatency = 5; + + +static constexpr uint32_t kArm64DataProcWithShifterOpLatency = 3; +static constexpr uint32_t kArm64DivDoubleLatency = 30; +static constexpr uint32_t kArm64DivFloatLatency = 15; +static constexpr uint32_t kArm64DivIntegerLatency = 5; +static constexpr uint32_t kArm64LoadStringInternalLatency = 7; +static constexpr uint32_t kArm64MulFloatingPointLatency = 6; +static constexpr uint32_t kArm64MulIntegerLatency = 6; +static constexpr uint32_t kArm64TypeConversionFloatingPointIntegerLatency = 5; + +class SchedulingLatencyVisitorARM64 : public SchedulingLatencyVisitor { + public: + // Default visitor for instructions not handled specifically below. + void VisitInstruction(HInstruction* ATTRIBUTE_UNUSED) { + last_visited_latency_ = kArm64IntegerOpLatency; + } + +// We add a second unused parameter to be able to use this macro like the others +// defined in `nodes.h`. +#define FOR_EACH_SCHEDULED_COMMON_INSTRUCTION(M) \ + M(ArrayGet , unused) \ + M(ArrayLength , unused) \ + M(ArraySet , unused) \ + M(BinaryOperation , unused) \ + M(BoundsCheck , unused) \ + M(Div , unused) \ + M(InstanceFieldGet , unused) \ + M(InstanceOf , unused) \ + M(Invoke , unused) \ + M(LoadString , unused) \ + M(Mul , unused) \ + M(NewArray , unused) \ + M(NewInstance , unused) \ + M(Rem , unused) \ + M(StaticFieldGet , unused) \ + M(SuspendCheck , unused) \ + M(TypeConversion , unused) + +#define FOR_EACH_SCHEDULED_SHARED_INSTRUCTION(M) \ + M(BitwiseNegatedRight, unused) \ + M(MultiplyAccumulate, unused) \ + M(IntermediateAddress, unused) + +#define DECLARE_VISIT_INSTRUCTION(type, unused) \ + void Visit##type(H##type* instruction) OVERRIDE; + + FOR_EACH_SCHEDULED_COMMON_INSTRUCTION(DECLARE_VISIT_INSTRUCTION) + FOR_EACH_SCHEDULED_SHARED_INSTRUCTION(DECLARE_VISIT_INSTRUCTION) + FOR_EACH_CONCRETE_INSTRUCTION_ARM64(DECLARE_VISIT_INSTRUCTION) + +#undef DECLARE_VISIT_INSTRUCTION +}; + +class HSchedulerARM64 : public HScheduler { + public: + HSchedulerARM64(ArenaAllocator* arena, SchedulingNodeSelector* selector) + : HScheduler(arena, &arm64_latency_visitor_, selector) {} + ~HSchedulerARM64() OVERRIDE {} + + bool IsSchedulable(const HInstruction* instruction) const OVERRIDE { +#define CASE_INSTRUCTION_KIND(type, unused) case \ + HInstruction::InstructionKind::k##type: + switch (instruction->GetKind()) { + FOR_EACH_SCHEDULED_SHARED_INSTRUCTION(CASE_INSTRUCTION_KIND) + return true; + FOR_EACH_CONCRETE_INSTRUCTION_ARM64(CASE_INSTRUCTION_KIND) + return true; + default: + return HScheduler::IsSchedulable(instruction); + } +#undef CASE_INSTRUCTION_KIND + } + + private: + SchedulingLatencyVisitorARM64 arm64_latency_visitor_; + DISALLOW_COPY_AND_ASSIGN(HSchedulerARM64); +}; + +} // namespace arm64 +} // namespace art + +#endif // ART_COMPILER_OPTIMIZING_SCHEDULER_ARM64_H_ diff --git a/compiler/optimizing/scheduler_test.cc b/compiler/optimizing/scheduler_test.cc new file mode 100644 index 0000000000..31d13e2a26 --- /dev/null +++ b/compiler/optimizing/scheduler_test.cc @@ -0,0 +1,238 @@ +/* + * 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 "codegen_test_utils.h" +#include "common_compiler_test.h" +#include "nodes.h" +#include "optimizing_unit_test.h" +#include "pc_relative_fixups_x86.h" +#include "register_allocator.h" +#include "scheduler.h" + +#ifdef ART_ENABLE_CODEGEN_arm64 +#include "scheduler_arm64.h" +#endif + +namespace art { + +// Return all combinations of ISA and code generator that are executable on +// hardware, or on simulator, and that we'd like to test. +static ::std::vector<CodegenTargetConfig> GetTargetConfigs() { + ::std::vector<CodegenTargetConfig> v; + ::std::vector<CodegenTargetConfig> test_config_candidates = { +#ifdef ART_ENABLE_CODEGEN_arm + CodegenTargetConfig(kArm, create_codegen_arm), + CodegenTargetConfig(kThumb2, create_codegen_arm), +#endif +#ifdef ART_ENABLE_CODEGEN_arm64 + CodegenTargetConfig(kArm64, create_codegen_arm64), +#endif +#ifdef ART_ENABLE_CODEGEN_x86 + CodegenTargetConfig(kX86, create_codegen_x86), +#endif +#ifdef ART_ENABLE_CODEGEN_x86_64 + CodegenTargetConfig(kX86_64, create_codegen_x86_64), +#endif +#ifdef ART_ENABLE_CODEGEN_mips + CodegenTargetConfig(kMips, create_codegen_mips), +#endif +#ifdef ART_ENABLE_CODEGEN_mips64 + CodegenTargetConfig(kMips64, create_codegen_mips64) +#endif + }; + + for (auto test_config : test_config_candidates) { + if (CanExecute(test_config.GetInstructionSet())) { + v.push_back(test_config); + } + } + + return v; +} + +class SchedulerTest : public CommonCompilerTest {}; + +#ifdef ART_ENABLE_CODEGEN_arm64 +TEST_F(SchedulerTest, DependencyGraph) { + ArenaPool pool; + ArenaAllocator allocator(&pool); + HGraph* graph = CreateGraph(&allocator); + HBasicBlock* entry = new (&allocator) HBasicBlock(graph); + HBasicBlock* block1 = new (&allocator) HBasicBlock(graph); + graph->AddBlock(entry); + graph->AddBlock(block1); + graph->SetEntryBlock(entry); + + // entry: + // array ParameterValue + // c1 IntConstant + // c2 IntConstant + // block1: + // add1 Add [c1, c2] + // add2 Add [add1, c2] + // mul Mul [add1, add2] + // div_check DivZeroCheck [add2] (env: add2, mul) + // div Div [add1, div_check] + // array_get1 ArrayGet [array, add1] + // array_set1 ArraySet [array, add1, add2] + // array_get2 ArrayGet [array, add1] + // array_set2 ArraySet [array, add1, add2] + + HInstruction* array = new (&allocator) HParameterValue(graph->GetDexFile(), + dex::TypeIndex(0), + 0, + Primitive::kPrimNot); + HInstruction* c1 = graph->GetIntConstant(1); + HInstruction* c2 = graph->GetIntConstant(10); + HInstruction* add1 = new (&allocator) HAdd(Primitive::kPrimInt, c1, c2); + HInstruction* add2 = new (&allocator) HAdd(Primitive::kPrimInt, add1, c2); + HInstruction* mul = new (&allocator) HMul(Primitive::kPrimInt, add1, add2); + HInstruction* div_check = new (&allocator) HDivZeroCheck(add2, 0); + HInstruction* div = new (&allocator) HDiv(Primitive::kPrimInt, add1, div_check, 0); + HInstruction* array_get1 = new (&allocator) HArrayGet(array, add1, Primitive::kPrimInt, 0); + HInstruction* array_set1 = new (&allocator) HArraySet(array, add1, add2, Primitive::kPrimInt, 0); + HInstruction* array_get2 = new (&allocator) HArrayGet(array, add1, Primitive::kPrimInt, 0); + HInstruction* array_set2 = new (&allocator) HArraySet(array, add1, add2, Primitive::kPrimInt, 0); + + DCHECK(div_check->CanThrow()); + + entry->AddInstruction(array); + + HInstruction* block_instructions[] = {add1, + add2, + mul, + div_check, + div, + array_get1, + array_set1, + array_get2, + array_set2}; + for (auto instr : block_instructions) { + block1->AddInstruction(instr); + } + + HEnvironment* environment = new (&allocator) HEnvironment(&allocator, + 2, + graph->GetArtMethod(), + 0, + div_check); + div_check->SetRawEnvironment(environment); + environment->SetRawEnvAt(0, add2); + add2->AddEnvUseAt(div_check->GetEnvironment(), 0); + environment->SetRawEnvAt(1, mul); + mul->AddEnvUseAt(div_check->GetEnvironment(), 1); + + ArenaAllocator* arena = graph->GetArena(); + CriticalPathSchedulingNodeSelector critical_path_selector; + arm64::HSchedulerARM64 scheduler(arena, &critical_path_selector); + SchedulingGraph scheduling_graph(&scheduler, arena); + // Instructions must be inserted in reverse order into the scheduling graph. + for (auto instr : ReverseRange(block_instructions)) { + scheduling_graph.AddNode(instr); + } + + // Should not have dependencies cross basic blocks. + ASSERT_FALSE(scheduling_graph.HasImmediateDataDependency(add1, c1)); + ASSERT_FALSE(scheduling_graph.HasImmediateDataDependency(add2, c2)); + + // Define-use dependency. + ASSERT_TRUE(scheduling_graph.HasImmediateDataDependency(add2, add1)); + ASSERT_FALSE(scheduling_graph.HasImmediateDataDependency(add1, add2)); + ASSERT_TRUE(scheduling_graph.HasImmediateDataDependency(div_check, add2)); + ASSERT_FALSE(scheduling_graph.HasImmediateDataDependency(div_check, add1)); + ASSERT_TRUE(scheduling_graph.HasImmediateDataDependency(div, div_check)); + ASSERT_TRUE(scheduling_graph.HasImmediateDataDependency(array_set1, add1)); + ASSERT_TRUE(scheduling_graph.HasImmediateDataDependency(array_set1, add2)); + + // Read and write dependencies + ASSERT_TRUE(scheduling_graph.HasImmediateOtherDependency(array_set1, array_get1)); + ASSERT_TRUE(scheduling_graph.HasImmediateOtherDependency(array_set2, array_get2)); + ASSERT_TRUE(scheduling_graph.HasImmediateOtherDependency(array_get2, array_set1)); + ASSERT_TRUE(scheduling_graph.HasImmediateOtherDependency(array_set2, array_set1)); + + // Env dependency. + ASSERT_TRUE(scheduling_graph.HasImmediateOtherDependency(div_check, mul)); + ASSERT_FALSE(scheduling_graph.HasImmediateOtherDependency(mul, div_check)); + + // CanThrow. + ASSERT_TRUE(scheduling_graph.HasImmediateOtherDependency(array_set1, div_check)); +} +#endif + +static void CompileWithRandomSchedulerAndRun(const uint16_t* data, + bool has_result, + int expected) { + for (CodegenTargetConfig target_config : GetTargetConfigs()) { + ArenaPool pool; + ArenaAllocator arena(&pool); + HGraph* graph = CreateCFG(&arena, data); + + // Schedule the graph randomly. + HInstructionScheduling scheduling(graph, target_config.GetInstructionSet()); + scheduling.Run(/*only_optimize_loop_blocks*/ false, /*schedule_randomly*/ true); + + RunCode(target_config, + graph, + [](HGraph* graph_arg) { RemoveSuspendChecks(graph_arg); }, + has_result, expected); + } +} + +TEST_F(SchedulerTest, RandomScheduling) { + // + // Java source: crafted code to make sure (random) scheduling should get correct result. + // + // int result = 0; + // float fr = 10.0f; + // for (int i = 1; i < 10; i++) { + // fr ++; + // int t1 = result >> i; + // int t2 = result * i; + // result = result + t1 - t2; + // fr = fr / i; + // result += (int)fr; + // } + // return result; + // + const uint16_t data[] = SIX_REGISTERS_CODE_ITEM( + Instruction::CONST_4 | 0 << 12 | 2 << 8, // const/4 v2, #int 0 + Instruction::CONST_HIGH16 | 0 << 8, 0x4120, // const/high16 v0, #float 10.0 // #41200000 + Instruction::CONST_4 | 1 << 12 | 1 << 8, // const/4 v1, #int 1 + Instruction::CONST_16 | 5 << 8, 0x000a, // const/16 v5, #int 10 + Instruction::IF_GE | 5 << 12 | 1 << 8, 0x0014, // if-ge v1, v5, 001a // +0014 + Instruction::CONST_HIGH16 | 5 << 8, 0x3f80, // const/high16 v5, #float 1.0 // #3f800000 + Instruction::ADD_FLOAT_2ADDR | 5 << 12 | 0 << 8, // add-float/2addr v0, v5 + Instruction::SHR_INT | 3 << 8, 1 << 8 | 2 , // shr-int v3, v2, v1 + Instruction::MUL_INT | 4 << 8, 1 << 8 | 2, // mul-int v4, v2, v1 + Instruction::ADD_INT | 5 << 8, 3 << 8 | 2, // add-int v5, v2, v3 + Instruction::SUB_INT | 2 << 8, 4 << 8 | 5, // sub-int v2, v5, v4 + Instruction::INT_TO_FLOAT | 1 << 12 | 5 << 8, // int-to-float v5, v1 + Instruction::DIV_FLOAT_2ADDR | 5 << 12 | 0 << 8, // div-float/2addr v0, v5 + Instruction::FLOAT_TO_INT | 0 << 12 | 5 << 8, // float-to-int v5, v0 + Instruction::ADD_INT_2ADDR | 5 << 12 | 2 << 8, // add-int/2addr v2, v5 + Instruction::ADD_INT_LIT8 | 1 << 8, 1 << 8 | 1, // add-int/lit8 v1, v1, #int 1 // #01 + Instruction::GOTO | 0xeb << 8, // goto 0004 // -0015 + Instruction::RETURN | 2 << 8); // return v2 + + constexpr int kNumberOfRuns = 10; + for (int i = 0; i < kNumberOfRuns; ++i) { + CompileWithRandomSchedulerAndRun(data, true, 138774); + } +} + +} // namespace art diff --git a/compiler/optimizing/sharpening.cc b/compiler/optimizing/sharpening.cc index c5294107ae..f07f02a719 100644 --- a/compiler/optimizing/sharpening.cc +++ b/compiler/optimizing/sharpening.cc @@ -42,8 +42,6 @@ void HSharpening::Run() { HInstruction* instruction = it.Current(); if (instruction->IsInvokeStaticOrDirect()) { ProcessInvokeStaticOrDirect(instruction->AsInvokeStaticOrDirect()); - } else if (instruction->IsLoadClass()) { - ProcessLoadClass(instruction->AsLoadClass()); } else if (instruction->IsLoadString()) { ProcessLoadString(instruction->AsLoadString()); } @@ -97,7 +95,9 @@ void HSharpening::ProcessInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { // class is initialized already or being initialized, and the call will not // be invoked once the method is deoptimized. - if (callee == codegen_->GetGraph()->GetArtMethod()) { + // We don't optimize for debuggable as it would prevent us from obsoleting the method in some + // situations. + if (callee == codegen_->GetGraph()->GetArtMethod() && !codegen_->GetGraph()->IsDebuggable()) { // Recursive call. method_load_kind = HInvokeStaticOrDirect::MethodLoadKind::kRecursive; code_ptr_location = HInvokeStaticOrDirect::CodePtrLocation::kCallSelf; @@ -131,104 +131,93 @@ void HSharpening::ProcessInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { invoke->SetDispatchInfo(dispatch_info); } -void HSharpening::ProcessLoadClass(HLoadClass* load_class) { - ScopedObjectAccess soa(Thread::Current()); - SharpenClass(load_class, codegen_, compiler_driver_); -} - -void HSharpening::SharpenClass(HLoadClass* load_class, - CodeGenerator* codegen, - CompilerDriver* compiler_driver) { +HLoadClass::LoadKind HSharpening::SharpenClass(HLoadClass* load_class, + CodeGenerator* codegen, + CompilerDriver* compiler_driver, + const DexCompilationUnit& dex_compilation_unit) { Handle<mirror::Class> klass = load_class->GetClass(); DCHECK(load_class->GetLoadKind() == HLoadClass::LoadKind::kDexCacheViaMethod || load_class->GetLoadKind() == HLoadClass::LoadKind::kReferrersClass) << load_class->GetLoadKind(); DCHECK(!load_class->IsInBootImage()) << "HLoadClass should not be optimized before sharpening."; + HLoadClass::LoadKind load_kind = load_class->GetLoadKind(); + if (load_class->NeedsAccessCheck()) { // We need to call the runtime anyway, so we simply get the class as that call's return value. - return; - } - - if (load_class->GetLoadKind() == HLoadClass::LoadKind::kReferrersClass) { + } else if (load_kind == HLoadClass::LoadKind::kReferrersClass) { // Loading from the ArtMethod* is the most efficient retrieval in code size. // TODO: This may not actually be true for all architectures and // locations of target classes. The additional register pressure // for using the ArtMethod* should be considered. - return; - } - - const DexFile& dex_file = load_class->GetDexFile(); - dex::TypeIndex type_index = load_class->GetTypeIndex(); + } else { + const DexFile& dex_file = load_class->GetDexFile(); + dex::TypeIndex type_index = load_class->GetTypeIndex(); - bool is_in_boot_image = false; - HLoadClass::LoadKind desired_load_kind = static_cast<HLoadClass::LoadKind>(-1); - Runtime* runtime = Runtime::Current(); - if (codegen->GetCompilerOptions().IsBootImage()) { - // Compiling boot image. Check if the class is a boot image class. - DCHECK(!runtime->UseJitCompilation()); - if (!compiler_driver->GetSupportBootImageFixup()) { - // compiler_driver_test. Do not sharpen. - desired_load_kind = HLoadClass::LoadKind::kDexCacheViaMethod; - } else if ((klass.Get() != nullptr) && compiler_driver->IsImageClass( - dex_file.StringDataByIdx(dex_file.GetTypeId(type_index).descriptor_idx_))) { - is_in_boot_image = true; - desired_load_kind = codegen->GetCompilerOptions().GetCompilePic() - ? HLoadClass::LoadKind::kBootImageLinkTimePcRelative - : HLoadClass::LoadKind::kBootImageLinkTimeAddress; + bool is_in_boot_image = false; + HLoadClass::LoadKind desired_load_kind = HLoadClass::LoadKind::kInvalid; + Runtime* runtime = Runtime::Current(); + if (codegen->GetCompilerOptions().IsBootImage()) { + // Compiling boot image. Check if the class is a boot image class. + DCHECK(!runtime->UseJitCompilation()); + if (!compiler_driver->GetSupportBootImageFixup()) { + // compiler_driver_test. Do not sharpen. + desired_load_kind = HLoadClass::LoadKind::kDexCacheViaMethod; + } else if ((klass.Get() != nullptr) && compiler_driver->IsImageClass( + dex_file.StringDataByIdx(dex_file.GetTypeId(type_index).descriptor_idx_))) { + is_in_boot_image = true; + desired_load_kind = codegen->GetCompilerOptions().GetCompilePic() + ? HLoadClass::LoadKind::kBootImageLinkTimePcRelative + : HLoadClass::LoadKind::kBootImageLinkTimeAddress; + } else { + // Not a boot image class. + DCHECK(ContainsElement(compiler_driver->GetDexFilesForOatFile(), &dex_file)); + desired_load_kind = HLoadClass::LoadKind::kBssEntry; + } } else { - // Not a boot image class. - DCHECK(ContainsElement(compiler_driver->GetDexFilesForOatFile(), &dex_file)); - desired_load_kind = HLoadClass::LoadKind::kBssEntry; - } - } else { - is_in_boot_image = (klass.Get() != nullptr) && - runtime->GetHeap()->ObjectIsInBootImageSpace(klass.Get()); - if (runtime->UseJitCompilation()) { - // TODO: Make sure we don't set the "compile PIC" flag for JIT as that's bogus. - // DCHECK(!codegen_->GetCompilerOptions().GetCompilePic()); - if (is_in_boot_image) { - // TODO: Use direct pointers for all non-moving spaces, not just boot image. Bug: 29530787 + is_in_boot_image = (klass.Get() != nullptr) && + runtime->GetHeap()->ObjectIsInBootImageSpace(klass.Get()); + if (runtime->UseJitCompilation()) { + // TODO: Make sure we don't set the "compile PIC" flag for JIT as that's bogus. + // DCHECK(!codegen_->GetCompilerOptions().GetCompilePic()); + if (is_in_boot_image) { + // TODO: Use direct pointers for all non-moving spaces, not just boot image. Bug: 29530787 + desired_load_kind = HLoadClass::LoadKind::kBootImageAddress; + } else if (klass.Get() != nullptr) { + desired_load_kind = HLoadClass::LoadKind::kJitTableAddress; + } else { + // Class not loaded yet. This happens when the dex code requesting + // this `HLoadClass` hasn't been executed in the interpreter. + // Fallback to the dex cache. + // TODO(ngeoffray): Generate HDeoptimize instead. + desired_load_kind = HLoadClass::LoadKind::kDexCacheViaMethod; + } + } else if (is_in_boot_image && !codegen->GetCompilerOptions().GetCompilePic()) { + // AOT app compilation. Check if the class is in the boot image. desired_load_kind = HLoadClass::LoadKind::kBootImageAddress; - } else if (klass.Get() != nullptr) { - desired_load_kind = HLoadClass::LoadKind::kJitTableAddress; } else { - // Class not loaded yet. This happens when the dex code requesting - // this `HLoadClass` hasn't been executed in the interpreter. - // Fallback to the dex cache. - // TODO(ngeoffray): Generate HDeoptimize instead. - desired_load_kind = HLoadClass::LoadKind::kDexCacheViaMethod; + // Not JIT and either the klass is not in boot image or we are compiling in PIC mode. + desired_load_kind = HLoadClass::LoadKind::kBssEntry; } - } else if (is_in_boot_image && !codegen->GetCompilerOptions().GetCompilePic()) { - // AOT app compilation. Check if the class is in the boot image. - desired_load_kind = HLoadClass::LoadKind::kBootImageAddress; - } else { - // Not JIT and either the klass is not in boot image or we are compiling in PIC mode. - desired_load_kind = HLoadClass::LoadKind::kBssEntry; } - } - DCHECK_NE(desired_load_kind, static_cast<HLoadClass::LoadKind>(-1)); + DCHECK_NE(desired_load_kind, HLoadClass::LoadKind::kInvalid); - if (is_in_boot_image) { - load_class->MarkInBootImage(); + if (is_in_boot_image) { + load_class->MarkInBootImage(); + } + load_kind = codegen->GetSupportedLoadClassKind(desired_load_kind); } - HLoadClass::LoadKind load_kind = codegen->GetSupportedLoadClassKind(desired_load_kind); - switch (load_kind) { - case HLoadClass::LoadKind::kBootImageLinkTimeAddress: - case HLoadClass::LoadKind::kBootImageLinkTimePcRelative: - case HLoadClass::LoadKind::kBssEntry: - case HLoadClass::LoadKind::kDexCacheViaMethod: - load_class->SetLoadKindWithTypeReference(load_kind, dex_file, type_index); - break; - case HLoadClass::LoadKind::kBootImageAddress: - case HLoadClass::LoadKind::kJitTableAddress: - load_class->SetLoadKind(load_kind); - break; - default: - LOG(FATAL) << "Unexpected load kind: " << load_kind; - UNREACHABLE(); + if (!IsSameDexFile(load_class->GetDexFile(), *dex_compilation_unit.GetDexFile())) { + if ((load_kind == HLoadClass::LoadKind::kDexCacheViaMethod) || + (load_kind == HLoadClass::LoadKind::kBssEntry)) { + // We actually cannot reference this class, we're forced to bail. + // We cannot reference this class with Bss, as the entrypoint will lookup the class + // in the caller's dex file, but that dex file does not reference the class. + return HLoadClass::LoadKind::kInvalid; + } } + return load_kind; } void HSharpening::ProcessLoadString(HLoadString* load_string) { diff --git a/compiler/optimizing/sharpening.h b/compiler/optimizing/sharpening.h index ae3d83ef2c..4240b2f339 100644 --- a/compiler/optimizing/sharpening.h +++ b/compiler/optimizing/sharpening.h @@ -17,6 +17,7 @@ #ifndef ART_COMPILER_OPTIMIZING_SHARPENING_H_ #define ART_COMPILER_OPTIMIZING_SHARPENING_H_ +#include "nodes.h" #include "optimization.h" namespace art { @@ -24,7 +25,6 @@ namespace art { class CodeGenerator; class CompilerDriver; class DexCompilationUnit; -class HInvokeStaticOrDirect; // Optimization that tries to improve the way we dispatch methods and access types, // fields, etc. Besides actual method sharpening based on receiver type (for example @@ -47,15 +47,15 @@ class HSharpening : public HOptimization { static constexpr const char* kSharpeningPassName = "sharpening"; - // Used internally but also by the inliner. - static void SharpenClass(HLoadClass* load_class, - CodeGenerator* codegen, - CompilerDriver* compiler_driver) + // Used by the builder and the inliner. + static HLoadClass::LoadKind SharpenClass(HLoadClass* load_class, + CodeGenerator* codegen, + CompilerDriver* compiler_driver, + const DexCompilationUnit& dex_compilation_unit) REQUIRES_SHARED(Locks::mutator_lock_); private: void ProcessInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke); - void ProcessLoadClass(HLoadClass* load_class); void ProcessLoadString(HLoadString* load_string); CodeGenerator* codegen_; diff --git a/compiler/optimizing/ssa_builder.cc b/compiler/optimizing/ssa_builder.cc index ae1e369999..487e4dd498 100644 --- a/compiler/optimizing/ssa_builder.cc +++ b/compiler/optimizing/ssa_builder.cc @@ -17,8 +17,10 @@ #include "ssa_builder.h" #include "bytecode_utils.h" +#include "mirror/class-inl.h" #include "nodes.h" #include "reference_type_propagation.h" +#include "scoped_thread_state_change-inl.h" #include "ssa_phi_elimination.h" namespace art { diff --git a/compiler/optimizing/stack_map_stream.cc b/compiler/optimizing/stack_map_stream.cc index a9a1e6f592..10f5cab907 100644 --- a/compiler/optimizing/stack_map_stream.cc +++ b/compiler/optimizing/stack_map_stream.cc @@ -16,7 +16,11 @@ #include "stack_map_stream.h" -#include "art_method.h" +#include <unordered_map> + +#include "art_method-inl.h" +#include "base/stl_util.h" +#include "optimizing/optimizing_compiler.h" #include "runtime.h" #include "scoped_thread_state_change-inl.h" @@ -40,6 +44,7 @@ void StackMapStream::BeginStackMapEntry(uint32_t dex_pc, current_entry_.inline_infos_start_index = inline_infos_.size(); current_entry_.dex_register_map_hash = 0; current_entry_.same_dex_register_map_as_ = kNoSameDexMapFound; + current_entry_.stack_mask_index = 0; if (num_dex_registers != 0) { current_entry_.live_dex_registers_mask = ArenaBitVector::Create(allocator_, num_dex_registers, true, kArenaAllocStackMapStream); @@ -103,11 +108,6 @@ void StackMapStream::AddDexRegisterEntry(DexRegisterLocation::Kind kind, int32_t current_dex_register_++; } -static bool EncodeArtMethodInInlineInfo(ArtMethod* method ATTRIBUTE_UNUSED) { - // Note: the runtime is null only for unit testing. - return Runtime::Current() == nullptr || !Runtime::Current()->IsAotCompiler(); -} - void StackMapStream::BeginInlineInfoEntry(ArtMethod* method, uint32_t dex_pc, uint32_t num_dex_registers, @@ -153,32 +153,43 @@ CodeOffset StackMapStream::ComputeMaxNativePcCodeOffset() const { } size_t StackMapStream::PrepareForFillIn() { - int stack_mask_number_of_bits = stack_mask_max_ + 1; // Need room for max element too. + const size_t stack_mask_size_in_bits = stack_mask_max_ + 1; // Need room for max element too. + const size_t number_of_stack_masks = PrepareStackMasks(stack_mask_size_in_bits); + const size_t register_mask_size_in_bits = MinimumBitsToStore(register_mask_max_); + const size_t number_of_register_masks = PrepareRegisterMasks(); dex_register_maps_size_ = ComputeDexRegisterMapsSize(); ComputeInlineInfoEncoding(); // needs dex_register_maps_size_. inline_info_size_ = inline_infos_.size() * inline_info_encoding_.GetEntrySize(); CodeOffset max_native_pc_offset = ComputeMaxNativePcCodeOffset(); - // The stack map contains compressed native offsets. - size_t stack_map_size = stack_map_encoding_.SetFromSizes(max_native_pc_offset.CompressedValue(), - dex_pc_max_, - dex_register_maps_size_, - inline_info_size_, - register_mask_max_, - stack_mask_number_of_bits); - stack_maps_size_ = stack_maps_.size() * stack_map_size; + // The stack map contains compressed native PC offsets. + const size_t stack_map_size = stack_map_encoding_.SetFromSizes( + max_native_pc_offset.CompressedValue(), + dex_pc_max_, + dex_register_maps_size_, + inline_info_size_, + number_of_register_masks, + number_of_stack_masks); + stack_maps_size_ = RoundUp(stack_maps_.size() * stack_map_size, kBitsPerByte) / kBitsPerByte; dex_register_location_catalog_size_ = ComputeDexRegisterLocationCatalogSize(); - - size_t non_header_size = + const size_t stack_masks_bits = number_of_stack_masks * stack_mask_size_in_bits; + const size_t register_masks_bits = number_of_register_masks * register_mask_size_in_bits; + // Register masks are last, stack masks are right before that last. + // They are both bit packed / aligned. + const size_t non_header_size = stack_maps_size_ + dex_register_location_catalog_size_ + dex_register_maps_size_ + - inline_info_size_; + inline_info_size_ + + RoundUp(stack_masks_bits + register_masks_bits, kBitsPerByte) / kBitsPerByte; // Prepare the CodeInfo variable-sized encoding. CodeInfoEncoding code_info_encoding; code_info_encoding.non_header_size = non_header_size; code_info_encoding.number_of_stack_maps = stack_maps_.size(); - code_info_encoding.stack_map_size_in_bytes = stack_map_size; + code_info_encoding.number_of_stack_masks = number_of_stack_masks; + code_info_encoding.number_of_register_masks = number_of_register_masks; + code_info_encoding.stack_mask_size_in_bits = stack_mask_size_in_bits; + code_info_encoding.register_mask_size_in_bits = register_mask_size_in_bits; code_info_encoding.stack_map_encoding = stack_map_encoding_; code_info_encoding.inline_info_encoding = inline_info_encoding_; code_info_encoding.number_of_location_catalog_entries = location_catalog_entries_.size(); @@ -321,18 +332,8 @@ void StackMapStream::FillIn(MemoryRegion region) { stack_map.SetDexPc(stack_map_encoding_, entry.dex_pc); stack_map.SetNativePcCodeOffset(stack_map_encoding_, entry.native_pc_code_offset); - stack_map.SetRegisterMask(stack_map_encoding_, entry.register_mask); - size_t number_of_stack_mask_bits = stack_map.GetNumberOfStackMaskBits(stack_map_encoding_); - if (entry.sp_mask != nullptr) { - for (size_t bit = 0; bit < number_of_stack_mask_bits; bit++) { - stack_map.SetStackMaskBit(stack_map_encoding_, bit, entry.sp_mask->IsBitSet(bit)); - } - } else { - // The MemoryRegion does not have to be zeroed, so make sure we clear the bits. - for (size_t bit = 0; bit < number_of_stack_mask_bits; bit++) { - stack_map.SetStackMaskBit(stack_map_encoding_, bit, false); - } - } + stack_map.SetRegisterMaskIndex(stack_map_encoding_, entry.register_mask_index); + stack_map.SetStackMaskIndex(stack_map_encoding_, entry.stack_mask_index); if (entry.num_dex_registers == 0 || (entry.live_dex_registers_mask->NumSetBits() == 0)) { // No dex map available. @@ -353,7 +354,7 @@ void StackMapStream::FillIn(MemoryRegion region) { next_dex_register_map_offset += register_region.size(); DexRegisterMap dex_register_map(register_region); stack_map.SetDexRegisterMapOffset( - stack_map_encoding_, register_region.start() - dex_register_locations_region.start()); + stack_map_encoding_, register_region.begin() - dex_register_locations_region.begin()); // Set the dex register location. FillInDexRegisterMap(dex_register_map, @@ -373,7 +374,7 @@ void StackMapStream::FillIn(MemoryRegion region) { // Currently relative to the dex register map. stack_map.SetInlineDescriptorOffset( - stack_map_encoding_, inline_region.start() - dex_register_locations_region.start()); + stack_map_encoding_, inline_region.begin() - dex_register_locations_region.begin()); inline_info.SetDepth(inline_info_encoding_, entry.inlining_depth); DCHECK_LE(entry.inline_infos_start_index + entry.inlining_depth, inline_infos_.size()); @@ -408,7 +409,7 @@ void StackMapStream::FillIn(MemoryRegion region) { DexRegisterMap dex_register_map(register_region); inline_info.SetDexRegisterMapOffsetAtDepth( inline_info_encoding_, - depth, register_region.start() - dex_register_locations_region.start()); + depth, register_region.begin() - dex_register_locations_region.begin()); FillInDexRegisterMap(dex_register_map, inline_entry.num_dex_registers, @@ -423,6 +424,25 @@ void StackMapStream::FillIn(MemoryRegion region) { } } + // Write stack masks table. + size_t stack_mask_bits = encoding.stack_mask_size_in_bits; + if (stack_mask_bits > 0) { + size_t stack_mask_bytes = RoundUp(stack_mask_bits, kBitsPerByte) / kBitsPerByte; + for (size_t i = 0; i < encoding.number_of_stack_masks; ++i) { + MemoryRegion source(&stack_masks_[i * stack_mask_bytes], stack_mask_bytes); + BitMemoryRegion stack_mask = code_info.GetStackMask(encoding, i); + for (size_t bit_index = 0; bit_index < encoding.stack_mask_size_in_bits; ++bit_index) { + stack_mask.StoreBit(bit_index, source.LoadBit(bit_index)); + } + } + } + + // Write register masks table. + for (size_t i = 0; i < encoding.number_of_register_masks; ++i) { + BitMemoryRegion register_mask = code_info.GetRegisterMask(encoding, i); + register_mask.StoreBits(0, register_masks_[i], encoding.register_mask_size_in_bits); + } + // Verify all written data in debug build. if (kIsDebugBuild) { CheckCodeInfo(region); @@ -536,6 +556,38 @@ void StackMapStream::CheckDexRegisterMap(const CodeInfo& code_info, } } +size_t StackMapStream::PrepareRegisterMasks() { + register_masks_.resize(stack_maps_.size(), 0u); + std::unordered_map<uint32_t, size_t> dedupe; + for (StackMapEntry& stack_map : stack_maps_) { + const size_t index = dedupe.size(); + stack_map.register_mask_index = dedupe.emplace(stack_map.register_mask, index).first->second; + register_masks_[index] = stack_map.register_mask; + } + return dedupe.size(); +} + +size_t StackMapStream::PrepareStackMasks(size_t entry_size_in_bits) { + // Preallocate memory since we do not want it to move (the dedup map will point into it). + const size_t byte_entry_size = RoundUp(entry_size_in_bits, kBitsPerByte) / kBitsPerByte; + stack_masks_.resize(byte_entry_size * stack_maps_.size(), 0u); + // For deduplicating we store the stack masks as byte packed for simplicity. We can bit pack later + // when copying out from stack_masks_. + std::unordered_map<MemoryRegion, + size_t, + FNVHash<MemoryRegion>, + MemoryRegion::ContentEquals> dedup(stack_maps_.size()); + for (StackMapEntry& stack_map : stack_maps_) { + size_t index = dedup.size(); + MemoryRegion stack_mask(stack_masks_.data() + index * byte_entry_size, byte_entry_size); + for (size_t i = 0; i < entry_size_in_bits; i++) { + stack_mask.StoreBit(i, stack_map.sp_mask != nullptr && stack_map.sp_mask->IsBitSet(i)); + } + stack_map.stack_mask_index = dedup.emplace(stack_mask, index).first->second; + } + return dedup.size(); +} + // Check that all StackMapStream inputs are correctly encoded by trying to read them back. void StackMapStream::CheckCodeInfo(MemoryRegion region) const { CodeInfo code_info(region); @@ -550,16 +602,19 @@ void StackMapStream::CheckCodeInfo(MemoryRegion region) const { DCHECK_EQ(stack_map.GetNativePcOffset(stack_map_encoding, instruction_set_), entry.native_pc_code_offset.Uint32Value(instruction_set_)); DCHECK_EQ(stack_map.GetDexPc(stack_map_encoding), entry.dex_pc); - DCHECK_EQ(stack_map.GetRegisterMask(stack_map_encoding), entry.register_mask); - size_t num_stack_mask_bits = stack_map.GetNumberOfStackMaskBits(stack_map_encoding); + DCHECK_EQ(stack_map.GetRegisterMaskIndex(stack_map_encoding), entry.register_mask_index); + DCHECK_EQ(code_info.GetRegisterMaskOf(encoding, stack_map), entry.register_mask); + const size_t num_stack_mask_bits = code_info.GetNumberOfStackMaskBits(encoding); + DCHECK_EQ(stack_map.GetStackMaskIndex(stack_map_encoding), entry.stack_mask_index); + BitMemoryRegion stack_mask = code_info.GetStackMaskOf(encoding, stack_map); if (entry.sp_mask != nullptr) { - DCHECK_GE(num_stack_mask_bits, entry.sp_mask->GetNumberOfBits()); + DCHECK_GE(stack_mask.size_in_bits(), entry.sp_mask->GetNumberOfBits()); for (size_t b = 0; b < num_stack_mask_bits; b++) { - DCHECK_EQ(stack_map.GetStackMaskBit(stack_map_encoding, b), entry.sp_mask->IsBitSet(b)); + DCHECK_EQ(stack_mask.LoadBit(b), entry.sp_mask->IsBitSet(b)); } } else { for (size_t b = 0; b < num_stack_mask_bits; b++) { - DCHECK_EQ(stack_map.GetStackMaskBit(stack_map_encoding, b), 0u); + DCHECK_EQ(stack_mask.LoadBit(b), 0u); } } diff --git a/compiler/optimizing/stack_map_stream.h b/compiler/optimizing/stack_map_stream.h index 8fec472437..b1069a17be 100644 --- a/compiler/optimizing/stack_map_stream.h +++ b/compiler/optimizing/stack_map_stream.h @@ -68,6 +68,8 @@ class StackMapStream : public ValueObject { location_catalog_entries_indices_(allocator->Adapter(kArenaAllocStackMapStream)), dex_register_locations_(allocator->Adapter(kArenaAllocStackMapStream)), inline_infos_(allocator->Adapter(kArenaAllocStackMapStream)), + stack_masks_(allocator->Adapter(kArenaAllocStackMapStream)), + register_masks_(allocator->Adapter(kArenaAllocStackMapStream)), stack_mask_max_(-1), dex_pc_max_(0), register_mask_max_(0), @@ -107,6 +109,8 @@ class StackMapStream : public ValueObject { BitVector* live_dex_registers_mask; uint32_t dex_register_map_hash; size_t same_dex_register_map_as_; + uint32_t stack_mask_index; + uint32_t register_mask_index; }; struct InlineInfoEntry { @@ -160,6 +164,12 @@ class StackMapStream : public ValueObject { CodeOffset ComputeMaxNativePcCodeOffset() const; + // Returns the number of unique stack masks. + size_t PrepareStackMasks(size_t entry_size_in_bits); + + // Returns the number of unique register masks. + size_t PrepareRegisterMasks(); + // Returns the index of an entry with the same dex register map as the current_entry, // or kNoSameDexMapFound if no such entry exists. size_t FindEntryWithTheSameDexMap(); @@ -193,6 +203,8 @@ class StackMapStream : public ValueObject { // A set of concatenated maps of Dex register locations indices to `location_catalog_entries_`. ArenaVector<size_t> dex_register_locations_; ArenaVector<InlineInfoEntry> inline_infos_; + ArenaVector<uint8_t> stack_masks_; + ArenaVector<uint32_t> register_masks_; int stack_mask_max_; uint32_t dex_pc_max_; uint32_t register_mask_max_; diff --git a/compiler/optimizing/stack_map_test.cc b/compiler/optimizing/stack_map_test.cc index f68695bcbc..ce6d5c2b22 100644 --- a/compiler/optimizing/stack_map_test.cc +++ b/compiler/optimizing/stack_map_test.cc @@ -27,15 +27,16 @@ namespace art { // Check that the stack mask of given stack map is identical // to the given bit vector. Returns true if they are same. static bool CheckStackMask( + const CodeInfo& code_info, + const CodeInfoEncoding& encoding, const StackMap& stack_map, - StackMapEncoding& encoding, const BitVector& bit_vector) { - int number_of_bits = stack_map.GetNumberOfStackMaskBits(encoding); - if (bit_vector.GetHighestBitSet() >= number_of_bits) { + BitMemoryRegion stack_mask = code_info.GetStackMaskOf(encoding, stack_map); + if (bit_vector.GetNumberOfBits() > encoding.stack_mask_size_in_bits) { return false; } - for (int i = 0; i < number_of_bits; ++i) { - if (stack_map.GetStackMaskBit(encoding, i) != bit_vector.IsBitSet(i)) { + for (size_t i = 0; i < encoding.stack_mask_size_in_bits; ++i) { + if (stack_mask.LoadBit(i) != bit_vector.IsBitSet(i)) { return false; } } @@ -79,9 +80,9 @@ TEST(StackMapTest, Test1) { ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForNativePcOffset(64, encoding))); ASSERT_EQ(0u, stack_map.GetDexPc(encoding.stack_map_encoding)); ASSERT_EQ(64u, stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA)); - ASSERT_EQ(0x3u, stack_map.GetRegisterMask(encoding.stack_map_encoding)); + ASSERT_EQ(0x3u, code_info.GetRegisterMaskOf(encoding, stack_map)); - ASSERT_TRUE(CheckStackMask(stack_map, encoding.stack_map_encoding, sp_mask)); + ASSERT_TRUE(CheckStackMask(code_info, encoding, stack_map, sp_mask)); ASSERT_TRUE(stack_map.HasDexRegisterMap(encoding.stack_map_encoding)); DexRegisterMap dex_register_map = @@ -194,9 +195,9 @@ TEST(StackMapTest, Test2) { ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForNativePcOffset(64, encoding))); ASSERT_EQ(0u, stack_map.GetDexPc(encoding.stack_map_encoding)); ASSERT_EQ(64u, stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA)); - ASSERT_EQ(0x3u, stack_map.GetRegisterMask(encoding.stack_map_encoding)); + ASSERT_EQ(0x3u, code_info.GetRegisterMaskOf(encoding, stack_map)); - ASSERT_TRUE(CheckStackMask(stack_map, encoding.stack_map_encoding, sp_mask1)); + ASSERT_TRUE(CheckStackMask(code_info, encoding, stack_map, sp_mask1)); ASSERT_TRUE(stack_map.HasDexRegisterMap(encoding.stack_map_encoding)); DexRegisterMap dex_register_map = @@ -253,9 +254,9 @@ TEST(StackMapTest, Test2) { ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForNativePcOffset(128u, encoding))); ASSERT_EQ(1u, stack_map.GetDexPc(encoding.stack_map_encoding)); ASSERT_EQ(128u, stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA)); - ASSERT_EQ(0xFFu, stack_map.GetRegisterMask(encoding.stack_map_encoding)); + ASSERT_EQ(0xFFu, code_info.GetRegisterMaskOf(encoding, stack_map)); - ASSERT_TRUE(CheckStackMask(stack_map, encoding.stack_map_encoding, sp_mask2)); + ASSERT_TRUE(CheckStackMask(code_info, encoding, stack_map, sp_mask2)); ASSERT_TRUE(stack_map.HasDexRegisterMap(encoding.stack_map_encoding)); DexRegisterMap dex_register_map = @@ -307,9 +308,9 @@ TEST(StackMapTest, Test2) { ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForNativePcOffset(192u, encoding))); ASSERT_EQ(2u, stack_map.GetDexPc(encoding.stack_map_encoding)); ASSERT_EQ(192u, stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA)); - ASSERT_EQ(0xABu, stack_map.GetRegisterMask(encoding.stack_map_encoding)); + ASSERT_EQ(0xABu, code_info.GetRegisterMaskOf(encoding, stack_map)); - ASSERT_TRUE(CheckStackMask(stack_map, encoding.stack_map_encoding, sp_mask3)); + ASSERT_TRUE(CheckStackMask(code_info, encoding, stack_map, sp_mask3)); ASSERT_TRUE(stack_map.HasDexRegisterMap(encoding.stack_map_encoding)); DexRegisterMap dex_register_map = @@ -361,9 +362,9 @@ TEST(StackMapTest, Test2) { ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForNativePcOffset(256u, encoding))); ASSERT_EQ(3u, stack_map.GetDexPc(encoding.stack_map_encoding)); ASSERT_EQ(256u, stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA)); - ASSERT_EQ(0xCDu, stack_map.GetRegisterMask(encoding.stack_map_encoding)); + ASSERT_EQ(0xCDu, code_info.GetRegisterMaskOf(encoding, stack_map)); - ASSERT_TRUE(CheckStackMask(stack_map, encoding.stack_map_encoding, sp_mask4)); + ASSERT_TRUE(CheckStackMask(code_info, encoding, stack_map, sp_mask4)); ASSERT_TRUE(stack_map.HasDexRegisterMap(encoding.stack_map_encoding)); DexRegisterMap dex_register_map = @@ -443,7 +444,7 @@ TEST(StackMapTest, TestNonLiveDexRegisters) { ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForNativePcOffset(64, encoding))); ASSERT_EQ(0u, stack_map.GetDexPc(encoding.stack_map_encoding)); ASSERT_EQ(64u, stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA)); - ASSERT_EQ(0x3u, stack_map.GetRegisterMask(encoding.stack_map_encoding)); + ASSERT_EQ(0x3u, code_info.GetRegisterMaskOf(encoding, stack_map)); ASSERT_TRUE(stack_map.HasDexRegisterMap(encoding.stack_map_encoding)); DexRegisterMap dex_register_map = @@ -642,7 +643,7 @@ TEST(StackMapTest, TestNoDexRegisterMap) { ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForNativePcOffset(64, encoding))); ASSERT_EQ(0u, stack_map.GetDexPc(encoding.stack_map_encoding)); ASSERT_EQ(64u, stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA)); - ASSERT_EQ(0x3u, stack_map.GetRegisterMask(encoding.stack_map_encoding)); + ASSERT_EQ(0x3u, code_info.GetRegisterMaskOf(encoding, stack_map)); ASSERT_FALSE(stack_map.HasDexRegisterMap(encoding.stack_map_encoding)); ASSERT_FALSE(stack_map.HasInlineInfo(encoding.stack_map_encoding)); @@ -652,7 +653,7 @@ TEST(StackMapTest, TestNoDexRegisterMap) { ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForNativePcOffset(68, encoding))); ASSERT_EQ(1u, stack_map.GetDexPc(encoding.stack_map_encoding)); ASSERT_EQ(68u, stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA)); - ASSERT_EQ(0x4u, stack_map.GetRegisterMask(encoding.stack_map_encoding)); + ASSERT_EQ(0x4u, code_info.GetRegisterMaskOf(encoding, stack_map)); ASSERT_FALSE(stack_map.HasDexRegisterMap(encoding.stack_map_encoding)); ASSERT_FALSE(stack_map.HasInlineInfo(encoding.stack_map_encoding)); @@ -839,4 +840,33 @@ TEST(StackMapTest, CodeOffsetTest) { EXPECT_EQ(offset_mips64.Uint32Value(kMips64), kMips64InstructionAlignment); } + +TEST(StackMapTest, TestDeduplicateStackMask) { + ArenaPool pool; + ArenaAllocator arena(&pool); + StackMapStream stream(&arena, kRuntimeISA); + + ArenaBitVector sp_mask(&arena, 0, true); + sp_mask.SetBit(1); + sp_mask.SetBit(4); + stream.BeginStackMapEntry(0, 4, 0x3, &sp_mask, 0, 0); + stream.EndStackMapEntry(); + stream.BeginStackMapEntry(0, 8, 0x3, &sp_mask, 0, 0); + stream.EndStackMapEntry(); + + size_t size = stream.PrepareForFillIn(); + void* memory = arena.Alloc(size, kArenaAllocMisc); + MemoryRegion region(memory, size); + stream.FillIn(region); + + CodeInfo code_info(region); + CodeInfoEncoding encoding = code_info.ExtractEncoding(); + ASSERT_EQ(2u, code_info.GetNumberOfStackMaps(encoding)); + + StackMap stack_map1 = code_info.GetStackMapForNativePcOffset(4, encoding); + StackMap stack_map2 = code_info.GetStackMapForNativePcOffset(8, encoding); + EXPECT_EQ(stack_map1.GetStackMaskIndex(encoding.stack_map_encoding), + stack_map2.GetStackMaskIndex(encoding.stack_map_encoding)); +} + } // namespace art diff --git a/compiler/utils/assembler_test_base.h b/compiler/utils/assembler_test_base.h index e7edf96722..d76cb1c1df 100644 --- a/compiler/utils/assembler_test_base.h +++ b/compiler/utils/assembler_test_base.h @@ -26,6 +26,7 @@ #include "android-base/strings.h" #include "common_runtime_test.h" // For ScratchFile +#include "exec_utils.h" #include "utils.h" namespace art { diff --git a/compiler/utils/assembler_thumb_test_expected.cc.inc b/compiler/utils/assembler_thumb_test_expected.cc.inc index f132e2737d..071cd575e1 100644 --- a/compiler/utils/assembler_thumb_test_expected.cc.inc +++ b/compiler/utils/assembler_thumb_test_expected.cc.inc @@ -5610,7 +5610,7 @@ const char* const VixlJniHelpersResults[] = { " 214: ecbd 8a10 vpop {s16-s31}\n", " 218: e8bd 8de0 ldmia.w sp!, {r5, r6, r7, r8, sl, fp, pc}\n", " 21c: 4660 mov r0, ip\n", - " 21e: f8d9 c2a4 ldr.w ip, [r9, #676] ; 0x2a4\n", + " 21e: f8d9 c2b4 ldr.w ip, [r9, #692] ; 0x2b4\n", " 222: 47e0 blx ip\n", nullptr }; diff --git a/compiler/utils/x86/assembler_x86.cc b/compiler/utils/x86/assembler_x86.cc index d3b15ac8cf..a24d49e08d 100644 --- a/compiler/utils/x86/assembler_x86.cc +++ b/compiler/utils/x86/assembler_x86.cc @@ -1057,6 +1057,25 @@ void X86Assembler::andpd(XmmRegister dst, const Address& src) { } +void X86Assembler::shufpd(XmmRegister dst, XmmRegister src, const Immediate& imm) { + AssemblerBuffer::EnsureCapacity ensured(&buffer_); + EmitUint8(0x66); + EmitUint8(0x0F); + EmitUint8(0xC6); + EmitXmmRegisterOperand(dst, src); + EmitUint8(imm.value()); +} + + +void X86Assembler::shufps(XmmRegister dst, XmmRegister src, const Immediate& imm) { + AssemblerBuffer::EnsureCapacity ensured(&buffer_); + EmitUint8(0x0F); + EmitUint8(0xC6); + EmitXmmRegisterOperand(dst, src); + EmitUint8(imm.value()); +} + + void X86Assembler::fldl(const Address& src) { AssemblerBuffer::EnsureCapacity ensured(&buffer_); EmitUint8(0xDD); diff --git a/compiler/utils/x86/assembler_x86.h b/compiler/utils/x86/assembler_x86.h index a93616c3e5..4056ca67fb 100644 --- a/compiler/utils/x86/assembler_x86.h +++ b/compiler/utils/x86/assembler_x86.h @@ -472,6 +472,9 @@ class X86Assembler FINAL : public Assembler { void orpd(XmmRegister dst, XmmRegister src); void orps(XmmRegister dst, XmmRegister src); + void shufpd(XmmRegister dst, XmmRegister src, const Immediate& imm); + void shufps(XmmRegister dst, XmmRegister src, const Immediate& imm); + void flds(const Address& src); void fstps(const Address& dst); void fsts(const Address& dst); diff --git a/compiler/utils/x86/assembler_x86_test.cc b/compiler/utils/x86/assembler_x86_test.cc index 4d60a12cb9..1768d8b715 100644 --- a/compiler/utils/x86/assembler_x86_test.cc +++ b/compiler/utils/x86/assembler_x86_test.cc @@ -468,51 +468,43 @@ TEST_F(AssemblerX86Test, MovupdAddr) { } TEST_F(AssemblerX86Test, AddPS) { - GetAssembler()->addps(x86::XmmRegister(x86::XMM0), x86::XmmRegister(x86::XMM1)); - const char* expected = "addps %xmm1, %xmm0\n"; - DriverStr(expected, "addps"); + DriverStr(RepeatFF(&x86::X86Assembler::addps, "addps %{reg2}, %{reg1}"), "addps"); } TEST_F(AssemblerX86Test, AddPD) { - GetAssembler()->addpd(x86::XmmRegister(x86::XMM0), x86::XmmRegister(x86::XMM1)); - const char* expected = "addpd %xmm1, %xmm0\n"; - DriverStr(expected, "addpd"); + DriverStr(RepeatFF(&x86::X86Assembler::addpd, "addpd %{reg2}, %{reg1}"), "addpd"); } TEST_F(AssemblerX86Test, SubPS) { - GetAssembler()->subps(x86::XmmRegister(x86::XMM0), x86::XmmRegister(x86::XMM1)); - const char* expected = "subps %xmm1, %xmm0\n"; - DriverStr(expected, "subps"); + DriverStr(RepeatFF(&x86::X86Assembler::subps, "subps %{reg2}, %{reg1}"), "subps"); } TEST_F(AssemblerX86Test, SubPD) { - GetAssembler()->subpd(x86::XmmRegister(x86::XMM0), x86::XmmRegister(x86::XMM1)); - const char* expected = "subpd %xmm1, %xmm0\n"; - DriverStr(expected, "subpd"); + DriverStr(RepeatFF(&x86::X86Assembler::subpd, "subpd %{reg2}, %{reg1}"), "subpd"); } TEST_F(AssemblerX86Test, MulPS) { - GetAssembler()->mulps(x86::XmmRegister(x86::XMM0), x86::XmmRegister(x86::XMM1)); - const char* expected = "mulps %xmm1, %xmm0\n"; - DriverStr(expected, "mulps"); + DriverStr(RepeatFF(&x86::X86Assembler::mulps, "mulps %{reg2}, %{reg1}"), "mulps"); } TEST_F(AssemblerX86Test, MulPD) { - GetAssembler()->mulpd(x86::XmmRegister(x86::XMM0), x86::XmmRegister(x86::XMM1)); - const char* expected = "mulpd %xmm1, %xmm0\n"; - DriverStr(expected, "mulpd"); + DriverStr(RepeatFF(&x86::X86Assembler::mulpd, "mulpd %{reg2}, %{reg1}"), "mulpd"); } TEST_F(AssemblerX86Test, DivPS) { - GetAssembler()->divps(x86::XmmRegister(x86::XMM0), x86::XmmRegister(x86::XMM1)); - const char* expected = "divps %xmm1, %xmm0\n"; - DriverStr(expected, "divps"); + DriverStr(RepeatFF(&x86::X86Assembler::divps, "divps %{reg2}, %{reg1}"), "divps"); } TEST_F(AssemblerX86Test, DivPD) { - GetAssembler()->divpd(x86::XmmRegister(x86::XMM0), x86::XmmRegister(x86::XMM1)); - const char* expected = "divpd %xmm1, %xmm0\n"; - DriverStr(expected, "divpd"); + DriverStr(RepeatFF(&x86::X86Assembler::divpd, "divpd %{reg2}, %{reg1}"), "divpd"); +} + +TEST_F(AssemblerX86Test, ShufPS) { + DriverStr(RepeatFFI(&x86::X86Assembler::shufps, 1, "shufps ${imm}, %{reg2}, %{reg1}"), "shufps"); +} + +TEST_F(AssemblerX86Test, ShufPD) { + DriverStr(RepeatFFI(&x86::X86Assembler::shufpd, 1, "shufpd ${imm}, %{reg2}, %{reg1}"), "shufpd"); } ///////////////// diff --git a/compiler/utils/x86_64/assembler_x86_64.cc b/compiler/utils/x86_64/assembler_x86_64.cc index 2366b68f11..c2c44ab58c 100644 --- a/compiler/utils/x86_64/assembler_x86_64.cc +++ b/compiler/utils/x86_64/assembler_x86_64.cc @@ -1213,6 +1213,28 @@ void X86_64Assembler::orps(XmmRegister dst, XmmRegister src) { EmitXmmRegisterOperand(dst.LowBits(), src); } + +void X86_64Assembler::shufpd(XmmRegister dst, XmmRegister src, const Immediate& imm) { + AssemblerBuffer::EnsureCapacity ensured(&buffer_); + EmitUint8(0x66); + EmitOptionalRex32(dst, src); + EmitUint8(0x0F); + EmitUint8(0xC6); + EmitXmmRegisterOperand(dst.LowBits(), src); + EmitUint8(imm.value()); +} + + +void X86_64Assembler::shufps(XmmRegister dst, XmmRegister src, const Immediate& imm) { + AssemblerBuffer::EnsureCapacity ensured(&buffer_); + EmitOptionalRex32(dst, src); + EmitUint8(0x0F); + EmitUint8(0xC6); + EmitXmmRegisterOperand(dst.LowBits(), src); + EmitUint8(imm.value()); +} + + void X86_64Assembler::fldl(const Address& src) { AssemblerBuffer::EnsureCapacity ensured(&buffer_); EmitUint8(0xDD); diff --git a/compiler/utils/x86_64/assembler_x86_64.h b/compiler/utils/x86_64/assembler_x86_64.h index 5923a41fe3..e140b45a00 100644 --- a/compiler/utils/x86_64/assembler_x86_64.h +++ b/compiler/utils/x86_64/assembler_x86_64.h @@ -495,6 +495,9 @@ class X86_64Assembler FINAL : public Assembler { void orpd(XmmRegister dst, XmmRegister src); void orps(XmmRegister dst, XmmRegister src); + void shufpd(XmmRegister dst, XmmRegister src, const Immediate& imm); + void shufps(XmmRegister dst, XmmRegister src, const Immediate& imm); + void flds(const Address& src); void fstps(const Address& dst); void fsts(const Address& dst); diff --git a/compiler/utils/x86_64/assembler_x86_64_test.cc b/compiler/utils/x86_64/assembler_x86_64_test.cc index 2812c34406..efa5cc97ea 100644 --- a/compiler/utils/x86_64/assembler_x86_64_test.cc +++ b/compiler/utils/x86_64/assembler_x86_64_test.cc @@ -1203,6 +1203,14 @@ TEST_F(AssemblerX86_64Test, Orpd) { DriverStr(RepeatFF(&x86_64::X86_64Assembler::orpd, "orpd %{reg2}, %{reg1}"), "orpd"); } +TEST_F(AssemblerX86_64Test, Shufps) { + DriverStr(RepeatFFI(&x86_64::X86_64Assembler::shufps, 1, "shufps ${imm}, %{reg2}, %{reg1}"), "shufps"); +} + +TEST_F(AssemblerX86_64Test, Shufpd) { + DriverStr(RepeatFFI(&x86_64::X86_64Assembler::shufpd, 1, "shufpd ${imm}, %{reg2}, %{reg1}"), "shufpd"); +} + TEST_F(AssemblerX86_64Test, UcomissAddress) { GetAssembler()->ucomiss(x86_64::XmmRegister(x86_64::XMM0), x86_64::Address( x86_64::CpuRegister(x86_64::RDI), x86_64::CpuRegister(x86_64::RBX), x86_64::TIMES_4, 12)); diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index e8a92c1914..196d8d4220 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -277,7 +277,6 @@ NO_RETURN static void Usage(const char* fmt, ...) { "|balanced" "|speed-profile" "|speed" - "|layout-profile" "|everything-profile" "|everything):"); UsageError(" select compiler filter."); @@ -1283,13 +1282,10 @@ class Dex2Oat FINAL { DCHECK_EQ(input_vdex_fd_, -1); if (!input_vdex_.empty()) { std::string error_msg; - input_vdex_file_.reset(VdexFile::Open(input_vdex_, - /* writable */ false, - /* low_4gb */ false, - &error_msg)); - if (input_vdex_file_ != nullptr && !input_vdex_file_->IsValid()) { - input_vdex_file_.reset(nullptr); - } + input_vdex_file_ = VdexFile::Open(input_vdex_, + /* writable */ false, + /* low_4gb */ false, + &error_msg); } DCHECK_EQ(output_vdex_fd_, -1); @@ -1331,19 +1327,16 @@ class Dex2Oat FINAL { PLOG(WARNING) << "Failed getting length of vdex file"; } else { std::string error_msg; - input_vdex_file_.reset(VdexFile::Open(input_vdex_fd_, - s.st_size, - "vdex", - /* writable */ false, - /* low_4gb */ false, - &error_msg)); + input_vdex_file_ = VdexFile::Open(input_vdex_fd_, + s.st_size, + "vdex", + /* writable */ false, + /* low_4gb */ false, + &error_msg); // If there's any problem with the passed vdex, just warn and proceed // without it. if (input_vdex_file_ == nullptr) { - PLOG(WARNING) << "Failed opening vdex file " << error_msg; - } else if (!input_vdex_file_->IsValid()) { - PLOG(WARNING) << "Existing vdex file is invalid"; - input_vdex_file_.reset(nullptr); + PLOG(WARNING) << "Failed opening vdex file: " << error_msg; } } } @@ -1540,9 +1533,9 @@ class Dex2Oat FINAL { std::unique_ptr<MemMap> opened_dex_files_map; std::vector<std::unique_ptr<const DexFile>> opened_dex_files; // No need to verify the dex file for: - // 1) dexlayout, which already verified it + // 1) kSpeedProfile, since it includes dexlayout, which does the verification. // 2) when we have a vdex file, which means it was already verified. - bool verify = compiler_options_->GetCompilerFilter() != CompilerFilter::kLayoutProfile && + bool verify = compiler_options_->GetCompilerFilter() != CompilerFilter::kSpeedProfile && (input_vdex_file_ == nullptr); if (!oat_writers_[i]->WriteAndOpenDexFiles( kIsVdexEnabled ? vdex_files_[i].get() : oat_files_[i].get(), @@ -2349,7 +2342,7 @@ class Dex2Oat FINAL { compiler_options_.get(), oat_file.get())); elf_writers_.back()->Start(); - bool do_dexlayout = compiler_options_->GetCompilerFilter() == CompilerFilter::kLayoutProfile; + bool do_dexlayout = compiler_options_->GetCompilerFilter() == CompilerFilter::kSpeedProfile; oat_writers_.emplace_back(new OatWriter( IsBootImage(), timings_, do_dexlayout ? profile_compilation_info_.get() : nullptr)); } diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc index e86e560b1a..c2275aca95 100644 --- a/dex2oat/dex2oat_test.cc +++ b/dex2oat/dex2oat_test.cc @@ -125,7 +125,7 @@ class Dex2oatTest : public Dex2oatEnvironmentTest { class_path = OatFile::kSpecialSharedLibrary; } argv.push_back(class_path); - if (runtime->IsDebuggable()) { + if (runtime->IsJavaDebuggable()) { argv.push_back("--debuggable"); } runtime->AddCurrentRuntimeFeaturesAsDex2OatArguments(&argv); @@ -591,7 +591,7 @@ class Dex2oatLayoutTest : public Dex2oatTest { GenerateProfile(profile_location, dex_location, dex_file->GetLocationChecksum()); const std::vector<std::string>& extra_args = { "--profile-file=" + profile_location }; - GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kLayoutProfile, extra_args); + GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeedProfile, extra_args); CheckValidity(); ASSERT_TRUE(success_); @@ -632,7 +632,7 @@ class Dex2oatLayoutTest : public Dex2oatTest { EXPECT_EQ(old_class1, new_class0); } - EXPECT_EQ(odex_file->GetCompilerFilter(), CompilerFilter::kLayoutProfile); + EXPECT_EQ(odex_file->GetCompilerFilter(), CompilerFilter::kSpeedProfile); } // Check whether the dex2oat run was really successful. diff --git a/dexdump/dexdump_test.cc b/dexdump/dexdump_test.cc index 53dda6a995..640f387a80 100644 --- a/dexdump/dexdump_test.cc +++ b/dexdump/dexdump_test.cc @@ -23,6 +23,7 @@ #include "common_runtime_test.h" #include "runtime/arch/instruction_set.h" +#include "runtime/exec_utils.h" #include "runtime/os.h" #include "runtime/utils.h" #include "utils.h" diff --git a/dexlayout/dexlayout_test.cc b/dexlayout/dexlayout_test.cc index 46a1c43548..da1e1d26dc 100644 --- a/dexlayout/dexlayout_test.cc +++ b/dexlayout/dexlayout_test.cc @@ -23,6 +23,7 @@ #include "base/unix_file/fd_file.h" #include "common_runtime_test.h" +#include "exec_utils.h" #include "utils.h" namespace art { diff --git a/dexlist/dexlist_test.cc b/dexlist/dexlist_test.cc index 13209427c9..173a456982 100644 --- a/dexlist/dexlist_test.cc +++ b/dexlist/dexlist_test.cc @@ -23,6 +23,7 @@ #include "common_runtime_test.h" #include "runtime/arch/instruction_set.h" +#include "runtime/exec_utils.h" #include "runtime/gc/heap.h" #include "runtime/gc/space/image_space.h" #include "runtime/os.h" diff --git a/dexoptanalyzer/Android.bp b/dexoptanalyzer/Android.bp new file mode 100644 index 0000000000..cf4c99ec6d --- /dev/null +++ b/dexoptanalyzer/Android.bp @@ -0,0 +1,68 @@ +// +// Copyright (C) 2017 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. +// + +cc_defaults { + name: "dexoptanalyzer-defaults", + host_supported: true, + defaults: ["art_defaults"], + srcs: [ + "dexoptanalyzer.cc", + ], + + target: { + android: { + compile_multilib: "prefer32", + }, + }, + + include_dirs: [ + "art/cmdline", + ], + + shared_libs: [ + "libbase", + ], +} + +art_cc_binary { + name: "dexoptanalyzer", + defaults: ["dexoptanalyzer-defaults"], + shared_libs: [ + "libart", + ], +} + +art_cc_binary { + name: "dexoptanalyzerd", + defaults: [ + "dexoptanalyzer-defaults", + "art_debug_defaults", + ], + shared_libs: [ + "libartd", + ], +} + +art_cc_test { + name: "art_dexoptanalyzer_tests", + defaults: [ + "art_gtest_defaults", + ], + shared_libs: [ + "libbacktrace" + ], + srcs: ["dexoptanalyzer_test.cc"], +} diff --git a/dexoptanalyzer/dexoptanalyzer.cc b/dexoptanalyzer/dexoptanalyzer.cc new file mode 100644 index 0000000000..965e4073ea --- /dev/null +++ b/dexoptanalyzer/dexoptanalyzer.cc @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2017 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 <string> + +#include "android-base/stringprintf.h" +#include "android-base/strings.h" +#include "compiler_filter.h" +#include "dex_file.h" +#include "noop_compiler_callbacks.h" +#include "oat_file_assistant.h" +#include "os.h" +#include "runtime.h" +#include "thread-inl.h" +#include "utils.h" + +namespace art { + +// See OatFileAssistant docs for the meaning of the valid return codes. +enum ReturnCodes { + kNoDexOptNeeded = 0, + kDex2OatFromScratch = 1, + kDex2OatForBootImageOat = 2, + kDex2OatForFilterOat = 3, + kDex2OatForRelocationOat = 4, + kDex2OatForBootImageOdex = 5, + kDex2OatForFilterOdex = 6, + kDex2OatForRelocationOdex = 7, + + kErrorInvalidArguments = 101, + kErrorCannotCreateRuntime = 102, + kErrorUnknownDexOptNeeded = 103 +}; + +static int original_argc; +static char** original_argv; + +static std::string CommandLine() { + std::vector<std::string> command; + for (int i = 0; i < original_argc; ++i) { + command.push_back(original_argv[i]); + } + return android::base::Join(command, ' '); +} + +static void UsageErrorV(const char* fmt, va_list ap) { + std::string error; + android::base::StringAppendV(&error, fmt, ap); + LOG(ERROR) << error; +} + +static void UsageError(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + UsageErrorV(fmt, ap); + va_end(ap); +} + +NO_RETURN static void Usage(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + UsageErrorV(fmt, ap); + va_end(ap); + + UsageError("Command: %s", CommandLine().c_str()); + UsageError(" Performs a dexopt analysis on the given dex file and returns whether or not"); + UsageError(" the dex file needs to be dexopted."); + UsageError("Usage: dexoptanalyzer [options]..."); + UsageError(""); + UsageError(" --dex-file=<filename>: the dex file which should be analyzed."); + UsageError(""); + UsageError(" --isa=<string>: the instruction set for which the analysis should be performed."); + UsageError(""); + UsageError(" --compiler-filter=<string>: the target compiler filter to be used as reference"); + UsageError(" when deciding if the dex file needs to be optimized."); + UsageError(""); + UsageError(" --assume-profile-changed: assumes the profile information has changed"); + UsageError(" when deciding if the dex file needs to be optimized."); + UsageError(""); + UsageError(" --image=<filename>: optional, the image to be used to decide if the associated"); + UsageError(" oat file is up to date. Defaults to $ANDROID_ROOT/framework/boot.art."); + UsageError(" Example: --image=/system/framework/boot.art"); + UsageError(""); + UsageError(" --android-data=<directory>: optional, the directory which should be used as"); + UsageError(" android-data. By default ANDROID_DATA env variable is used."); + UsageError(""); + UsageError("Return code:"); + UsageError(" To make it easier to integrate with the internal tools this command will make"); + UsageError(" available its result (dexoptNeeded) as the exit/return code. i.e. it will not"); + UsageError(" return 0 for success and a non zero values for errors as the conventional"); + UsageError(" commands. The following return codes are possible:"); + UsageError(" kNoDexOptNeeded = 0"); + UsageError(" kDex2OatFromScratch = 1"); + UsageError(" kDex2OatForBootImageOat = 2"); + UsageError(" kDex2OatForFilterOat = 3"); + UsageError(" kDex2OatForRelocationOat = 4"); + UsageError(" kDex2OatForBootImageOdex = 5"); + UsageError(" kDex2OatForFilterOdex = 6"); + UsageError(" kDex2OatForRelocationOdex = 7"); + + UsageError(" kErrorInvalidArguments = 101"); + UsageError(" kErrorCannotCreateRuntime = 102"); + UsageError(" kErrorUnknownDexOptNeeded = 103"); + UsageError(""); + + exit(kErrorInvalidArguments); +} + +class DexoptAnalyzer FINAL { + public: + DexoptAnalyzer() : assume_profile_changed_(false) {} + + void ParseArgs(int argc, char **argv) { + original_argc = argc; + original_argv = argv; + + InitLogging(argv, Runtime::Aborter); + // Skip over the command name. + argv++; + argc--; + + if (argc == 0) { + Usage("No arguments specified"); + } + + for (int i = 0; i < argc; ++i) { + const StringPiece option(argv[i]); + if (option == "--assume-profile-changed") { + assume_profile_changed_ = true; + } else if (option.starts_with("--dex-file=")) { + dex_file_ = option.substr(strlen("--dex-file=")).ToString(); + } else if (option.starts_with("--compiler-filter=")) { + std::string filter_str = option.substr(strlen("--compiler-filter=")).ToString(); + if (!CompilerFilter::ParseCompilerFilter(filter_str.c_str(), &compiler_filter_)) { + Usage("Invalid compiler filter '%s'", option.data()); + } + } else if (option.starts_with("--isa=")) { + std::string isa_str = option.substr(strlen("--isa=")).ToString(); + isa_ = GetInstructionSetFromString(isa_str.c_str()); + if (isa_ == kNone) { + Usage("Invalid isa '%s'", option.data()); + } + } else if (option.starts_with("--image=")) { + image_ = option.substr(strlen("--image=")).ToString(); + } else if (option.starts_with("--android-data=")) { + // Overwrite android-data if needed (oat file assistant relies on a valid directory to + // compute dalvik-cache folder). This is mostly used in tests. + std::string new_android_data = option.substr(strlen("--android-data=")).ToString(); + setenv("ANDROID_DATA", new_android_data.c_str(), 1); + } else { + Usage("Unknown argument '%s'", option.data()); + } + } + + if (image_.empty()) { + // If we don't receive the image, try to use the default one. + // Tests may specify a different image (e.g. core image). + std::string error_msg; + image_ = GetDefaultBootImageLocation(&error_msg); + + if (image_.empty()) { + LOG(ERROR) << error_msg; + Usage("--image unspecified and ANDROID_ROOT not set or image file does not exist."); + } + } + } + + bool CreateRuntime() { + RuntimeOptions options; + // The image could be custom, so make sure we explicitly pass it. + std::string img = "-Ximage:" + image_; + options.push_back(std::make_pair(img.c_str(), nullptr)); + // The instruction set of the image should match the instruction set we will test. + const void* isa_opt = reinterpret_cast<const void*>(GetInstructionSetString(isa_)); + options.push_back(std::make_pair("imageinstructionset", isa_opt)); + // Disable libsigchain. We don't don't need it to evaluate DexOptNeeded status. + options.push_back(std::make_pair("-Xno-sig-chain", nullptr)); + // Pretend we are a compiler so that we can re-use the same infrastructure to load a different + // ISA image and minimize the amount of things that get started. + NoopCompilerCallbacks callbacks; + options.push_back(std::make_pair("compilercallbacks", &callbacks)); + // Make sure we don't attempt to relocate. The tool should only retrieve the DexOptNeeded + // status and not attempt to relocate the boot image. + options.push_back(std::make_pair("-Xnorelocate", nullptr)); + + if (!Runtime::Create(options, false)) { + LOG(ERROR) << "Unable to initialize runtime"; + return false; + } + // Runtime::Create acquired the mutator_lock_ that is normally given away when we + // Runtime::Start. Give it away now. + Thread::Current()->TransitionFromRunnableToSuspended(kNative); + + return true; + } + + int GetDexOptNeeded() { + // If the file does not exist there's nothing to do. + // This is a fast path to avoid creating the runtime (b/34385298). + if (!OS::FileExists(dex_file_.c_str())) { + return kNoDexOptNeeded; + } + if (!CreateRuntime()) { + return kErrorCannotCreateRuntime; + } + OatFileAssistant oat_file_assistant(dex_file_.c_str(), isa_, /*load_executable*/ false); + // Always treat elements of the bootclasspath as up-to-date. + // TODO(calin): this check should be in OatFileAssistant. + if (oat_file_assistant.IsInBootClassPath()) { + return kNoDexOptNeeded; + } + int dexoptNeeded = oat_file_assistant.GetDexOptNeeded( + compiler_filter_, assume_profile_changed_); + + // Convert OatFileAssitant codes to dexoptanalyzer codes. + switch (dexoptNeeded) { + case OatFileAssistant::kNoDexOptNeeded: return kNoDexOptNeeded; + case OatFileAssistant::kDex2OatFromScratch: return kDex2OatFromScratch; + case OatFileAssistant::kDex2OatForBootImage: return kDex2OatForBootImageOat; + case OatFileAssistant::kDex2OatForFilter: return kDex2OatForFilterOat; + case OatFileAssistant::kDex2OatForRelocation: return kDex2OatForRelocationOat; + + case -OatFileAssistant::kDex2OatForBootImage: return kDex2OatForBootImageOdex; + case -OatFileAssistant::kDex2OatForFilter: return kDex2OatForFilterOdex; + case -OatFileAssistant::kDex2OatForRelocation: return kDex2OatForRelocationOdex; + default: + LOG(ERROR) << "Unknown dexoptNeeded " << dexoptNeeded; + return kErrorUnknownDexOptNeeded; + } + } + + private: + std::string dex_file_; + InstructionSet isa_; + CompilerFilter::Filter compiler_filter_; + bool assume_profile_changed_; + std::string image_; +}; + +static int dexoptAnalyze(int argc, char** argv) { + DexoptAnalyzer analyzer; + + // Parse arguments. Argument mistakes will lead to exit(kErrorInvalidArguments) in UsageError. + analyzer.ParseArgs(argc, argv); + return analyzer.GetDexOptNeeded(); +} + +} // namespace art + +int main(int argc, char **argv) { + return art::dexoptAnalyze(argc, argv); +} diff --git a/dexoptanalyzer/dexoptanalyzer_test.cc b/dexoptanalyzer/dexoptanalyzer_test.cc new file mode 100644 index 0000000000..57d3f1f68b --- /dev/null +++ b/dexoptanalyzer/dexoptanalyzer_test.cc @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2017 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 <gtest/gtest.h> + +#include "arch/instruction_set.h" +#include "compiler_filter.h" +#include "dexopt_test.h" + +namespace art { + +class DexoptAnalyzerTest : public DexoptTest { + protected: + std::string GetDexoptAnalyzerCmd() { + std::string file_path = GetTestAndroidRoot(); + file_path += "/bin/dexoptanalyzer"; + if (kIsDebugBuild) { + file_path += "d"; + } + EXPECT_TRUE(OS::FileExists(file_path.c_str())) << file_path << " should be a valid file path"; + return file_path; + } + + int Analyze(const std::string& dex_file, + CompilerFilter::Filter compiler_filter, + bool assume_profile_changed) { + std::string dexoptanalyzer_cmd = GetDexoptAnalyzerCmd(); + std::vector<std::string> argv_str; + argv_str.push_back(dexoptanalyzer_cmd); + argv_str.push_back("--dex-file=" + dex_file); + argv_str.push_back("--isa=" + std::string(GetInstructionSetString(kRuntimeISA))); + argv_str.push_back("--compiler-filter=" + CompilerFilter::NameOfFilter(compiler_filter)); + if (assume_profile_changed) { + argv_str.push_back("--assume-profile-changed"); + } + argv_str.push_back("--image=" + GetImageLocation()); + argv_str.push_back("--android-data=" + android_data_); + + std::string error; + return ExecAndReturnCode(argv_str, &error); + } + + int DexoptanalyzerToOatFileAssistant(int dexoptanalyzerResult) { + switch (dexoptanalyzerResult) { + case 0: return OatFileAssistant::kNoDexOptNeeded; + case 1: return OatFileAssistant::kDex2OatFromScratch; + case 2: return OatFileAssistant::kDex2OatForBootImage; + case 3: return OatFileAssistant::kDex2OatForFilter; + case 4: return OatFileAssistant::kDex2OatForRelocation; + case 5: return -OatFileAssistant::kDex2OatForBootImage; + case 6: return -OatFileAssistant::kDex2OatForFilter; + case 7: return -OatFileAssistant::kDex2OatForRelocation; + default: return dexoptanalyzerResult; + } + } + + // Verify that the output of dexoptanalyzer for the given arguments is the same + // as the output of OatFileAssistant::GetDexOptNeeded. + void Verify(const std::string& dex_file, + CompilerFilter::Filter compiler_filter, + bool assume_profile_changed = false) { + int dexoptanalyzerResult = Analyze(dex_file, compiler_filter, assume_profile_changed); + dexoptanalyzerResult = DexoptanalyzerToOatFileAssistant(dexoptanalyzerResult); + OatFileAssistant oat_file_assistant(dex_file.c_str(), kRuntimeISA, /*load_executable*/ false); + int assistantResult = oat_file_assistant.GetDexOptNeeded( + compiler_filter, assume_profile_changed); + EXPECT_EQ(assistantResult, dexoptanalyzerResult); + } +}; + +// The tests below exercise the same test case from oat_file_assistant_test.cc. + +// Case: We have a DEX file, but no OAT file for it. +TEST_F(DexoptAnalyzerTest, DexNoOat) { + std::string dex_location = GetScratchDir() + "/DexNoOat.jar"; + Copy(GetDexSrc1(), dex_location); + + Verify(dex_location, CompilerFilter::kSpeed); + Verify(dex_location, CompilerFilter::kVerifyAtRuntime); + Verify(dex_location, CompilerFilter::kInterpretOnly); + Verify(dex_location, CompilerFilter::kSpeedProfile); +} + +// Case: We have a DEX file and up-to-date OAT file for it. +TEST_F(DexoptAnalyzerTest, OatUpToDate) { + std::string dex_location = GetScratchDir() + "/OatUpToDate.jar"; + Copy(GetDexSrc1(), dex_location); + GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed); + + Verify(dex_location, CompilerFilter::kSpeed); + Verify(dex_location, CompilerFilter::kInterpretOnly); + Verify(dex_location, CompilerFilter::kVerifyAtRuntime); + Verify(dex_location, CompilerFilter::kEverything); +} + +// Case: We have a DEX file and speed-profile OAT file for it. +TEST_F(DexoptAnalyzerTest, ProfileOatUpToDate) { + std::string dex_location = GetScratchDir() + "/ProfileOatUpToDate.jar"; + Copy(GetDexSrc1(), dex_location); + GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeedProfile); + + Verify(dex_location, CompilerFilter::kSpeedProfile, false); + Verify(dex_location, CompilerFilter::kInterpretOnly, false); + Verify(dex_location, CompilerFilter::kSpeedProfile, true); + Verify(dex_location, CompilerFilter::kInterpretOnly, true); +} + +// Case: We have a MultiDEX file and up-to-date OAT file for it. +TEST_F(DexoptAnalyzerTest, MultiDexOatUpToDate) { + std::string dex_location = GetScratchDir() + "/MultiDexOatUpToDate.jar"; + Copy(GetMultiDexSrc1(), dex_location); + GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed); + + Verify(dex_location, CompilerFilter::kSpeed, false); +} + +// Case: We have a MultiDEX file where the secondary dex file is out of date. +TEST_F(DexoptAnalyzerTest, MultiDexSecondaryOutOfDate) { + std::string dex_location = GetScratchDir() + "/MultiDexSecondaryOutOfDate.jar"; + + // Compile code for GetMultiDexSrc1. + Copy(GetMultiDexSrc1(), dex_location); + GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed); + + // Now overwrite the dex file with GetMultiDexSrc2 so the secondary checksum + // is out of date. + Copy(GetMultiDexSrc2(), dex_location); + + Verify(dex_location, CompilerFilter::kSpeed, false); +} + + +// Case: We have a DEX file and an OAT file out of date with respect to the +// dex checksum. +TEST_F(DexoptAnalyzerTest, OatDexOutOfDate) { + std::string dex_location = GetScratchDir() + "/OatDexOutOfDate.jar"; + + // We create a dex, generate an oat for it, then overwrite the dex with a + // different dex to make the oat out of date. + Copy(GetDexSrc1(), dex_location); + GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed); + Copy(GetDexSrc2(), dex_location); + + Verify(dex_location, CompilerFilter::kVerifyAtRuntime); + Verify(dex_location, CompilerFilter::kSpeed); +} + +// Case: We have a DEX file and an OAT file out of date with respect to the +// boot image. +TEST_F(DexoptAnalyzerTest, OatImageOutOfDate) { + std::string dex_location = GetScratchDir() + "/OatImageOutOfDate.jar"; + + Copy(GetDexSrc1(), dex_location); + GenerateOatForTest(dex_location.c_str(), + CompilerFilter::kSpeed, + /*relocate*/true, + /*pic*/false, + /*with_alternate_image*/true); + + Verify(dex_location, CompilerFilter::kVerifyAtRuntime); + Verify(dex_location, CompilerFilter::kInterpretOnly); + Verify(dex_location, CompilerFilter::kSpeed); +} + +// Case: We have a DEX file and a verify-at-runtime OAT file out of date with +// respect to the boot image. +// It shouldn't matter that the OAT file is out of date, because it is +// verify-at-runtime. +TEST_F(DexoptAnalyzerTest, OatVerifyAtRuntimeImageOutOfDate) { + std::string dex_location = GetScratchDir() + "/OatVerifyAtRuntimeImageOutOfDate.jar"; + + Copy(GetDexSrc1(), dex_location); + GenerateOatForTest(dex_location.c_str(), + CompilerFilter::kVerifyAtRuntime, + /*relocate*/true, + /*pic*/false, + /*with_alternate_image*/true); + + Verify(dex_location, CompilerFilter::kVerifyAtRuntime); + Verify(dex_location, CompilerFilter::kInterpretOnly); +} + +// Case: We have a DEX file and an ODEX file, but no OAT file. +TEST_F(DexoptAnalyzerTest, DexOdexNoOat) { + std::string dex_location = GetScratchDir() + "/DexOdexNoOat.jar"; + std::string odex_location = GetOdexDir() + "/DexOdexNoOat.odex"; + + Copy(GetDexSrc1(), dex_location); + GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed); + + Verify(dex_location, CompilerFilter::kVerifyAtRuntime); + Verify(dex_location, CompilerFilter::kSpeed); +} + +// Case: We have a stripped DEX file and a PIC ODEX file, but no OAT file. +TEST_F(DexoptAnalyzerTest, StrippedDexOdexNoOat) { + std::string dex_location = GetScratchDir() + "/StrippedDexOdexNoOat.jar"; + std::string odex_location = GetOdexDir() + "/StrippedDexOdexNoOat.odex"; + + Copy(GetDexSrc1(), dex_location); + GeneratePicOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed); + + // Strip the dex file + Copy(GetStrippedDexSrc1(), dex_location); + + Verify(dex_location, CompilerFilter::kSpeed); +} + +// Case: We have a stripped DEX file, a PIC ODEX file, and an out-of-date OAT file. +TEST_F(DexoptAnalyzerTest, StrippedDexOdexOat) { + std::string dex_location = GetScratchDir() + "/StrippedDexOdexOat.jar"; + std::string odex_location = GetOdexDir() + "/StrippedDexOdexOat.odex"; + + // Create the oat file from a different dex file so it looks out of date. + Copy(GetDexSrc2(), dex_location); + GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed); + + // Create the odex file + Copy(GetDexSrc1(), dex_location); + GeneratePicOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed); + + // Strip the dex file. + Copy(GetStrippedDexSrc1(), dex_location); + + Verify(dex_location, CompilerFilter::kVerifyAtRuntime); + Verify(dex_location, CompilerFilter::kSpeed); + Verify(dex_location, CompilerFilter::kEverything); +} + +// Case: We have a stripped (or resource-only) DEX file, no ODEX file and no +// OAT file. Expect: The status is kNoDexOptNeeded. +TEST_F(DexoptAnalyzerTest, ResourceOnlyDex) { + std::string dex_location = GetScratchDir() + "/ResourceOnlyDex.jar"; + + Copy(GetStrippedDexSrc1(), dex_location); + + Verify(dex_location, CompilerFilter::kSpeed); + Verify(dex_location, CompilerFilter::kVerifyAtRuntime); + Verify(dex_location, CompilerFilter::kInterpretOnly); +} + +// Case: We have a DEX file, an ODEX file and an OAT file, where the ODEX and +// OAT files both have patch delta of 0. +TEST_F(DexoptAnalyzerTest, OdexOatOverlap) { + std::string dex_location = GetScratchDir() + "/OdexOatOverlap.jar"; + std::string odex_location = GetOdexDir() + "/OdexOatOverlap.odex"; + std::string oat_location = GetOdexDir() + "/OdexOatOverlap.oat"; + + Copy(GetDexSrc1(), dex_location); + GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed); + + // Create the oat file by copying the odex so they are located in the same + // place in memory. + Copy(odex_location, oat_location); + + Verify(dex_location, CompilerFilter::kSpeed); +} + +// Case: We have a DEX file and a PIC ODEX file, but no OAT file. +TEST_F(DexoptAnalyzerTest, DexPicOdexNoOat) { + std::string dex_location = GetScratchDir() + "/DexPicOdexNoOat.jar"; + std::string odex_location = GetOdexDir() + "/DexPicOdexNoOat.odex"; + + Copy(GetDexSrc1(), dex_location); + GeneratePicOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed); + + Verify(dex_location, CompilerFilter::kSpeed); + Verify(dex_location, CompilerFilter::kEverything); +} + +// Case: We have a DEX file and a VerifyAtRuntime ODEX file, but no OAT file.. +TEST_F(DexoptAnalyzerTest, DexVerifyAtRuntimeOdexNoOat) { + std::string dex_location = GetScratchDir() + "/DexVerifyAtRuntimeOdexNoOat.jar"; + std::string odex_location = GetOdexDir() + "/DexVerifyAtRuntimeOdexNoOat.odex"; + + Copy(GetDexSrc1(), dex_location); + GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kVerifyAtRuntime); + + Verify(dex_location, CompilerFilter::kVerifyAtRuntime); + Verify(dex_location, CompilerFilter::kSpeed); +} + +// Case: Non-standard extension for dex file. +TEST_F(DexoptAnalyzerTest, LongDexExtension) { + std::string dex_location = GetScratchDir() + "/LongDexExtension.jarx"; + Copy(GetDexSrc1(), dex_location); + + Verify(dex_location, CompilerFilter::kSpeed); +} + +// Case: Very short, non-existent Dex location. +TEST_F(DexoptAnalyzerTest, ShortDexLocation) { + std::string dex_location = "/xx"; + + Verify(dex_location, CompilerFilter::kSpeed); +} + +} // namespace art diff --git a/imgdiag/imgdiag_test.cc b/imgdiag/imgdiag_test.cc index 3f2afc0696..0d46b2ea7a 100644 --- a/imgdiag/imgdiag_test.cc +++ b/imgdiag/imgdiag_test.cc @@ -24,6 +24,7 @@ #include "runtime/os.h" #include "runtime/arch/instruction_set.h" +#include "runtime/exec_utils.h" #include "runtime/utils.h" #include "runtime/gc/space/image_space.h" #include "runtime/gc/heap.h" diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc index 69901c13cd..0f02da77a1 100644 --- a/oatdump/oatdump.cc +++ b/oatdump/oatdump.cc @@ -589,16 +589,17 @@ class OatDumper { kByteKindCodeInfoInlineInfo, kByteKindCodeInfoEncoding, kByteKindCodeInfoOther, + kByteKindCodeInfoStackMasks, + kByteKindCodeInfoRegisterMasks, kByteKindStackMapNativePc, kByteKindStackMapDexPc, kByteKindStackMapDexRegisterMap, kByteKindStackMapInlineInfo, - kByteKindStackMapRegisterMask, - kByteKindStackMapMask, - kByteKindStackMapOther, + kByteKindStackMapRegisterMaskIndex, + kByteKindStackMapStackMaskIndex, kByteKindCount, kByteKindStackMapFirst = kByteKindCodeInfoOther, - kByteKindStackMapLast = kByteKindStackMapOther, + kByteKindStackMapLast = kByteKindStackMapStackMaskIndex, }; int64_t bits[kByteKindCount] = {}; // Since code has deduplication, seen tracks already seen pointers to avoid double counting @@ -626,48 +627,45 @@ class OatDumper { const int64_t stack_map_bits = std::accumulate(bits + kByteKindStackMapFirst, bits + kByteKindStackMapLast + 1, 0u); - Dump(os, "Code ", bits[kByteKindCode], sum); - Dump(os, "QuickMethodHeader ", bits[kByteKindQuickMethodHeader], sum); - Dump(os, "CodeInfoEncoding ", bits[kByteKindCodeInfoEncoding], sum); - Dump(os, "CodeInfoLocationCatalog ", bits[kByteKindCodeInfoLocationCatalog], sum); - Dump(os, "CodeInfoDexRegisterMap ", bits[kByteKindCodeInfoDexRegisterMap], sum); - Dump(os, "CodeInfoInlineInfo ", bits[kByteKindCodeInfoInlineInfo], sum); - Dump(os, "CodeInfoStackMap ", stack_map_bits, sum); + Dump(os, "Code ", bits[kByteKindCode], sum); + Dump(os, "QuickMethodHeader ", bits[kByteKindQuickMethodHeader], sum); + Dump(os, "CodeInfoEncoding ", bits[kByteKindCodeInfoEncoding], sum); + Dump(os, "CodeInfoLocationCatalog ", bits[kByteKindCodeInfoLocationCatalog], sum); + Dump(os, "CodeInfoDexRegisterMap ", bits[kByteKindCodeInfoDexRegisterMap], sum); + Dump(os, "CodeInfoInlineInfo ", bits[kByteKindCodeInfoInlineInfo], sum); + Dump(os, "CodeInfoStackMasks ", bits[kByteKindCodeInfoStackMasks], sum); + Dump(os, "CodeInfoRegisterMasks ", bits[kByteKindCodeInfoRegisterMasks], sum); + Dump(os, "CodeInfoStackMap ", stack_map_bits, sum); { ScopedIndentation indent1(&os); Dump(os, - "StackMapNativePc ", + "StackMapNativePc ", bits[kByteKindStackMapNativePc], stack_map_bits, "stack map"); Dump(os, - "StackMapDexPcEncoding ", + "StackMapDexPcEncoding ", bits[kByteKindStackMapDexPc], stack_map_bits, "stack map"); Dump(os, - "StackMapDexRegisterMap ", + "StackMapDexRegisterMap ", bits[kByteKindStackMapDexRegisterMap], stack_map_bits, "stack map"); Dump(os, - "StackMapInlineInfo ", + "StackMapInlineInfo ", bits[kByteKindStackMapInlineInfo], stack_map_bits, "stack map"); Dump(os, - "StackMapRegisterMaskEncoding ", - bits[kByteKindStackMapRegisterMask], + "StackMapRegisterMaskIndex ", + bits[kByteKindStackMapRegisterMaskIndex], stack_map_bits, "stack map"); Dump(os, - "StackMapMask ", - bits[kByteKindStackMapMask], - stack_map_bits, - "stack map"); - Dump(os, - "StackMapOther ", - bits[kByteKindStackMapOther], + "StackMapStackMaskIndex ", + bits[kByteKindStackMapStackMaskIndex], stack_map_bits, "stack map"); } @@ -1573,18 +1571,18 @@ class OatDumper { Stats::kByteKindStackMapInlineInfo, stack_map_encoding.GetInlineInfoEncoding().BitSize() * num_stack_maps); stats_.AddBits( - Stats::kByteKindStackMapRegisterMask, - stack_map_encoding.GetRegisterMaskEncoding().BitSize() * num_stack_maps); - const size_t stack_mask_bits = encoding.stack_map_size_in_bytes * kBitsPerByte - - stack_map_encoding.GetStackMaskBitOffset(); + Stats::kByteKindStackMapRegisterMaskIndex, + stack_map_encoding.GetRegisterMaskIndexEncoding().BitSize() * num_stack_maps); + stats_.AddBits( + Stats::kByteKindStackMapStackMaskIndex, + stack_map_encoding.GetStackMaskIndexEncoding().BitSize() * num_stack_maps); stats_.AddBits( - Stats::kByteKindStackMapMask, - stack_mask_bits * num_stack_maps); - const size_t stack_map_bits = - stack_map_encoding.GetStackMaskBitOffset() + stack_mask_bits; + Stats::kByteKindCodeInfoStackMasks, + helper.GetCodeInfo().GetNumberOfStackMaskBits(encoding) * + encoding.number_of_stack_masks); stats_.AddBits( - Stats::kByteKindStackMapOther, - (encoding.stack_map_size_in_bytes * kBitsPerByte - stack_map_bits) * num_stack_maps); + Stats::kByteKindCodeInfoRegisterMasks, + encoding.register_mask_size_in_bits * encoding.number_of_stack_masks); const size_t stack_map_bytes = helper.GetCodeInfo().GetStackMapsSize(encoding); const size_t location_catalog_bytes = helper.GetCodeInfo().GetDexRegisterLocationCatalogSize(encoding); diff --git a/oatdump/oatdump_test.cc b/oatdump/oatdump_test.cc index ba57d1860c..503cd4d581 100644 --- a/oatdump/oatdump_test.cc +++ b/oatdump/oatdump_test.cc @@ -24,6 +24,7 @@ #include "base/unix_file/fd_file.h" #include "runtime/arch/instruction_set.h" +#include "runtime/exec_utils.h" #include "runtime/gc/heap.h" #include "runtime/gc/space/image_space.h" #include "runtime/os.h" diff --git a/profman/profile_assistant_test.cc b/profman/profile_assistant_test.cc index 2f40fef42e..a6c3cf067b 100644 --- a/profman/profile_assistant_test.cc +++ b/profman/profile_assistant_test.cc @@ -18,6 +18,7 @@ #include "base/unix_file/fd_file.h" #include "common_runtime_test.h" +#include "exec_utils.h" #include "profile_assistant.h" #include "jit/profile_compilation_info.h" #include "utils.h" diff --git a/runtime/Android.bp b/runtime/Android.bp index 81f174e43b..9585ba2d8e 100644 --- a/runtime/Android.bp +++ b/runtime/Android.bp @@ -57,6 +57,7 @@ cc_defaults { "dex_file_verifier.cc", "dex_instruction.cc", "elf_file.cc", + "exec_utils.cc", "fault_handler.cc", "gc/allocation_record.cc", "gc/allocator/dlmalloc.cc", @@ -154,6 +155,7 @@ cc_defaults { "native/java_lang_Thread.cc", "native/java_lang_Throwable.cc", "native/java_lang_VMClassLoader.cc", + "native/java_lang_invoke_MethodHandleImpl.cc", "native/java_lang_ref_FinalizerReference.cc", "native/java_lang_ref_Reference.cc", "native/java_lang_reflect_Array.cc", @@ -185,6 +187,7 @@ cc_defaults { "reflection.cc", "runtime.cc", "runtime_callbacks.cc", + "runtime_common.cc", "runtime_options.cc", "signal_catcher.cc", "stack.cc", @@ -205,6 +208,7 @@ cc_defaults { "verifier/reg_type_cache.cc", "verifier/register_line.cc", "verifier/verifier_deps.cc", + "verify_object.cc", "well_known_classes.cc", "zip_archive.cc", @@ -376,6 +380,10 @@ cc_defaults { }, cflags: ["-DBUILDING_LIBART=1"], generated_sources: ["art_operator_srcs"], + // asm_support_gen.h (used by asm_support.h) is generated with cpp-define-generator + generated_headers: ["cpp-define-generator-asm-support"], + // export our headers so the libart-gtest targets can use it as well. + export_generated_headers: ["cpp-define-generator-asm-support"], clang: true, include_dirs: [ "art/cmdline", @@ -474,10 +482,14 @@ art_cc_library { art_cc_library { name: "libart-runtime-gtest", defaults: ["libart-gtest-defaults"], - srcs: ["common_runtime_test.cc"], + srcs: [ + "common_runtime_test.cc", + "dexopt_test.cc" + ], shared_libs: [ "libartd", "libbase", + "libbacktrace" ], } @@ -570,6 +582,7 @@ art_cc_test { "type_lookup_table_test.cc", "utf_test.cc", "utils_test.cc", + "vdex_file_test.cc", "verifier/method_verifier_test.cc", "verifier/reg_type_test.cc", "zip_archive_test.cc", diff --git a/runtime/arch/arm/instruction_set_features_arm.cc b/runtime/arch/arm/instruction_set_features_arm.cc index 6c2c81576e..8384460171 100644 --- a/runtime/arch/arm/instruction_set_features_arm.cc +++ b/runtime/arch/arm/instruction_set_features_arm.cc @@ -31,6 +31,7 @@ #if defined(__arm__) extern "C" bool artCheckForArmSdivInstruction(); +extern "C" bool artCheckForArmv8AInstructions(); #endif namespace art { @@ -39,22 +40,34 @@ using android::base::StringPrintf; ArmFeaturesUniquePtr ArmInstructionSetFeatures::FromVariant( const std::string& variant, std::string* error_msg) { + static const char* arm_variants_with_armv8a[] = { + "cortex-a32", + "cortex-a35", + "cortex-a53", + "cortex-a53.a57", + "cortex-a53.a72", + "cortex-a57", + "cortex-a72", + "cortex-a73", + "exynos-m1", + "denver", + "kryo" + }; + bool has_armv8a = FindVariantInArray(arm_variants_with_armv8a, + arraysize(arm_variants_with_armv8a), + variant); + // Look for variants that have divide support. static const char* arm_variants_with_div[] = { "cortex-a7", "cortex-a12", "cortex-a15", "cortex-a17", - "cortex-a53", - "cortex-a53.a57", - "cortex-a57", - "denver", "krait", }; - - bool has_div = FindVariantInArray(arm_variants_with_div, - arraysize(arm_variants_with_div), - variant); + bool has_div = has_armv8a || FindVariantInArray(arm_variants_with_div, + arraysize(arm_variants_with_div), + variant); // Look for variants that have LPAE support. static const char* arm_variants_with_lpae[] = { @@ -62,17 +75,13 @@ ArmFeaturesUniquePtr ArmInstructionSetFeatures::FromVariant( "cortex-a12", "cortex-a15", "cortex-a17", - "cortex-a53", - "cortex-a53.a57", - "cortex-a57", - "denver", "krait", }; - bool has_lpae = FindVariantInArray(arm_variants_with_lpae, - arraysize(arm_variants_with_lpae), - variant); + bool has_atomic_ldrd_strd = has_armv8a || FindVariantInArray(arm_variants_with_lpae, + arraysize(arm_variants_with_lpae), + variant); - if (has_div == false && has_lpae == false) { + if (has_armv8a == false && has_div == false && has_atomic_ldrd_strd == false) { static const char* arm_variants_with_default_features[] = { "cortex-a5", "cortex-a8", @@ -92,34 +101,48 @@ ArmFeaturesUniquePtr ArmInstructionSetFeatures::FromVariant( << ") using conservative defaults"; } } - return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, has_lpae)); + return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, + has_atomic_ldrd_strd, + has_armv8a)); } ArmFeaturesUniquePtr ArmInstructionSetFeatures::FromBitmap(uint32_t bitmap) { bool has_div = (bitmap & kDivBitfield) != 0; bool has_atomic_ldrd_strd = (bitmap & kAtomicLdrdStrdBitfield) != 0; - return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, has_atomic_ldrd_strd)); + bool has_armv8a = (bitmap & kARMv8A) != 0; + return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, + has_atomic_ldrd_strd, + has_armv8a)); } ArmFeaturesUniquePtr ArmInstructionSetFeatures::FromCppDefines() { -#if defined(__ARM_ARCH_EXT_IDIV__) +// Note: This will not work for now since we still build the 32-bit as __ARCH_ARM_7A__. +#if defined(__ARM_ARCH_8A__) + const bool has_armv8a = true; +#else + const bool has_armv8a = false; +#endif +#if defined (__ARM_ARCH_8A__) || defined(__ARM_ARCH_EXT_IDIV__) const bool has_div = true; #else const bool has_div = false; #endif -#if defined(__ARM_FEATURE_LPAE) - const bool has_lpae = true; +#if defined (__ARM_ARCH_8A__) || defined(__ARM_FEATURE_LPAE) + const bool has_atomic_ldrd_strd = true; #else - const bool has_lpae = false; + const bool has_atomic_ldrd_strd = false; #endif - return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, has_lpae)); + return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, + has_atomic_ldrd_strd, + has_armv8a)); } ArmFeaturesUniquePtr ArmInstructionSetFeatures::FromCpuInfo() { // Look in /proc/cpuinfo for features we need. Only use this when we can guarantee that // the kernel puts the appropriate feature flags in here. Sometimes it doesn't. - bool has_lpae = false; + bool has_atomic_ldrd_strd = false; bool has_div = false; + bool has_armv8a = false; std::ifstream in("/proc/cpuinfo"); if (!in.fail()) { @@ -137,21 +160,33 @@ ArmFeaturesUniquePtr ArmInstructionSetFeatures::FromCpuInfo() { has_div = true; } if (line.find("lpae") != std::string::npos) { - has_lpae = true; + has_atomic_ldrd_strd = true; } } + if (line.find("architecture") != std::string::npos + && line.find(": 8") != std::string::npos) { + LOG(INFO) << "found architecture ARMv8"; + // Android is only run on A cores, so ARMv8 implies ARMv8-A. + has_armv8a = true; + // ARMv8 CPUs have LPAE and div support. + has_div = true; + has_atomic_ldrd_strd = true; + } } } in.close(); } else { LOG(ERROR) << "Failed to open /proc/cpuinfo"; } - return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, has_lpae)); + return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, + has_atomic_ldrd_strd, + has_armv8a)); } ArmFeaturesUniquePtr ArmInstructionSetFeatures::FromHwcap() { bool has_div = false; - bool has_lpae = false; + bool has_atomic_ldrd_strd = false; + bool has_armv8a = false; #if defined(ART_TARGET_ANDROID) && defined(__arm__) uint64_t hwcaps = getauxval(AT_HWCAP); @@ -163,18 +198,27 @@ ArmFeaturesUniquePtr ArmInstructionSetFeatures::FromHwcap() { has_div = true; } if ((hwcaps & HWCAP_LPAE) != 0) { - has_lpae = true; + has_atomic_ldrd_strd = true; + } + // TODO: Fix this once FPMISC makes it upstream. + // For now we detect if we run on an ARMv8 CPU by looking for CRC32 and SHA1 + // (only available on ARMv8 CPUs). + if ((hwcaps & HWCAP2_CRC32) != 0 && (hwcaps & HWCAP2_SHA1) != 0) { + has_armv8a = true; } #endif - return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, has_lpae)); + return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, + has_atomic_ldrd_strd, + has_armv8a)); } // A signal handler called by a fault for an illegal instruction. We record the fact in r0 // and then increment the PC in the signal context to return to the next instruction. We know the -// instruction is an sdiv (4 bytes long). -static void bad_divide_inst_handle(int signo ATTRIBUTE_UNUSED, siginfo_t* si ATTRIBUTE_UNUSED, - void* data) { +// instruction is 4 bytes long. +static void bad_instr_handle(int signo ATTRIBUTE_UNUSED, + siginfo_t* si ATTRIBUTE_UNUSED, + void* data) { #if defined(__arm__) struct ucontext *uc = (struct ucontext *)data; struct sigcontext *sc = &uc->uc_mcontext; @@ -190,15 +234,19 @@ ArmFeaturesUniquePtr ArmInstructionSetFeatures::FromAssembly() { // instruction. If we get a SIGILL then it's not supported. struct sigaction sa, osa; sa.sa_flags = SA_ONSTACK | SA_RESTART | SA_SIGINFO; - sa.sa_sigaction = bad_divide_inst_handle; + sa.sa_sigaction = bad_instr_handle; sigemptyset(&sa.sa_mask); sigaction(SIGILL, &sa, &osa); bool has_div = false; + bool has_armv8a = false; #if defined(__arm__) if (artCheckForArmSdivInstruction()) { has_div = true; } + if (artCheckForArmv8AInstructions()) { + has_armv8a = true; + } #endif // Restore the signal handler. @@ -207,11 +255,13 @@ ArmFeaturesUniquePtr ArmInstructionSetFeatures::FromAssembly() { // Use compile time features to "detect" LPAE support. // TODO: write an assembly LPAE support test. #if defined(__ARM_FEATURE_LPAE) - const bool has_lpae = true; + const bool has_atomic_ldrd_strd = true; #else - const bool has_lpae = false; + const bool has_atomic_ldrd_strd = false; #endif - return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, has_lpae)); + return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, + has_atomic_ldrd_strd, + has_armv8a)); } bool ArmInstructionSetFeatures::Equals(const InstructionSetFeatures* other) const { @@ -219,13 +269,26 @@ bool ArmInstructionSetFeatures::Equals(const InstructionSetFeatures* other) cons return false; } const ArmInstructionSetFeatures* other_as_arm = other->AsArmInstructionSetFeatures(); - return has_div_ == other_as_arm->has_div_ && - has_atomic_ldrd_strd_ == other_as_arm->has_atomic_ldrd_strd_; + return has_div_ == other_as_arm->has_div_ + && has_atomic_ldrd_strd_ == other_as_arm->has_atomic_ldrd_strd_ + && has_armv8a_ == other_as_arm->has_armv8a_; +} + +bool ArmInstructionSetFeatures::HasAtLeast(const InstructionSetFeatures* other) const { + if (kArm != other->GetInstructionSet()) { + return false; + } + const ArmInstructionSetFeatures* other_as_arm = other->AsArmInstructionSetFeatures(); + + return (has_div_ || (has_div_ == other_as_arm->has_div_)) + && (has_atomic_ldrd_strd_ || (has_atomic_ldrd_strd_ == other_as_arm->has_atomic_ldrd_strd_)) + && (has_armv8a_ || (has_armv8a_ == other_as_arm->has_armv8a_)); } uint32_t ArmInstructionSetFeatures::AsBitmap() const { - return (has_div_ ? kDivBitfield : 0) | - (has_atomic_ldrd_strd_ ? kAtomicLdrdStrdBitfield : 0); + return (has_div_ ? kDivBitfield : 0) + | (has_atomic_ldrd_strd_ ? kAtomicLdrdStrdBitfield : 0) + | (has_armv8a_ ? kARMv8A : 0); } std::string ArmInstructionSetFeatures::GetFeatureString() const { @@ -240,6 +303,11 @@ std::string ArmInstructionSetFeatures::GetFeatureString() const { } else { result += ",-atomic_ldrd_strd"; } + if (has_armv8a_) { + result += ",armv8a"; + } else { + result += ",-armv8a"; + } return result; } @@ -248,6 +316,7 @@ ArmInstructionSetFeatures::AddFeaturesFromSplitString( const std::vector<std::string>& features, std::string* error_msg) const { bool has_atomic_ldrd_strd = has_atomic_ldrd_strd_; bool has_div = has_div_; + bool has_armv8a = has_armv8a_; for (auto i = features.begin(); i != features.end(); i++) { std::string feature = android::base::Trim(*i); if (feature == "div") { @@ -258,13 +327,17 @@ ArmInstructionSetFeatures::AddFeaturesFromSplitString( has_atomic_ldrd_strd = true; } else if (feature == "-atomic_ldrd_strd") { has_atomic_ldrd_strd = false; + } else if (feature == "armv8a") { + has_armv8a = true; + } else if (feature == "-armv8a") { + has_armv8a = false; } else { *error_msg = StringPrintf("Unknown instruction set feature: '%s'", feature.c_str()); return nullptr; } } return std::unique_ptr<const InstructionSetFeatures>( - new ArmInstructionSetFeatures(has_div, has_atomic_ldrd_strd)); + new ArmInstructionSetFeatures(has_div, has_atomic_ldrd_strd, has_armv8a)); } } // namespace art diff --git a/runtime/arch/arm/instruction_set_features_arm.h b/runtime/arch/arm/instruction_set_features_arm.h index 11f8bf0117..f438a768a5 100644 --- a/runtime/arch/arm/instruction_set_features_arm.h +++ b/runtime/arch/arm/instruction_set_features_arm.h @@ -49,6 +49,8 @@ class ArmInstructionSetFeatures FINAL : public InstructionSetFeatures { bool Equals(const InstructionSetFeatures* other) const OVERRIDE; + bool HasAtLeast(const InstructionSetFeatures* other) const OVERRIDE; + InstructionSet GetInstructionSet() const OVERRIDE { return kArm; } @@ -69,6 +71,11 @@ class ArmInstructionSetFeatures FINAL : public InstructionSetFeatures { return has_atomic_ldrd_strd_; } + // Are ARMv8-A instructions available? + bool HasARMv8AInstructions() const { + return has_armv8a_; + } + virtual ~ArmInstructionSetFeatures() {} protected: @@ -78,19 +85,24 @@ class ArmInstructionSetFeatures FINAL : public InstructionSetFeatures { std::string* error_msg) const OVERRIDE; private: - ArmInstructionSetFeatures(bool has_div, bool has_atomic_ldrd_strd) + ArmInstructionSetFeatures(bool has_div, + bool has_atomic_ldrd_strd, + bool has_armv8a) : InstructionSetFeatures(), - has_div_(has_div), has_atomic_ldrd_strd_(has_atomic_ldrd_strd) { - } + has_div_(has_div), + has_atomic_ldrd_strd_(has_atomic_ldrd_strd), + has_armv8a_(has_armv8a) {} // Bitmap positions for encoding features as a bitmap. enum { kDivBitfield = 1 << 0, kAtomicLdrdStrdBitfield = 1 << 1, + kARMv8A = 1 << 2, }; const bool has_div_; const bool has_atomic_ldrd_strd_; + const bool has_armv8a_; DISALLOW_COPY_AND_ASSIGN(ArmInstructionSetFeatures); }; diff --git a/runtime/arch/arm/instruction_set_features_arm_test.cc b/runtime/arch/arm/instruction_set_features_arm_test.cc index 697ca9015e..6d5dd6d50d 100644 --- a/runtime/arch/arm/instruction_set_features_arm_test.cc +++ b/runtime/arch/arm/instruction_set_features_arm_test.cc @@ -31,7 +31,7 @@ TEST(ArmInstructionSetFeaturesTest, ArmFeaturesFromVariant) { EXPECT_TRUE(krait_features->Equals(krait_features.get())); EXPECT_TRUE(krait_features->AsArmInstructionSetFeatures()->HasDivideInstruction()); EXPECT_TRUE(krait_features->AsArmInstructionSetFeatures()->HasAtomicLdrdAndStrd()); - EXPECT_STREQ("div,atomic_ldrd_strd", krait_features->GetFeatureString().c_str()); + EXPECT_STREQ("div,atomic_ldrd_strd,-armv8a", krait_features->GetFeatureString().c_str()); EXPECT_EQ(krait_features->AsBitmap(), 3U); // Build features for a 32-bit ARM denver processor. @@ -40,12 +40,13 @@ TEST(ArmInstructionSetFeaturesTest, ArmFeaturesFromVariant) { ASSERT_TRUE(denver_features.get() != nullptr) << error_msg; EXPECT_TRUE(denver_features->Equals(denver_features.get())); - EXPECT_TRUE(denver_features->Equals(krait_features.get())); - EXPECT_TRUE(krait_features->Equals(denver_features.get())); + EXPECT_TRUE(denver_features->HasAtLeast(krait_features.get())); + EXPECT_FALSE(krait_features->Equals(denver_features.get())); + EXPECT_FALSE(krait_features->HasAtLeast(denver_features.get())); EXPECT_TRUE(denver_features->AsArmInstructionSetFeatures()->HasDivideInstruction()); EXPECT_TRUE(denver_features->AsArmInstructionSetFeatures()->HasAtomicLdrdAndStrd()); - EXPECT_STREQ("div,atomic_ldrd_strd", denver_features->GetFeatureString().c_str()); - EXPECT_EQ(denver_features->AsBitmap(), 3U); + EXPECT_STREQ("div,atomic_ldrd_strd,armv8a", denver_features->GetFeatureString().c_str()); + EXPECT_EQ(denver_features->AsBitmap(), 7U); // Build features for a 32-bit ARMv7 processor. std::unique_ptr<const InstructionSetFeatures> generic_features( @@ -57,7 +58,7 @@ TEST(ArmInstructionSetFeaturesTest, ArmFeaturesFromVariant) { EXPECT_FALSE(krait_features->Equals(generic_features.get())); EXPECT_FALSE(generic_features->AsArmInstructionSetFeatures()->HasDivideInstruction()); EXPECT_FALSE(generic_features->AsArmInstructionSetFeatures()->HasAtomicLdrdAndStrd()); - EXPECT_STREQ("-div,-atomic_ldrd_strd", generic_features->GetFeatureString().c_str()); + EXPECT_STREQ("-div,-atomic_ldrd_strd,-armv8a", generic_features->GetFeatureString().c_str()); EXPECT_EQ(generic_features->AsBitmap(), 0U); // ARM6 is not a supported architecture variant. @@ -82,21 +83,22 @@ TEST(ArmInstructionSetFeaturesTest, ArmAddFeaturesFromString) { EXPECT_TRUE(krait_features->Equals(krait_features.get())); EXPECT_TRUE(krait_features->AsArmInstructionSetFeatures()->HasDivideInstruction()); EXPECT_TRUE(krait_features->AsArmInstructionSetFeatures()->HasAtomicLdrdAndStrd()); - EXPECT_STREQ("div,atomic_ldrd_strd", krait_features->GetFeatureString().c_str()); + EXPECT_STREQ("div,atomic_ldrd_strd,-armv8a", krait_features->GetFeatureString().c_str()); EXPECT_EQ(krait_features->AsBitmap(), 3U); // Build features for a 32-bit ARM processor with LPAE and div flipped. std::unique_ptr<const InstructionSetFeatures> denver_features( - base_features->AddFeaturesFromString("div,atomic_ldrd_strd", &error_msg)); + base_features->AddFeaturesFromString("div,atomic_ldrd_strd,armv8a", &error_msg)); ASSERT_TRUE(denver_features.get() != nullptr) << error_msg; EXPECT_TRUE(denver_features->Equals(denver_features.get())); - EXPECT_TRUE(denver_features->Equals(krait_features.get())); - EXPECT_TRUE(krait_features->Equals(denver_features.get())); + EXPECT_FALSE(denver_features->Equals(krait_features.get())); + EXPECT_TRUE(denver_features->HasAtLeast(krait_features.get())); + EXPECT_FALSE(krait_features->Equals(denver_features.get())); EXPECT_TRUE(denver_features->AsArmInstructionSetFeatures()->HasDivideInstruction()); EXPECT_TRUE(denver_features->AsArmInstructionSetFeatures()->HasAtomicLdrdAndStrd()); - EXPECT_STREQ("div,atomic_ldrd_strd", denver_features->GetFeatureString().c_str()); - EXPECT_EQ(denver_features->AsBitmap(), 3U); + EXPECT_STREQ("div,atomic_ldrd_strd,armv8a", denver_features->GetFeatureString().c_str()); + EXPECT_EQ(denver_features->AsBitmap(), 7U); // Build features for a 32-bit default ARM processor. std::unique_ptr<const InstructionSetFeatures> generic_features( @@ -108,7 +110,7 @@ TEST(ArmInstructionSetFeaturesTest, ArmAddFeaturesFromString) { EXPECT_FALSE(krait_features->Equals(generic_features.get())); EXPECT_FALSE(generic_features->AsArmInstructionSetFeatures()->HasDivideInstruction()); EXPECT_FALSE(generic_features->AsArmInstructionSetFeatures()->HasAtomicLdrdAndStrd()); - EXPECT_STREQ("-div,-atomic_ldrd_strd", generic_features->GetFeatureString().c_str()); + EXPECT_STREQ("-div,-atomic_ldrd_strd,-armv8a", generic_features->GetFeatureString().c_str()); EXPECT_EQ(generic_features->AsBitmap(), 0U); } diff --git a/runtime/arch/arm/instruction_set_features_assembly_tests.S b/runtime/arch/arm/instruction_set_features_assembly_tests.S index c1086df0f6..5c7f2025c2 100644 --- a/runtime/arch/arm/instruction_set_features_assembly_tests.S +++ b/runtime/arch/arm/instruction_set_features_assembly_tests.S @@ -17,22 +17,49 @@ #include "asm_support_arm.S" .section .text -// This function is used to check for the CPU's support for the sdiv -// instruction at runtime. It will either return the value 1 or -// will cause an invalid instruction trap (SIGILL signal). The -// caller must arrange for the signal handler to set the r0 -// register to 0 and move the pc forward by 4 bytes (to skip -// the invalid instruction). +// These functions are used to check for the CPU's support for the sdiv and +// ARMv8-A instructions at runtime. They will either return the value 1 or will +// cause an invalid instruction trap (SIGILL signal), for which the signal handler +// (bad_instr_handle(), in instruction_set_features_arm.cc) must arrange to set +// the r0 register to 0 and move the pc forward by 4 bytes (to skip the invalid +// instruction). +// Note: For ARM T32, instructions can be either 16b or 32b, but bad_instr_handle() +// deals only with 32b instructions for now. + ENTRY artCheckForArmSdivInstruction mov r1,#1 - // depending on the architecture, the assembler will not allow an + // Depending on the architecture, the assembler will not allow an // sdiv instruction, so we will have to output the bytes directly. - // sdiv r0,r1,r1 is two words: 0xfb91 0xf1f0. We need little endian. - .byte 0x91,0xfb,0xf1,0xf0 + // The T32 encoding for sdiv r0,r1,r1 is two 16bit words: 0xfb91 0xf0f1, with little endianness. + .byte 0x91,0xfb + .byte 0xf1,0xf0 - // if the divide worked, r0 will have the value #1 (result of sdiv). + // If the divide worked, r0 will have the value #1 (result of sdiv). // It will have 0 otherwise (set by the signal handler) // the value is just returned from this function. bx lr END artCheckForArmSdivInstruction + +ENTRY artCheckForArmv8AInstructions + // Depending on the architecture, the assembler will not allow a + // `vrint` instruction, so we will have to output the bytes directly. + + // Move `true` into the result register. The signal handler will set it to 0 + // if execution of the instruction below fails + mov r0,#1 + + // Store S0 in the caller saved R1. If the instruction below succeeds, S0 will + // be clobbered but it will not be caller saved (ARM still uses soft FP). + vmov r1, s0 + + // The T32 encoding for vrinta.f32.f32 s0,s0 is two 16bit words: 0xfeb8,0x0a40, with little + // endianness. + .byte 0xb8,0xfe + .byte 0x40,0x0a + + // Restore S0 (see above comment). + vmov s0, r1 + + bx lr +END artCheckForArmv8AInstructions diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S index db1cad670d..a443a4060d 100644 --- a/runtime/arch/arm/quick_entrypoints_arm.S +++ b/runtime/arch/arm/quick_entrypoints_arm.S @@ -351,14 +351,13 @@ END \c_name DELIVER_PENDING_EXCEPTION .endm -// Macros taking opportunity of code similarities for downcalls with referrer for non-wide fields. +// Macros taking opportunity of code similarities for downcalls. .macro ONE_ARG_REF_DOWNCALL name, entrypoint, return .extern \entrypoint ENTRY \name SETUP_SAVE_REFS_ONLY_FRAME r1 @ save callee saves in case of GC - ldr r1, [sp, #FRAME_SIZE_SAVE_REFS_ONLY] @ pass referrer - mov r2, r9 @ pass Thread::Current - bl \entrypoint @ (uint32_t field_idx, const Method* referrer, Thread*) + mov r1, r9 @ pass Thread::Current + bl \entrypoint @ (uint32_t field_idx, Thread*) RESTORE_SAVE_REFS_ONLY_FRAME \return END \name @@ -368,9 +367,8 @@ END \name .extern \entrypoint ENTRY \name SETUP_SAVE_REFS_ONLY_FRAME r2 @ save callee saves in case of GC - ldr r2, [sp, #FRAME_SIZE_SAVE_REFS_ONLY] @ pass referrer - mov r3, r9 @ pass Thread::Current - bl \entrypoint @ (field_idx, Object*, referrer, Thread*) + mov r2, r9 @ pass Thread::Current + bl \entrypoint @ (field_idx, Object*, Thread*) RESTORE_SAVE_REFS_ONLY_FRAME \return END \name @@ -380,12 +378,8 @@ END \name .extern \entrypoint ENTRY \name SETUP_SAVE_REFS_ONLY_FRAME r3 @ save callee saves in case of GC - ldr r3, [sp, #FRAME_SIZE_SAVE_REFS_ONLY] @ pass referrer - str r9, [sp, #-16]! @ expand the frame and pass Thread::Current - .cfi_adjust_cfa_offset 16 - bl \entrypoint @ (field_idx, Object*, new_val, referrer, Thread*) - add sp, #16 @ release out args - .cfi_adjust_cfa_offset -16 + mov r3, r9 @ pass Thread::Current + bl \entrypoint @ (field_idx, Object*, new_val, Thread*) RESTORE_SAVE_REFS_ONLY_FRAME @ TODO: we can clearly save an add here \return END \name @@ -978,21 +972,20 @@ ONE_ARG_DOWNCALL art_quick_initialize_type_and_verify_access, artInitializeTypeA /* * Called by managed code to resolve a static field and load a non-wide value. */ -ONE_ARG_REF_DOWNCALL art_quick_get_byte_static, artGetByteStaticFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1 -ONE_ARG_REF_DOWNCALL art_quick_get_boolean_static, artGetBooleanStaticFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1 -ONE_ARG_REF_DOWNCALL art_quick_get_short_static, artGetShortStaticFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1 -ONE_ARG_REF_DOWNCALL art_quick_get_char_static, artGetCharStaticFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1 -ONE_ARG_REF_DOWNCALL art_quick_get32_static, artGet32StaticFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1 -ONE_ARG_REF_DOWNCALL art_quick_get_obj_static, artGetObjStaticFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1 +ONE_ARG_REF_DOWNCALL art_quick_get_byte_static, artGetByteStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1 +ONE_ARG_REF_DOWNCALL art_quick_get_boolean_static, artGetBooleanStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1 +ONE_ARG_REF_DOWNCALL art_quick_get_short_static, artGetShortStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1 +ONE_ARG_REF_DOWNCALL art_quick_get_char_static, artGetCharStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1 +ONE_ARG_REF_DOWNCALL art_quick_get32_static, artGet32StaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1 +ONE_ARG_REF_DOWNCALL art_quick_get_obj_static, artGetObjStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1 /* * Called by managed code to resolve a static field and load a 64-bit primitive value. */ - .extern artGet64StaticFromCode + .extern artGet64StaticFromCompiledCode ENTRY art_quick_get64_static SETUP_SAVE_REFS_ONLY_FRAME r2 @ save callee saves in case of GC - ldr r1, [sp, #FRAME_SIZE_SAVE_REFS_ONLY] @ pass referrer - mov r2, r9 @ pass Thread::Current - bl artGet64StaticFromCode @ (uint32_t field_idx, const Method* referrer, Thread*) + mov r1, r9 @ pass Thread::Current + bl artGet64StaticFromCompiledCode @ (uint32_t field_idx, Thread*) ldr r2, [r9, #THREAD_EXCEPTION_OFFSET] @ load Thread::Current()->exception_ RESTORE_SAVE_REFS_ONLY_FRAME cbnz r2, 1f @ success if no exception pending @@ -1004,21 +997,20 @@ END art_quick_get64_static /* * Called by managed code to resolve an instance field and load a non-wide value. */ -TWO_ARG_REF_DOWNCALL art_quick_get_byte_instance, artGetByteInstanceFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1 -TWO_ARG_REF_DOWNCALL art_quick_get_boolean_instance, artGetBooleanInstanceFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1 -TWO_ARG_REF_DOWNCALL art_quick_get_short_instance, artGetShortInstanceFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1 -TWO_ARG_REF_DOWNCALL art_quick_get_char_instance, artGetCharInstanceFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1 -TWO_ARG_REF_DOWNCALL art_quick_get32_instance, artGet32InstanceFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1 -TWO_ARG_REF_DOWNCALL art_quick_get_obj_instance, artGetObjInstanceFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1 +TWO_ARG_REF_DOWNCALL art_quick_get_byte_instance, artGetByteInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1 +TWO_ARG_REF_DOWNCALL art_quick_get_boolean_instance, artGetBooleanInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1 +TWO_ARG_REF_DOWNCALL art_quick_get_short_instance, artGetShortInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1 +TWO_ARG_REF_DOWNCALL art_quick_get_char_instance, artGetCharInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1 +TWO_ARG_REF_DOWNCALL art_quick_get32_instance, artGet32InstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1 +TWO_ARG_REF_DOWNCALL art_quick_get_obj_instance, artGetObjInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1 /* * Called by managed code to resolve an instance field and load a 64-bit primitive value. */ - .extern artGet64InstanceFromCode + .extern artGet64InstanceFromCompiledCode ENTRY art_quick_get64_instance SETUP_SAVE_REFS_ONLY_FRAME r2 @ save callee saves in case of GC - ldr r2, [sp, #FRAME_SIZE_SAVE_REFS_ONLY] @ pass referrer - mov r3, r9 @ pass Thread::Current - bl artGet64InstanceFromCode @ (field_idx, Object*, referrer, Thread*) + mov r2, r9 @ pass Thread::Current + bl artGet64InstanceFromCompiledCode @ (field_idx, Object*, Thread*) ldr r2, [r9, #THREAD_EXCEPTION_OFFSET] @ load Thread::Current()->exception_ RESTORE_SAVE_REFS_ONLY_FRAME cbnz r2, 1f @ success if no exception pending @@ -1028,51 +1020,31 @@ ENTRY art_quick_get64_instance END art_quick_get64_instance /* - * Called by managed code to resolve a static field and store a non-wide value. + * Called by managed code to resolve a static field and store a value. */ -TWO_ARG_REF_DOWNCALL art_quick_set8_static, artSet8StaticFromCode, RETURN_IF_RESULT_IS_ZERO_OR_DELIVER -TWO_ARG_REF_DOWNCALL art_quick_set16_static, artSet16StaticFromCode, RETURN_IF_RESULT_IS_ZERO_OR_DELIVER -TWO_ARG_REF_DOWNCALL art_quick_set32_static, artSet32StaticFromCode, RETURN_IF_RESULT_IS_ZERO_OR_DELIVER -TWO_ARG_REF_DOWNCALL art_quick_set_obj_static, artSetObjStaticFromCode, RETURN_IF_RESULT_IS_ZERO_OR_DELIVER - /* - * Called by managed code to resolve a static field and store a 64-bit primitive value. - * On entry r0 holds field index, r2:r3 hold new_val - */ - .extern artSet64StaticFromCode -ENTRY art_quick_set64_static - SETUP_SAVE_REFS_ONLY_FRAME r1 @ save callee saves in case of GC - @ r2:r3 contain the wide argument - ldr r1, [sp, #FRAME_SIZE_SAVE_REFS_ONLY] @ pass referrer - str r9, [sp, #-16]! @ expand the frame and pass Thread::Current - .cfi_adjust_cfa_offset 16 - bl artSet64StaticFromCode @ (field_idx, referrer, new_val, Thread*) - add sp, #16 @ release out args - .cfi_adjust_cfa_offset -16 - RESTORE_SAVE_REFS_ONLY_FRAME @ TODO: we can clearly save an add here - RETURN_IF_RESULT_IS_ZERO - DELIVER_PENDING_EXCEPTION -END art_quick_set64_static +TWO_ARG_REF_DOWNCALL art_quick_set8_static, artSet8StaticFromCompiledCode, RETURN_IF_RESULT_IS_ZERO_OR_DELIVER +TWO_ARG_REF_DOWNCALL art_quick_set16_static, artSet16StaticFromCompiledCode, RETURN_IF_RESULT_IS_ZERO_OR_DELIVER +TWO_ARG_REF_DOWNCALL art_quick_set32_static, artSet32StaticFromCompiledCode, RETURN_IF_RESULT_IS_ZERO_OR_DELIVER +TWO_ARG_REF_DOWNCALL art_quick_set_obj_static, artSetObjStaticFromCompiledCode, RETURN_IF_RESULT_IS_ZERO_OR_DELIVER /* * Called by managed code to resolve an instance field and store a non-wide value. */ -THREE_ARG_REF_DOWNCALL art_quick_set8_instance, artSet8InstanceFromCode, RETURN_IF_RESULT_IS_ZERO_OR_DELIVER -THREE_ARG_REF_DOWNCALL art_quick_set16_instance, artSet16InstanceFromCode, RETURN_IF_RESULT_IS_ZERO_OR_DELIVER -THREE_ARG_REF_DOWNCALL art_quick_set32_instance, artSet32InstanceFromCode, RETURN_IF_RESULT_IS_ZERO_OR_DELIVER -THREE_ARG_REF_DOWNCALL art_quick_set_obj_instance, artSetObjInstanceFromCode, RETURN_IF_RESULT_IS_ZERO_OR_DELIVER +THREE_ARG_REF_DOWNCALL art_quick_set8_instance, artSet8InstanceFromCompiledCode, RETURN_IF_RESULT_IS_ZERO_OR_DELIVER +THREE_ARG_REF_DOWNCALL art_quick_set16_instance, artSet16InstanceFromCompiledCode, RETURN_IF_RESULT_IS_ZERO_OR_DELIVER +THREE_ARG_REF_DOWNCALL art_quick_set32_instance, artSet32InstanceFromCompiledCode, RETURN_IF_RESULT_IS_ZERO_OR_DELIVER +THREE_ARG_REF_DOWNCALL art_quick_set_obj_instance, artSetObjInstanceFromCompiledCode, RETURN_IF_RESULT_IS_ZERO_OR_DELIVER + /* - * Called by managed code to resolve an instance field and store a 64-bit primitive value. + * Called by managed code to resolve an instance field and store a wide value. */ - .extern artSet64InstanceFromCode + .extern artSet64InstanceFromCompiledCode ENTRY art_quick_set64_instance SETUP_SAVE_REFS_ONLY_FRAME r12 @ save callee saves in case of GC @ r2:r3 contain the wide argument - ldr r12, [sp, #FRAME_SIZE_SAVE_REFS_ONLY] @ pass referrer - str r9, [sp, #-12]! @ expand the frame and pass Thread::Current - .cfi_adjust_cfa_offset 12 - str r12, [sp, #-4]! @ expand the frame and pass the referrer - .cfi_adjust_cfa_offset 4 - bl artSet64InstanceFromCode @ (field_idx, Object*, new_val, Method* referrer, Thread*) + str r9, [sp, #-16]! @ expand the frame and pass Thread::Current + .cfi_adjust_cfa_offset 16 + bl artSet64InstanceFromCompiledCode @ (field_idx, Object*, new_val, Thread*) add sp, #16 @ release out args .cfi_adjust_cfa_offset -16 RESTORE_SAVE_REFS_ONLY_FRAME @ TODO: we can clearly save an add here @@ -1080,6 +1052,20 @@ ENTRY art_quick_set64_instance DELIVER_PENDING_EXCEPTION END art_quick_set64_instance + .extern artSet64StaticFromCompiledCode +ENTRY art_quick_set64_static + SETUP_SAVE_REFS_ONLY_FRAME r12 @ save callee saves in case of GC + @ r2:r3 contain the wide argument + str r9, [sp, #-16]! @ expand the frame and pass Thread::Current + .cfi_adjust_cfa_offset 16 + bl artSet64StaticFromCompiledCode @ (field_idx, new_val, Thread*) + add sp, #16 @ release out args + .cfi_adjust_cfa_offset -16 + RESTORE_SAVE_REFS_ONLY_FRAME @ TODO: we can clearly save an add here + RETURN_IF_RESULT_IS_ZERO + DELIVER_PENDING_EXCEPTION +END art_quick_set64_static + /* * Entry from managed code to resolve a string, this stub will * check the dex cache for a matching string (the fast path), and if not found, @@ -1100,11 +1086,37 @@ ENTRY art_quick_resolve_string DELIVER_PENDING_EXCEPTION_FRAME_READY END art_quick_resolve_string + // Generate the allocation entrypoints for each allocator. -GENERATE_ALLOC_ENTRYPOINTS_FOR_EACH_ALLOCATOR +GENERATE_ALLOC_ENTRYPOINTS_FOR_NON_TLAB_ALLOCATORS +// Comment out allocators that have arm specific asm. +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_region_tlab, RegionTLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(_region_tlab, RegionTLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_WITH_ACCESS_CHECK(_region_tlab, RegionTLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED(_region_tlab, RegionTLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED8(_region_tlab, RegionTLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED16(_region_tlab, RegionTLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED32(_region_tlab, RegionTLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED64(_region_tlab, RegionTLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_BYTES(_region_tlab, RegionTLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_CHARS(_region_tlab, RegionTLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_STRING(_region_tlab, RegionTLAB) + +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_tlab, TLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(_tlab, TLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_WITH_ACCESS_CHECK(_tlab, TLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED(_tlab, TLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED8(_tlab, TLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED16(_tlab, TLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED32(_tlab, TLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED64(_tlab, TLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_BYTES(_tlab, TLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_CHARS(_tlab, TLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_STRING(_tlab, TLAB) // A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_RESOLVED_OBJECT(_rosalloc, RosAlloc). -ENTRY art_quick_alloc_object_resolved_rosalloc +.macro ART_QUICK_ALLOC_OBJECT_ROSALLOC c_name, cxx_name +ENTRY \c_name // Fast path rosalloc allocation. // r0: type/return value, r9: Thread::Current // r1, r2, r3, r12: free. @@ -1113,13 +1125,13 @@ ENTRY art_quick_alloc_object_resolved_rosalloc // TODO: consider using ldrd. ldr r12, [r9, #THREAD_LOCAL_ALLOC_STACK_END_OFFSET] cmp r3, r12 - bhs .Lart_quick_alloc_object_resolved_rosalloc_slow_path + bhs .Lslow_path\c_name ldr r3, [r0, #MIRROR_CLASS_OBJECT_SIZE_ALLOC_FAST_PATH_OFFSET] // Load the object size (r3) cmp r3, #ROSALLOC_MAX_THREAD_LOCAL_BRACKET_SIZE // Check if the size is for a thread // local allocation. Also does the // initialized and finalizable checks. - bhs .Lart_quick_alloc_object_resolved_rosalloc_slow_path + bhs .Lslow_path\c_name // Compute the rosalloc bracket index // from the size. Since the size is // already aligned we can combine the @@ -1133,7 +1145,7 @@ ENTRY art_quick_alloc_object_resolved_rosalloc // Load the free list head (r3). This // will be the return val. ldr r3, [r12, #(ROSALLOC_RUN_FREE_LIST_OFFSET + ROSALLOC_RUN_FREE_LIST_HEAD_OFFSET)] - cbz r3, .Lart_quick_alloc_object_resolved_rosalloc_slow_path + cbz r3, .Lslow_path\c_name // "Point of no slow path". Won't go to the slow path from here on. OK to clobber r0 and r1. ldr r1, [r3, #ROSALLOC_SLOT_NEXT_OFFSET] // Load the next pointer of the head // and update the list head with the @@ -1178,16 +1190,20 @@ ENTRY art_quick_alloc_object_resolved_rosalloc mov r0, r3 // Set the return value and return. bx lr -.Lart_quick_alloc_object_resolved_rosalloc_slow_path: +.Lslow_path\c_name: SETUP_SAVE_REFS_ONLY_FRAME r2 @ save callee saves in case of GC mov r1, r9 @ pass Thread::Current - bl artAllocObjectFromCodeResolvedRosAlloc @ (mirror::Class* cls, Thread*) + bl \cxx_name @ (mirror::Class* cls, Thread*) RESTORE_SAVE_REFS_ONLY_FRAME RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER -END art_quick_alloc_object_resolved_rosalloc +END \c_name +.endm + +ART_QUICK_ALLOC_OBJECT_ROSALLOC art_quick_alloc_object_resolved_rosalloc, artAllocObjectFromCodeResolvedRosAlloc +ART_QUICK_ALLOC_OBJECT_ROSALLOC art_quick_alloc_object_initialized_rosalloc, artAllocObjectFromCodeInitializedRosAlloc -// The common fast path code for art_quick_alloc_object_resolved_tlab -// and art_quick_alloc_object_resolved_region_tlab. +// The common fast path code for art_quick_alloc_object_resolved/initialized_tlab +// and art_quick_alloc_object_resolved/initialized_region_tlab. // // r0: type r9: Thread::Current, r1, r2, r3, r12: free. // Need to preserve r0 to the slow path. @@ -1226,41 +1242,173 @@ END art_quick_alloc_object_resolved_rosalloc bx lr .endm -// A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_RESOLVED_OBJECT(_tlab, TLAB). -ENTRY art_quick_alloc_object_resolved_tlab +// The common code for art_quick_alloc_object_*region_tlab +.macro GENERATE_ALLOC_OBJECT_RESOLVED_TLAB name, entrypoint +ENTRY \name // Fast path tlab allocation. // r0: type, r9: Thread::Current // r1, r2, r3, r12: free. -#if defined(USE_READ_BARRIER) - mvn r0, #0 // Read barrier not supported here. - bx lr // Return -1. -#endif - ALLOC_OBJECT_RESOLVED_TLAB_FAST_PATH .Lart_quick_alloc_object_resolved_tlab_slow_path -.Lart_quick_alloc_object_resolved_tlab_slow_path: + ALLOC_OBJECT_RESOLVED_TLAB_FAST_PATH .Lslow_path\name +.Lslow_path\name: SETUP_SAVE_REFS_ONLY_FRAME r2 // Save callee saves in case of GC. mov r1, r9 // Pass Thread::Current. - bl artAllocObjectFromCodeResolvedTLAB // (mirror::Class* klass, Thread*) + bl \entrypoint // (mirror::Class* klass, Thread*) RESTORE_SAVE_REFS_ONLY_FRAME RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER -END art_quick_alloc_object_resolved_tlab +END \name +.endm -// A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_region_tlab, RegionTLAB) -ENTRY art_quick_alloc_object_resolved_region_tlab - // Fast path tlab allocation. - // r0: type, r9: Thread::Current, r1, r2, r3, r12: free. -#if !defined(USE_READ_BARRIER) - eor r0, r0, r0 // Read barrier must be enabled here. - sub r0, r0, #1 // Return -1. - bx lr +GENERATE_ALLOC_OBJECT_RESOLVED_TLAB art_quick_alloc_object_resolved_region_tlab, artAllocObjectFromCodeResolvedRegionTLAB +GENERATE_ALLOC_OBJECT_RESOLVED_TLAB art_quick_alloc_object_initialized_region_tlab, artAllocObjectFromCodeInitializedRegionTLAB +GENERATE_ALLOC_OBJECT_RESOLVED_TLAB art_quick_alloc_object_resolved_tlab, artAllocObjectFromCodeResolvedTLAB +GENERATE_ALLOC_OBJECT_RESOLVED_TLAB art_quick_alloc_object_initialized_tlab, artAllocObjectFromCodeInitializedTLAB + + +// The common fast path code for art_quick_alloc_array_resolved/initialized_tlab +// and art_quick_alloc_array_resolved/initialized_region_tlab. +// +// r0: type r1: component_count r2: total_size r9: Thread::Current, r3, r12: free. +// Need to preserve r0 and r1 to the slow path. +.macro ALLOC_ARRAY_TLAB_FAST_PATH_RESOLVED_WITH_SIZE slowPathLabel + and r2, r2, #OBJECT_ALIGNMENT_MASK_TOGGLED // Apply alignemnt mask + // (addr + 7) & ~7. + + // Load thread_local_pos (r3) and + // thread_local_end (r12) with ldrd. + // Check constraints for ldrd. +#if !((THREAD_LOCAL_POS_OFFSET + 4 == THREAD_LOCAL_END_OFFSET) && (THREAD_LOCAL_POS_OFFSET % 8 == 0)) +#error "Thread::thread_local_pos/end must be consecutive and are 8 byte aligned for performance" #endif - ALLOC_OBJECT_RESOLVED_TLAB_FAST_PATH .Lart_quick_alloc_object_resolved_region_tlab_slow_path -.Lart_quick_alloc_object_resolved_region_tlab_slow_path: - SETUP_SAVE_REFS_ONLY_FRAME r2 // Save callee saves in case of GC. - mov r1, r9 // Pass Thread::Current. - bl artAllocObjectFromCodeResolvedRegionTLAB // (mirror::Class* klass, Thread*) + ldrd r3, r12, [r9, #THREAD_LOCAL_POS_OFFSET] + sub r12, r12, r3 // Compute the remaining buf size. + cmp r2, r12 // Check if the total_size fits. + bhi \slowPathLabel + // "Point of no slow path". Won't go to the slow path from here on. OK to clobber r0 and r1. + add r2, r2, r3 + str r2, [r9, #THREAD_LOCAL_POS_OFFSET] // Store new thread_local_pos. + ldr r2, [r9, #THREAD_LOCAL_OBJECTS_OFFSET] // Increment thread_local_objects. + add r2, r2, #1 + str r2, [r9, #THREAD_LOCAL_OBJECTS_OFFSET] + POISON_HEAP_REF r0 + str r0, [r3, #MIRROR_OBJECT_CLASS_OFFSET] // Store the class pointer. + str r1, [r3, #MIRROR_ARRAY_LENGTH_OFFSET] // Store the array length. + // Fence. This is "ish" not "ishst" so + // that the code after this allocation + // site will see the right values in + // the fields of the class. + // Alternatively we could use "ishst" + // if we use load-acquire for the + // object size load.) + mov r0, r3 + dmb ish + bx lr +.endm + +.macro GENERATE_ALLOC_ARRAY_TLAB name, entrypoint, size_setup +ENTRY \name + // Fast path array allocation for region tlab allocation. + // r0: mirror::Class* type + // r1: int32_t component_count + // r9: thread + // r2, r3, r12: free. + \size_setup .Lslow_path\name + ALLOC_ARRAY_TLAB_FAST_PATH_RESOLVED_WITH_SIZE .Lslow_path\name +.Lslow_path\name: + // r0: mirror::Class* klass + // r1: int32_t component_count + // r2: Thread* self + SETUP_SAVE_REFS_ONLY_FRAME r2 // save callee saves in case of GC + mov r2, r9 // pass Thread::Current + bl \entrypoint RESTORE_SAVE_REFS_ONLY_FRAME RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER -END art_quick_alloc_object_resolved_region_tlab +END \name +.endm + +.macro COMPUTE_ARRAY_SIZE_UNKNOWN slow_path + bkpt // We should never enter here. + // Code below is for reference. + // Possibly a large object, go slow. + // Also does negative array size check. + movw r2, #((MIN_LARGE_OBJECT_THRESHOLD - MIRROR_WIDE_ARRAY_DATA_OFFSET) / 8) + cmp r1, r2 + bhi \slow_path + // Array classes are never finalizable + // or uninitialized, no need to check. + ldr r3, [r0, #MIRROR_CLASS_COMPONENT_TYPE_OFFSET] // Load component type + UNPOISON_HEAP_REF r3 + ldr r3, [r3, #MIRROR_CLASS_OBJECT_PRIMITIVE_TYPE_OFFSET] + lsr r3, r3, #PRIMITIVE_TYPE_SIZE_SHIFT_SHIFT // Component size shift is in high 16 + // bits. + lsl r2, r1, r3 // Calculate data size + // Add array data offset and alignment. + add r2, r2, #(MIRROR_INT_ARRAY_DATA_OFFSET + OBJECT_ALIGNMENT_MASK) +#if MIRROR_WIDE_ARRAY_DATA_OFFSET != MIRROR_INT_ARRAY_DATA_OFFSET + 4 +#error Long array data offset must be 4 greater than int array data offset. +#endif + + add r3, r3, #1 // Add 4 to the length only if the + // component size shift is 3 + // (for 64 bit alignment). + and r3, r3, #4 + add r2, r2, r3 +.endm + +.macro COMPUTE_ARRAY_SIZE_8 slow_path + // Possibly a large object, go slow. + // Also does negative array size check. + movw r2, #(MIN_LARGE_OBJECT_THRESHOLD - MIRROR_INT_ARRAY_DATA_OFFSET) + cmp r1, r2 + bhi \slow_path + // Add array data offset and alignment. + add r2, r1, #(MIRROR_INT_ARRAY_DATA_OFFSET + OBJECT_ALIGNMENT_MASK) +.endm + +.macro COMPUTE_ARRAY_SIZE_16 slow_path + // Possibly a large object, go slow. + // Also does negative array size check. + movw r2, #((MIN_LARGE_OBJECT_THRESHOLD - MIRROR_INT_ARRAY_DATA_OFFSET) / 2) + cmp r1, r2 + bhi \slow_path + lsl r2, r1, #1 + // Add array data offset and alignment. + add r2, r2, #(MIRROR_INT_ARRAY_DATA_OFFSET + OBJECT_ALIGNMENT_MASK) +.endm + +.macro COMPUTE_ARRAY_SIZE_32 slow_path + // Possibly a large object, go slow. + // Also does negative array size check. + movw r2, #((MIN_LARGE_OBJECT_THRESHOLD - MIRROR_INT_ARRAY_DATA_OFFSET) / 4) + cmp r1, r2 + bhi \slow_path + lsl r2, r1, #2 + // Add array data offset and alignment. + add r2, r2, #(MIRROR_INT_ARRAY_DATA_OFFSET + OBJECT_ALIGNMENT_MASK) +.endm + +.macro COMPUTE_ARRAY_SIZE_64 slow_path + // Possibly a large object, go slow. + // Also does negative array size check. + movw r2, #((MIN_LARGE_OBJECT_THRESHOLD - MIRROR_LONG_ARRAY_DATA_OFFSET) / 8) + cmp r1, r2 + bhi \slow_path + lsl r2, r1, #3 + // Add array data offset and alignment. + add r2, r2, #(MIRROR_WIDE_ARRAY_DATA_OFFSET + OBJECT_ALIGNMENT_MASK) +.endm + +# TODO(ngeoffray): art_quick_alloc_array_resolved_region_tlab is not used for arm, remove +# the entrypoint once all backends have been updated to use the size variants. +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved_region_tlab, artAllocArrayFromCodeResolvedRegionTLAB, COMPUTE_ARRAY_SIZE_UNKNOWN +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved8_region_tlab, artAllocArrayFromCodeResolvedRegionTLAB, COMPUTE_ARRAY_SIZE_8 +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved16_region_tlab, artAllocArrayFromCodeResolvedRegionTLAB, COMPUTE_ARRAY_SIZE_16 +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved32_region_tlab, artAllocArrayFromCodeResolvedRegionTLAB, COMPUTE_ARRAY_SIZE_32 +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved64_region_tlab, artAllocArrayFromCodeResolvedRegionTLAB, COMPUTE_ARRAY_SIZE_64 +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved_tlab, artAllocArrayFromCodeResolvedTLAB, COMPUTE_ARRAY_SIZE_UNKNOWN +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved8_tlab, artAllocArrayFromCodeResolvedTLAB, COMPUTE_ARRAY_SIZE_8 +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved16_tlab, artAllocArrayFromCodeResolvedTLAB, COMPUTE_ARRAY_SIZE_16 +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved32_tlab, artAllocArrayFromCodeResolvedTLAB, COMPUTE_ARRAY_SIZE_32 +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved64_tlab, artAllocArrayFromCodeResolvedTLAB, COMPUTE_ARRAY_SIZE_64 /* * Called by managed code when the value in rSUSPEND has been decremented to 0. diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S index 00518e16be..219d8b447a 100644 --- a/runtime/arch/arm64/quick_entrypoints_arm64.S +++ b/runtime/arch/arm64/quick_entrypoints_arm64.S @@ -1519,14 +1519,13 @@ ENTRY \name END \name .endm -// Macros taking opportunity of code similarities for downcalls with referrer. +// Macros taking opportunity of code similarities for downcalls. .macro ONE_ARG_REF_DOWNCALL name, entrypoint, return .extern \entrypoint ENTRY \name SETUP_SAVE_REFS_ONLY_FRAME // save callee saves in case of GC - ldr x1, [sp, #FRAME_SIZE_SAVE_REFS_ONLY] // Load referrer - mov x2, xSELF // pass Thread::Current - bl \entrypoint // (uint32_t type_idx, Method* method, Thread*, SP) + mov x1, xSELF // pass Thread::Current + bl \entrypoint // (uint32_t type_idx, Thread*) RESTORE_SAVE_REFS_ONLY_FRAME \return END \name @@ -1536,8 +1535,7 @@ END \name .extern \entrypoint ENTRY \name SETUP_SAVE_REFS_ONLY_FRAME // save callee saves in case of GC - ldr x2, [sp, #FRAME_SIZE_SAVE_REFS_ONLY] // Load referrer - mov x3, xSELF // pass Thread::Current + mov x2, xSELF // pass Thread::Current bl \entrypoint RESTORE_SAVE_REFS_ONLY_FRAME \return @@ -1548,8 +1546,7 @@ END \name .extern \entrypoint ENTRY \name SETUP_SAVE_REFS_ONLY_FRAME // save callee saves in case of GC - ldr x3, [sp, #FRAME_SIZE_SAVE_REFS_ONLY] // Load referrer - mov x4, xSELF // pass Thread::Current + mov x3, xSELF // pass Thread::Current bl \entrypoint RESTORE_SAVE_REFS_ONLY_FRAME \return @@ -1579,44 +1576,33 @@ ONE_ARG_DOWNCALL art_quick_initialize_static_storage, artInitializeStaticStorage ONE_ARG_DOWNCALL art_quick_initialize_type, artInitializeTypeFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER ONE_ARG_DOWNCALL art_quick_initialize_type_and_verify_access, artInitializeTypeAndVerifyAccessFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER -ONE_ARG_REF_DOWNCALL art_quick_get_boolean_static, artGetBooleanStaticFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 -ONE_ARG_REF_DOWNCALL art_quick_get_byte_static, artGetByteStaticFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 -ONE_ARG_REF_DOWNCALL art_quick_get_char_static, artGetCharStaticFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 -ONE_ARG_REF_DOWNCALL art_quick_get_short_static, artGetShortStaticFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 -ONE_ARG_REF_DOWNCALL art_quick_get32_static, artGet32StaticFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 -ONE_ARG_REF_DOWNCALL art_quick_get64_static, artGet64StaticFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 -ONE_ARG_REF_DOWNCALL art_quick_get_obj_static, artGetObjStaticFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 - -TWO_ARG_REF_DOWNCALL art_quick_get_boolean_instance, artGetBooleanInstanceFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 -TWO_ARG_REF_DOWNCALL art_quick_get_byte_instance, artGetByteInstanceFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 -TWO_ARG_REF_DOWNCALL art_quick_get_char_instance, artGetCharInstanceFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 -TWO_ARG_REF_DOWNCALL art_quick_get_short_instance, artGetShortInstanceFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 -TWO_ARG_REF_DOWNCALL art_quick_get32_instance, artGet32InstanceFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 -TWO_ARG_REF_DOWNCALL art_quick_get64_instance, artGet64InstanceFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 -TWO_ARG_REF_DOWNCALL art_quick_get_obj_instance, artGetObjInstanceFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 - -TWO_ARG_REF_DOWNCALL art_quick_set8_static, artSet8StaticFromCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER -TWO_ARG_REF_DOWNCALL art_quick_set16_static, artSet16StaticFromCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER -TWO_ARG_REF_DOWNCALL art_quick_set32_static, artSet32StaticFromCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER -TWO_ARG_REF_DOWNCALL art_quick_set_obj_static, artSetObjStaticFromCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER - -THREE_ARG_REF_DOWNCALL art_quick_set8_instance, artSet8InstanceFromCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER -THREE_ARG_REF_DOWNCALL art_quick_set16_instance, artSet16InstanceFromCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER -THREE_ARG_REF_DOWNCALL art_quick_set32_instance, artSet32InstanceFromCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER -THREE_ARG_REF_DOWNCALL art_quick_set64_instance, artSet64InstanceFromCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER -THREE_ARG_REF_DOWNCALL art_quick_set_obj_instance, artSetObjInstanceFromCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER - -// This is separated out as the argument order is different. - .extern artSet64StaticFromCode -ENTRY art_quick_set64_static - SETUP_SAVE_REFS_ONLY_FRAME // save callee saves in case of GC - ldr x1, [sp, #FRAME_SIZE_SAVE_REFS_ONLY] // Load referrer - // x2 contains the parameter - mov x3, xSELF // pass Thread::Current - bl artSet64StaticFromCode - RESTORE_SAVE_REFS_ONLY_FRAME - RETURN_IF_W0_IS_ZERO_OR_DELIVER -END art_quick_set64_static +ONE_ARG_REF_DOWNCALL art_quick_get_boolean_static, artGetBooleanStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 +ONE_ARG_REF_DOWNCALL art_quick_get_byte_static, artGetByteStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 +ONE_ARG_REF_DOWNCALL art_quick_get_char_static, artGetCharStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 +ONE_ARG_REF_DOWNCALL art_quick_get_short_static, artGetShortStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 +ONE_ARG_REF_DOWNCALL art_quick_get32_static, artGet32StaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 +ONE_ARG_REF_DOWNCALL art_quick_get64_static, artGet64StaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 +ONE_ARG_REF_DOWNCALL art_quick_get_obj_static, artGetObjStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 + +TWO_ARG_REF_DOWNCALL art_quick_get_boolean_instance, artGetBooleanInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 +TWO_ARG_REF_DOWNCALL art_quick_get_byte_instance, artGetByteInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 +TWO_ARG_REF_DOWNCALL art_quick_get_char_instance, artGetCharInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 +TWO_ARG_REF_DOWNCALL art_quick_get_short_instance, artGetShortInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 +TWO_ARG_REF_DOWNCALL art_quick_get32_instance, artGet32InstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 +TWO_ARG_REF_DOWNCALL art_quick_get64_instance, artGet64InstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 +TWO_ARG_REF_DOWNCALL art_quick_get_obj_instance, artGetObjInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1 + +TWO_ARG_REF_DOWNCALL art_quick_set8_static, artSet8StaticFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER +TWO_ARG_REF_DOWNCALL art_quick_set16_static, artSet16StaticFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER +TWO_ARG_REF_DOWNCALL art_quick_set32_static, artSet32StaticFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER +TWO_ARG_REF_DOWNCALL art_quick_set64_static, artSet64StaticFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER +TWO_ARG_REF_DOWNCALL art_quick_set_obj_static, artSetObjStaticFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER + +THREE_ARG_REF_DOWNCALL art_quick_set8_instance, artSet8InstanceFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER +THREE_ARG_REF_DOWNCALL art_quick_set16_instance, artSet16InstanceFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER +THREE_ARG_REF_DOWNCALL art_quick_set32_instance, artSet32InstanceFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER +THREE_ARG_REF_DOWNCALL art_quick_set64_instance, artSet64InstanceFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER +THREE_ARG_REF_DOWNCALL art_quick_set_obj_instance, artSetObjInstanceFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER /* * Entry from managed code to resolve a string, this stub will @@ -1640,18 +1626,34 @@ ENTRY art_quick_resolve_string END art_quick_resolve_string // Generate the allocation entrypoints for each allocator. -GENERATE_ALLOC_ENTRYPOINTS_FOR_NON_REGION_TLAB_ALLOCATORS +GENERATE_ALLOC_ENTRYPOINTS_FOR_NON_TLAB_ALLOCATORS // Comment out allocators that have arm64 specific asm. // GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_region_tlab, RegionTLAB) // GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(_region_tlab, RegionTLAB) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_WITH_ACCESS_CHECK(_region_tlab, RegionTLAB) // GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED(_region_tlab, RegionTLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED8(_region_tlab, RegionTLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED16(_region_tlab, RegionTLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED32(_region_tlab, RegionTLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED64(_region_tlab, RegionTLAB) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_BYTES(_region_tlab, RegionTLAB) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_CHARS(_region_tlab, RegionTLAB) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_STRING(_region_tlab, RegionTLAB) -// A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_rosalloc, RosAlloc). -ENTRY art_quick_alloc_object_resolved_rosalloc +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_tlab, TLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(_tlab, TLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_WITH_ACCESS_CHECK(_tlab, TLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED(_tlab, TLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED8(_tlab, TLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED16(_tlab, TLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED32(_tlab, TLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED64(_tlab, TLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_BYTES(_tlab, TLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_CHARS(_tlab, TLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_STRING(_tlab, TLAB) + +.macro ART_QUICK_ALLOC_OBJECT_ROSALLOC c_name, cxx_name +ENTRY \c_name // Fast path rosalloc allocation. // x0: type, xSELF(x19): Thread::Current // x1-x7: free. @@ -1660,13 +1662,13 @@ ENTRY art_quick_alloc_object_resolved_rosalloc // ldp won't work due to large offset. ldr x4, [xSELF, #THREAD_LOCAL_ALLOC_STACK_END_OFFSET] cmp x3, x4 - bhs .Lart_quick_alloc_object_resolved_rosalloc_slow_path + bhs .Lslow_path\c_name ldr w3, [x0, #MIRROR_CLASS_OBJECT_SIZE_ALLOC_FAST_PATH_OFFSET] // Load the object size (x3) cmp x3, #ROSALLOC_MAX_THREAD_LOCAL_BRACKET_SIZE // Check if the size is for a thread // local allocation. Also does the // finalizable and initialization // checks. - bhs .Lart_quick_alloc_object_resolved_rosalloc_slow_path + bhs .Lslow_path\c_name // Compute the rosalloc bracket index // from the size. Since the size is // already aligned we can combine the @@ -1679,7 +1681,7 @@ ENTRY art_quick_alloc_object_resolved_rosalloc // Load the free list head (x3). This // will be the return val. ldr x3, [x4, #(ROSALLOC_RUN_FREE_LIST_OFFSET + ROSALLOC_RUN_FREE_LIST_HEAD_OFFSET)] - cbz x3, .Lart_quick_alloc_object_resolved_rosalloc_slow_path + cbz x3, .Lslow_path\c_name // "Point of no slow path". Won't go to the slow path from here on. OK to clobber x0 and x1. ldr x1, [x3, #ROSALLOC_SLOT_NEXT_OFFSET] // Load the next pointer of the head // and update the list head with the @@ -1723,81 +1725,19 @@ ENTRY art_quick_alloc_object_resolved_rosalloc mov x0, x3 // Set the return value and return. ret -.Lart_quick_alloc_object_resolved_rosalloc_slow_path: +.Lslow_path\c_name: SETUP_SAVE_REFS_ONLY_FRAME // save callee saves in case of GC mov x1, xSELF // pass Thread::Current - bl artAllocObjectFromCodeResolvedRosAlloc // (mirror::Class* klass, Thread*) + bl \cxx_name RESTORE_SAVE_REFS_ONLY_FRAME RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER -END art_quick_alloc_object_resolved_rosalloc - - -// The common fast path code for art_quick_alloc_array_region_tlab. -.macro ALLOC_ARRAY_TLAB_FAST_PATH_RESOLVED slowPathLabel, xClass, wClass, xCount, wCount, xTemp0, wTemp0, xTemp1, wTemp1, xTemp2, wTemp2 - // Array classes are never finalizable or uninitialized, no need to check. - ldr \wTemp0, [\xClass, #MIRROR_CLASS_COMPONENT_TYPE_OFFSET] // Load component type - UNPOISON_HEAP_REF \wTemp0 - ldr \wTemp0, [\xTemp0, #MIRROR_CLASS_OBJECT_PRIMITIVE_TYPE_OFFSET] - lsr \xTemp0, \xTemp0, #PRIMITIVE_TYPE_SIZE_SHIFT_SHIFT // Component size shift is in high 16 - // bits. - // xCount is holding a 32 bit value, - // it can not overflow. - lsl \xTemp1, \xCount, \xTemp0 // Calculate data size - // Add array data offset and alignment. - add \xTemp1, \xTemp1, #(MIRROR_INT_ARRAY_DATA_OFFSET + OBJECT_ALIGNMENT_MASK) -#if MIRROR_LONG_ARRAY_DATA_OFFSET != MIRROR_INT_ARRAY_DATA_OFFSET + 4 -#error Long array data offset must be 4 greater than int array data offset. -#endif - - add \xTemp0, \xTemp0, #1 // Add 4 to the length only if the - // component size shift is 3 - // (for 64 bit alignment). - and \xTemp0, \xTemp0, #4 - add \xTemp1, \xTemp1, \xTemp0 - and \xTemp1, \xTemp1, #OBJECT_ALIGNMENT_MASK_TOGGLED64 // Apply alignemnt mask - // (addr + 7) & ~7. The mask must - // be 64 bits to keep high bits in - // case of overflow. - // Negative sized arrays are handled here since xCount holds a zero extended 32 bit value. - // Negative ints become large 64 bit unsigned ints which will always be larger than max signed - // 32 bit int. Since the max shift for arrays is 3, it can not become a negative 64 bit int. - cmp \xTemp1, #MIN_LARGE_OBJECT_THRESHOLD // Possibly a large object, go slow - bhs \slowPathLabel // path. - - ldr \xTemp0, [xSELF, #THREAD_LOCAL_POS_OFFSET] // Check tlab for space, note that - // we use (end - begin) to handle - // negative size arrays. It is - // assumed that a negative size will - // always be greater unsigned than - // region size. - ldr \xTemp2, [xSELF, #THREAD_LOCAL_END_OFFSET] - sub \xTemp2, \xTemp2, \xTemp0 - cmp \xTemp1, \xTemp2 - bhi \slowPathLabel - // "Point of no slow path". Won't go to the slow path from here on. OK to clobber x0 and x1. - // Move old thread_local_pos to x0 - // for the return value. - mov x0, \xTemp0 - add \xTemp0, \xTemp0, \xTemp1 - str \xTemp0, [xSELF, #THREAD_LOCAL_POS_OFFSET] // Store new thread_local_pos. - ldr \xTemp0, [xSELF, #THREAD_LOCAL_OBJECTS_OFFSET] // Increment thread_local_objects. - add \xTemp0, \xTemp0, #1 - str \xTemp0, [xSELF, #THREAD_LOCAL_OBJECTS_OFFSET] - POISON_HEAP_REF \wClass - str \wClass, [x0, #MIRROR_OBJECT_CLASS_OFFSET] // Store the class pointer. - str \wCount, [x0, #MIRROR_ARRAY_LENGTH_OFFSET] // Store the array length. - // Fence. - dmb ishst - ret +END \c_name .endm -// TODO: delete ALLOC_OBJECT_TLAB_FAST_PATH_RESOLVED since it is the same as -// ALLOC_OBJECT_TLAB_FAST_PATH_INITIALIZED. -.macro ALLOC_OBJECT_TLAB_FAST_PATH_RESOLVED slowPathLabel - ALLOC_OBJECT_TLAB_FAST_PATH_INITIALIZED \slowPathLabel -.endm +ART_QUICK_ALLOC_OBJECT_ROSALLOC art_quick_alloc_object_resolved_rosalloc, artAllocObjectFromCodeResolvedRosAlloc +ART_QUICK_ALLOC_OBJECT_ROSALLOC art_quick_alloc_object_initialized_rosalloc, artAllocObjectFromCodeInitializedRosAlloc -.macro ALLOC_OBJECT_TLAB_FAST_PATH_INITIALIZED slowPathLabel +.macro ALLOC_OBJECT_TLAB_FAST_PATH_RESOLVED slowPathLabel ldr x4, [xSELF, #THREAD_LOCAL_POS_OFFSET] ldr x5, [xSELF, #THREAD_LOCAL_END_OFFSET] ldr w7, [x0, #MIRROR_CLASS_OBJECT_SIZE_ALLOC_FAST_PATH_OFFSET] // Load the object size (x7). @@ -1824,36 +1764,13 @@ END art_quick_alloc_object_resolved_rosalloc ret .endm -// A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_tlab, TLAB). -ENTRY art_quick_alloc_object_resolved_tlab - // Fast path tlab allocation. - // x0: type, xSELF(x19): Thread::Current - // x1-x7: free. -#if defined(USE_READ_BARRIER) - mvn x0, xzr // Read barrier not supported here. - ret // Return -1. -#endif - ALLOC_OBJECT_TLAB_FAST_PATH_RESOLVED .Lart_quick_alloc_object_resolved_tlab_slow_path -.Lart_quick_alloc_object_resolved_tlab_slow_path: - SETUP_SAVE_REFS_ONLY_FRAME // Save callee saves in case of GC. - mov x1, xSELF // Pass Thread::Current. - bl artAllocObjectFromCodeResolvedTLAB // (mirror::Class*, Thread*) - RESTORE_SAVE_REFS_ONLY_FRAME - RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER -END art_quick_alloc_object_resolved_tlab - // The common code for art_quick_alloc_object_*region_tlab -.macro GENERATE_ALLOC_OBJECT_RESOLVED_REGION_TLAB name, entrypoint, fast_path +.macro GENERATE_ALLOC_OBJECT_RESOLVED_TLAB name, entrypoint ENTRY \name // Fast path region tlab allocation. // x0: type, xSELF(x19): Thread::Current // x1-x7: free. -#if !defined(USE_READ_BARRIER) - mvn x0, xzr // Read barrier must be enabled here. - ret // Return -1. -#endif -.Ldo_allocation\name: - \fast_path .Lslow_path\name + ALLOC_OBJECT_TLAB_FAST_PATH_RESOLVED .Lslow_path\name .Lslow_path\name: SETUP_SAVE_REFS_ONLY_FRAME // Save callee saves in case of GC. mov x1, xSELF // Pass Thread::Current. @@ -1863,24 +1780,58 @@ ENTRY \name END \name .endm -GENERATE_ALLOC_OBJECT_RESOLVED_REGION_TLAB art_quick_alloc_object_resolved_region_tlab, artAllocObjectFromCodeResolvedRegionTLAB, ALLOC_OBJECT_TLAB_FAST_PATH_RESOLVED -GENERATE_ALLOC_OBJECT_RESOLVED_REGION_TLAB art_quick_alloc_object_initialized_region_tlab, artAllocObjectFromCodeInitializedRegionTLAB, ALLOC_OBJECT_TLAB_FAST_PATH_INITIALIZED +GENERATE_ALLOC_OBJECT_RESOLVED_TLAB art_quick_alloc_object_resolved_region_tlab, artAllocObjectFromCodeResolvedRegionTLAB +GENERATE_ALLOC_OBJECT_RESOLVED_TLAB art_quick_alloc_object_initialized_region_tlab, artAllocObjectFromCodeInitializedRegionTLAB +GENERATE_ALLOC_OBJECT_RESOLVED_TLAB art_quick_alloc_object_resolved_tlab, artAllocObjectFromCodeResolvedTLAB +GENERATE_ALLOC_OBJECT_RESOLVED_TLAB art_quick_alloc_object_initialized_tlab, artAllocObjectFromCodeInitializedTLAB -// TODO: We could use this macro for the normal tlab allocator too. +.macro ALLOC_ARRAY_TLAB_FAST_PATH_RESOLVED_WITH_SIZE slowPathLabel, xClass, wClass, xCount, wCount, xTemp0, wTemp0, xTemp1, wTemp1, xTemp2, wTemp2 + and \xTemp1, \xTemp1, #OBJECT_ALIGNMENT_MASK_TOGGLED64 // Apply alignemnt mask + // (addr + 7) & ~7. The mask must + // be 64 bits to keep high bits in + // case of overflow. + // Negative sized arrays are handled here since xCount holds a zero extended 32 bit value. + // Negative ints become large 64 bit unsigned ints which will always be larger than max signed + // 32 bit int. Since the max shift for arrays is 3, it can not become a negative 64 bit int. + cmp \xTemp1, #MIN_LARGE_OBJECT_THRESHOLD // Possibly a large object, go slow + bhs \slowPathLabel // path. + + ldr \xTemp0, [xSELF, #THREAD_LOCAL_POS_OFFSET] // Check tlab for space, note that + // we use (end - begin) to handle + // negative size arrays. It is + // assumed that a negative size will + // always be greater unsigned than + // region size. + ldr \xTemp2, [xSELF, #THREAD_LOCAL_END_OFFSET] + sub \xTemp2, \xTemp2, \xTemp0 + cmp \xTemp1, \xTemp2 + bhi \slowPathLabel + // "Point of no slow path". Won't go to the slow path from here on. OK to clobber x0 and x1. + // Move old thread_local_pos to x0 + // for the return value. + mov x0, \xTemp0 + add \xTemp0, \xTemp0, \xTemp1 + str \xTemp0, [xSELF, #THREAD_LOCAL_POS_OFFSET] // Store new thread_local_pos. + ldr \xTemp0, [xSELF, #THREAD_LOCAL_OBJECTS_OFFSET] // Increment thread_local_objects. + add \xTemp0, \xTemp0, #1 + str \xTemp0, [xSELF, #THREAD_LOCAL_OBJECTS_OFFSET] + POISON_HEAP_REF \wClass + str \wClass, [x0, #MIRROR_OBJECT_CLASS_OFFSET] // Store the class pointer. + str \wCount, [x0, #MIRROR_ARRAY_LENGTH_OFFSET] // Store the array length. + // Fence. + dmb ishst + ret +.endm -// The common code for art_quick_alloc_array_*region_tlab -.macro GENERATE_ALLOC_ARRAY_REGION_TLAB name, entrypoint, fast_path +.macro GENERATE_ALLOC_ARRAY_TLAB name, entrypoint, size_setup ENTRY \name // Fast path array allocation for region tlab allocation. // x0: mirror::Class* type // x1: int32_t component_count // x2-x7: free. -#if !defined(USE_READ_BARRIER) - mvn x0, xzr // Read barrier must be enabled here. - ret // Return -1. -#endif mov x3, x0 - \fast_path .Lslow_path\name, x3, w3, x1, w1, x4, w4, x5, w5, x6, w6 + \size_setup x3, w3, x1, w1, x4, w4, x5, w5, x6, w6 + ALLOC_ARRAY_TLAB_FAST_PATH_RESOLVED_WITH_SIZE .Lslow_path\name, x3, w3, x1, w1, x4, w4, x5, w5, x6, w6 .Lslow_path\name: // x0: mirror::Class* klass // x1: int32_t component_count @@ -1893,7 +1844,64 @@ ENTRY \name END \name .endm -GENERATE_ALLOC_ARRAY_REGION_TLAB art_quick_alloc_array_resolved_region_tlab, artAllocArrayFromCodeResolvedRegionTLAB, ALLOC_ARRAY_TLAB_FAST_PATH_RESOLVED +.macro COMPUTE_ARRAY_SIZE_UNKNOWN xClass, wClass, xCount, wCount, xTemp0, wTemp0, xTemp1, wTemp1, xTemp2, wTemp2 + // Array classes are never finalizable or uninitialized, no need to check. + ldr \wTemp0, [\xClass, #MIRROR_CLASS_COMPONENT_TYPE_OFFSET] // Load component type + UNPOISON_HEAP_REF \wTemp0 + ldr \wTemp0, [\xTemp0, #MIRROR_CLASS_OBJECT_PRIMITIVE_TYPE_OFFSET] + lsr \xTemp0, \xTemp0, #PRIMITIVE_TYPE_SIZE_SHIFT_SHIFT // Component size shift is in high 16 + // bits. + // xCount is holding a 32 bit value, + // it can not overflow. + lsl \xTemp1, \xCount, \xTemp0 // Calculate data size + // Add array data offset and alignment. + add \xTemp1, \xTemp1, #(MIRROR_INT_ARRAY_DATA_OFFSET + OBJECT_ALIGNMENT_MASK) +#if MIRROR_LONG_ARRAY_DATA_OFFSET != MIRROR_INT_ARRAY_DATA_OFFSET + 4 +#error Long array data offset must be 4 greater than int array data offset. +#endif + + add \xTemp0, \xTemp0, #1 // Add 4 to the length only if the + // component size shift is 3 + // (for 64 bit alignment). + and \xTemp0, \xTemp0, #4 + add \xTemp1, \xTemp1, \xTemp0 +.endm + +.macro COMPUTE_ARRAY_SIZE_8 xClass, wClass, xCount, wCount, xTemp0, wTemp0, xTemp1, wTemp1, xTemp2, wTemp2 + // Add array data offset and alignment. + add \xTemp1, \xCount, #(MIRROR_INT_ARRAY_DATA_OFFSET + OBJECT_ALIGNMENT_MASK) +.endm + +.macro COMPUTE_ARRAY_SIZE_16 xClass, wClass, xCount, wCount, xTemp0, wTemp0, xTemp1, wTemp1, xTemp2, wTemp2 + lsl \xTemp1, \xCount, #1 + // Add array data offset and alignment. + add \xTemp1, \xTemp1, #(MIRROR_INT_ARRAY_DATA_OFFSET + OBJECT_ALIGNMENT_MASK) +.endm + +.macro COMPUTE_ARRAY_SIZE_32 xClass, wClass, xCount, wCount, xTemp0, wTemp0, xTemp1, wTemp1, xTemp2, wTemp2 + lsl \xTemp1, \xCount, #2 + // Add array data offset and alignment. + add \xTemp1, \xTemp1, #(MIRROR_INT_ARRAY_DATA_OFFSET + OBJECT_ALIGNMENT_MASK) +.endm + +.macro COMPUTE_ARRAY_SIZE_64 xClass, wClass, xCount, wCount, xTemp0, wTemp0, xTemp1, wTemp1, xTemp2, wTemp2 + lsl \xTemp1, \xCount, #3 + // Add array data offset and alignment. + add \xTemp1, \xTemp1, #(MIRROR_WIDE_ARRAY_DATA_OFFSET + OBJECT_ALIGNMENT_MASK) +.endm + +# TODO(ngeoffray): art_quick_alloc_array_resolved_region_tlab is not used for arm64, remove +# the entrypoint once all backends have been updated to use the size variants. +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved_region_tlab, artAllocArrayFromCodeResolvedRegionTLAB, COMPUTE_ARRAY_SIZE_UNKNOWN +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved8_region_tlab, artAllocArrayFromCodeResolvedRegionTLAB, COMPUTE_ARRAY_SIZE_8 +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved16_region_tlab, artAllocArrayFromCodeResolvedRegionTLAB, COMPUTE_ARRAY_SIZE_16 +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved32_region_tlab, artAllocArrayFromCodeResolvedRegionTLAB, COMPUTE_ARRAY_SIZE_32 +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved64_region_tlab, artAllocArrayFromCodeResolvedRegionTLAB, COMPUTE_ARRAY_SIZE_64 +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved_tlab, artAllocArrayFromCodeResolvedTLAB, COMPUTE_ARRAY_SIZE_UNKNOWN +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved8_tlab, artAllocArrayFromCodeResolvedTLAB, COMPUTE_ARRAY_SIZE_8 +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved16_tlab, artAllocArrayFromCodeResolvedTLAB, COMPUTE_ARRAY_SIZE_16 +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved32_tlab, artAllocArrayFromCodeResolvedTLAB, COMPUTE_ARRAY_SIZE_32 +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved64_tlab, artAllocArrayFromCodeResolvedTLAB, COMPUTE_ARRAY_SIZE_64 /* * Called by managed code when the thread has been asked to suspend. diff --git a/runtime/arch/instruction_set.h b/runtime/arch/instruction_set.h index 99aea62468..7ef9a7abb5 100644 --- a/runtime/arch/instruction_set.h +++ b/runtime/arch/instruction_set.h @@ -68,8 +68,8 @@ static constexpr size_t kArmAlignment = 8; // ARM64 instruction alignment. This is the recommended alignment for maximum performance. static constexpr size_t kArm64Alignment = 16; -// MIPS instruction alignment. MIPS processors require code to be 4-byte aligned. -// TODO: Can this be 4? +// MIPS instruction alignment. MIPS processors require code to be 4-byte aligned, +// but 64-bit literals must be 8-byte aligned. static constexpr size_t kMipsAlignment = 8; // X86 instruction alignment. This is the recommended alignment for maximum performance. @@ -80,8 +80,8 @@ static constexpr size_t kThumb2InstructionAlignment = 2; static constexpr size_t kArm64InstructionAlignment = 4; static constexpr size_t kX86InstructionAlignment = 1; static constexpr size_t kX86_64InstructionAlignment = 1; -static constexpr size_t kMipsInstructionAlignment = 2; -static constexpr size_t kMips64InstructionAlignment = 2; +static constexpr size_t kMipsInstructionAlignment = 4; +static constexpr size_t kMips64InstructionAlignment = 4; const char* GetInstructionSetString(InstructionSet isa); diff --git a/runtime/arch/instruction_set_features.h b/runtime/arch/instruction_set_features.h index b6c5c71818..5f1a507f7a 100644 --- a/runtime/arch/instruction_set_features.h +++ b/runtime/arch/instruction_set_features.h @@ -67,6 +67,24 @@ class InstructionSetFeatures { // Are these features the same as the other given features? virtual bool Equals(const InstructionSetFeatures* other) const = 0; + // For testing purposes we want to make sure that the system we run on has at + // least the options we claim it has. In this cases Equals() does not + // suffice and will cause the test to fail, since the runtime cpu feature + // detection claims more capabilities then statically specified from the + // build system. + // + // A good example of this is the armv8 ART test target that declares + // "CPU_VARIANT=generic". If the generic target is specified and the code + // is run on a platform with enhanced capabilities, the + // instruction_set_features test will fail if we resort to using Equals() + // between statically defined cpu features and runtime cpu features. + // + // For now we default this to Equals() in case the architecture does not + // provide it. + virtual bool HasAtLeast(const InstructionSetFeatures* other) const { + return Equals(other); + } + // Return the ISA these features relate to. virtual InstructionSet GetInstructionSet() const = 0; diff --git a/runtime/arch/instruction_set_features_test.cc b/runtime/arch/instruction_set_features_test.cc index d4893923d6..67e2f358c8 100644 --- a/runtime/arch/instruction_set_features_test.cc +++ b/runtime/arch/instruction_set_features_test.cc @@ -52,7 +52,7 @@ TEST(InstructionSetFeaturesTest, FeaturesFromSystemPropertyVariant) { InstructionSetFeatures::FromVariant(kRuntimeISA, dex2oat_isa_variant, &error_msg)); ASSERT_TRUE(property_features.get() != nullptr) << error_msg; - EXPECT_TRUE(property_features->Equals(instruction_set_features.get())) + EXPECT_TRUE(property_features->HasAtLeast(instruction_set_features.get())) << "System property features: " << *property_features.get() << "\nFeatures from build: " << *instruction_set_features.get(); } @@ -89,7 +89,7 @@ TEST(InstructionSetFeaturesTest, FeaturesFromSystemPropertyString) { base_features->AddFeaturesFromString(dex2oat_isa_features, &error_msg)); ASSERT_TRUE(property_features.get() != nullptr) << error_msg; - EXPECT_TRUE(property_features->Equals(instruction_set_features.get())) + EXPECT_TRUE(property_features->HasAtLeast(instruction_set_features.get())) << "System property features: " << *property_features.get() << "\nFeatures from build: " << *instruction_set_features.get(); } @@ -109,7 +109,7 @@ TEST(InstructionSetFeaturesTest, FeaturesFromCpuInfo) { // Check we get the same instruction set features using /proc/cpuinfo. std::unique_ptr<const InstructionSetFeatures> cpuinfo_features( InstructionSetFeatures::FromCpuInfo()); - EXPECT_TRUE(cpuinfo_features->Equals(instruction_set_features.get())) + EXPECT_TRUE(cpuinfo_features->HasAtLeast(instruction_set_features.get())) << "CPU Info features: " << *cpuinfo_features.get() << "\nFeatures from build: " << *instruction_set_features.get(); } @@ -124,7 +124,7 @@ TEST(InstructionSetFeaturesTest, HostFeaturesFromCppDefines) { std::unique_ptr<const InstructionSetFeatures> cpp_features( InstructionSetFeatures::FromCppDefines()); - EXPECT_TRUE(default_features->Equals(cpp_features.get())) + EXPECT_TRUE(cpp_features->HasAtLeast(default_features.get())) << "Default variant features: " << *default_features.get() << "\nFeatures from build: " << *cpp_features.get(); } @@ -143,7 +143,7 @@ TEST(InstructionSetFeaturesTest, FeaturesFromHwcap) { // Check we get the same instruction set features using AT_HWCAP. std::unique_ptr<const InstructionSetFeatures> hwcap_features( InstructionSetFeatures::FromHwcap()); - EXPECT_TRUE(hwcap_features->Equals(instruction_set_features.get())) + EXPECT_TRUE(hwcap_features->HasAtLeast(instruction_set_features.get())) << "Hwcap features: " << *hwcap_features.get() << "\nFeatures from build: " << *instruction_set_features.get(); } @@ -156,7 +156,7 @@ TEST(InstructionSetFeaturesTest, FeaturesFromAssembly) { // Check we get the same instruction set features using assembly tests. std::unique_ptr<const InstructionSetFeatures> assembly_features( InstructionSetFeatures::FromAssembly()); - EXPECT_TRUE(assembly_features->Equals(instruction_set_features.get())) + EXPECT_TRUE(assembly_features->HasAtLeast(instruction_set_features.get())) << "Assembly features: " << *assembly_features.get() << "\nFeatures from build: " << *instruction_set_features.get(); } diff --git a/runtime/arch/mips/quick_entrypoints_mips.S b/runtime/arch/mips/quick_entrypoints_mips.S index 76218fb542..663cb6c62f 100644 --- a/runtime/arch/mips/quick_entrypoints_mips.S +++ b/runtime/arch/mips/quick_entrypoints_mips.S @@ -1450,316 +1450,83 @@ ENTRY art_quick_aput_obj move $a2, rSELF # pass Thread::Current END art_quick_aput_obj - /* - * Called by managed code to resolve a static field and load a boolean primitive value. - */ - .extern artGetBooleanStaticFromCode -ENTRY art_quick_get_boolean_static - lw $a1, 0($sp) # pass referrer's Method* - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - la $t9, artGetBooleanStaticFromCode - jalr $t9 # (uint32_t field_idx, const Method* referrer, Thread*) - move $a2, rSELF # pass Thread::Current - RETURN_IF_NO_EXCEPTION -END art_quick_get_boolean_static - /* - * Called by managed code to resolve a static field and load a byte primitive value. - */ - .extern artGetByteStaticFromCode -ENTRY art_quick_get_byte_static - lw $a1, 0($sp) # pass referrer's Method* - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - la $t9, artGetByteStaticFromCode - jalr $t9 # (uint32_t field_idx, const Method* referrer, Thread*) - move $a2, rSELF # pass Thread::Current - RETURN_IF_NO_EXCEPTION -END art_quick_get_byte_static - - /* - * Called by managed code to resolve a static field and load a char primitive value. - */ - .extern artGetCharStaticFromCode -ENTRY art_quick_get_char_static - lw $a1, 0($sp) # pass referrer's Method* - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - la $t9, artGetCharStaticFromCode - jalr $t9 # (uint32_t field_idx, const Method* referrer, Thread*) - move $a2, rSELF # pass Thread::Current - RETURN_IF_NO_EXCEPTION -END art_quick_get_char_static - /* - * Called by managed code to resolve a static field and load a short primitive value. - */ - .extern artGetShortStaticFromCode -ENTRY art_quick_get_short_static - lw $a1, 0($sp) # pass referrer's Method* - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - la $t9, artGetShortStaticFromCode - jalr $t9 # (uint32_t field_idx, const Method* referrer, Thread*) - move $a2, rSELF # pass Thread::Current - RETURN_IF_NO_EXCEPTION -END art_quick_get_short_static - - /* - * Called by managed code to resolve a static field and load a 32-bit primitive value. - */ - .extern artGet32StaticFromCode -ENTRY art_quick_get32_static - lw $a1, 0($sp) # pass referrer's Method* - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - la $t9, artGet32StaticFromCode - jalr $t9 # (uint32_t field_idx, const Method* referrer, Thread*) - move $a2, rSELF # pass Thread::Current - RETURN_IF_NO_EXCEPTION -END art_quick_get32_static - - /* - * Called by managed code to resolve a static field and load a 64-bit primitive value. - */ - .extern artGet64StaticFromCode -ENTRY art_quick_get64_static - lw $a1, 0($sp) # pass referrer's Method* - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - la $t9, artGet64StaticFromCode - jalr $t9 # (uint32_t field_idx, const Method* referrer, Thread*) - move $a2, rSELF # pass Thread::Current - RETURN_IF_NO_EXCEPTION -END art_quick_get64_static - - /* - * Called by managed code to resolve a static field and load an object reference. - */ - .extern artGetObjStaticFromCode -ENTRY art_quick_get_obj_static - lw $a1, 0($sp) # pass referrer's Method* - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - la $t9, artGetObjStaticFromCode - jalr $t9 # (uint32_t field_idx, const Method* referrer, Thread*) - move $a2, rSELF # pass Thread::Current - RETURN_IF_NO_EXCEPTION -END art_quick_get_obj_static - - /* - * Called by managed code to resolve an instance field and load a boolean primitive value. - */ - .extern artGetBooleanInstanceFromCode -ENTRY art_quick_get_boolean_instance - lw $a2, 0($sp) # pass referrer's Method* - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - la $t9, artGetBooleanInstanceFromCode - jalr $t9 # (field_idx, Object*, referrer, Thread*) - move $a3, rSELF # pass Thread::Current - RETURN_IF_NO_EXCEPTION -END art_quick_get_boolean_instance - /* - * Called by managed code to resolve an instance field and load a byte primitive value. - */ - .extern artGetByteInstanceFromCode -ENTRY art_quick_get_byte_instance - lw $a2, 0($sp) # pass referrer's Method* - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - la $t9, artGetByteInstanceFromCode - jalr $t9 # (field_idx, Object*, referrer, Thread*) - move $a3, rSELF # pass Thread::Current - RETURN_IF_NO_EXCEPTION -END art_quick_get_byte_instance - - /* - * Called by managed code to resolve an instance field and load a char primitive value. - */ - .extern artGetCharInstanceFromCode -ENTRY art_quick_get_char_instance - lw $a2, 0($sp) # pass referrer's Method* - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - la $t9, artGetCharInstanceFromCode - jalr $t9 # (field_idx, Object*, referrer, Thread*) - move $a3, rSELF # pass Thread::Current - RETURN_IF_NO_EXCEPTION -END art_quick_get_char_instance - /* - * Called by managed code to resolve an instance field and load a short primitive value. - */ - .extern artGetShortInstanceFromCode -ENTRY art_quick_get_short_instance - lw $a2, 0($sp) # pass referrer's Method* - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - la $t9, artGetShortInstanceFromCode - jalr $t9 # (field_idx, Object*, referrer, Thread*) - move $a3, rSELF # pass Thread::Current - RETURN_IF_NO_EXCEPTION -END art_quick_get_short_instance - - /* - * Called by managed code to resolve an instance field and load a 32-bit primitive value. - */ - .extern artGet32InstanceFromCode -ENTRY art_quick_get32_instance - lw $a2, 0($sp) # pass referrer's Method* - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - la $t9, artGet32InstanceFromCode - jalr $t9 # (field_idx, Object*, referrer, Thread*) - move $a3, rSELF # pass Thread::Current - RETURN_IF_NO_EXCEPTION -END art_quick_get32_instance - - /* - * Called by managed code to resolve an instance field and load a 64-bit primitive value. - */ - .extern artGet64InstanceFromCode -ENTRY art_quick_get64_instance - lw $a2, 0($sp) # pass referrer's Method* - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - la $t9, artGet64InstanceFromCode - jalr $t9 # (field_idx, Object*, referrer, Thread*) - move $a3, rSELF # pass Thread::Current - RETURN_IF_NO_EXCEPTION -END art_quick_get64_instance - - /* - * Called by managed code to resolve an instance field and load an object reference. - */ - .extern artGetObjInstanceFromCode -ENTRY art_quick_get_obj_instance - lw $a2, 0($sp) # pass referrer's Method* - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - la $t9, artGetObjInstanceFromCode - jalr $t9 # (field_idx, Object*, referrer, Thread*) - move $a3, rSELF # pass Thread::Current - RETURN_IF_NO_EXCEPTION -END art_quick_get_obj_instance - - /* - * Called by managed code to resolve a static field and store a 8-bit primitive value. - */ - .extern artSet8StaticFromCode -ENTRY art_quick_set8_static - lw $a2, 0($sp) # pass referrer's Method* - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - la $t9, artSet8StaticFromCode - jalr $t9 # (field_idx, new_val, referrer, Thread*) - move $a3, rSELF # pass Thread::Current - RETURN_IF_ZERO -END art_quick_set8_static - - /* - * Called by managed code to resolve a static field and store a 16-bit primitive value. - */ - .extern artSet16StaticFromCode -ENTRY art_quick_set16_static - lw $a2, 0($sp) # pass referrer's Method* - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - la $t9, artSet16StaticFromCode - jalr $t9 # (field_idx, new_val, referrer, Thread*, $sp) - move $a3, rSELF # pass Thread::Current - RETURN_IF_ZERO -END art_quick_set16_static - - /* - * Called by managed code to resolve a static field and store a 32-bit primitive value. - */ - .extern artSet32StaticFromCode -ENTRY art_quick_set32_static - lw $a2, 0($sp) # pass referrer's Method* - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - la $t9, artSet32StaticFromCode - jalr $t9 # (field_idx, new_val, referrer, Thread*) - move $a3, rSELF # pass Thread::Current - RETURN_IF_ZERO -END art_quick_set32_static - - /* - * Called by managed code to resolve a static field and store a 64-bit primitive value. - */ - .extern artSet64StaticFromCode -ENTRY art_quick_set64_static - lw $a1, 0($sp) # pass referrer's Method* - # 64 bit new_val is in a2:a3 pair - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - la $t9, artSet64StaticFromCode - jalr $t9 # (field_idx, referrer, new_val, Thread*) - sw rSELF, 16($sp) # pass Thread::Current - RETURN_IF_ZERO -END art_quick_set64_static - - /* - * Called by managed code to resolve a static field and store an object reference. - */ - .extern artSetObjStaticFromCode -ENTRY art_quick_set_obj_static - lw $a2, 0($sp) # pass referrer's Method* - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - la $t9, artSetObjStaticFromCode - jalr $t9 # (field_idx, new_val, referrer, Thread*) - move $a3, rSELF # pass Thread::Current - RETURN_IF_ZERO -END art_quick_set_obj_static - - /* - * Called by managed code to resolve an instance field and store a 8-bit primitive value. - */ - .extern artSet8InstanceFromCode -ENTRY art_quick_set8_instance - lw $a3, 0($sp) # pass referrer's Method* - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - la $t9, artSet8InstanceFromCode - jalr $t9 # (field_idx, Object*, new_val, referrer, Thread*) - sw rSELF, 16($sp) # pass Thread::Current - RETURN_IF_ZERO -END art_quick_set8_instance +// Macros taking opportunity of code similarities for downcalls. +.macro ONE_ARG_REF_DOWNCALL name, entrypoint, return + .extern \entrypoint +ENTRY \name + SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC + la $t9, \entrypoint + jalr $t9 # (field_idx, Thread*) + move $a1, rSELF # pass Thread::Current + \return # RETURN_IF_NO_EXCEPTION or RETURN_IF_ZERO +END \name +.endm - /* - * Called by managed code to resolve an instance field and store a 16-bit primitive value. - */ - .extern artSet16InstanceFromCode -ENTRY art_quick_set16_instance - lw $a3, 0($sp) # pass referrer's Method* - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - la $t9, artSet16InstanceFromCode - jalr $t9 # (field_idx, Object*, new_val, referrer, Thread*) - sw rSELF, 16($sp) # pass Thread::Current - RETURN_IF_ZERO -END art_quick_set16_instance +.macro TWO_ARG_REF_DOWNCALL name, entrypoint, return + .extern \entrypoint +ENTRY \name + SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC + la $t9, \entrypoint + jalr $t9 # (field_idx, Object*, Thread*) or + # (field_idx, new_val, Thread*) + move $a2, rSELF # pass Thread::Current + \return # RETURN_IF_NO_EXCEPTION or RETURN_IF_ZERO +END \name +.endm - /* - * Called by managed code to resolve an instance field and store a 32-bit primitive value. - */ - .extern artSet32InstanceFromCode -ENTRY art_quick_set32_instance - lw $a3, 0($sp) # pass referrer's Method* - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - la $t9, artSet32InstanceFromCode - jalr $t9 # (field_idx, Object*, new_val, referrer, Thread*) - sw rSELF, 16($sp) # pass Thread::Current - RETURN_IF_ZERO -END art_quick_set32_instance +.macro THREE_ARG_REF_DOWNCALL name, entrypoint, return + .extern \entrypoint +ENTRY \name + SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC + la $t9, \entrypoint + jalr $t9 # (field_idx, Object*, new_val, Thread*) + move $a3, rSELF # pass Thread::Current + \return # RETURN_IF_NO_EXCEPTION or RETURN_IF_ZERO +END \name +.endm - /* - * Called by managed code to resolve an instance field and store a 64-bit primitive value. - */ - .extern artSet64InstanceFromCode -ENTRY art_quick_set64_instance - lw $t1, 0($sp) # load referrer's Method* - # 64 bit new_val is in a2:a3 pair - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - sw rSELF, 20($sp) # pass Thread::Current - la $t9, artSet64InstanceFromCode - jalr $t9 # (field_idx, Object*, new_val, referrer, Thread*) - sw $t1, 16($sp) # pass referrer's Method* - RETURN_IF_ZERO -END art_quick_set64_instance +.macro FOUR_ARG_REF_DOWNCALL name, entrypoint, return + .extern \entrypoint +ENTRY \name + SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC + la $t9, \entrypoint + jalr $t9 # (field_idx, Object*, 64-bit new_val, Thread*) or + # (field_idx, 64-bit new_val, Thread*) + # Note that a 64-bit new_val needs to be aligned with + # an even-numbered register, hence A1 may be skipped + # for new_val to reside in A2-A3. + sw rSELF, 16($sp) # pass Thread::Current + \return # RETURN_IF_NO_EXCEPTION or RETURN_IF_ZERO +END \name +.endm /* - * Called by managed code to resolve an instance field and store an object reference. + * Called by managed code to resolve a static/instance field and load/store a value. */ - .extern artSetObjInstanceFromCode -ENTRY art_quick_set_obj_instance - lw $a3, 0($sp) # pass referrer's Method* - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - la $t9, artSetObjInstanceFromCode - jalr $t9 # (field_idx, Object*, new_val, referrer, Thread*) - sw rSELF, 16($sp) # pass Thread::Current - RETURN_IF_ZERO -END art_quick_set_obj_instance +ONE_ARG_REF_DOWNCALL art_quick_get_byte_static, artGetByteStaticFromCompiledCode, RETURN_IF_NO_EXCEPTION +ONE_ARG_REF_DOWNCALL art_quick_get_boolean_static, artGetBooleanStaticFromCompiledCode, RETURN_IF_NO_EXCEPTION +ONE_ARG_REF_DOWNCALL art_quick_get_short_static, artGetShortStaticFromCompiledCode, RETURN_IF_NO_EXCEPTION +ONE_ARG_REF_DOWNCALL art_quick_get_char_static, artGetCharStaticFromCompiledCode, RETURN_IF_NO_EXCEPTION +ONE_ARG_REF_DOWNCALL art_quick_get32_static, artGet32StaticFromCompiledCode, RETURN_IF_NO_EXCEPTION +ONE_ARG_REF_DOWNCALL art_quick_get_obj_static, artGetObjStaticFromCompiledCode, RETURN_IF_NO_EXCEPTION +ONE_ARG_REF_DOWNCALL art_quick_get64_static, artGet64StaticFromCompiledCode, RETURN_IF_NO_EXCEPTION +TWO_ARG_REF_DOWNCALL art_quick_get_byte_instance, artGetByteInstanceFromCompiledCode, RETURN_IF_NO_EXCEPTION +TWO_ARG_REF_DOWNCALL art_quick_get_boolean_instance, artGetBooleanInstanceFromCompiledCode, RETURN_IF_NO_EXCEPTION +TWO_ARG_REF_DOWNCALL art_quick_get_short_instance, artGetShortInstanceFromCompiledCode, RETURN_IF_NO_EXCEPTION +TWO_ARG_REF_DOWNCALL art_quick_get_char_instance, artGetCharInstanceFromCompiledCode, RETURN_IF_NO_EXCEPTION +TWO_ARG_REF_DOWNCALL art_quick_get32_instance, artGet32InstanceFromCompiledCode, RETURN_IF_NO_EXCEPTION +TWO_ARG_REF_DOWNCALL art_quick_get_obj_instance, artGetObjInstanceFromCompiledCode, RETURN_IF_NO_EXCEPTION +TWO_ARG_REF_DOWNCALL art_quick_get64_instance, artGet64InstanceFromCompiledCode, RETURN_IF_NO_EXCEPTION +TWO_ARG_REF_DOWNCALL art_quick_set8_static, artSet8StaticFromCompiledCode, RETURN_IF_ZERO +TWO_ARG_REF_DOWNCALL art_quick_set16_static, artSet16StaticFromCompiledCode, RETURN_IF_ZERO +TWO_ARG_REF_DOWNCALL art_quick_set32_static, artSet32StaticFromCompiledCode, RETURN_IF_ZERO +TWO_ARG_REF_DOWNCALL art_quick_set_obj_static, artSetObjStaticFromCompiledCode, RETURN_IF_ZERO +FOUR_ARG_REF_DOWNCALL art_quick_set64_static, artSet64StaticFromCompiledCode, RETURN_IF_ZERO +THREE_ARG_REF_DOWNCALL art_quick_set8_instance, artSet8InstanceFromCompiledCode, RETURN_IF_ZERO +THREE_ARG_REF_DOWNCALL art_quick_set16_instance, artSet16InstanceFromCompiledCode, RETURN_IF_ZERO +THREE_ARG_REF_DOWNCALL art_quick_set32_instance, artSet32InstanceFromCompiledCode, RETURN_IF_ZERO +THREE_ARG_REF_DOWNCALL art_quick_set_obj_instance, artSetObjInstanceFromCompiledCode, RETURN_IF_ZERO +FOUR_ARG_REF_DOWNCALL art_quick_set64_instance, artSet64InstanceFromCompiledCode, RETURN_IF_ZERO // Macro to facilitate adding new allocation entrypoints. .macro ONE_ARG_DOWNCALL name, entrypoint, return @@ -1811,6 +1578,7 @@ GENERATE_ALLOC_ENTRYPOINTS_FOR_EACH_ALLOCATOR GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_rosalloc, RosAlloc) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(_rosalloc, RosAlloc) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_tlab, TLAB) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_region_tlab, RegionTLAB) diff --git a/runtime/arch/mips64/quick_entrypoints_mips64.S b/runtime/arch/mips64/quick_entrypoints_mips64.S index b53fd100fa..5fee575331 100644 --- a/runtime/arch/mips64/quick_entrypoints_mips64.S +++ b/runtime/arch/mips64/quick_entrypoints_mips64.S @@ -1416,296 +1416,77 @@ ENTRY art_quick_aput_obj move $a2, rSELF # pass Thread::Current END art_quick_aput_obj - /* - * Called by managed code to resolve a static field and load a boolean primitive value. - */ - .extern artGetBooleanStaticFromCode -ENTRY art_quick_get_boolean_static - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - ld $a1, FRAME_SIZE_SAVE_REFS_ONLY($sp) # pass referrer's Method* - jal artGetBooleanStaticFromCode # (uint32_t field_idx, const Method* referrer, Thread*) - move $a2, rSELF # pass Thread::Current - RETURN_IF_NO_EXCEPTION -END art_quick_get_boolean_static - - /* - * Called by managed code to resolve a static field and load a byte primitive value. - */ - .extern artGetByteStaticFromCode -ENTRY art_quick_get_byte_static - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - ld $a1, FRAME_SIZE_SAVE_REFS_ONLY($sp) # pass referrer's Method* - jal artGetByteStaticFromCode # (uint32_t field_idx, const Method* referrer, Thread*) - move $a2, rSELF # pass Thread::Current - RETURN_IF_NO_EXCEPTION -END art_quick_get_byte_static - - /* - * Called by managed code to resolve a static field and load a char primitive value. - */ - .extern artGetCharStaticFromCode -ENTRY art_quick_get_char_static - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - ld $a1, FRAME_SIZE_SAVE_REFS_ONLY($sp) # pass referrer's Method* - jal artGetCharStaticFromCode # (uint32_t field_idx, const Method* referrer, Thread*) - move $a2, rSELF # pass Thread::Current - RETURN_IF_NO_EXCEPTION -END art_quick_get_char_static - - /* - * Called by managed code to resolve a static field and load a short primitive value. - */ - .extern artGetShortStaticFromCode -ENTRY art_quick_get_short_static - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - ld $a1, FRAME_SIZE_SAVE_REFS_ONLY($sp) # pass referrer's Method* - jal artGetShortStaticFromCode # (uint32_t field_idx, const Method* referrer, Thread*) - move $a2, rSELF # pass Thread::Current - RETURN_IF_NO_EXCEPTION -END art_quick_get_short_static - - /* - * Called by managed code to resolve a static field and load a 32-bit primitive value. - */ - .extern artGet32StaticFromCode -ENTRY art_quick_get32_static - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - ld $a1, FRAME_SIZE_SAVE_REFS_ONLY($sp) # pass referrer's Method* - jal artGet32StaticFromCode # (uint32_t field_idx, const Method* referrer, Thread*) - move $a2, rSELF # pass Thread::Current - sll $v0, $v0, 0 # sign-extend result - RETURN_IF_NO_EXCEPTION -END art_quick_get32_static - - /* - * Called by managed code to resolve a static field and load a 64-bit primitive value. - */ - .extern artGet64StaticFromCode -ENTRY art_quick_get64_static - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - ld $a1, FRAME_SIZE_SAVE_REFS_ONLY($sp) # pass referrer's Method* - jal artGet64StaticFromCode # (uint32_t field_idx, const Method* referrer, Thread*) - move $a2, rSELF # pass Thread::Current - RETURN_IF_NO_EXCEPTION -END art_quick_get64_static - - /* - * Called by managed code to resolve a static field and load an object reference. - */ - .extern artGetObjStaticFromCode -ENTRY art_quick_get_obj_static - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - ld $a1, FRAME_SIZE_SAVE_REFS_ONLY($sp) # pass referrer's Method* - jal artGetObjStaticFromCode # (uint32_t field_idx, const Method* referrer, Thread*) - move $a2, rSELF # pass Thread::Current - RETURN_IF_NO_EXCEPTION -END art_quick_get_obj_static - - /* - * Called by managed code to resolve an instance field and load a boolean primitive value. - */ - .extern artGetBooleanInstanceFromCode -ENTRY art_quick_get_boolean_instance - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - ld $a2, FRAME_SIZE_SAVE_REFS_ONLY($sp) # pass referrer's Method* - jal artGetBooleanInstanceFromCode # (field_idx, Object*, referrer, Thread*) - move $a3, rSELF # pass Thread::Current - RETURN_IF_NO_EXCEPTION -END art_quick_get_boolean_instance - - /* - * Called by managed code to resolve an instance field and load a byte primitive value. - */ - .extern artGetByteInstanceFromCode -ENTRY art_quick_get_byte_instance - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - ld $a2, FRAME_SIZE_SAVE_REFS_ONLY($sp) # pass referrer's Method* - jal artGetByteInstanceFromCode # (field_idx, Object*, referrer, Thread*) - move $a3, rSELF # pass Thread::Current - RETURN_IF_NO_EXCEPTION -END art_quick_get_byte_instance - - /* - * Called by managed code to resolve an instance field and load a char primitive value. - */ - .extern artGetCharInstanceFromCode -ENTRY art_quick_get_char_instance - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - ld $a2, FRAME_SIZE_SAVE_REFS_ONLY($sp) # pass referrer's Method* - jal artGetCharInstanceFromCode # (field_idx, Object*, referrer, Thread*) - move $a3, rSELF # pass Thread::Current - RETURN_IF_NO_EXCEPTION -END art_quick_get_char_instance - - /* - * Called by managed code to resolve an instance field and load a short primitive value. - */ - .extern artGetShortInstanceFromCode -ENTRY art_quick_get_short_instance - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - ld $a2, FRAME_SIZE_SAVE_REFS_ONLY($sp) # pass referrer's Method* - jal artGetShortInstanceFromCode # (field_idx, Object*, referrer, Thread*) - move $a3, rSELF # pass Thread::Current - RETURN_IF_NO_EXCEPTION -END art_quick_get_short_instance - - /* - * Called by managed code to resolve an instance field and load a 32-bit primitive value. - */ - .extern artGet32InstanceFromCode -ENTRY art_quick_get32_instance - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - ld $a2, FRAME_SIZE_SAVE_REFS_ONLY($sp) # pass referrer's Method* - jal artGet32InstanceFromCode # (field_idx, Object*, referrer, Thread*) - move $a3, rSELF # pass Thread::Current - sll $v0, $v0, 0 # sign-extend result - RETURN_IF_NO_EXCEPTION -END art_quick_get32_instance - - /* - * Called by managed code to resolve an instance field and load a 64-bit primitive value. - */ - .extern artGet64InstanceFromCode -ENTRY art_quick_get64_instance - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - ld $a2, FRAME_SIZE_SAVE_REFS_ONLY($sp) # pass referrer's Method* - jal artGet64InstanceFromCode # (field_idx, Object*, referrer, Thread*) - move $a3, rSELF # pass Thread::Current - RETURN_IF_NO_EXCEPTION -END art_quick_get64_instance - - /* - * Called by managed code to resolve an instance field and load an object reference. - */ - .extern artGetObjInstanceFromCode -ENTRY art_quick_get_obj_instance - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - ld $a2, FRAME_SIZE_SAVE_REFS_ONLY($sp) # pass referrer's Method* - jal artGetObjInstanceFromCode # (field_idx, Object*, referrer, Thread*) - move $a3, rSELF # pass Thread::Current - RETURN_IF_NO_EXCEPTION -END art_quick_get_obj_instance - - /* - * Called by managed code to resolve a static field and store a 8-bit primitive value. - */ - .extern artSet8StaticFromCode -ENTRY art_quick_set8_static - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - ld $a2, FRAME_SIZE_SAVE_REFS_ONLY($sp) # pass referrer's Method* - jal artSet8StaticFromCode # (field_idx, new_val, referrer, Thread*) - move $a3, rSELF # pass Thread::Current - RETURN_IF_ZERO -END art_quick_set8_static - - /* - * Called by managed code to resolve a static field and store a 16-bit primitive value. - */ - .extern artSet16StaticFromCode -ENTRY art_quick_set16_static - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - ld $a2, FRAME_SIZE_SAVE_REFS_ONLY($sp) # pass referrer's Method* - jal artSet16StaticFromCode # (field_idx, new_val, referrer, Thread*) - move $a3, rSELF # pass Thread::Current - RETURN_IF_ZERO -END art_quick_set16_static - - /* - * Called by managed code to resolve a static field and store a 32-bit primitive value. - */ - .extern artSet32StaticFromCode -ENTRY art_quick_set32_static - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - ld $a2, FRAME_SIZE_SAVE_REFS_ONLY($sp) # pass referrer's Method* - jal artSet32StaticFromCode # (field_idx, new_val, referrer, Thread*) - move $a3, rSELF # pass Thread::Current - RETURN_IF_ZERO -END art_quick_set32_static - - /* - * Called by managed code to resolve a static field and store a 64-bit primitive value. - */ - .extern artSet64StaticFromCode -ENTRY art_quick_set64_static - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - # a2 contains the new val - ld $a1, FRAME_SIZE_SAVE_REFS_ONLY($sp) # pass referrer's Method* - jal artSet64StaticFromCode # (field_idx, referrer, new_val, Thread*) - move $a3, rSELF # pass Thread::Current - RETURN_IF_ZERO -END art_quick_set64_static - - /* - * Called by managed code to resolve a static field and store an object reference. - */ - .extern artSetObjStaticFromCode -ENTRY art_quick_set_obj_static - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - ld $a2, FRAME_SIZE_SAVE_REFS_ONLY($sp) # pass referrer's Method* - jal artSetObjStaticFromCode # (field_idx, new_val, referrer, Thread*) - move $a3, rSELF # pass Thread::Current - RETURN_IF_ZERO -END art_quick_set_obj_static - - /* - * Called by managed code to resolve an instance field and store a 8-bit primitive value. - */ - .extern artSet8InstanceFromCode -ENTRY art_quick_set8_instance - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - ld $a3, FRAME_SIZE_SAVE_REFS_ONLY($sp) # pass referrer's Method* - jal artSet8InstanceFromCode # (field_idx, Object*, new_val, referrer, Thread*) - move $a4, rSELF # pass Thread::Current - RETURN_IF_ZERO -END art_quick_set8_instance - - /* - * Called by managed code to resolve an instance field and store a 16-bit primitive value. - */ - .extern artSet16InstanceFromCode -ENTRY art_quick_set16_instance - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - ld $a3, FRAME_SIZE_SAVE_REFS_ONLY($sp) # pass referrer's Method* - jal artSet16InstanceFromCode # (field_idx, Object*, new_val, referrer, Thread*) - move $a4, rSELF # pass Thread::Current - RETURN_IF_ZERO -END art_quick_set16_instance +// Macros taking opportunity of code similarities for downcalls. +.macro ONE_ARG_REF_DOWNCALL name, entrypoint, return, extend=0 + .extern \entrypoint +ENTRY \name + SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC + dla $t9, \entrypoint + jalr $t9 # (field_idx, Thread*) + move $a1, rSELF # pass Thread::Current + .if \extend + sll $v0, $v0, 0 # sign-extend 32-bit result + .endif + \return # RETURN_IF_NO_EXCEPTION or RETURN_IF_ZERO +END \name +.endm - /* - * Called by managed code to resolve an instance field and store a 32-bit primitive value. - */ - .extern artSet32InstanceFromCode -ENTRY art_quick_set32_instance - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - ld $a3, FRAME_SIZE_SAVE_REFS_ONLY($sp) # pass referrer's Method* - jal artSet32InstanceFromCode # (field_idx, Object*, new_val, referrer, Thread*) - move $a4, rSELF # pass Thread::Current - RETURN_IF_ZERO -END art_quick_set32_instance +.macro TWO_ARG_REF_DOWNCALL name, entrypoint, return, extend=0 + .extern \entrypoint +ENTRY \name + SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC + dla $t9, \entrypoint + jalr $t9 # (field_idx, Object*, Thread*) or + # (field_idx, new_val, Thread*) + move $a2, rSELF # pass Thread::Current + .if \extend + sll $v0, $v0, 0 # sign-extend 32-bit result + .endif + \return # RETURN_IF_NO_EXCEPTION or RETURN_IF_ZERO +END \name +.endm - /* - * Called by managed code to resolve an instance field and store a 64-bit primitive value. - */ - .extern artSet64InstanceFromCode -ENTRY art_quick_set64_instance - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - ld $a3, FRAME_SIZE_SAVE_REFS_ONLY($sp) # pass referrer's Method* - jal artSet64InstanceFromCode # (field_idx, Object*, new_val, referrer, Thread*) - move $a4, rSELF # pass Thread::Current - RETURN_IF_ZERO -END art_quick_set64_instance +.macro THREE_ARG_REF_DOWNCALL name, entrypoint, return, extend=0 + .extern \entrypoint +ENTRY \name + SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC + dla $t9, \entrypoint + jalr $t9 # (field_idx, Object*, new_val, Thread*) + move $a3, rSELF # pass Thread::Current + .if \extend + sll $v0, $v0, 0 # sign-extend 32-bit result + .endif + \return # RETURN_IF_NO_EXCEPTION or RETURN_IF_ZERO +END \name +.endm /* - * Called by managed code to resolve an instance field and store an object reference. + * Called by managed code to resolve a static/instance field and load/store a value. */ - .extern artSetObjInstanceFromCode -ENTRY art_quick_set_obj_instance - SETUP_SAVE_REFS_ONLY_FRAME # save callee saves in case of GC - ld $a3, FRAME_SIZE_SAVE_REFS_ONLY($sp) # pass referrer's Method* - jal artSetObjInstanceFromCode # (field_idx, Object*, new_val, referrer, Thread*) - move $a4, rSELF # pass Thread::Current - RETURN_IF_ZERO -END art_quick_set_obj_instance +ONE_ARG_REF_DOWNCALL art_quick_get_byte_static, artGetByteStaticFromCompiledCode, RETURN_IF_NO_EXCEPTION +ONE_ARG_REF_DOWNCALL art_quick_get_boolean_static, artGetBooleanStaticFromCompiledCode, RETURN_IF_NO_EXCEPTION +ONE_ARG_REF_DOWNCALL art_quick_get_short_static, artGetShortStaticFromCompiledCode, RETURN_IF_NO_EXCEPTION +ONE_ARG_REF_DOWNCALL art_quick_get_char_static, artGetCharStaticFromCompiledCode, RETURN_IF_NO_EXCEPTION +ONE_ARG_REF_DOWNCALL art_quick_get32_static, artGet32StaticFromCompiledCode, RETURN_IF_NO_EXCEPTION, 1 +ONE_ARG_REF_DOWNCALL art_quick_get_obj_static, artGetObjStaticFromCompiledCode, RETURN_IF_NO_EXCEPTION +ONE_ARG_REF_DOWNCALL art_quick_get64_static, artGet64StaticFromCompiledCode, RETURN_IF_NO_EXCEPTION +TWO_ARG_REF_DOWNCALL art_quick_get_byte_instance, artGetByteInstanceFromCompiledCode, RETURN_IF_NO_EXCEPTION +TWO_ARG_REF_DOWNCALL art_quick_get_boolean_instance, artGetBooleanInstanceFromCompiledCode, RETURN_IF_NO_EXCEPTION +TWO_ARG_REF_DOWNCALL art_quick_get_short_instance, artGetShortInstanceFromCompiledCode, RETURN_IF_NO_EXCEPTION +TWO_ARG_REF_DOWNCALL art_quick_get_char_instance, artGetCharInstanceFromCompiledCode, RETURN_IF_NO_EXCEPTION +TWO_ARG_REF_DOWNCALL art_quick_get32_instance, artGet32InstanceFromCompiledCode, RETURN_IF_NO_EXCEPTION, 1 +TWO_ARG_REF_DOWNCALL art_quick_get_obj_instance, artGetObjInstanceFromCompiledCode, RETURN_IF_NO_EXCEPTION +TWO_ARG_REF_DOWNCALL art_quick_get64_instance, artGet64InstanceFromCompiledCode, RETURN_IF_NO_EXCEPTION +TWO_ARG_REF_DOWNCALL art_quick_set8_static, artSet8StaticFromCompiledCode, RETURN_IF_ZERO +TWO_ARG_REF_DOWNCALL art_quick_set16_static, artSet16StaticFromCompiledCode, RETURN_IF_ZERO +TWO_ARG_REF_DOWNCALL art_quick_set32_static, artSet32StaticFromCompiledCode, RETURN_IF_ZERO +TWO_ARG_REF_DOWNCALL art_quick_set_obj_static, artSetObjStaticFromCompiledCode, RETURN_IF_ZERO +TWO_ARG_REF_DOWNCALL art_quick_set64_static, artSet64StaticFromCompiledCode, RETURN_IF_ZERO +THREE_ARG_REF_DOWNCALL art_quick_set8_instance, artSet8InstanceFromCompiledCode, RETURN_IF_ZERO +THREE_ARG_REF_DOWNCALL art_quick_set16_instance, artSet16InstanceFromCompiledCode, RETURN_IF_ZERO +THREE_ARG_REF_DOWNCALL art_quick_set32_instance, artSet32InstanceFromCompiledCode, RETURN_IF_ZERO +THREE_ARG_REF_DOWNCALL art_quick_set_obj_instance, artSetObjInstanceFromCompiledCode, RETURN_IF_ZERO +THREE_ARG_REF_DOWNCALL art_quick_set64_instance, artSet64InstanceFromCompiledCode, RETURN_IF_ZERO // Macro to facilitate adding new allocation entrypoints. .macro ONE_ARG_DOWNCALL name, entrypoint, return @@ -1753,6 +1534,7 @@ END \name GENERATE_ALLOC_ENTRYPOINTS_FOR_EACH_ALLOCATOR GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_rosalloc, RosAlloc) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(_rosalloc, RosAlloc) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_tlab, TLAB) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_region_tlab, RegionTLAB) diff --git a/runtime/arch/quick_alloc_entrypoints.S b/runtime/arch/quick_alloc_entrypoints.S index e79dc6029b..2b3525b189 100644 --- a/runtime/arch/quick_alloc_entrypoints.S +++ b/runtime/arch/quick_alloc_entrypoints.S @@ -30,6 +30,11 @@ FOUR_ARG_DOWNCALL art_quick_alloc_string_from_bytes\c_suffix, artAllocStringFrom THREE_ARG_DOWNCALL art_quick_alloc_string_from_chars\c_suffix, artAllocStringFromCharsFromCode\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER // Called by managed code to allocate a string from string ONE_ARG_DOWNCALL art_quick_alloc_string_from_string\c_suffix, artAllocStringFromStringFromCode\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER + +TWO_ARG_DOWNCALL art_quick_alloc_array_resolved8\c_suffix, artAllocArrayFromCodeResolved\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER +TWO_ARG_DOWNCALL art_quick_alloc_array_resolved16\c_suffix, artAllocArrayFromCodeResolved\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER +TWO_ARG_DOWNCALL art_quick_alloc_array_resolved32\c_suffix, artAllocArrayFromCodeResolved\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER +TWO_ARG_DOWNCALL art_quick_alloc_array_resolved64\c_suffix, artAllocArrayFromCodeResolved\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER .endm .macro GENERATE_ALL_ALLOC_ENTRYPOINTS @@ -56,14 +61,22 @@ GENERATE_ALLOC_ENTRYPOINTS _region_tlab_instrumented, RegionTLABInstrumented ONE_ARG_DOWNCALL art_quick_alloc_object_initialized ## c_suffix, artAllocObjectFromCodeInitialized ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER #define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_WITH_ACCESS_CHECK(c_suffix, cxx_suffix) \ ONE_ARG_DOWNCALL art_quick_alloc_object_with_checks ## c_suffix, artAllocObjectFromCodeWithChecks ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER -#define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED(c_suffix, cxx_suffix) \ - TWO_ARG_DOWNCALL art_quick_alloc_array_resolved ## c_suffix, artAllocArrayFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER #define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_BYTES(c_suffix, cxx_suffix) \ FOUR_ARG_DOWNCALL art_quick_alloc_string_from_bytes ## c_suffix, artAllocStringFromBytesFromCode ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER #define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_CHARS(c_suffix, cxx_suffix) \ THREE_ARG_DOWNCALL art_quick_alloc_string_from_chars ## c_suffix, artAllocStringFromCharsFromCode ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER #define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_STRING(c_suffix, cxx_suffix) \ ONE_ARG_DOWNCALL art_quick_alloc_string_from_string ## c_suffix, artAllocStringFromStringFromCode ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER +#define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED(c_suffix, cxx_suffix) \ + TWO_ARG_DOWNCALL art_quick_alloc_array_resolved ## c_suffix, artAllocArrayFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER +#define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED8(c_suffix, cxx_suffix) \ + TWO_ARG_DOWNCALL art_quick_alloc_array_resolved8 ## c_suffix, artAllocArrayFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER +#define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED16(c_suffix, cxx_suffix) \ + TWO_ARG_DOWNCALL art_quick_alloc_array_resolved16 ## c_suffix, artAllocArrayFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER +#define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED32(c_suffix, cxx_suffix) \ + TWO_ARG_DOWNCALL art_quick_alloc_array_resolved32 ## c_suffix, artAllocArrayFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER +#define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED64(c_suffix, cxx_suffix) \ + TWO_ARG_DOWNCALL art_quick_alloc_array_resolved64 ## c_suffix, artAllocArrayFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER .macro GENERATE_ALLOC_ENTRYPOINTS_FOR_EACH_ALLOCATOR GENERATE_ALLOC_ENTRYPOINTS_FOR_NON_REGION_TLAB_ALLOCATORS @@ -76,6 +89,10 @@ GENERATE_ALLOC_ENTRYPOINTS_FOR_REGION_TLAB_ALLOCATOR GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(_region_tlab, RegionTLAB) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_WITH_ACCESS_CHECK(_region_tlab, RegionTLAB) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED(_region_tlab, RegionTLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED8(_region_tlab, RegionTLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED16(_region_tlab, RegionTLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED32(_region_tlab, RegionTLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED64(_region_tlab, RegionTLAB) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_BYTES(_region_tlab, RegionTLAB) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_CHARS(_region_tlab, RegionTLAB) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_STRING(_region_tlab, RegionTLAB) @@ -87,6 +104,10 @@ GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_STRING(_region_tlab, RegionTLAB) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(_tlab, TLAB) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_WITH_ACCESS_CHECK(_tlab, TLAB) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED(_tlab, TLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED8(_tlab, TLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED16(_tlab, TLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED32(_tlab, TLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED64(_tlab, TLAB) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_BYTES(_tlab, TLAB) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_CHARS(_tlab, TLAB) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_STRING(_tlab, TLAB) @@ -102,6 +123,10 @@ GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_dlmalloc, DlMalloc) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(_dlmalloc, DlMalloc) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_WITH_ACCESS_CHECK(_dlmalloc, DlMalloc) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED(_dlmalloc, DlMalloc) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED8(_dlmalloc, DlMalloc) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED16(_dlmalloc, DlMalloc) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED32(_dlmalloc, DlMalloc) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED64(_dlmalloc, DlMalloc) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_BYTES(_dlmalloc, DlMalloc) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_CHARS(_dlmalloc, DlMalloc) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_STRING(_dlmalloc, DlMalloc) @@ -110,15 +135,23 @@ GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_dlmalloc_instrumented, DlMallo GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(_dlmalloc_instrumented, DlMallocInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_WITH_ACCESS_CHECK(_dlmalloc_instrumented, DlMallocInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED(_dlmalloc_instrumented, DlMallocInstrumented) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED8(_dlmalloc_instrumented, DlMallocInstrumented) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED16(_dlmalloc_instrumented, DlMallocInstrumented) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED32(_dlmalloc_instrumented, DlMallocInstrumented) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED64(_dlmalloc_instrumented, DlMallocInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_BYTES(_dlmalloc_instrumented, DlMallocInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_CHARS(_dlmalloc_instrumented, DlMallocInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_STRING(_dlmalloc_instrumented, DlMallocInstrumented) // This is to be separately defined for each architecture to allow a hand-written assembly fast path. // GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_rosalloc, RosAlloc) -GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(_rosalloc, RosAlloc) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(_rosalloc, RosAlloc) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_WITH_ACCESS_CHECK(_rosalloc, RosAlloc) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED(_rosalloc, RosAlloc) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED8(_rosalloc, RosAlloc) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED16(_rosalloc, RosAlloc) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED32(_rosalloc, RosAlloc) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED64(_rosalloc, RosAlloc) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_BYTES(_rosalloc, RosAlloc) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_CHARS(_rosalloc, RosAlloc) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_STRING(_rosalloc, RosAlloc) @@ -127,6 +160,10 @@ GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_rosalloc_instrumented, RosAllo GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(_rosalloc_instrumented, RosAllocInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_WITH_ACCESS_CHECK(_rosalloc_instrumented, RosAllocInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED(_rosalloc_instrumented, RosAllocInstrumented) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED8(_rosalloc_instrumented, RosAllocInstrumented) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED16(_rosalloc_instrumented, RosAllocInstrumented) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED32(_rosalloc_instrumented, RosAllocInstrumented) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED64(_rosalloc_instrumented, RosAllocInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_BYTES(_rosalloc_instrumented, RosAllocInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_CHARS(_rosalloc_instrumented, RosAllocInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_STRING(_rosalloc_instrumented, RosAllocInstrumented) @@ -135,6 +172,10 @@ GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_bump_pointer, BumpPointer) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(_bump_pointer, BumpPointer) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_WITH_ACCESS_CHECK(_bump_pointer, BumpPointer) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED(_bump_pointer, BumpPointer) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED8(_bump_pointer, BumpPointer) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED16(_bump_pointer, BumpPointer) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED32(_bump_pointer, BumpPointer) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED64(_bump_pointer, BumpPointer) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_BYTES(_bump_pointer, BumpPointer) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_CHARS(_bump_pointer, BumpPointer) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_STRING(_bump_pointer, BumpPointer) @@ -143,6 +184,10 @@ GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_bump_pointer_instrumented, Bum GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(_bump_pointer_instrumented, BumpPointerInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_WITH_ACCESS_CHECK(_bump_pointer_instrumented, BumpPointerInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED(_bump_pointer_instrumented, BumpPointerInstrumented) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED8(_bump_pointer_instrumented, BumpPointerInstrumented) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED16(_bump_pointer_instrumented, BumpPointerInstrumented) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED32(_bump_pointer_instrumented, BumpPointerInstrumented) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED64(_bump_pointer_instrumented, BumpPointerInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_BYTES(_bump_pointer_instrumented, BumpPointerInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_CHARS(_bump_pointer_instrumented, BumpPointerInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_STRING(_bump_pointer_instrumented, BumpPointerInstrumented) @@ -151,6 +196,10 @@ GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_tlab_instrumented, TLABInstrum GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(_tlab_instrumented, TLABInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_WITH_ACCESS_CHECK(_tlab_instrumented, TLABInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED(_tlab_instrumented, TLABInstrumented) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED8(_tlab_instrumented, TLABInstrumented) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED16(_tlab_instrumented, TLABInstrumented) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED32(_tlab_instrumented, TLABInstrumented) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED64(_tlab_instrumented, TLABInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_BYTES(_tlab_instrumented, TLABInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_CHARS(_tlab_instrumented, TLABInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_STRING(_tlab_instrumented, TLABInstrumented) @@ -159,6 +208,10 @@ GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_region, Region) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(_region, Region) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_WITH_ACCESS_CHECK(_region, Region) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED(_region, Region) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED8(_region, Region) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED16(_region, Region) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED32(_region, Region) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED64(_region, Region) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_BYTES(_region, Region) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_CHARS(_region, Region) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_STRING(_region, Region) @@ -167,6 +220,10 @@ GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_region_instrumented, RegionIns GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(_region_instrumented, RegionInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_WITH_ACCESS_CHECK(_region_instrumented, RegionInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED(_region_instrumented, RegionInstrumented) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED8(_region_instrumented, RegionInstrumented) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED16(_region_instrumented, RegionInstrumented) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED32(_region_instrumented, RegionInstrumented) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED64(_region_instrumented, RegionInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_BYTES(_region_instrumented, RegionInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_CHARS(_region_instrumented, RegionInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_STRING(_region_instrumented, RegionInstrumented) @@ -175,6 +232,10 @@ GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_region_tlab_instrumented, Regi GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(_region_tlab_instrumented, RegionTLABInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_WITH_ACCESS_CHECK(_region_tlab_instrumented, RegionTLABInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED(_region_tlab_instrumented, RegionTLABInstrumented) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED8(_region_tlab_instrumented, RegionTLABInstrumented) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED16(_region_tlab_instrumented, RegionTLABInstrumented) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED32(_region_tlab_instrumented, RegionTLABInstrumented) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED64(_region_tlab_instrumented, RegionTLABInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_BYTES(_region_tlab_instrumented, RegionTLABInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_CHARS(_region_tlab_instrumented, RegionTLABInstrumented) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_STRING(_region_tlab_instrumented, RegionTLABInstrumented) diff --git a/runtime/arch/stub_test.cc b/runtime/arch/stub_test.cc index 547b57ee1a..0bf08a6d97 100644 --- a/runtime/arch/stub_test.cc +++ b/runtime/arch/stub_test.cc @@ -1051,7 +1051,7 @@ TEST_F(StubTest, AllocObjectArray) { // resolved/initialized cases) size_t result = Invoke3(reinterpret_cast<size_t>(c.Get()), 10U, reinterpret_cast<size_t>(nullptr), - StubTest::GetEntrypoint(self, kQuickAllocArrayResolved), + StubTest::GetEntrypoint(self, kQuickAllocArrayResolved32), self); EXPECT_FALSE(self->IsExceptionPending()) << mirror::Object::PrettyTypeOf(self->GetException()); EXPECT_NE(reinterpret_cast<size_t>(nullptr), result); @@ -1071,7 +1071,7 @@ TEST_F(StubTest, AllocObjectArray) { size_t result = Invoke3(reinterpret_cast<size_t>(c.Get()), GB, // that should fail... reinterpret_cast<size_t>(nullptr), - StubTest::GetEntrypoint(self, kQuickAllocArrayResolved), + StubTest::GetEntrypoint(self, kQuickAllocArrayResolved32), self); EXPECT_TRUE(self->IsExceptionPending()); @@ -1610,8 +1610,8 @@ static void GetSet64Static(ArtField* f, Thread* self, ArtMethod* referrer, for (size_t i = 0; i < arraysize(values); ++i) { // 64 bit FieldSet stores the set value in the second register. test->Invoke3WithReferrer(static_cast<size_t>(f->GetDexFieldIndex()), - 0U, values[i], + 0U, StubTest::GetEntrypoint(self, kQuickSet64Static), self, referrer); diff --git a/runtime/arch/x86/quick_entrypoints_x86.S b/runtime/arch/x86/quick_entrypoints_x86.S index c4202596f2..76615e843b 100644 --- a/runtime/arch/x86/quick_entrypoints_x86.S +++ b/runtime/arch/x86/quick_entrypoints_x86.S @@ -875,13 +875,12 @@ MACRO3(ONE_ARG_REF_DOWNCALL, c_name, cxx_name, return_macro) DEFINE_FUNCTION VAR(c_name) SETUP_SAVE_REFS_ONLY_FRAME ebx, ebx // save ref containing registers for GC // Outgoing argument set up - mov FRAME_SIZE_SAVE_REFS_ONLY(%esp), %ecx // get referrer - PUSH eax // push padding + subl MACRO_LITERAL(8), %esp // alignment padding + CFI_ADJUST_CFA_OFFSET(8) pushl %fs:THREAD_SELF_OFFSET // pass Thread::Current() CFI_ADJUST_CFA_OFFSET(4) - PUSH ecx // pass referrer PUSH eax // pass arg1 - call CALLVAR(cxx_name) // cxx_name(arg1, referrer, Thread*) + call CALLVAR(cxx_name) // cxx_name(arg1, Thread*) addl MACRO_LITERAL(16), %esp // pop arguments CFI_ADJUST_CFA_OFFSET(-16) RESTORE_SAVE_REFS_ONLY_FRAME // restore frame up to return address @@ -893,10 +892,9 @@ MACRO3(TWO_ARG_REF_DOWNCALL, c_name, cxx_name, return_macro) DEFINE_FUNCTION VAR(c_name) SETUP_SAVE_REFS_ONLY_FRAME ebx, ebx // save ref containing registers for GC // Outgoing argument set up - mov FRAME_SIZE_SAVE_REFS_ONLY(%esp), %edx // get referrer + PUSH eax // alignment padding pushl %fs:THREAD_SELF_OFFSET // pass Thread::Current() CFI_ADJUST_CFA_OFFSET(4) - PUSH edx // pass referrer PUSH ecx // pass arg2 PUSH eax // pass arg1 call CALLVAR(cxx_name) // cxx_name(arg1, arg2, referrer, Thread*) @@ -911,18 +909,13 @@ MACRO3(THREE_ARG_REF_DOWNCALL, c_name, cxx_name, return_macro) DEFINE_FUNCTION VAR(c_name) SETUP_SAVE_REFS_ONLY_FRAME ebx, ebx // save ref containing registers for GC // Outgoing argument set up - mov FRAME_SIZE_SAVE_REFS_ONLY(%esp), %ebx // get referrer - subl MACRO_LITERAL(12), %esp // alignment padding - CFI_ADJUST_CFA_OFFSET(12) pushl %fs:THREAD_SELF_OFFSET // pass Thread::Current() CFI_ADJUST_CFA_OFFSET(4) - PUSH ebx // pass referrer PUSH edx // pass arg3 PUSH ecx // pass arg2 PUSH eax // pass arg1 - call CALLVAR(cxx_name) // cxx_name(arg1, arg2, arg3, referrer, - // Thread*) - addl LITERAL(32), %esp // pop arguments + call CALLVAR(cxx_name) // cxx_name(arg1, arg2, arg3, Thread*) + addl LITERAL(16), %esp // pop arguments CFI_ADJUST_CFA_OFFSET(-32) RESTORE_SAVE_REFS_ONLY_FRAME // restore frame up to return address CALL_MACRO(return_macro) // return or deliver exception @@ -954,10 +947,37 @@ MACRO0(RETURN_OR_DELIVER_PENDING_EXCEPTION) END_MACRO // Generate the allocation entrypoints for each allocator. -GENERATE_ALLOC_ENTRYPOINTS_FOR_EACH_ALLOCATOR +GENERATE_ALLOC_ENTRYPOINTS_FOR_NON_TLAB_ALLOCATORS + +// Comment out allocators that have x86 specific asm. +// Region TLAB: +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_region_tlab, RegionTLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(_region_tlab, RegionTLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_WITH_ACCESS_CHECK(_region_tlab, RegionTLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED(_region_tlab, RegionTLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED8(_region_tlab, RegionTLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED16(_region_tlab, RegionTLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED32(_region_tlab, RegionTLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED64(_region_tlab, RegionTLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_BYTES(_region_tlab, RegionTLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_CHARS(_region_tlab, RegionTLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_STRING(_region_tlab, RegionTLAB) +// Normal TLAB: +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_tlab, TLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(_tlab, TLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_WITH_ACCESS_CHECK(_tlab, TLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED(_tlab, TLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED8(_tlab, TLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED16(_tlab, TLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED32(_tlab, TLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED64(_tlab, TLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_BYTES(_tlab, TLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_CHARS(_tlab, TLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_STRING(_tlab, TLAB) // A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_rosalloc, RosAlloc). -DEFINE_FUNCTION art_quick_alloc_object_resolved_rosalloc +MACRO2(ART_QUICK_ALLOC_OBJECT_ROSALLOC, c_name, cxx_name) + DEFINE_FUNCTION VAR(c_name) // Fast path rosalloc allocation. // eax: type/return value // ecx, ebx, edx: free @@ -966,14 +986,14 @@ DEFINE_FUNCTION art_quick_alloc_object_resolved_rosalloc // stack has room movl THREAD_LOCAL_ALLOC_STACK_TOP_OFFSET(%ebx), %ecx cmpl THREAD_LOCAL_ALLOC_STACK_END_OFFSET(%ebx), %ecx - jae .Lart_quick_alloc_object_resolved_rosalloc_slow_path + jae .Lslow_path\c_name movl MIRROR_CLASS_OBJECT_SIZE_ALLOC_FAST_PATH_OFFSET(%eax), %ecx // Load the object size (ecx) // Check if the size is for a thread // local allocation. Also does the // finalizable and initialization check. cmpl LITERAL(ROSALLOC_MAX_THREAD_LOCAL_BRACKET_SIZE), %ecx - ja .Lart_quick_alloc_object_resolved_rosalloc_slow_path + ja .Lslow_path\c_name shrl LITERAL(ROSALLOC_BRACKET_QUANTUM_SIZE_SHIFT), %ecx // Calculate the rosalloc bracket index // from object size. // Load thread local rosalloc run (ebx) @@ -984,7 +1004,7 @@ DEFINE_FUNCTION art_quick_alloc_object_resolved_rosalloc // Load free_list head (edi), // this will be the return value. movl (ROSALLOC_RUN_FREE_LIST_OFFSET + ROSALLOC_RUN_FREE_LIST_HEAD_OFFSET)(%ebx), %ecx - jecxz .Lart_quick_alloc_object_resolved_rosalloc_slow_path + jecxz .Lslow_path\c_name // Point of no slow path. Won't go to // the slow path from here on. // Load the next pointer of the head @@ -1015,7 +1035,7 @@ DEFINE_FUNCTION art_quick_alloc_object_resolved_rosalloc // No fence needed for x86. movl %ecx, %eax // Move object to return register ret -.Lart_quick_alloc_object_resolved_rosalloc_slow_path: +.Lslow_path\c_name: SETUP_SAVE_REFS_ONLY_FRAME ebx, ebx // save ref containing registers for GC // Outgoing argument set up subl LITERAL(8), %esp // alignment padding @@ -1027,10 +1047,14 @@ DEFINE_FUNCTION art_quick_alloc_object_resolved_rosalloc CFI_ADJUST_CFA_OFFSET(-16) RESTORE_SAVE_REFS_ONLY_FRAME // restore frame up to return address RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER // return or deliver exception -END_FUNCTION art_quick_alloc_object_resolved_rosalloc + END_FUNCTION VAR(c_name) +END_MACRO + +ART_QUICK_ALLOC_OBJECT_ROSALLOC art_quick_alloc_object_resolved_rosalloc, artAllocObjectFromCodeResolvedRosAlloc +ART_QUICK_ALLOC_OBJECT_ROSALLOC art_quick_alloc_object_initialized_rosalloc, artAllocObjectFromCodeInitializedRosAlloc -// The common fast path code for art_quick_alloc_object_resolved_tlab -// and art_quick_alloc_object_resolved_region_tlab. +// The common fast path code for art_quick_alloc_object_resolved/initialized_tlab +// and art_quick_alloc_object_resolved/initialized_region_tlab. // // EAX: type/return_value MACRO1(ALLOC_OBJECT_RESOLVED_TLAB_FAST_PATH, slowPathLabel) @@ -1054,8 +1078,8 @@ MACRO1(ALLOC_OBJECT_RESOLVED_TLAB_FAST_PATH, slowPathLabel) ret // Fast path succeeded. END_MACRO -// The common slow path code for art_quick_alloc_object_resolved_tlab -// and art_quick_alloc_object_resolved_region_tlab. +// The common slow path code for art_quick_alloc_object_resolved/initialized_tlab +// and art_quick_alloc_object_resolved/initialized_region_tlab. MACRO1(ALLOC_OBJECT_RESOLVED_TLAB_SLOW_PATH, cxx_name) POP edi SETUP_SAVE_REFS_ONLY_FRAME ebx, ebx // save ref containing registers for GC @@ -1072,34 +1096,155 @@ MACRO1(ALLOC_OBJECT_RESOLVED_TLAB_SLOW_PATH, cxx_name) RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER // return or deliver exception END_MACRO -// A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_tlab, TLAB). May be called -// for CC if the GC is not marking. -DEFINE_FUNCTION art_quick_alloc_object_resolved_tlab +MACRO2(ART_QUICK_ALLOC_OBJECT_TLAB, c_name, cxx_name) + DEFINE_FUNCTION VAR(c_name) // Fast path tlab allocation. // EAX: type // EBX, ECX, EDX: free. PUSH edi - ALLOC_OBJECT_RESOLVED_TLAB_FAST_PATH .Lart_quick_alloc_object_resolved_tlab_slow_path -.Lart_quick_alloc_object_resolved_tlab_slow_path: - ALLOC_OBJECT_RESOLVED_TLAB_SLOW_PATH artAllocObjectFromCodeResolvedTLAB -END_FUNCTION art_quick_alloc_object_resolved_tlab - -// A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_region_tlab, RegionTLAB). -DEFINE_FUNCTION art_quick_alloc_object_resolved_region_tlab - // Fast path region tlab allocation. - // EAX: type/return value - // EBX, ECX, EDX: free. -#if !defined(USE_READ_BARRIER) - int3 + ALLOC_OBJECT_RESOLVED_TLAB_FAST_PATH .Lslow_path\c_name +.Lslow_path\c_name: + ALLOC_OBJECT_RESOLVED_TLAB_SLOW_PATH RAW_VAR(cxx_name) + END_FUNCTION VAR(c_name) +END_MACRO + +ART_QUICK_ALLOC_OBJECT_TLAB art_quick_alloc_object_resolved_tlab, artAllocObjectFromCodeResolvedTLAB +ART_QUICK_ALLOC_OBJECT_TLAB art_quick_alloc_object_initialized_tlab, artAllocObjectFromCodeInitializedTLAB +ART_QUICK_ALLOC_OBJECT_TLAB art_quick_alloc_object_resolved_region_tlab, artAllocObjectFromCodeResolvedRegionTLAB +ART_QUICK_ALLOC_OBJECT_TLAB art_quick_alloc_object_initialized_region_tlab, artAllocObjectFromCodeInitializedRegionTLAB + +// The fast path code for art_quick_alloc_array_region_tlab. +// Inputs: EAX: the class, ECX: int32_t component_count, EDX: total_size +// Free temp: EBX +// Output: EAX: return value. +MACRO1(ALLOC_ARRAY_TLAB_FAST_PATH_RESOLVED_WITH_SIZE, slowPathLabel) + mov %fs:THREAD_SELF_OFFSET, %ebx // ebx = thread + // Mask out the unaligned part to make sure we are 8 byte aligned. + andl LITERAL(OBJECT_ALIGNMENT_MASK_TOGGLED), %edx + movl THREAD_LOCAL_END_OFFSET(%ebx), %edi + subl THREAD_LOCAL_POS_OFFSET(%ebx), %edi + cmpl %edi, %edx // Check if it fits. + ja RAW_VAR(slowPathLabel) + movl THREAD_LOCAL_POS_OFFSET(%ebx), %edi + addl %edi, %edx // Add the object size. + movl %edx, 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 eax + movl %eax, MIRROR_OBJECT_CLASS_OFFSET(%edi) + movl %ecx, MIRROR_ARRAY_LENGTH_OFFSET(%edi) + movl %edi, %eax + POP edi + ret // Fast path succeeded. +END_MACRO + +MACRO1(COMPUTE_ARRAY_SIZE_UNKNOWN, slow_path) + // We should never enter here. Code is provided for reference. int3 + // Possibly a large object, go slow. + // Also does negative array size check. + cmpl LITERAL((MIN_LARGE_OBJECT_THRESHOLD - MIRROR_WIDE_ARRAY_DATA_OFFSET) / 8), %ecx + ja RAW_VAR(slow_path) + PUSH ecx + movl %ecx, %edx + movl MIRROR_CLASS_COMPONENT_TYPE_OFFSET(%eax), %ecx // Load component type. + UNPOISON_HEAP_REF ecx + movl MIRROR_CLASS_OBJECT_PRIMITIVE_TYPE_OFFSET(%ecx), %ecx // Load primitive type. + shr MACRO_LITERAL(PRIMITIVE_TYPE_SIZE_SHIFT_SHIFT), %ecx // Get component size shift. + sall %cl, %edx // Calculate array count shifted. + // Add array header + alignment rounding. + add MACRO_LITERAL(MIRROR_INT_ARRAY_DATA_OFFSET + OBJECT_ALIGNMENT_MASK), %edx + // Add 4 extra bytes if we are doing a long array. + add MACRO_LITERAL(1), %ecx + and MACRO_LITERAL(4), %ecx +#if MIRROR_WIDE_ARRAY_DATA_OFFSET != MIRROR_INT_ARRAY_DATA_OFFSET + 4 +#error Long array data offset must be 4 greater than int array data offset. #endif + addl %ecx, %edx + POP ecx +END_MACRO + +MACRO1(COMPUTE_ARRAY_SIZE_8, slow_path) + // EAX: mirror::Class* klass, ECX: int32_t component_count + // Possibly a large object, go slow. + // Also does negative array size check. + cmpl LITERAL(MIN_LARGE_OBJECT_THRESHOLD - MIRROR_INT_ARRAY_DATA_OFFSET), %ecx + ja RAW_VAR(slow_path) + // Add array header + alignment rounding. + leal (MIRROR_INT_ARRAY_DATA_OFFSET + OBJECT_ALIGNMENT_MASK)(%ecx), %edx +END_MACRO + +MACRO1(COMPUTE_ARRAY_SIZE_16, slow_path) + // EAX: mirror::Class* klass, ECX: int32_t component_count + // Possibly a large object, go slow. + // Also does negative array size check. + cmpl LITERAL((MIN_LARGE_OBJECT_THRESHOLD - MIRROR_INT_ARRAY_DATA_OFFSET) / 2), %ecx + ja RAW_VAR(slow_path) + // Add array header + alignment rounding. + leal ((MIRROR_INT_ARRAY_DATA_OFFSET + OBJECT_ALIGNMENT_MASK) / 2)(%ecx), %edx + sall MACRO_LITERAL(1), %edx +END_MACRO + +MACRO1(COMPUTE_ARRAY_SIZE_32, slow_path) + // EAX: mirror::Class* klass, ECX: int32_t component_count + // Possibly a large object, go slow. + // Also does negative array size check. + cmpl LITERAL((MIN_LARGE_OBJECT_THRESHOLD - MIRROR_INT_ARRAY_DATA_OFFSET) / 4), %ecx + ja RAW_VAR(slow_path) + // Add array header + alignment rounding. + leal ((MIRROR_INT_ARRAY_DATA_OFFSET + OBJECT_ALIGNMENT_MASK) / 4)(%ecx), %edx + sall MACRO_LITERAL(2), %edx +END_MACRO + +MACRO1(COMPUTE_ARRAY_SIZE_64, slow_path) + // EAX: mirror::Class* klass, ECX: int32_t component_count + // Possibly a large object, go slow. + // Also does negative array size check. + cmpl LITERAL((MIN_LARGE_OBJECT_THRESHOLD - MIRROR_WIDE_ARRAY_DATA_OFFSET) / 8), %ecx + ja RAW_VAR(slow_path) + // Add array header + alignment rounding. + leal ((MIRROR_WIDE_ARRAY_DATA_OFFSET + OBJECT_ALIGNMENT_MASK) / 8)(%ecx), %edx + sall MACRO_LITERAL(3), %edx +END_MACRO + +MACRO3(GENERATE_ALLOC_ARRAY_TLAB, c_entrypoint, cxx_name, size_setup) + DEFINE_FUNCTION VAR(c_entrypoint) + // EAX: mirror::Class* klass, ECX: int32_t component_count PUSH edi - ALLOC_OBJECT_RESOLVED_TLAB_FAST_PATH .Lart_quick_alloc_object_resolved_region_tlab_slow_path -.Lart_quick_alloc_object_resolved_region_tlab_slow_path: - ALLOC_OBJECT_RESOLVED_TLAB_SLOW_PATH artAllocObjectFromCodeResolvedRegionTLAB -END_FUNCTION art_quick_alloc_object_resolved_region_tlab + CALL_MACRO(size_setup) .Lslow_path\c_entrypoint + ALLOC_ARRAY_TLAB_FAST_PATH_RESOLVED_WITH_SIZE .Lslow_path\c_entrypoint +.Lslow_path\c_entrypoint: + POP edi + SETUP_SAVE_REFS_ONLY_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 // pop arguments + CFI_ADJUST_CFA_OFFSET(-16) + RESTORE_SAVE_REFS_ONLY_FRAME // restore frame up to return address + RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER // return or deliver exception + END_FUNCTION VAR(c_entrypoint) +END_MACRO +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved_region_tlab, artAllocArrayFromCodeResolvedRegionTLAB, COMPUTE_ARRAY_SIZE_UNKNOWN +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved8_region_tlab, artAllocArrayFromCodeResolvedRegionTLAB, COMPUTE_ARRAY_SIZE_8 +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved16_region_tlab, artAllocArrayFromCodeResolvedRegionTLAB, COMPUTE_ARRAY_SIZE_16 +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved32_region_tlab, artAllocArrayFromCodeResolvedRegionTLAB, COMPUTE_ARRAY_SIZE_32 +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved64_region_tlab, artAllocArrayFromCodeResolvedRegionTLAB, COMPUTE_ARRAY_SIZE_64 + +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved_tlab, artAllocArrayFromCodeResolvedTLAB, COMPUTE_ARRAY_SIZE_UNKNOWN +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved8_tlab, artAllocArrayFromCodeResolvedTLAB, COMPUTE_ARRAY_SIZE_8 +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved16_tlab, artAllocArrayFromCodeResolvedTLAB, COMPUTE_ARRAY_SIZE_16 +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved32_tlab, artAllocArrayFromCodeResolvedTLAB, COMPUTE_ARRAY_SIZE_32 +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved64_tlab, artAllocArrayFromCodeResolvedTLAB, COMPUTE_ARRAY_SIZE_64 + DEFINE_FUNCTION art_quick_resolve_string SETUP_SAVE_EVERYTHING_FRAME ebx, ebx // Outgoing argument set up @@ -1556,78 +1701,54 @@ DEFINE_FUNCTION art_quick_lushr ret END_FUNCTION art_quick_lushr -ONE_ARG_REF_DOWNCALL art_quick_get_boolean_static, artGetBooleanStaticFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION -ONE_ARG_REF_DOWNCALL art_quick_get_byte_static, artGetByteStaticFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION -ONE_ARG_REF_DOWNCALL art_quick_get_char_static, artGetCharStaticFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION -ONE_ARG_REF_DOWNCALL art_quick_get_short_static, artGetShortStaticFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION -ONE_ARG_REF_DOWNCALL art_quick_get32_static, artGet32StaticFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION -ONE_ARG_REF_DOWNCALL art_quick_get64_static, artGet64StaticFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION -ONE_ARG_REF_DOWNCALL art_quick_get_obj_static, artGetObjStaticFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION - -TWO_ARG_REF_DOWNCALL art_quick_get_boolean_instance, artGetBooleanInstanceFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION -TWO_ARG_REF_DOWNCALL art_quick_get_byte_instance, artGetByteInstanceFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION -TWO_ARG_REF_DOWNCALL art_quick_get_char_instance, artGetCharInstanceFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION -TWO_ARG_REF_DOWNCALL art_quick_get_short_instance, artGetShortInstanceFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION -TWO_ARG_REF_DOWNCALL art_quick_get32_instance, artGet32InstanceFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION -TWO_ARG_REF_DOWNCALL art_quick_get64_instance, artGet64InstanceFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION -TWO_ARG_REF_DOWNCALL art_quick_get_obj_instance, artGetObjInstanceFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION - -TWO_ARG_REF_DOWNCALL art_quick_set8_static, artSet8StaticFromCode, RETURN_IF_EAX_ZERO -TWO_ARG_REF_DOWNCALL art_quick_set16_static, artSet16StaticFromCode, RETURN_IF_EAX_ZERO -TWO_ARG_REF_DOWNCALL art_quick_set32_static, artSet32StaticFromCode, RETURN_IF_EAX_ZERO -TWO_ARG_REF_DOWNCALL art_quick_set_obj_static, artSetObjStaticFromCode, RETURN_IF_EAX_ZERO - -THREE_ARG_REF_DOWNCALL art_quick_set8_instance, artSet8InstanceFromCode, RETURN_IF_EAX_ZERO -THREE_ARG_REF_DOWNCALL art_quick_set16_instance, artSet16InstanceFromCode, RETURN_IF_EAX_ZERO -THREE_ARG_REF_DOWNCALL art_quick_set32_instance, artSet32InstanceFromCode, RETURN_IF_EAX_ZERO -THREE_ARG_REF_DOWNCALL art_quick_set_obj_instance, artSetObjInstanceFromCode, RETURN_IF_EAX_ZERO - -// Call artSet64InstanceFromCode with 4 word size arguments and the referrer. +ONE_ARG_REF_DOWNCALL art_quick_get_boolean_static, artGetBooleanStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION +ONE_ARG_REF_DOWNCALL art_quick_get_byte_static, artGetByteStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION +ONE_ARG_REF_DOWNCALL art_quick_get_char_static, artGetCharStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION +ONE_ARG_REF_DOWNCALL art_quick_get_short_static, artGetShortStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION +ONE_ARG_REF_DOWNCALL art_quick_get32_static, artGet32StaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION +ONE_ARG_REF_DOWNCALL art_quick_get64_static, artGet64StaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION +ONE_ARG_REF_DOWNCALL art_quick_get_obj_static, artGetObjStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION + +TWO_ARG_REF_DOWNCALL art_quick_get_boolean_instance, artGetBooleanInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION +TWO_ARG_REF_DOWNCALL art_quick_get_byte_instance, artGetByteInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION +TWO_ARG_REF_DOWNCALL art_quick_get_char_instance, artGetCharInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION +TWO_ARG_REF_DOWNCALL art_quick_get_short_instance, artGetShortInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION +TWO_ARG_REF_DOWNCALL art_quick_get32_instance, artGet32InstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION +TWO_ARG_REF_DOWNCALL art_quick_get64_instance, artGet64InstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION +TWO_ARG_REF_DOWNCALL art_quick_get_obj_instance, artGetObjInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION + +TWO_ARG_REF_DOWNCALL art_quick_set8_static, artSet8StaticFromCompiledCode, RETURN_IF_EAX_ZERO +TWO_ARG_REF_DOWNCALL art_quick_set16_static, artSet16StaticFromCompiledCode, RETURN_IF_EAX_ZERO +TWO_ARG_REF_DOWNCALL art_quick_set32_static, artSet32StaticFromCompiledCode, RETURN_IF_EAX_ZERO +TWO_ARG_REF_DOWNCALL art_quick_set_obj_static, artSetObjStaticFromCompiledCode, RETURN_IF_EAX_ZERO + +THREE_ARG_REF_DOWNCALL art_quick_set64_static, artSet64StaticFromCompiledCode, RETURN_IF_EAX_ZERO +THREE_ARG_REF_DOWNCALL art_quick_set8_instance, artSet8InstanceFromCompiledCode, RETURN_IF_EAX_ZERO +THREE_ARG_REF_DOWNCALL art_quick_set16_instance, artSet16InstanceFromCompiledCode, RETURN_IF_EAX_ZERO +THREE_ARG_REF_DOWNCALL art_quick_set32_instance, artSet32InstanceFromCompiledCode, RETURN_IF_EAX_ZERO +THREE_ARG_REF_DOWNCALL art_quick_set_obj_instance, artSetObjInstanceFromCompiledCode, RETURN_IF_EAX_ZERO + +// Call artSet64InstanceFromCode with 4 word size arguments. DEFINE_FUNCTION art_quick_set64_instance movd %ebx, %xmm0 SETUP_SAVE_REFS_ONLY_FRAME ebx, ebx // save ref containing registers for GC movd %xmm0, %ebx // Outgoing argument set up - subl LITERAL(8), %esp // alignment padding - CFI_ADJUST_CFA_OFFSET(8) + subl LITERAL(12), %esp // alignment padding + CFI_ADJUST_CFA_OFFSET(12) pushl %fs:THREAD_SELF_OFFSET // pass Thread::Current() CFI_ADJUST_CFA_OFFSET(4) - pushl (FRAME_SIZE_SAVE_REFS_ONLY+12)(%esp) // pass referrer - CFI_ADJUST_CFA_OFFSET(4) PUSH ebx // pass high half of new_val PUSH edx // pass low half of new_val PUSH ecx // pass object PUSH eax // pass field_idx - call SYMBOL(artSet64InstanceFromCode) // (field_idx, Object*, new_val, referrer, Thread*) + call SYMBOL(artSet64InstanceFromCompiledCode) // (field_idx, Object*, new_val, Thread*) addl LITERAL(32), %esp // pop arguments CFI_ADJUST_CFA_OFFSET(-32) RESTORE_SAVE_REFS_ONLY_FRAME // restore frame up to return address RETURN_IF_EAX_ZERO // return or deliver exception END_FUNCTION art_quick_set64_instance -// Call artSet64StaticFromCode with 3 word size arguments plus with the referrer in the 2nd position -// so that new_val is aligned on even registers were we passing arguments in registers. -DEFINE_FUNCTION art_quick_set64_static - // TODO: Implement SETUP_GOT_NOSAVE for got_reg = ecx to avoid moving around the registers. - movd %ebx, %xmm0 - SETUP_SAVE_REFS_ONLY_FRAME ebx, ebx // save ref containing registers for GC - movd %xmm0, %ebx - mov FRAME_SIZE_SAVE_REFS_ONLY(%esp), %ecx // get referrer - subl LITERAL(12), %esp // alignment padding - CFI_ADJUST_CFA_OFFSET(12) - pushl %fs:THREAD_SELF_OFFSET // pass Thread::Current() - CFI_ADJUST_CFA_OFFSET(4) - PUSH ebx // pass high half of new_val - PUSH edx // pass low half of new_val - PUSH ecx // pass referrer - PUSH eax // pass field_idx - call SYMBOL(artSet64StaticFromCode) // (field_idx, referrer, new_val, Thread*) - addl LITERAL(32), %esp // pop arguments - CFI_ADJUST_CFA_OFFSET(-32) - RESTORE_SAVE_REFS_ONLY_FRAME // restore frame up to return address - RETURN_IF_EAX_ZERO // return or deliver exception -END_FUNCTION art_quick_set64_static - DEFINE_FUNCTION art_quick_proxy_invoke_handler SETUP_SAVE_REFS_AND_ARGS_FRAME_WITH_METHOD_IN_EAX PUSH esp // pass SP diff --git a/runtime/arch/x86_64/quick_entrypoints_x86_64.S b/runtime/arch/x86_64/quick_entrypoints_x86_64.S index 46bee399f4..a1ae858735 100644 --- a/runtime/arch/x86_64/quick_entrypoints_x86_64.S +++ b/runtime/arch/x86_64/quick_entrypoints_x86_64.S @@ -919,11 +919,10 @@ END_MACRO MACRO3(ONE_ARG_REF_DOWNCALL, c_name, cxx_name, return_macro) DEFINE_FUNCTION VAR(c_name) - movq 8(%rsp), %rsi // pass referrer SETUP_SAVE_REFS_ONLY_FRAME // arg0 is in rdi - movq %gs:THREAD_SELF_OFFSET, %rdx // pass Thread::Current() - call CALLVAR(cxx_name) // cxx_name(arg0, referrer, Thread*) + movq %gs:THREAD_SELF_OFFSET, %rsi // pass Thread::Current() + call CALLVAR(cxx_name) // cxx_name(arg0, Thread*) RESTORE_SAVE_REFS_ONLY_FRAME // restore frame up to return address CALL_MACRO(return_macro) END_FUNCTION VAR(c_name) @@ -931,11 +930,10 @@ END_MACRO MACRO3(TWO_ARG_REF_DOWNCALL, c_name, cxx_name, return_macro) DEFINE_FUNCTION VAR(c_name) - movq 8(%rsp), %rdx // pass referrer SETUP_SAVE_REFS_ONLY_FRAME // arg0 and arg1 are in rdi/rsi - movq %gs:THREAD_SELF_OFFSET, %rcx // pass Thread::Current() - call CALLVAR(cxx_name) // (arg0, arg1, referrer, Thread*) + movq %gs:THREAD_SELF_OFFSET, %rdx // pass Thread::Current() + call CALLVAR(cxx_name) // (arg0, arg1, Thread*) RESTORE_SAVE_REFS_ONLY_FRAME // restore frame up to return address CALL_MACRO(return_macro) END_FUNCTION VAR(c_name) @@ -943,11 +941,10 @@ END_MACRO MACRO3(THREE_ARG_REF_DOWNCALL, c_name, cxx_name, return_macro) DEFINE_FUNCTION VAR(c_name) - movq 8(%rsp), %rcx // pass referrer SETUP_SAVE_REFS_ONLY_FRAME // arg0, arg1, and arg2 are in rdi/rsi/rdx - movq %gs:THREAD_SELF_OFFSET, %r8 // pass Thread::Current() - call CALLVAR(cxx_name) // cxx_name(arg0, arg1, arg2, referrer, Thread*) + movq %gs:THREAD_SELF_OFFSET, %rcx // pass Thread::Current() + call CALLVAR(cxx_name) // cxx_name(arg0, arg1, arg2, Thread*) RESTORE_SAVE_REFS_ONLY_FRAME // restore frame up to return address CALL_MACRO(return_macro) // return or deliver exception END_FUNCTION VAR(c_name) @@ -987,6 +984,10 @@ GENERATE_ALLOC_ENTRYPOINTS_FOR_NON_TLAB_ALLOCATORS // GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(_region_tlab, RegionTLAB) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_WITH_ACCESS_CHECK(_region_tlab, RegionTLAB) // GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED(_region_tlab, RegionTLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED8(_region_tlab, RegionTLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED16(_region_tlab, RegionTLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED32(_region_tlab, RegionTLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED64(_region_tlab, RegionTLAB) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_BYTES(_region_tlab, RegionTLAB) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_CHARS(_region_tlab, RegionTLAB) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_STRING(_region_tlab, RegionTLAB) @@ -995,13 +996,18 @@ GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_STRING(_region_tlab, RegionTLAB) // GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(_tlab, TLAB) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_WITH_ACCESS_CHECK(_tlab, TLAB) // GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED(_tlab, TLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED8(_tlab, TLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED16(_tlab, TLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED32(_tlab, TLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED64(_tlab, TLAB) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_BYTES(_tlab, TLAB) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_CHARS(_tlab, TLAB) GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_STRING(_tlab, TLAB) // A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_rosalloc, RosAlloc). -DEFINE_FUNCTION art_quick_alloc_object_resolved_rosalloc +MACRO2(ART_QUICK_ALLOC_OBJECT_ROSALLOC, c_name, cxx_name) + DEFINE_FUNCTION VAR(c_name) // Fast path rosalloc allocation. // RDI: mirror::Class*, RAX: return value // RSI, RDX, RCX, R8, R9: free. @@ -1010,14 +1016,14 @@ DEFINE_FUNCTION art_quick_alloc_object_resolved_rosalloc movq %gs:THREAD_SELF_OFFSET, %r8 // r8 = thread movq THREAD_LOCAL_ALLOC_STACK_TOP_OFFSET(%r8), %rcx // rcx = alloc stack top. cmpq THREAD_LOCAL_ALLOC_STACK_END_OFFSET(%r8), %rcx - jae .Lart_quick_alloc_object_resolved_rosalloc_slow_path + jae .Lslow_path\c_name // Load the object size movl MIRROR_CLASS_OBJECT_SIZE_ALLOC_FAST_PATH_OFFSET(%rdi), %eax // Check if the size is for a thread // local allocation. Also does the // initialized and finalizable checks. cmpl LITERAL(ROSALLOC_MAX_THREAD_LOCAL_BRACKET_SIZE), %eax - ja .Lart_quick_alloc_object_resolved_rosalloc_slow_path + ja .Lslow_path\c_name // Compute the rosalloc bracket index // from the size. shrq LITERAL(ROSALLOC_BRACKET_QUANTUM_SIZE_SHIFT), %rax @@ -1031,7 +1037,7 @@ DEFINE_FUNCTION art_quick_alloc_object_resolved_rosalloc // will be the return val. movq (ROSALLOC_RUN_FREE_LIST_OFFSET + ROSALLOC_RUN_FREE_LIST_HEAD_OFFSET)(%r9), %rax testq %rax, %rax - jz .Lart_quick_alloc_object_resolved_rosalloc_slow_path + jz .Lslow_path\c_name // "Point of no slow path". Won't go to the slow path from here on. OK to clobber rdi and rsi. // Push the new object onto the thread // local allocation stack and @@ -1058,25 +1064,19 @@ DEFINE_FUNCTION art_quick_alloc_object_resolved_rosalloc decl (ROSALLOC_RUN_FREE_LIST_OFFSET + ROSALLOC_RUN_FREE_LIST_SIZE_OFFSET)(%r9) // No fence necessary for x86. ret -.Lart_quick_alloc_object_resolved_rosalloc_slow_path: +.Lslow_path\c_name: SETUP_SAVE_REFS_ONLY_FRAME // save ref containing registers for GC // Outgoing argument set up movq %gs:THREAD_SELF_OFFSET, %rsi // pass Thread::Current() - call SYMBOL(artAllocObjectFromCodeResolvedRosAlloc) // cxx_name(arg0, Thread*) + call CALLVAR(cxx_name) // cxx_name(arg0, Thread*) RESTORE_SAVE_REFS_ONLY_FRAME // restore frame up to return address RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER // return or deliver exception -END_FUNCTION art_quick_alloc_object_rosalloc - -// 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 RAW_VAR(slowPathLabel) - ALLOC_OBJECT_RESOLVED_TLAB_FAST_PATH(RAW_VAR(slowPathLabel)) + END_FUNCTION VAR(c_name) END_MACRO +ART_QUICK_ALLOC_OBJECT_ROSALLOC art_quick_alloc_object_resolved_rosalloc, artAllocObjectFromCodeResolvedRosAlloc +ART_QUICK_ALLOC_OBJECT_ROSALLOC art_quick_alloc_object_initialized_rosalloc, artAllocObjectFromCodeInitializedRosAlloc + // The common fast path code for art_quick_alloc_object_resolved_region_tlab. // TODO: delete ALLOC_OBJECT_RESOLVED_TLAB_FAST_PATH since it is the same as // ALLOC_OBJECT_INITIALIZED_TLAB_FAST_PATH. @@ -1112,26 +1112,11 @@ MACRO1(ALLOC_OBJECT_INITIALIZED_TLAB_FAST_PATH, slowPathLabel) END_MACRO // The fast path code for art_quick_alloc_array_region_tlab. -// Inputs: RDI: the class, RSI: int32_t component_count -// Free temps: RCX, RDX, R8, R9 +// Inputs: RDI: the class, RSI: int32_t component_count, R9: total_size +// Free temps: RCX, RDX, R8 // Output: RAX: return value. -MACRO1(ALLOC_ARRAY_TLAB_FAST_PATH_RESOLVED, slowPathLabel) - movl MIRROR_CLASS_COMPONENT_TYPE_OFFSET(%rdi), %ecx // Load component type. - UNPOISON_HEAP_REF ecx - movl MIRROR_CLASS_OBJECT_PRIMITIVE_TYPE_OFFSET(%rcx), %ecx // Load primitive type. - shrq LITERAL(PRIMITIVE_TYPE_SIZE_SHIFT_SHIFT), %rcx // Get component size shift. - movq %rsi, %r9 - salq %cl, %r9 // Calculate array count shifted. - // Add array header + alignment rounding. - addq LITERAL(MIRROR_INT_ARRAY_DATA_OFFSET + OBJECT_ALIGNMENT_MASK), %r9 - // Add 4 extra bytes if we are doing a long array. - addq LITERAL(1), %rcx - andq LITERAL(4), %rcx - addq %rcx, %r9 +MACRO1(ALLOC_ARRAY_TLAB_FAST_PATH_RESOLVED_WITH_SIZE, slowPathLabel) movq %gs:THREAD_SELF_OFFSET, %rcx // rcx = thread -#if MIRROR_LONG_ARRAY_DATA_OFFSET != MIRROR_INT_ARRAY_DATA_OFFSET + 4 -#error Long array data offset must be 4 greater than int array data offset. -#endif // Mask out the unaligned part to make sure we are 8 byte aligned. andq LITERAL(OBJECT_ALIGNMENT_MASK_TOGGLED64), %r9 movq THREAD_LOCAL_POS_OFFSET(%rcx), %rax @@ -1149,7 +1134,6 @@ MACRO1(ALLOC_ARRAY_TLAB_FAST_PATH_RESOLVED, slowPathLabel) ret // Fast path succeeded. END_MACRO - // The common slow path code for art_quick_alloc_object_{resolved, initialized}_tlab // and art_quick_alloc_object_{resolved, initialized}_region_tlab. MACRO1(ALLOC_OBJECT_TLAB_SLOW_PATH, cxx_name) @@ -1161,16 +1145,6 @@ MACRO1(ALLOC_OBJECT_TLAB_SLOW_PATH, cxx_name) RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER // return or deliver exception END_MACRO -// The slow path code for art_quick_alloc_array_region_tlab. -MACRO1(ALLOC_ARRAY_TLAB_SLOW_PATH, cxx_name) - SETUP_SAVE_REFS_ONLY_FRAME // save ref containing registers for GC - // Outgoing argument set up - movq %gs:THREAD_SELF_OFFSET, %rdx // pass Thread::Current() - call CALLVAR(cxx_name) // cxx_name(arg0, arg1, Thread*) - RESTORE_SAVE_REFS_ONLY_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_RESOLVED(_tlab, TLAB). May be // called with CC if the GC is not active. DEFINE_FUNCTION art_quick_alloc_object_resolved_tlab @@ -1191,25 +1165,87 @@ DEFINE_FUNCTION art_quick_alloc_object_initialized_tlab ALLOC_OBJECT_TLAB_SLOW_PATH artAllocObjectFromCodeInitializedTLAB END_FUNCTION art_quick_alloc_object_initialized_tlab -// A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED(_tlab, TLAB). -DEFINE_FUNCTION art_quick_alloc_array_resolved_tlab +MACRO0(COMPUTE_ARRAY_SIZE_UNKNOWN) + movl MIRROR_CLASS_COMPONENT_TYPE_OFFSET(%rdi), %ecx // Load component type. + UNPOISON_HEAP_REF ecx + movl MIRROR_CLASS_OBJECT_PRIMITIVE_TYPE_OFFSET(%rcx), %ecx // Load primitive type. + shrq MACRO_LITERAL(PRIMITIVE_TYPE_SIZE_SHIFT_SHIFT), %rcx // Get component size shift. + movq %rsi, %r9 + salq %cl, %r9 // Calculate array count shifted. + // Add array header + alignment rounding. + addq MACRO_LITERAL(MIRROR_INT_ARRAY_DATA_OFFSET + OBJECT_ALIGNMENT_MASK), %r9 + // Add 4 extra bytes if we are doing a long array. + addq MACRO_LITERAL(1), %rcx + andq MACRO_LITERAL(4), %rcx +#if MIRROR_LONG_ARRAY_DATA_OFFSET != MIRROR_INT_ARRAY_DATA_OFFSET + 4 +#error Long array data offset must be 4 greater than int array data offset. +#endif + addq %rcx, %r9 +END_MACRO + +MACRO0(COMPUTE_ARRAY_SIZE_8) // RDI: mirror::Class* klass, RSI: int32_t component_count // RDX, RCX, R8, R9: free. RAX: return val. - ALLOC_ARRAY_TLAB_FAST_PATH_RESOLVED .Lart_quick_alloc_array_resolved_tlab_slow_path -.Lart_quick_alloc_array_resolved_tlab_slow_path: - ALLOC_ARRAY_TLAB_SLOW_PATH artAllocArrayFromCodeResolvedTLAB -END_FUNCTION art_quick_alloc_array_resolved_tlab + movq %rsi, %r9 + // Add array header + alignment rounding. + addq MACRO_LITERAL(MIRROR_INT_ARRAY_DATA_OFFSET + OBJECT_ALIGNMENT_MASK), %r9 +END_MACRO -// A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED(_region_tlab, RegionTLAB). -DEFINE_FUNCTION art_quick_alloc_array_resolved_region_tlab - // Fast path region tlab allocation. +MACRO0(COMPUTE_ARRAY_SIZE_16) // RDI: mirror::Class* klass, RSI: int32_t component_count - // RCX, RDX, R8, R9: free. RAX: return val. - ASSERT_USE_READ_BARRIER - ALLOC_ARRAY_TLAB_FAST_PATH_RESOLVED .Lart_quick_alloc_array_resolved_region_tlab_slow_path -.Lart_quick_alloc_array_resolved_region_tlab_slow_path: - ALLOC_ARRAY_TLAB_SLOW_PATH artAllocArrayFromCodeResolvedRegionTLAB -END_FUNCTION art_quick_alloc_array_resolved_region_tlab + // RDX, RCX, R8, R9: free. RAX: return val. + movq %rsi, %r9 + salq MACRO_LITERAL(1), %r9 + // Add array header + alignment rounding. + addq MACRO_LITERAL(MIRROR_INT_ARRAY_DATA_OFFSET + OBJECT_ALIGNMENT_MASK), %r9 +END_MACRO + +MACRO0(COMPUTE_ARRAY_SIZE_32) + // RDI: mirror::Class* klass, RSI: int32_t component_count + // RDX, RCX, R8, R9: free. RAX: return val. + movq %rsi, %r9 + salq MACRO_LITERAL(2), %r9 + // Add array header + alignment rounding. + addq MACRO_LITERAL(MIRROR_INT_ARRAY_DATA_OFFSET + OBJECT_ALIGNMENT_MASK), %r9 +END_MACRO + +MACRO0(COMPUTE_ARRAY_SIZE_64) + // RDI: mirror::Class* klass, RSI: int32_t component_count + // RDX, RCX, R8, R9: free. RAX: return val. + movq %rsi, %r9 + salq MACRO_LITERAL(3), %r9 + // Add array header + alignment rounding. + addq MACRO_LITERAL(MIRROR_WIDE_ARRAY_DATA_OFFSET + OBJECT_ALIGNMENT_MASK), %r9 +END_MACRO + +MACRO3(GENERATE_ALLOC_ARRAY_TLAB, c_entrypoint, cxx_name, size_setup) + DEFINE_FUNCTION VAR(c_entrypoint) + // RDI: mirror::Class* klass, RSI: int32_t component_count + // RDX, RCX, R8, R9: free. RAX: return val. + CALL_MACRO(size_setup) + ALLOC_ARRAY_TLAB_FAST_PATH_RESOLVED_WITH_SIZE .Lslow_path\c_entrypoint +.Lslow_path\c_entrypoint: + SETUP_SAVE_REFS_ONLY_FRAME // save ref containing registers for GC + // Outgoing argument set up + movq %gs:THREAD_SELF_OFFSET, %rdx // pass Thread::Current() + call CALLVAR(cxx_name) // cxx_name(arg0, arg1, Thread*) + RESTORE_SAVE_REFS_ONLY_FRAME // restore frame up to return address + RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER // return or deliver exception + END_FUNCTION VAR(c_entrypoint) +END_MACRO + + +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved_region_tlab, artAllocArrayFromCodeResolvedRegionTLAB, COMPUTE_ARRAY_SIZE_UNKNOWN +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved8_region_tlab, artAllocArrayFromCodeResolvedRegionTLAB, COMPUTE_ARRAY_SIZE_8 +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved16_region_tlab, artAllocArrayFromCodeResolvedRegionTLAB, COMPUTE_ARRAY_SIZE_16 +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved32_region_tlab, artAllocArrayFromCodeResolvedRegionTLAB, COMPUTE_ARRAY_SIZE_32 +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved64_region_tlab, artAllocArrayFromCodeResolvedRegionTLAB, COMPUTE_ARRAY_SIZE_64 + +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved_tlab, artAllocArrayFromCodeResolvedTLAB, COMPUTE_ARRAY_SIZE_UNKNOWN +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved8_tlab, artAllocArrayFromCodeResolvedTLAB, COMPUTE_ARRAY_SIZE_8 +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved16_tlab, artAllocArrayFromCodeResolvedTLAB, COMPUTE_ARRAY_SIZE_16 +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved32_tlab, artAllocArrayFromCodeResolvedTLAB, COMPUTE_ARRAY_SIZE_32 +GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved64_tlab, artAllocArrayFromCodeResolvedTLAB, COMPUTE_ARRAY_SIZE_64 // A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_region_tlab, RegionTLAB). DEFINE_FUNCTION art_quick_alloc_object_resolved_region_tlab @@ -1239,7 +1275,7 @@ DEFINE_FUNCTION art_quick_resolve_string // Outgoing argument set up movl %eax, %edi // pass string index movq %gs:THREAD_SELF_OFFSET, %rsi // pass Thread::Current() - call SYMBOL(artResolveStringFromCode) // artResolveStringFromCode(arg0, referrer, Thread*) + call SYMBOL(artResolveStringFromCode) // artResolveStringFromCode(arg0, Thread*) testl %eax, %eax // If result is null, deliver the OOME. jz 1f @@ -1551,45 +1587,33 @@ UNIMPLEMENTED art_quick_lshl UNIMPLEMENTED art_quick_lshr UNIMPLEMENTED art_quick_lushr -THREE_ARG_REF_DOWNCALL art_quick_set8_instance, artSet8InstanceFromCode, RETURN_IF_EAX_ZERO -THREE_ARG_REF_DOWNCALL art_quick_set16_instance, artSet16InstanceFromCode, RETURN_IF_EAX_ZERO -THREE_ARG_REF_DOWNCALL art_quick_set32_instance, artSet32InstanceFromCode, RETURN_IF_EAX_ZERO -THREE_ARG_REF_DOWNCALL art_quick_set64_instance, artSet64InstanceFromCode, RETURN_IF_EAX_ZERO -THREE_ARG_REF_DOWNCALL art_quick_set_obj_instance, artSetObjInstanceFromCode, RETURN_IF_EAX_ZERO - -TWO_ARG_REF_DOWNCALL art_quick_get_byte_instance, artGetByteInstanceFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION -TWO_ARG_REF_DOWNCALL art_quick_get_boolean_instance, artGetBooleanInstanceFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION -TWO_ARG_REF_DOWNCALL art_quick_get_short_instance, artGetShortInstanceFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION -TWO_ARG_REF_DOWNCALL art_quick_get_char_instance, artGetCharInstanceFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION -TWO_ARG_REF_DOWNCALL art_quick_get32_instance, artGet32InstanceFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION -TWO_ARG_REF_DOWNCALL art_quick_get64_instance, artGet64InstanceFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION -TWO_ARG_REF_DOWNCALL art_quick_get_obj_instance, artGetObjInstanceFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION - -TWO_ARG_REF_DOWNCALL art_quick_set8_static, artSet8StaticFromCode, RETURN_IF_EAX_ZERO -TWO_ARG_REF_DOWNCALL art_quick_set16_static, artSet16StaticFromCode, RETURN_IF_EAX_ZERO -TWO_ARG_REF_DOWNCALL art_quick_set32_static, artSet32StaticFromCode, RETURN_IF_EAX_ZERO -TWO_ARG_REF_DOWNCALL art_quick_set_obj_static, artSetObjStaticFromCode, RETURN_IF_EAX_ZERO - -ONE_ARG_REF_DOWNCALL art_quick_get_byte_static, artGetByteStaticFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION -ONE_ARG_REF_DOWNCALL art_quick_get_boolean_static, artGetBooleanStaticFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION -ONE_ARG_REF_DOWNCALL art_quick_get_short_static, artGetShortStaticFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION -ONE_ARG_REF_DOWNCALL art_quick_get_char_static, artGetCharStaticFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION -ONE_ARG_REF_DOWNCALL art_quick_get32_static, artGet32StaticFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION -ONE_ARG_REF_DOWNCALL art_quick_get64_static, artGet64StaticFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION -ONE_ARG_REF_DOWNCALL art_quick_get_obj_static, artGetObjStaticFromCode, RETURN_OR_DELIVER_PENDING_EXCEPTION - -// This is singled out as the argument order is different. -DEFINE_FUNCTION art_quick_set64_static - // new_val is already in %rdx - movq 8(%rsp), %rsi // pass referrer - SETUP_SAVE_REFS_ONLY_FRAME - // field_idx is in rdi - movq %gs:THREAD_SELF_OFFSET, %rcx // pass Thread::Current() - call SYMBOL(artSet64StaticFromCode) // (field_idx, referrer, new_val, Thread*) - RESTORE_SAVE_REFS_ONLY_FRAME // restore frame up to return address - RETURN_IF_EAX_ZERO // return or deliver exception -END_FUNCTION art_quick_set64_static - +THREE_ARG_REF_DOWNCALL art_quick_set8_instance, artSet8InstanceFromCompiledCode, RETURN_IF_EAX_ZERO +THREE_ARG_REF_DOWNCALL art_quick_set16_instance, artSet16InstanceFromCompiledCode, RETURN_IF_EAX_ZERO +THREE_ARG_REF_DOWNCALL art_quick_set32_instance, artSet32InstanceFromCompiledCode, RETURN_IF_EAX_ZERO +THREE_ARG_REF_DOWNCALL art_quick_set64_instance, artSet64InstanceFromCompiledCode, RETURN_IF_EAX_ZERO +THREE_ARG_REF_DOWNCALL art_quick_set_obj_instance, artSetObjInstanceFromCompiledCode, RETURN_IF_EAX_ZERO + +TWO_ARG_REF_DOWNCALL art_quick_get_byte_instance, artGetByteInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION +TWO_ARG_REF_DOWNCALL art_quick_get_boolean_instance, artGetBooleanInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION +TWO_ARG_REF_DOWNCALL art_quick_get_short_instance, artGetShortInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION +TWO_ARG_REF_DOWNCALL art_quick_get_char_instance, artGetCharInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION +TWO_ARG_REF_DOWNCALL art_quick_get32_instance, artGet32InstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION +TWO_ARG_REF_DOWNCALL art_quick_get64_instance, artGet64InstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION +TWO_ARG_REF_DOWNCALL art_quick_get_obj_instance, artGetObjInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION + +TWO_ARG_REF_DOWNCALL art_quick_set8_static, artSet8StaticFromCompiledCode, RETURN_IF_EAX_ZERO +TWO_ARG_REF_DOWNCALL art_quick_set16_static, artSet16StaticFromCompiledCode, RETURN_IF_EAX_ZERO +TWO_ARG_REF_DOWNCALL art_quick_set32_static, artSet32StaticFromCompiledCode, RETURN_IF_EAX_ZERO +TWO_ARG_REF_DOWNCALL art_quick_set64_static, artSet64StaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION +TWO_ARG_REF_DOWNCALL art_quick_set_obj_static, artSetObjStaticFromCompiledCode, RETURN_IF_EAX_ZERO + +ONE_ARG_REF_DOWNCALL art_quick_get_byte_static, artGetByteStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION +ONE_ARG_REF_DOWNCALL art_quick_get_boolean_static, artGetBooleanStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION +ONE_ARG_REF_DOWNCALL art_quick_get_short_static, artGetShortStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION +ONE_ARG_REF_DOWNCALL art_quick_get_char_static, artGetCharStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION +ONE_ARG_REF_DOWNCALL art_quick_get32_static, artGet32StaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION +ONE_ARG_REF_DOWNCALL art_quick_get64_static, artGet64StaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION +ONE_ARG_REF_DOWNCALL art_quick_get_obj_static, artGetObjStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION DEFINE_FUNCTION art_quick_proxy_invoke_handler SETUP_SAVE_REFS_AND_ARGS_FRAME_WITH_METHOD_IN_RDI diff --git a/runtime/art_field-inl.h b/runtime/art_field-inl.h index b9f688de57..80af8e7bde 100644 --- a/runtime/art_field-inl.h +++ b/runtime/art_field-inl.h @@ -52,7 +52,7 @@ inline uint32_t ArtField::GetAccessFlags() { } inline MemberOffset ArtField::GetOffset() { - DCHECK(GetDeclaringClass()->IsResolved() || GetDeclaringClass()->IsErroneous()); + DCHECK(GetDeclaringClass()->IsResolved()); return MemberOffset(offset_); } @@ -132,7 +132,6 @@ inline void ArtField::SetObj(ObjPtr<mirror::Object> object, ObjPtr<mirror::Objec return (object)->GetField ## type(GetOffset()); #define FIELD_SET(object, type, value) \ - DCHECK_EQ(Primitive::kPrim ## type, GetTypeAsPrimitiveType()) << PrettyField(); \ DCHECK((object) != nullptr) << PrettyField(); \ DCHECK(!IsStatic() || ((object) == GetDeclaringClass()) || !Runtime::Current()->IsStarted()); \ if (UNLIKELY(IsVolatile())) { \ @@ -147,6 +146,12 @@ inline uint8_t ArtField::GetBoolean(ObjPtr<mirror::Object> object) { template<bool kTransactionActive> inline void ArtField::SetBoolean(ObjPtr<mirror::Object> object, uint8_t z) { + if (kIsDebugBuild) { + // For simplicity, this method is being called by the compiler entrypoint for + // both boolean and byte fields. + Primitive::Type type = GetTypeAsPrimitiveType(); + DCHECK(type == Primitive::kPrimBoolean || type == Primitive::kPrimByte) << PrettyField(); + } FIELD_SET(object, Boolean, z); } @@ -156,6 +161,7 @@ inline int8_t ArtField::GetByte(ObjPtr<mirror::Object> object) { template<bool kTransactionActive> inline void ArtField::SetByte(ObjPtr<mirror::Object> object, int8_t b) { + DCHECK_EQ(Primitive::kPrimByte, GetTypeAsPrimitiveType()) << PrettyField(); FIELD_SET(object, Byte, b); } @@ -165,6 +171,12 @@ inline uint16_t ArtField::GetChar(ObjPtr<mirror::Object> object) { template<bool kTransactionActive> inline void ArtField::SetChar(ObjPtr<mirror::Object> object, uint16_t c) { + if (kIsDebugBuild) { + // For simplicity, this method is being called by the compiler entrypoint for + // both char and short fields. + Primitive::Type type = GetTypeAsPrimitiveType(); + DCHECK(type == Primitive::kPrimChar || type == Primitive::kPrimShort) << PrettyField(); + } FIELD_SET(object, Char, c); } @@ -174,6 +186,7 @@ inline int16_t ArtField::GetShort(ObjPtr<mirror::Object> object) { template<bool kTransactionActive> inline void ArtField::SetShort(ObjPtr<mirror::Object> object, int16_t s) { + DCHECK_EQ(Primitive::kPrimShort, GetTypeAsPrimitiveType()) << PrettyField(); FIELD_SET(object, Short, s); } @@ -182,6 +195,8 @@ inline void ArtField::SetShort(ObjPtr<mirror::Object> object, int16_t s) { inline int32_t ArtField::GetInt(ObjPtr<mirror::Object> object) { if (kIsDebugBuild) { + // For simplicity, this method is being called by the compiler entrypoint for + // both int and float fields. Primitive::Type type = GetTypeAsPrimitiveType(); CHECK(type == Primitive::kPrimInt || type == Primitive::kPrimFloat) << PrettyField(); } @@ -191,6 +206,8 @@ inline int32_t ArtField::GetInt(ObjPtr<mirror::Object> object) { template<bool kTransactionActive> inline void ArtField::SetInt(ObjPtr<mirror::Object> object, int32_t i) { if (kIsDebugBuild) { + // For simplicity, this method is being called by the compiler entrypoint for + // both int and float fields. Primitive::Type type = GetTypeAsPrimitiveType(); CHECK(type == Primitive::kPrimInt || type == Primitive::kPrimFloat) << PrettyField(); } @@ -199,6 +216,8 @@ inline void ArtField::SetInt(ObjPtr<mirror::Object> object, int32_t i) { inline int64_t ArtField::GetLong(ObjPtr<mirror::Object> object) { if (kIsDebugBuild) { + // For simplicity, this method is being called by the compiler entrypoint for + // both long and double fields. Primitive::Type type = GetTypeAsPrimitiveType(); CHECK(type == Primitive::kPrimLong || type == Primitive::kPrimDouble) << PrettyField(); } @@ -208,6 +227,8 @@ inline int64_t ArtField::GetLong(ObjPtr<mirror::Object> object) { template<bool kTransactionActive> inline void ArtField::SetLong(ObjPtr<mirror::Object> object, int64_t j) { if (kIsDebugBuild) { + // For simplicity, this method is being called by the compiler entrypoint for + // both long and double fields. Primitive::Type type = GetTypeAsPrimitiveType(); CHECK(type == Primitive::kPrimLong || type == Primitive::kPrimDouble) << PrettyField(); } diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h index 15938c52be..950f1aa9f4 100644 --- a/runtime/art_method-inl.h +++ b/runtime/art_method-inl.h @@ -109,8 +109,7 @@ inline uint32_t ArtMethod::GetAccessFlags() { } inline uint16_t ArtMethod::GetMethodIndex() { - DCHECK(IsRuntimeMethod() || GetDeclaringClass()->IsResolved() || - GetDeclaringClass()->IsErroneous()); + DCHECK(IsRuntimeMethod() || GetDeclaringClass()->IsResolved()); return method_index_; } @@ -245,7 +244,9 @@ inline bool ArtMethod::IsImtUnimplementedMethod() { } inline const DexFile* ArtMethod::GetDexFile() { - return GetDexCache()->GetDexFile(); + // It is safe to avoid the read barrier here since the dex file is constant, so if we read the + // from-space dex file pointer it will be equal to the to-space copy. + return GetDexCache<kWithoutReadBarrier>()->GetDexFile(); } inline const char* ArtMethod::GetDeclaringClassDescriptor() { @@ -362,32 +363,34 @@ inline mirror::ClassLoader* ArtMethod::GetClassLoader() { return GetDeclaringClass()->GetClassLoader(); } +template <ReadBarrierOption kReadBarrierOption> inline mirror::DexCache* ArtMethod::GetDexCache() { if (LIKELY(!IsObsolete())) { - return GetDeclaringClass()->GetDexCache(); + mirror::Class* klass = GetDeclaringClass<kReadBarrierOption>(); + return klass->GetDexCache<kDefaultVerifyFlags, kReadBarrierOption>(); } else { DCHECK(!IsProxyMethod()); return GetObsoleteDexCache(); } } -template<ReadBarrierOption kReadBarrierOption> inline bool ArtMethod::IsProxyMethod() { - return GetDeclaringClass<kReadBarrierOption>()->IsProxyClass(); + // Avoid read barrier since the from-space version of the class will have the correct proxy class + // flags since they are constant for the lifetime of the class. + return GetDeclaringClass<kWithoutReadBarrier>()->IsProxyClass(); } inline ArtMethod* ArtMethod::GetInterfaceMethodIfProxy(PointerSize pointer_size) { if (LIKELY(!IsProxyMethod())) { return this; } - mirror::Class* klass = GetDeclaringClass(); ArtMethod* interface_method = mirror::DexCache::GetElementPtrSize( GetDexCacheResolvedMethods(pointer_size), GetDexMethodIndex(), pointer_size); DCHECK(interface_method != nullptr); DCHECK_EQ(interface_method, - Runtime::Current()->GetClassLinker()->FindMethodForProxy(klass, this)); + Runtime::Current()->GetClassLinker()->FindMethodForProxy(GetDeclaringClass(), this)); return interface_method; } diff --git a/runtime/art_method.cc b/runtime/art_method.cc index d7d39afa8f..6cb8544617 100644 --- a/runtime/art_method.cc +++ b/runtime/art_method.cc @@ -55,15 +55,24 @@ extern "C" void art_quick_invoke_stub(ArtMethod*, uint32_t*, uint32_t, Thread*, extern "C" void art_quick_invoke_static_stub(ArtMethod*, uint32_t*, uint32_t, Thread*, JValue*, const char*); -ArtMethod* ArtMethod::GetSingleImplementation() { +ArtMethod* ArtMethod::GetNonObsoleteMethod() { + DCHECK_EQ(kRuntimePointerSize, Runtime::Current()->GetClassLinker()->GetImagePointerSize()); + if (LIKELY(!IsObsolete())) { + return this; + } else if (IsDirect()) { + return &GetDeclaringClass()->GetDirectMethodsSlice(kRuntimePointerSize)[GetMethodIndex()]; + } else { + return GetDeclaringClass()->GetVTableEntry(GetMethodIndex(), kRuntimePointerSize); + } +} + +ArtMethod* ArtMethod::GetSingleImplementation(PointerSize pointer_size) { DCHECK(!IsNative()); if (!IsAbstract()) { // A non-abstract's single implementation is itself. return this; } - // TODO: add single-implementation logic for abstract method by storing it - // in ptr_sized_fields_. - return nullptr; + return reinterpret_cast<ArtMethod*>(GetDataPtrSize(pointer_size)); } ArtMethod* ArtMethod::FromReflectedMethod(const ScopedObjectAccessAlreadyRunnable& soa, @@ -437,6 +446,8 @@ static const OatFile::OatMethod FindOatMethodFor(ArtMethod* method, PointerSize pointer_size, bool* found) REQUIRES_SHARED(Locks::mutator_lock_) { + // We shouldn't be calling this with obsolete methods. + DCHECK(!method->IsObsolete()); // Although we overwrite the trampoline of non-static methods, we may get here via the resolution // method for direct methods (or virtual methods made direct). mirror::Class* declaring_class = method->GetDeclaringClass(); @@ -719,21 +730,7 @@ std::string ArtMethod::PrettyMethod(bool with_signature) { } std::string ArtMethod::JniShortName() { - std::string class_name(GetDeclaringClassDescriptor()); - // Remove the leading 'L' and trailing ';'... - CHECK_EQ(class_name[0], 'L') << class_name; - CHECK_EQ(class_name[class_name.size() - 1], ';') << class_name; - class_name.erase(0, 1); - class_name.erase(class_name.size() - 1, 1); - - std::string method_name(GetName()); - - std::string short_name; - short_name += "Java_"; - short_name += MangleForJni(class_name); - short_name += "_"; - short_name += MangleForJni(method_name); - return short_name; + return GetJniShortName(GetDeclaringClassDescriptor(), GetName()); } std::string ArtMethod::JniLongName() { diff --git a/runtime/art_method.h b/runtime/art_method.h index 17f343d442..383630363e 100644 --- a/runtime/art_method.h +++ b/runtime/art_method.h @@ -201,6 +201,10 @@ class ArtMethod FINAL { return (GetAccessFlags() & kAccCompileDontBother) == 0; } + void SetDontCompile() { + AddAccessFlags(kAccCompileDontBother); + } + // 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. @@ -226,7 +230,7 @@ class ArtMethod FINAL { void SetIsObsolete() { // TODO We should really support redefining intrinsic if possible. DCHECK(!IsIntrinsic()); - SetAccessFlags(GetAccessFlags() | kAccObsoleteMethod); + AddAccessFlags(kAccObsoleteMethod); } template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier> @@ -251,7 +255,6 @@ class ArtMethod FINAL { return (GetAccessFlags() & kAccVarargs) != 0; } - template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier> bool IsProxyMethod() REQUIRES_SHARED(Locks::mutator_lock_); bool SkipAccessChecks() { @@ -456,7 +459,7 @@ class ArtMethod FINAL { } } - ArtMethod* GetSingleImplementation() + ArtMethod* GetSingleImplementation(PointerSize pointer_size) REQUIRES_SHARED(Locks::mutator_lock_); ALWAYS_INLINE void SetSingleImplementation(ArtMethod* method, PointerSize pointer_size) { @@ -563,12 +566,15 @@ class ArtMethod FINAL { mirror::ClassLoader* GetClassLoader() REQUIRES_SHARED(Locks::mutator_lock_); + template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier> mirror::DexCache* GetDexCache() REQUIRES_SHARED(Locks::mutator_lock_); mirror::DexCache* GetObsoleteDexCache() REQUIRES_SHARED(Locks::mutator_lock_); ALWAYS_INLINE ArtMethod* GetInterfaceMethodIfProxy(PointerSize pointer_size) REQUIRES_SHARED(Locks::mutator_lock_); + ArtMethod* GetNonObsoleteMethod() REQUIRES_SHARED(Locks::mutator_lock_); + // May cause thread suspension due to class resolution. bool EqualParameters(Handle<mirror::ObjectArray<mirror::Class>> params) REQUIRES_SHARED(Locks::mutator_lock_); @@ -684,7 +690,8 @@ class ArtMethod FINAL { ArtMethod** dex_cache_resolved_methods_; // Pointer to JNI function registered to this method, or a function to resolve the JNI function, - // or the profiling data for non-native methods, or an ImtConflictTable. + // or the profiling data for non-native methods, or an ImtConflictTable, or the + // single-implementation of an abstract method. void* data_; // Method dispatch from quick compiled code invokes this pointer which may cause bridging into diff --git a/runtime/asm_support.h b/runtime/asm_support.h index ed83f1c57f..c7a94a90dc 100644 --- a/runtime/asm_support.h +++ b/runtime/asm_support.h @@ -72,7 +72,7 @@ ADD_TEST_EQ(static_cast<size_t>(1U << POINTER_SIZE_SHIFT), // Import platform-independent constant defines from our autogenerated list. // Export new defines (for assembly use) by editing cpp-define-generator def files. #define DEFINE_CHECK_EQ ADD_TEST_EQ -#include "generated/asm_support_gen.h" +#include "asm_support_gen.h" // Offset of field Thread::tlsPtr_.exception. #define THREAD_EXCEPTION_OFFSET (THREAD_CARD_TABLE_OFFSET + __SIZEOF_POINTER__) @@ -104,7 +104,7 @@ ADD_TEST_EQ(THREAD_LOCAL_OBJECTS_OFFSET, // Offset of field Thread::tlsPtr_.mterp_current_ibase. #define THREAD_CURRENT_IBASE_OFFSET \ - (THREAD_LOCAL_OBJECTS_OFFSET + __SIZEOF_SIZE_T__ + (1 + 157) * __SIZEOF_POINTER__) + (THREAD_LOCAL_OBJECTS_OFFSET + __SIZEOF_SIZE_T__ + (1 + 161) * __SIZEOF_POINTER__) ADD_TEST_EQ(THREAD_CURRENT_IBASE_OFFSET, art::Thread::MterpCurrentIBaseOffset<POINTER_SIZE>().Int32Value()) // Offset of field Thread::tlsPtr_.mterp_default_ibase. diff --git a/runtime/atomic.h b/runtime/atomic.h index e2a7259784..45c3165b18 100644 --- a/runtime/atomic.h +++ b/runtime/atomic.h @@ -235,6 +235,11 @@ class PACKED(sizeof(T)) Atomic : public std::atomic<T> { this->store(desired, std::memory_order_seq_cst); } + // Atomically replace the value with desired value. + T ExchangeRelaxed(T desired_value) { + return this->exchange(desired_value, std::memory_order_relaxed); + } + // Atomically replace the value with desired value if it matches the expected value. // Participates in total ordering of atomic operations. bool CompareExchangeStrongSequentiallyConsistent(T expected_value, T desired_value) { @@ -283,6 +288,10 @@ class PACKED(sizeof(T)) Atomic : public std::atomic<T> { return this->fetch_sub(value, std::memory_order_seq_cst); // Return old value. } + T FetchAndSubRelaxed(const T value) { + return this->fetch_sub(value, std::memory_order_relaxed); // Return old value. + } + T FetchAndOrSequentiallyConsistent(const T value) { return this->fetch_or(value, std::memory_order_seq_cst); // Return old_value. } diff --git a/runtime/base/arena_allocator.cc b/runtime/base/arena_allocator.cc index 61e0aabbaf..9fdb0cc9d0 100644 --- a/runtime/base/arena_allocator.cc +++ b/runtime/base/arena_allocator.cc @@ -84,6 +84,7 @@ const char* const ArenaAllocatorStatsImpl<kCount>::kAllocNames[] = { "Verifier ", "CallingConv ", "CHA ", + "Scheduler ", }; template <bool kCount> @@ -144,8 +145,11 @@ void ArenaAllocatorStatsImpl<kCount>::Dump(std::ostream& os, const Arena* first, } } +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Winstantiation-after-specialization" // Explicitly instantiate the used implementation. template class ArenaAllocatorStatsImpl<kArenaAllocatorCountAllocations>; +#pragma GCC diagnostic pop void ArenaAllocatorMemoryTool::DoMakeDefined(void* ptr, size_t size) { MEMORY_TOOL_MAKE_DEFINED(ptr, size); diff --git a/runtime/base/arena_allocator.h b/runtime/base/arena_allocator.h index 6c764cb715..245ab3b24f 100644 --- a/runtime/base/arena_allocator.h +++ b/runtime/base/arena_allocator.h @@ -96,6 +96,7 @@ enum ArenaAllocKind { kArenaAllocVerifier, kArenaAllocCallingConvention, kArenaAllocCHA, + kArenaAllocScheduler, kNumArenaAllocKinds }; diff --git a/runtime/base/iteration_range.h b/runtime/base/iteration_range.h index 9d45707596..3f6f5d6221 100644 --- a/runtime/base/iteration_range.h +++ b/runtime/base/iteration_range.h @@ -55,7 +55,7 @@ inline IterationRange<Iter> MakeEmptyIterationRange(const Iter& it) { } template <typename Container> -inline auto ReverseRange(Container& c) { +inline auto ReverseRange(Container&& c) { typedef typename std::reverse_iterator<decltype(c.begin())> riter; return MakeIterationRange(riter(c.end()), riter(c.begin())); } diff --git a/runtime/bit_memory_region.h b/runtime/bit_memory_region.h new file mode 100644 index 0000000000..c3b5be458e --- /dev/null +++ b/runtime/bit_memory_region.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2017 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_BIT_MEMORY_REGION_H_ +#define ART_RUNTIME_BIT_MEMORY_REGION_H_ + +#include "memory_region.h" + +namespace art { + +// Bit memory region is a bit offset subregion of a normal memoryregion. This is useful for +// abstracting away the bit start offset to avoid needing passing as an argument everywhere. +class BitMemoryRegion FINAL : public ValueObject { + public: + BitMemoryRegion() = default; + ALWAYS_INLINE BitMemoryRegion(MemoryRegion region, size_t bit_offset, size_t bit_size) { + bit_start_ = bit_offset % kBitsPerByte; + const size_t start = bit_offset / kBitsPerByte; + const size_t end = (bit_offset + bit_size + kBitsPerByte - 1) / kBitsPerByte; + region_ = region.Subregion(start, end - start); + } + + void* pointer() const { return region_.pointer(); } + size_t size() const { return region_.size(); } + size_t BitOffset() const { return bit_start_; } + size_t size_in_bits() const { + return region_.size_in_bits(); + } + + // Load a single bit in the region. The bit at offset 0 is the least + // significant bit in the first byte. + ALWAYS_INLINE bool LoadBit(uintptr_t bit_offset) const { + return region_.LoadBit(bit_offset + bit_start_); + } + + ALWAYS_INLINE void StoreBit(uintptr_t bit_offset, bool value) const { + region_.StoreBit(bit_offset + bit_start_, value); + } + + ALWAYS_INLINE uint32_t LoadBits(uintptr_t bit_offset, size_t length) const { + return region_.LoadBits(bit_offset + bit_start_, length); + } + + // Store at a bit offset from inside the bit memory region. + ALWAYS_INLINE void StoreBits(uintptr_t bit_offset, uint32_t value, size_t length) { + region_.StoreBits(bit_offset + bit_start_, value, length); + } + + private: + MemoryRegion region_; + size_t bit_start_ = 0; +}; + +} // namespace art + +#endif // ART_RUNTIME_BIT_MEMORY_REGION_H_ diff --git a/runtime/cha.cc b/runtime/cha.cc index d94b091ebd..d11b12f700 100644 --- a/runtime/cha.cc +++ b/runtime/cha.cc @@ -16,6 +16,7 @@ #include "cha.h" +#include "art_method-inl.h" #include "jit/jit.h" #include "jit/jit_code_cache.h" #include "runtime.h" @@ -185,7 +186,8 @@ class CHACheckpoint FINAL : public Closure { }; void ClassHierarchyAnalysis::VerifyNonSingleImplementation(mirror::Class* verify_class, - uint16_t verify_index) { + uint16_t verify_index, + ArtMethod* excluded_method) { // Grab cha_lock_ to make sure all single-implementation updates are seen. PointerSize image_pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize(); @@ -195,9 +197,14 @@ void ClassHierarchyAnalysis::VerifyNonSingleImplementation(mirror::Class* verify return; } ArtMethod* verify_method = verify_class->GetVTableEntry(verify_index, image_pointer_size); - DCHECK(!verify_method->HasSingleImplementation()) - << "class: " << verify_class->PrettyClass() - << " verify_method: " << verify_method->PrettyMethod(true); + if (verify_method != excluded_method) { + DCHECK(!verify_method->HasSingleImplementation()) + << "class: " << verify_class->PrettyClass() + << " verify_method: " << verify_method->PrettyMethod(true); + if (verify_method->IsAbstract()) { + DCHECK(verify_method->GetSingleImplementation(image_pointer_size) == nullptr); + } + } verify_class = verify_class->GetSuperClass(); } } @@ -206,41 +213,160 @@ void ClassHierarchyAnalysis::CheckSingleImplementationInfo( Handle<mirror::Class> klass, ArtMethod* virtual_method, ArtMethod* method_in_super, - std::unordered_set<ArtMethod*>& invalidated_single_impl_methods) { + std::unordered_set<ArtMethod*>& invalidated_single_impl_methods, + PointerSize pointer_size) { // TODO: if klass is not instantiable, virtual_method isn't invocable yet so // even if it overrides, it doesn't invalidate single-implementation // assumption. - DCHECK_NE(virtual_method, method_in_super); + DCHECK((virtual_method != method_in_super) || virtual_method->IsAbstract()); DCHECK(method_in_super->GetDeclaringClass()->IsResolved()) << "class isn't resolved"; // If virtual_method doesn't come from a default interface method, it should // be supplied by klass. - DCHECK(virtual_method->IsCopied() || + DCHECK(virtual_method == method_in_super || + virtual_method->IsCopied() || virtual_method->GetDeclaringClass() == klass.Get()); - // A new virtual_method should set method_in_super to - // non-single-implementation (if not set already). - // We don't grab cha_lock_. Single-implementation flag won't be set to true - // again once it's set to false. + // To make updating single-implementation flags simple, we always maintain the following + // invariant: + // Say all virtual methods in the same vtable slot, starting from the bottom child class + // to super classes, is a sequence of unique methods m3, m2, m1, ... (after removing duplicate + // methods for inherited methods). + // For example for the following class hierarchy, + // class A { void m() { ... } } + // class B extends A { void m() { ... } } + // class C extends B {} + // class D extends C { void m() { ... } } + // the sequence is D.m(), B.m(), A.m(). + // The single-implementation status for that sequence of methods begin with one or two true's, + // then become all falses. The only case where two true's are possible is for one abstract + // method m and one non-abstract method mImpl that overrides method m. + // With the invariant, when linking in a new class, we only need to at most update one or + // two methods in the sequence for their single-implementation status, in order to maintain + // the invariant. + if (!method_in_super->HasSingleImplementation()) { // method_in_super already has multiple implementations. All methods in the // same vtable slots in its super classes should have // non-single-implementation already. if (kIsDebugBuild) { VerifyNonSingleImplementation(klass->GetSuperClass()->GetSuperClass(), - method_in_super->GetMethodIndex()); + method_in_super->GetMethodIndex(), + nullptr /* excluded_method */); } return; } // Native methods don't have single-implementation flag set. DCHECK(!method_in_super->IsNative()); - // Invalidate method_in_super's single-implementation status. - invalidated_single_impl_methods.insert(method_in_super); + + uint16_t method_index = method_in_super->GetMethodIndex(); + if (method_in_super->IsAbstract()) { + if (kIsDebugBuild) { + // An abstract method should have made all methods in the same vtable + // slot above it in the class hierarchy having non-single-implementation. + mirror::Class* super_super = klass->GetSuperClass()->GetSuperClass(); + VerifyNonSingleImplementation(super_super, + method_index, + method_in_super); + } + + if (virtual_method->IsAbstract()) { + // SUPER: abstract, VIRTUAL: abstract. + if (method_in_super == virtual_method) { + DCHECK(klass->IsInstantiable()); + // An instantiable subclass hasn't provided a concrete implementation of + // the abstract method. Invoking method_in_super may throw AbstractMethodError. + // This is an uncommon case, so we simply treat method_in_super as not + // having single-implementation. + invalidated_single_impl_methods.insert(method_in_super); + return; + } else { + // One abstract method overrides another abstract method. This is an uncommon + // case. We simply treat method_in_super as not having single-implementation. + invalidated_single_impl_methods.insert(method_in_super); + return; + } + } else { + // SUPER: abstract, VIRTUAL: non-abstract. + // A non-abstract method overrides an abstract method. + if (method_in_super->GetSingleImplementation(pointer_size) == nullptr) { + // Abstract method_in_super has no implementation yet. + // We need to grab cha_lock_ for further checking/updating due to possible + // races. + MutexLock cha_mu(Thread::Current(), *Locks::cha_lock_); + if (!method_in_super->HasSingleImplementation()) { + return; + } + if (method_in_super->GetSingleImplementation(pointer_size) == nullptr) { + // virtual_method becomes the first implementation for method_in_super. + method_in_super->SetSingleImplementation(virtual_method, pointer_size); + // Keep method_in_super's single-implementation status. + return; + } + // Fall through to invalidate method_in_super's single-implementation status. + } + // Abstract method_in_super already got one implementation. + // Invalidate method_in_super's single-implementation status. + invalidated_single_impl_methods.insert(method_in_super); + return; + } + } else { + if (virtual_method->IsAbstract()) { + // SUPER: non-abstract, VIRTUAL: abstract. + // An abstract method overrides a non-abstract method. This is an uncommon + // case, we simply treat both methods as not having single-implementation. + invalidated_single_impl_methods.insert(virtual_method); + // Fall-through to handle invalidating method_in_super of its + // single-implementation status. + } + + // SUPER: non-abstract, VIRTUAL: non-abstract/abstract(fall-through from previous if). + // Invalidate method_in_super's single-implementation status. + invalidated_single_impl_methods.insert(method_in_super); + + // method_in_super might be the single-implementation of another abstract method, + // which should be also invalidated of its single-implementation status. + mirror::Class* super_super = klass->GetSuperClass()->GetSuperClass(); + while (super_super != nullptr && + method_index < super_super->GetVTableLength()) { + ArtMethod* method_in_super_super = super_super->GetVTableEntry(method_index, pointer_size); + if (method_in_super_super != method_in_super) { + if (method_in_super_super->IsAbstract()) { + if (method_in_super_super->HasSingleImplementation()) { + // Invalidate method_in_super's single-implementation status. + invalidated_single_impl_methods.insert(method_in_super_super); + // No need to further traverse up the class hierarchy since if there + // are cases that one abstract method overrides another method, we + // should have made that method having non-single-implementation already. + } else { + // method_in_super_super is already non-single-implementation. + // No need to further traverse up the class hierarchy. + } + } else { + DCHECK(!method_in_super_super->HasSingleImplementation()); + // No need to further traverse up the class hierarchy since two non-abstract + // methods (method_in_super and method_in_super_super) should have set all + // other methods (abstract or not) in the vtable slot to be non-single-implementation. + } + + if (kIsDebugBuild) { + VerifyNonSingleImplementation(super_super->GetSuperClass(), + method_index, + method_in_super_super); + } + // No need to go any further. + return; + } else { + super_super = super_super->GetSuperClass(); + } + } + } } void ClassHierarchyAnalysis::InitSingleImplementationFlag(Handle<mirror::Class> klass, - ArtMethod* method) { + ArtMethod* method, + PointerSize pointer_size) { DCHECK(method->IsCopied() || method->GetDeclaringClass() == klass.Get()); if (klass->IsFinal() || method->IsFinal()) { // Final classes or methods do not need CHA for devirtualization. @@ -253,16 +379,21 @@ void ClassHierarchyAnalysis::InitSingleImplementationFlag(Handle<mirror::Class> // cannot be inlined. It's not worthwhile to devirtualize the // call which can add a deoptimization point. DCHECK(!method->HasSingleImplementation()); - } else { - method->SetHasSingleImplementation(true); - if (method->IsAbstract()) { - // There is no real implementation yet. - // TODO: implement single-implementation logic for abstract methods. - DCHECK(method->GetSingleImplementation() == nullptr); + } else if (method->IsAbstract()) { + if (method->GetDeclaringClass()->IsInstantiable()) { + // Rare case, but we do accept it (such as 800-smali/smali/b_26143249.smali). + // Do not attempt to devirtualize it. + method->SetHasSingleImplementation(false); } else { - // Single implementation of non-abstract method is itself. - DCHECK_EQ(method->GetSingleImplementation(), method); + // Abstract method starts with single-implementation flag set and null + // implementation method. + method->SetHasSingleImplementation(true); + DCHECK(method->GetSingleImplementation(pointer_size) == nullptr); } + } else { + method->SetHasSingleImplementation(true); + // Single implementation of non-abstract method is itself. + DCHECK_EQ(method->GetSingleImplementation(pointer_size), method); } } @@ -286,19 +417,29 @@ void ClassHierarchyAnalysis::UpdateAfterLoadingOf(Handle<mirror::Class> klass) { ArtMethod* method_in_super = super_class->GetVTableEntry(i, image_pointer_size); if (method == method_in_super) { // vtable slot entry is inherited from super class. + if (method->IsAbstract() && klass->IsInstantiable()) { + // An instantiable class that inherits an abstract method is treated as + // supplying an implementation that throws AbstractMethodError. + CheckSingleImplementationInfo(klass, + method, + method_in_super, + invalidated_single_impl_methods, + image_pointer_size); + } continue; } - InitSingleImplementationFlag(klass, method); + InitSingleImplementationFlag(klass, method, image_pointer_size); CheckSingleImplementationInfo(klass, method, method_in_super, - invalidated_single_impl_methods); + invalidated_single_impl_methods, + image_pointer_size); } // For new virtual methods that don't override. for (int32_t i = super_class->GetVTableLength(); i < klass->GetVTableLength(); ++i) { ArtMethod* method = klass->GetVTableEntry(i, image_pointer_size); - InitSingleImplementationFlag(klass, method); + InitSingleImplementationFlag(klass, method, image_pointer_size); } Runtime* const runtime = Runtime::Current(); @@ -321,6 +462,10 @@ void ClassHierarchyAnalysis::UpdateAfterLoadingOf(Handle<mirror::Class> klass) { continue; } invalidated->SetHasSingleImplementation(false); + if (invalidated->IsAbstract()) { + // Clear the single implementation method. + invalidated->SetSingleImplementation(nullptr, image_pointer_size); + } if (runtime->IsAotCompiler()) { // No need to invalidate any compiled code as the AotCompiler doesn't diff --git a/runtime/cha.h b/runtime/cha.h index ada5c89981..a56a752d8c 100644 --- a/runtime/cha.h +++ b/runtime/cha.h @@ -112,7 +112,9 @@ class ClassHierarchyAnalysis { void UpdateAfterLoadingOf(Handle<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_); private: - void InitSingleImplementationFlag(Handle<mirror::Class> klass, ArtMethod* method) + void InitSingleImplementationFlag(Handle<mirror::Class> klass, + ArtMethod* method, + PointerSize pointer_size) REQUIRES_SHARED(Locks::mutator_lock_); // `virtual_method` in `klass` overrides `method_in_super`. @@ -123,12 +125,16 @@ class ClassHierarchyAnalysis { Handle<mirror::Class> klass, ArtMethod* virtual_method, ArtMethod* method_in_super, - std::unordered_set<ArtMethod*>& invalidated_single_impl_methods) + std::unordered_set<ArtMethod*>& invalidated_single_impl_methods, + PointerSize pointer_size) REQUIRES_SHARED(Locks::mutator_lock_); - // Verify all methods in the same vtable slot from verify_class and its supers - // don't have single-implementation. - void VerifyNonSingleImplementation(mirror::Class* verify_class, uint16_t verify_index) + // For all methods in vtable slot at `verify_index` of `verify_class` and its + // superclasses, single-implementation status should be false, except if the + // method is `excluded_method`. + void VerifyNonSingleImplementation(mirror::Class* verify_class, + uint16_t verify_index, + ArtMethod* excluded_method) REQUIRES_SHARED(Locks::mutator_lock_); // A map that maps a method to a set of compiled code that assumes that method has a diff --git a/runtime/check_reference_map_visitor.h b/runtime/check_reference_map_visitor.h index 93fdaa6161..a955cb5acb 100644 --- a/runtime/check_reference_map_visitor.h +++ b/runtime/check_reference_map_visitor.h @@ -67,7 +67,8 @@ class CheckReferenceMapVisitor : public StackVisitor { uint16_t number_of_dex_registers = m->GetCodeItem()->registers_size_; DexRegisterMap dex_register_map = code_info.GetDexRegisterMapOf(stack_map, encoding, number_of_dex_registers); - uint32_t register_mask = stack_map.GetRegisterMask(encoding.stack_map_encoding); + uint32_t register_mask = code_info.GetRegisterMaskOf(encoding, stack_map); + BitMemoryRegion stack_mask = code_info.GetStackMaskOf(encoding, stack_map); for (int i = 0; i < number_of_references; ++i) { int reg = registers[i]; CHECK(reg < m->GetCodeItem()->registers_size_); @@ -80,8 +81,7 @@ class CheckReferenceMapVisitor : public StackVisitor { break; case DexRegisterLocation::Kind::kInStack: DCHECK_EQ(location.GetValue() % kFrameSlotSize, 0); - CHECK(stack_map.GetStackMaskBit(encoding.stack_map_encoding, - location.GetValue() / kFrameSlotSize)); + CHECK(stack_mask.LoadBit(location.GetValue() / kFrameSlotSize)); break; case DexRegisterLocation::Kind::kInRegister: case DexRegisterLocation::Kind::kInRegisterHigh: diff --git a/runtime/class_linker-inl.h b/runtime/class_linker-inl.h index 2e17dd85e6..3438810069 100644 --- a/runtime/class_linker-inl.h +++ b/runtime/class_linker-inl.h @@ -68,16 +68,10 @@ inline mirror::Class* ClassLinker::FindArrayClass(Thread* self, inline mirror::String* ClassLinker::ResolveString(dex::StringIndex string_idx, ArtMethod* referrer) { Thread::PoisonObjectPointersIfDebug(); - ObjPtr<mirror::Class> declaring_class = referrer->GetDeclaringClass(); - // MethodVerifier refuses methods with string_idx out of bounds. - DCHECK_LT(string_idx.index_, declaring_class->GetDexFile().NumStringIds()); - ObjPtr<mirror::String> string = - mirror::StringDexCachePair::Lookup(declaring_class->GetDexCache()->GetStrings(), - string_idx.index_, - mirror::DexCache::kDexCacheStringCacheSize).Read(); + ObjPtr<mirror::String> string = referrer->GetDexCache()->GetResolvedString(string_idx); if (UNLIKELY(string == nullptr)) { StackHandleScope<1> hs(Thread::Current()); - Handle<mirror::DexCache> dex_cache(hs.NewHandle(declaring_class->GetDexCache())); + Handle<mirror::DexCache> dex_cache(hs.NewHandle(referrer->GetDexCache())); const DexFile& dex_file = *dex_cache->GetDexFile(); string = ResolveString(dex_file, string_idx, dex_cache); } @@ -93,7 +87,7 @@ inline mirror::Class* ClassLinker::ResolveType(dex::TypeIndex type_idx, ArtMetho if (UNLIKELY(resolved_type == nullptr)) { StackHandleScope<2> hs(Thread::Current()); ObjPtr<mirror::Class> declaring_class = referrer->GetDeclaringClass(); - Handle<mirror::DexCache> dex_cache(hs.NewHandle(declaring_class->GetDexCache())); + Handle<mirror::DexCache> dex_cache(hs.NewHandle(referrer->GetDexCache())); Handle<mirror::ClassLoader> class_loader(hs.NewHandle(declaring_class->GetClassLoader())); const DexFile& dex_file = *dex_cache->GetDexFile(); resolved_type = ResolveType(dex_file, type_idx, dex_cache, class_loader); @@ -159,7 +153,7 @@ inline ArtMethod* ClassLinker::ResolveMethod(Thread* self, if (UNLIKELY(resolved_method == nullptr)) { ObjPtr<mirror::Class> declaring_class = referrer->GetDeclaringClass(); StackHandleScope<2> hs(self); - Handle<mirror::DexCache> h_dex_cache(hs.NewHandle(declaring_class->GetDexCache())); + Handle<mirror::DexCache> h_dex_cache(hs.NewHandle(referrer->GetDexCache())); Handle<mirror::ClassLoader> h_class_loader(hs.NewHandle(declaring_class->GetClassLoader())); const DexFile* dex_file = h_dex_cache->GetDexFile(); resolved_method = ResolveMethod<kResolveMode>(*dex_file, @@ -239,7 +233,7 @@ template<ReadBarrierOption kReadBarrierOption> ArtMethod* ClassLinker::FindMethodForProxy(ObjPtr<mirror::Class> proxy_class, ArtMethod* proxy_method) { DCHECK(proxy_class->IsProxyClass()); - DCHECK(proxy_method->IsProxyMethod<kReadBarrierOption>()); + DCHECK(proxy_method->IsProxyMethod()); { Thread* const self = Thread::Current(); ReaderMutexLock mu(self, *Locks::dex_lock_); diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index b8ed530981..edd6e3b522 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -19,6 +19,7 @@ #include <algorithm> #include <deque> #include <iostream> +#include <map> #include <memory> #include <queue> #include <string> @@ -1340,7 +1341,7 @@ bool ClassLinker::UpdateAppImageClassLoadersAndDexCaches( // The image space is not yet added to the heap, avoid read barriers. ObjPtr<mirror::Class> klass = types[j].Read(); if (space->HasAddress(klass.Ptr())) { - DCHECK_NE(klass->GetStatus(), mirror::Class::kStatusError); + DCHECK(!klass->IsErroneous()) << klass->GetStatus(); auto it = new_class_set->Find(ClassTable::TableSlot(klass)); DCHECK(it != new_class_set->end()); DCHECK_EQ(it->Read(), klass); @@ -1703,7 +1704,7 @@ bool ClassLinker::AddImageSpace( for (int32_t j = 0, num_types = h_dex_cache->NumResolvedTypes(); j < num_types; j++) { ObjPtr<mirror::Class> klass = types[j].Read(); if (klass != nullptr) { - DCHECK_NE(klass->GetStatus(), mirror::Class::kStatusError); + DCHECK(!klass->IsErroneous()) << klass->GetStatus(); } } } else { @@ -2232,7 +2233,7 @@ mirror::Class* ClassLinker::EnsureResolved(Thread* self, // For temporary classes we must wait for them to be retired. if (init_done_ && klass->IsTemp()) { CHECK(!klass->IsResolved()); - if (klass->IsErroneous()) { + if (klass->IsErroneousUnresolved()) { ThrowEarlierClassFailure(klass); return nullptr; } @@ -2240,10 +2241,10 @@ mirror::Class* ClassLinker::EnsureResolved(Thread* self, Handle<mirror::Class> h_class(hs.NewHandle(klass)); ObjectLock<mirror::Class> lock(self, h_class); // Loop and wait for the resolving thread to retire this class. - while (!h_class->IsRetired() && !h_class->IsErroneous()) { + while (!h_class->IsRetired() && !h_class->IsErroneousUnresolved()) { lock.WaitIgnoringInterrupts(); } - if (h_class->IsErroneous()) { + if (h_class->IsErroneousUnresolved()) { ThrowEarlierClassFailure(h_class.Get()); return nullptr; } @@ -2258,7 +2259,7 @@ mirror::Class* ClassLinker::EnsureResolved(Thread* self, static const size_t kNumYieldIterations = 1000; // How long each sleep is in us. static const size_t kSleepDurationUS = 1000; // 1 ms. - while (!klass->IsResolved() && !klass->IsErroneous()) { + while (!klass->IsResolved() && !klass->IsErroneousUnresolved()) { StackHandleScope<1> hs(self); HandleWrapperObjPtr<mirror::Class> h_class(hs.NewHandleWrapper(&klass)); { @@ -2269,7 +2270,7 @@ mirror::Class* ClassLinker::EnsureResolved(Thread* self, // Check for circular dependencies between classes, the lock is required for SetStatus. if (!h_class->IsResolved() && h_class->GetClinitThreadId() == self->GetTid()) { ThrowClassCircularityError(h_class.Get()); - mirror::Class::SetStatus(h_class, mirror::Class::kStatusError, self); + mirror::Class::SetStatus(h_class, mirror::Class::kStatusErrorUnresolved, self); return nullptr; } } @@ -2286,7 +2287,7 @@ mirror::Class* ClassLinker::EnsureResolved(Thread* self, ++index; } - if (klass->IsErroneous()) { + if (klass->IsErroneousUnresolved()) { ThrowEarlierClassFailure(klass); return nullptr; } @@ -2649,6 +2650,10 @@ mirror::Class* ClassLinker::DefineClass(Thread* self, dex_class_def, &new_dex_file, &new_class_def); + // Check to see if an exception happened during runtime callbacks. Return if so. + if (self->IsExceptionPending()) { + return nullptr; + } ObjPtr<mirror::DexCache> dex_cache = RegisterDexFile(*new_dex_file, class_loader.Get()); if (dex_cache == nullptr) { self->AssertPendingOOMException(); @@ -2687,7 +2692,7 @@ mirror::Class* ClassLinker::DefineClass(Thread* self, // An exception occured during load, set status to erroneous while holding klass' lock in case // notification is necessary. if (!klass->IsErroneous()) { - mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); + mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorUnresolved, self); } return nullptr; } @@ -2697,7 +2702,7 @@ mirror::Class* ClassLinker::DefineClass(Thread* self, if (!LoadSuperAndInterfaces(klass, *new_dex_file)) { // Loading failed. if (!klass->IsErroneous()) { - mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); + mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorUnresolved, self); } return nullptr; } @@ -2716,13 +2721,13 @@ mirror::Class* ClassLinker::DefineClass(Thread* self, if (!LinkClass(self, descriptor, klass, interfaces, &h_new_class)) { // Linking failed. if (!klass->IsErroneous()) { - mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); + mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorUnresolved, self); } return nullptr; } self->AssertNoPendingException(); CHECK(h_new_class.Get() != nullptr) << descriptor; - CHECK(h_new_class->IsResolved()) << descriptor; + CHECK(h_new_class->IsResolved() && !h_new_class->IsErroneousResolved()) << descriptor; // Instrumentation may have updated entrypoints for all methods of all // classes. However it could not update methods of this class while we @@ -2857,9 +2862,12 @@ bool ClassLinker::ShouldUseInterpreterEntrypoint(ArtMethod* method, const void* return true; } - if (runtime->IsFullyDeoptable()) { - // We need to be able to deoptimize at any time so we should always just ignore precompiled - // code and go to the interpreter assuming we don't already have jitted code. + if (runtime->IsJavaDebuggable()) { + // For simplicity, we ignore precompiled code and go to the interpreter + // assuming we don't already have jitted code. + // We could look at the oat file where `quick_code` is being defined, + // and check whether it's been compiled debuggable, but we decided to + // only rely on the JIT for debuggable apps. jit::Jit* jit = Runtime::Current()->GetJit(); return (jit == nullptr) || !jit->GetCodeCache()->ContainsPc(quick_code); } @@ -2867,18 +2875,13 @@ bool ClassLinker::ShouldUseInterpreterEntrypoint(ArtMethod* method, const void* if (runtime->IsNativeDebuggable()) { 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 - // blocking JIT would results in non-negligible performance impact. + // since we want to JIT it (at first use) with extra stackmaps for native + // debugging. We keep however all AOT code from the boot image, + // since the JIT-at-first-use is blocking and would result in non-negligible + // startup performance impact. return !runtime->GetHeap()->IsInBootImageOatFile(quick_code); } - if (Dbg::IsDebuggerActive()) { - // Boot image classes may be AOT-compiled as non-debuggable. - // This is not suitable for the Java debugger, so ignore the AOT code. - return runtime->GetHeap()->IsInBootImageOatFile(quick_code); - } - return false; } @@ -3817,7 +3820,7 @@ bool ClassLinker::AttemptSupertypeVerification(Thread* self, } // Need to grab the lock to change status. ObjectLock<mirror::Class> super_lock(self, klass); - mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); + mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self); return false; } @@ -3939,8 +3942,8 @@ verifier::MethodVerifier::FailureKind ClassLinker::VerifyClass( bool preverified = VerifyClassUsingOatFile(dex_file, klass.Get(), oat_file_class_status); // If the oat file says the class had an error, re-run the verifier. That way we will get a // precise error message. To ensure a rerun, test: - // oat_file_class_status == mirror::Class::kStatusError => !preverified - DCHECK(!(oat_file_class_status == mirror::Class::kStatusError) || !preverified); + // mirror::Class::IsErroneous(oat_file_class_status) => !preverified + DCHECK(!mirror::Class::IsErroneous(oat_file_class_status) || !preverified); std::string error_msg; verifier::MethodVerifier::FailureKind verifier_failure = verifier::MethodVerifier::kNoFailure; @@ -3998,7 +4001,7 @@ verifier::MethodVerifier::FailureKind ClassLinker::VerifyClass( << " because: " << error_msg; self->AssertNoPendingException(); ThrowVerifyError(klass.Get(), "%s", error_msg.c_str()); - mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); + mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self); } if (preverified || verifier_failure == verifier::MethodVerifier::kNoFailure) { // Class is verified so we don't need to do any access check on its methods. @@ -4089,7 +4092,7 @@ bool ClassLinker::VerifyClassUsingOatFile(const DexFile& dex_file, // at compile time). return false; } - if (oat_file_class_status == mirror::Class::kStatusError) { + if (mirror::Class::IsErroneous(oat_file_class_status)) { // Compile time verification failed with a hard error. This is caused by invalid instructions // in the class. These errors are unrecoverable. return false; @@ -4248,7 +4251,7 @@ mirror::Class* ClassLinker::CreateProxyClass(ScopedObjectAccessAlreadyRunnable& Handle<mirror::ObjectArray<mirror::Class>> h_interfaces( hs.NewHandle(soa.Decode<mirror::ObjectArray<mirror::Class>>(interfaces))); if (!LinkClass(self, descriptor.c_str(), klass, h_interfaces, &new_class)) { - mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); + mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorUnresolved, self); return nullptr; } } @@ -4463,7 +4466,8 @@ bool ClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass, return false; } - CHECK(klass->IsResolved()) << klass->PrettyClass() << ": state=" << klass->GetStatus(); + CHECK(klass->IsResolved() && !klass->IsErroneousResolved()) + << klass->PrettyClass() << ": state=" << klass->GetStatus(); if (!klass->IsVerified()) { VerifyClass(self, klass); @@ -4498,7 +4502,7 @@ bool ClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass, // 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()); + DCHECK(!klass->IsErroneous()) << klass->GetStatus(); if (klass->IsInitialized()) { return true; } @@ -4525,7 +4529,7 @@ bool ClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass, } if (!ValidateSuperClassDescriptors(klass)) { - mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); + mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self); return false; } self->AllowThreadSuspension(); @@ -4561,7 +4565,7 @@ bool ClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass, << (self->GetException() != nullptr ? self->GetException()->Dump() : ""); ObjectLock<mirror::Class> lock(self, klass); // Initialization failed because the super-class is erroneous. - mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); + mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self); return false; } } @@ -4592,7 +4596,7 @@ bool ClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass, if (!iface_initialized) { ObjectLock<mirror::Class> lock(self, klass); // Initialization failed because one of our interfaces with default methods is erroneous. - mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); + mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self); return false; } } @@ -4665,7 +4669,7 @@ bool ClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass, if (self->IsExceptionPending()) { WrapExceptionInInitializer(klass); - mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); + mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self); success = false; } else if (Runtime::Current()->IsTransactionAborted()) { // The exception thrown when the transaction aborted has been caught and cleared @@ -4674,7 +4678,7 @@ bool ClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass, << mirror::Class::PrettyDescriptor(klass.Get()) << " without exception while transaction was aborted: re-throw it now."; Runtime::Current()->ThrowTransactionAbortError(self); - mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); + mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self); success = false; } else { RuntimeStats* global_stats = Runtime::Current()->GetStats(); @@ -4758,7 +4762,7 @@ bool ClassLinker::WaitForInitializeClass(Handle<mirror::Class> klass, // we were not using WaitIgnoringInterrupts), bail out. if (self->IsExceptionPending()) { WrapExceptionInInitializer(klass); - mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); + mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self); return false; } // Spurious wakeup? Go back to waiting. @@ -5169,7 +5173,7 @@ bool ClassLinker::LinkClass(Thread* self, klass->SetIFieldsPtrUnchecked(nullptr); if (UNLIKELY(h_new_class.Get() == nullptr)) { self->AssertPendingOOMException(); - mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); + mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorUnresolved, self); return false; } @@ -7781,7 +7785,7 @@ mirror::Class* ClassLinker::ResolveType(const DexFile& dex_file, } } } - DCHECK((resolved == nullptr) || resolved->IsResolved() || resolved->IsErroneous()) + DCHECK((resolved == nullptr) || resolved->IsResolved()) << resolved->PrettyDescriptor() << " " << resolved->GetStatus(); return resolved.Ptr(); } @@ -8478,71 +8482,94 @@ void ClassLinker::CleanupClassLoaders() { } } -std::set<DexCacheResolvedClasses> ClassLinker::GetResolvedClasses(bool ignore_boot_classes) { - ScopedTrace trace(__PRETTY_FUNCTION__); - ScopedObjectAccess soa(Thread::Current()); - ScopedAssertNoThreadSuspension ants(__FUNCTION__); - std::set<DexCacheResolvedClasses> ret; - VLOG(class_linker) << "Collecting resolved classes"; - const uint64_t start_time = NanoTime(); - ReaderMutexLock mu(soa.Self(), *Locks::dex_lock_); - // Loop through all the dex caches and inspect resolved classes. - for (const ClassLinker::DexCacheData& data : GetDexCachesData()) { - if (soa.Self()->IsJWeakCleared(data.weak_root)) { - continue; - } - ObjPtr<mirror::DexCache> dex_cache = soa.Decode<mirror::DexCache>(data.weak_root); - if (dex_cache == nullptr) { - continue; - } - const DexFile* dex_file = dex_cache->GetDexFile(); - const std::string& location = dex_file->GetLocation(); - const size_t num_class_defs = dex_file->NumClassDefs(); - // Use the resolved types, this will miss array classes. - const size_t num_types = dex_file->NumTypeIds(); - 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<dex::TypeIndex> class_set; - CHECK_EQ(num_types, dex_cache->NumResolvedTypes()); - for (size_t i = 0; i < num_types; ++i) { - ObjPtr<mirror::Class> klass = dex_cache->GetResolvedType(dex::TypeIndex(i)); - // Filter out null class loader since that is the boot class loader. - if (klass == nullptr || (ignore_boot_classes && klass->GetClassLoader() == nullptr)) { - continue; - } - ++num_resolved; - DCHECK(!klass->IsProxyClass()); - if (!klass->IsResolved()) { - DCHECK(klass->IsErroneous()); - continue; +class GetResolvedClassesVisitor : public ClassVisitor { + public: + GetResolvedClassesVisitor(std::set<DexCacheResolvedClasses>* result, bool ignore_boot_classes) + : result_(result), + ignore_boot_classes_(ignore_boot_classes), + last_resolved_classes_(result->end()), + last_dex_file_(nullptr), + vlog_is_on_(VLOG_IS_ON(class_linker)), + extra_stats_(), + last_extra_stats_(extra_stats_.end()) { } + + bool operator()(ObjPtr<mirror::Class> klass) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) { + if (!klass->IsProxyClass() && + !klass->IsArrayClass() && + klass->IsResolved() && + !klass->IsErroneousResolved() && + (!ignore_boot_classes_ || klass->GetClassLoader() != nullptr)) { + const DexFile& dex_file = klass->GetDexFile(); + if (&dex_file != last_dex_file_) { + last_dex_file_ = &dex_file; + DexCacheResolvedClasses resolved_classes(dex_file.GetLocation(), + dex_file.GetBaseLocation(), + dex_file.GetLocationChecksum()); + last_resolved_classes_ = result_->find(resolved_classes); + if (last_resolved_classes_ == result_->end()) { + last_resolved_classes_ = result_->insert(resolved_classes).first; + } } - ObjPtr<mirror::DexCache> klass_dex_cache = klass->GetDexCache(); - if (klass_dex_cache == dex_cache) { - DCHECK(klass->IsResolved()); - CHECK_LT(klass->GetDexClassDefIndex(), num_class_defs); - class_set.insert(klass->GetDexTypeIndex()); + bool added = last_resolved_classes_->AddClass(klass->GetDexTypeIndex()); + if (UNLIKELY(vlog_is_on_) && added) { + const DexCacheResolvedClasses* resolved_classes = std::addressof(*last_resolved_classes_); + if (last_extra_stats_ == extra_stats_.end() || + last_extra_stats_->first != resolved_classes) { + last_extra_stats_ = extra_stats_.find(resolved_classes); + if (last_extra_stats_ == extra_stats_.end()) { + last_extra_stats_ = + extra_stats_.emplace(resolved_classes, ExtraStats(dex_file.NumClassDefs())).first; + } + } } } + return true; + } - if (!class_set.empty()) { - auto it = ret.find(resolved_classes); - if (it != ret.end()) { - // Already have the key, union the class type indexes. - it->AddClasses(class_set.begin(), class_set.end()); - } else { - resolved_classes.AddClasses(class_set.begin(), class_set.end()); - ret.insert(resolved_classes); + void PrintStatistics() const { + if (vlog_is_on_) { + for (const DexCacheResolvedClasses& resolved_classes : *result_) { + auto it = extra_stats_.find(std::addressof(resolved_classes)); + DCHECK(it != extra_stats_.end()); + const ExtraStats& extra_stats = it->second; + LOG(INFO) << "Dex location " << resolved_classes.GetDexLocation() + << " has " << resolved_classes.GetClasses().size() << " / " + << extra_stats.number_of_class_defs_ << " resolved classes"; } } + } + + private: + struct ExtraStats { + explicit ExtraStats(uint32_t number_of_class_defs) + : number_of_class_defs_(number_of_class_defs) {} + uint32_t number_of_class_defs_; + }; + + std::set<DexCacheResolvedClasses>* result_; + bool ignore_boot_classes_; + std::set<DexCacheResolvedClasses>::iterator last_resolved_classes_; + const DexFile* last_dex_file_; - VLOG(class_linker) << "Dex location " << location << " has " << num_resolved << " / " - << num_class_defs << " resolved classes"; + // Statistics. + bool vlog_is_on_; + std::map<const DexCacheResolvedClasses*, ExtraStats> extra_stats_; + std::map<const DexCacheResolvedClasses*, ExtraStats>::iterator last_extra_stats_; +}; + +std::set<DexCacheResolvedClasses> ClassLinker::GetResolvedClasses(bool ignore_boot_classes) { + ScopedTrace trace(__PRETTY_FUNCTION__); + ScopedObjectAccess soa(Thread::Current()); + ScopedAssertNoThreadSuspension ants(__FUNCTION__); + std::set<DexCacheResolvedClasses> ret; + VLOG(class_linker) << "Collecting resolved classes"; + const uint64_t start_time = NanoTime(); + GetResolvedClassesVisitor visitor(&ret, ignore_boot_classes); + VisitClasses(&visitor); + if (VLOG_IS_ON(class_linker)) { + visitor.PrintStatistics(); + LOG(INFO) << "Collecting class profile took " << PrettyDuration(NanoTime() - start_time); } - VLOG(class_linker) << "Collecting class profile took " << PrettyDuration(NanoTime() - start_time); return ret; } diff --git a/runtime/class_linker.h b/runtime/class_linker.h index d3bb58dc33..5042fb7609 100644 --- a/runtime/class_linker.h +++ b/runtime/class_linker.h @@ -1213,7 +1213,7 @@ class ClassLoadCallback { const DexFile& initial_dex_file ATTRIBUTE_UNUSED, const DexFile::ClassDef& initial_class_def ATTRIBUTE_UNUSED, /*out*/DexFile const** final_dex_file ATTRIBUTE_UNUSED, - /*out*/DexFile::ClassDef const** final_dex_cache ATTRIBUTE_UNUSED) + /*out*/DexFile::ClassDef const** final_class_def ATTRIBUTE_UNUSED) REQUIRES_SHARED(Locks::mutator_lock_) {} // A class has been loaded. diff --git a/runtime/class_linker_test.cc b/runtime/class_linker_test.cc index 7b6c0dc510..17510bb598 100644 --- a/runtime/class_linker_test.cc +++ b/runtime/class_linker_test.cc @@ -87,6 +87,7 @@ class ClassLinkerTest : public CommonRuntimeTest { EXPECT_FALSE(primitive->IsErroneous()); EXPECT_TRUE(primitive->IsLoaded()); EXPECT_TRUE(primitive->IsResolved()); + EXPECT_FALSE(primitive->IsErroneousResolved()); EXPECT_TRUE(primitive->IsVerified()); EXPECT_TRUE(primitive->IsInitialized()); EXPECT_FALSE(primitive->IsArrayInstance()); @@ -125,6 +126,7 @@ class ClassLinkerTest : public CommonRuntimeTest { EXPECT_FALSE(JavaLangObject->IsErroneous()); EXPECT_TRUE(JavaLangObject->IsLoaded()); EXPECT_TRUE(JavaLangObject->IsResolved()); + EXPECT_FALSE(JavaLangObject->IsErroneousResolved()); EXPECT_TRUE(JavaLangObject->IsVerified()); EXPECT_TRUE(JavaLangObject->IsInitialized()); EXPECT_FALSE(JavaLangObject->IsArrayInstance()); @@ -199,6 +201,7 @@ class ClassLinkerTest : public CommonRuntimeTest { EXPECT_FALSE(array->IsErroneous()); EXPECT_TRUE(array->IsLoaded()); EXPECT_TRUE(array->IsResolved()); + EXPECT_FALSE(array->IsErroneousResolved()); EXPECT_TRUE(array->IsVerified()); EXPECT_TRUE(array->IsInitialized()); EXPECT_FALSE(array->IsArrayInstance()); @@ -270,6 +273,7 @@ class ClassLinkerTest : public CommonRuntimeTest { EXPECT_TRUE(klass->GetDexCache() != nullptr); EXPECT_TRUE(klass->IsLoaded()); EXPECT_TRUE(klass->IsResolved()); + EXPECT_FALSE(klass->IsErroneousResolved()); EXPECT_FALSE(klass->IsErroneous()); EXPECT_FALSE(klass->IsArrayClass()); EXPECT_TRUE(klass->GetComponentType() == nullptr); @@ -739,13 +743,22 @@ struct MethodTypeOffsets : public CheckOffsets<mirror::MethodType> { } }; +struct MethodHandleOffsets : public CheckOffsets<mirror::MethodHandle> { + MethodHandleOffsets() : CheckOffsets<mirror::MethodHandle>( + false, "Ljava/lang/invoke/MethodHandle;") { + addOffset(OFFSETOF_MEMBER(mirror::MethodHandle, art_field_or_method_), "artFieldOrMethod"); + addOffset(OFFSETOF_MEMBER(mirror::MethodHandle, cached_spread_invoker_), + "cachedSpreadInvoker"); + addOffset(OFFSETOF_MEMBER(mirror::MethodHandle, handle_kind_), "handleKind"); + addOffset(OFFSETOF_MEMBER(mirror::MethodHandle, nominal_type_), "nominalType"); + addOffset(OFFSETOF_MEMBER(mirror::MethodHandle, method_type_), "type"); + } +}; + struct MethodHandleImplOffsets : public CheckOffsets<mirror::MethodHandleImpl> { MethodHandleImplOffsets() : CheckOffsets<mirror::MethodHandleImpl>( - false, "Ljava/lang/invoke/MethodHandle;") { - addOffset(OFFSETOF_MEMBER(mirror::MethodHandleImpl, art_field_or_method_), "artFieldOrMethod"); - addOffset(OFFSETOF_MEMBER(mirror::MethodHandleImpl, handle_kind_), "handleKind"); - addOffset(OFFSETOF_MEMBER(mirror::MethodHandleImpl, nominal_type_), "nominalType"); - addOffset(OFFSETOF_MEMBER(mirror::MethodHandleImpl, method_type_), "type"); + false, "Ljava/lang/invoke/MethodHandleImpl;") { + addOffset(OFFSETOF_MEMBER(mirror::MethodHandleImpl, info_), "info"); } }; @@ -779,6 +792,7 @@ TEST_F(ClassLinkerTest, ValidateFieldOrderOfJavaCppUnionClasses) { EXPECT_TRUE(FieldOffsets().Check()); EXPECT_TRUE(ExecutableOffsets().Check()); EXPECT_TRUE(MethodTypeOffsets().Check()); + EXPECT_TRUE(MethodHandleOffsets().Check()); EXPECT_TRUE(MethodHandleImplOffsets().Check()); EXPECT_TRUE(EmulatedStackFrameOffsets().Check()); } @@ -857,6 +871,7 @@ TEST_F(ClassLinkerTest, FindClass) { EXPECT_FALSE(MyClass->IsErroneous()); EXPECT_TRUE(MyClass->IsLoaded()); EXPECT_TRUE(MyClass->IsResolved()); + EXPECT_FALSE(MyClass->IsErroneousResolved()); EXPECT_FALSE(MyClass->IsVerified()); EXPECT_FALSE(MyClass->IsInitialized()); EXPECT_FALSE(MyClass->IsArrayInstance()); @@ -941,6 +956,47 @@ TEST_F(ClassLinkerTest, LookupResolvedTypeArray) { array_klass); } +TEST_F(ClassLinkerTest, LookupResolvedTypeErroneousInit) { + ScopedObjectAccess soa(Thread::Current()); + StackHandleScope<3> hs(soa.Self()); + Handle<mirror::ClassLoader> class_loader( + hs.NewHandle(soa.Decode<mirror::ClassLoader>(LoadDex("ErroneousInit")))); + AssertNonExistentClass("LErroneousInit;"); + Handle<mirror::Class> klass = + hs.NewHandle(class_linker_->FindClass(soa.Self(), "LErroneousInit;", class_loader)); + ASSERT_OBJ_PTR_NE(klass.Get(), ObjPtr<mirror::Class>(nullptr)); + dex::TypeIndex type_idx = klass->GetClassDef()->class_idx_; + Handle<mirror::DexCache> dex_cache = hs.NewHandle(klass->GetDexCache()); + const DexFile& dex_file = klass->GetDexFile(); + EXPECT_OBJ_PTR_EQ( + class_linker_->LookupResolvedType(dex_file, type_idx, dex_cache.Get(), class_loader.Get()), + klass.Get()); + // Zero out the resolved type and make sure LookupResolvedType still finds it. + dex_cache->SetResolvedType(type_idx, nullptr); + EXPECT_TRUE(dex_cache->GetResolvedType(type_idx) == nullptr); + EXPECT_OBJ_PTR_EQ( + class_linker_->LookupResolvedType(dex_file, type_idx, dex_cache.Get(), class_loader.Get()), + klass.Get()); + // Force initialization to turn the class erroneous. + bool initialized = class_linker_->EnsureInitialized(soa.Self(), + klass, + /* can_init_fields */ true, + /* can_init_parents */ true); + EXPECT_FALSE(initialized); + EXPECT_TRUE(soa.Self()->IsExceptionPending()); + soa.Self()->ClearException(); + // Check that the LookupResolvedType() can still find the resolved type. + EXPECT_OBJ_PTR_EQ( + class_linker_->LookupResolvedType(dex_file, type_idx, dex_cache.Get(), class_loader.Get()), + klass.Get()); + // Zero out the resolved type and make sure LookupResolvedType() still finds it. + dex_cache->SetResolvedType(type_idx, nullptr); + EXPECT_TRUE(dex_cache->GetResolvedType(type_idx) == nullptr); + EXPECT_OBJ_PTR_EQ( + class_linker_->LookupResolvedType(dex_file, type_idx, dex_cache.Get(), class_loader.Get()), + klass.Get()); +} + TEST_F(ClassLinkerTest, LibCore) { ScopedObjectAccess soa(Thread::Current()); ASSERT_TRUE(java_lang_dex_file_ != nullptr); diff --git a/runtime/compiler_filter.cc b/runtime/compiler_filter.cc index dc89d32f50..cb8c11db96 100644 --- a/runtime/compiler_filter.cc +++ b/runtime/compiler_filter.cc @@ -33,7 +33,6 @@ bool CompilerFilter::IsBytecodeCompilationEnabled(Filter filter) { case CompilerFilter::kTime: case CompilerFilter::kSpeedProfile: case CompilerFilter::kSpeed: - case CompilerFilter::kLayoutProfile: case CompilerFilter::kEverythingProfile: case CompilerFilter::kEverything: return true; } @@ -53,7 +52,6 @@ bool CompilerFilter::IsJniCompilationEnabled(Filter filter) { case CompilerFilter::kTime: case CompilerFilter::kSpeedProfile: case CompilerFilter::kSpeed: - case CompilerFilter::kLayoutProfile: case CompilerFilter::kEverythingProfile: case CompilerFilter::kEverything: return true; } @@ -73,7 +71,6 @@ bool CompilerFilter::IsAnyMethodCompilationEnabled(Filter filter) { case CompilerFilter::kTime: case CompilerFilter::kSpeedProfile: case CompilerFilter::kSpeed: - case CompilerFilter::kLayoutProfile: case CompilerFilter::kEverythingProfile: case CompilerFilter::kEverything: return true; } @@ -93,7 +90,6 @@ bool CompilerFilter::IsVerificationEnabled(Filter filter) { case CompilerFilter::kTime: case CompilerFilter::kSpeedProfile: case CompilerFilter::kSpeed: - case CompilerFilter::kLayoutProfile: case CompilerFilter::kEverythingProfile: case CompilerFilter::kEverything: return true; } @@ -120,7 +116,6 @@ bool CompilerFilter::DependsOnProfile(Filter filter) { case CompilerFilter::kVerifyProfile: case CompilerFilter::kSpaceProfile: case CompilerFilter::kSpeedProfile: - case CompilerFilter::kLayoutProfile: case CompilerFilter::kEverythingProfile: return true; } UNREACHABLE(); @@ -145,7 +140,6 @@ CompilerFilter::Filter CompilerFilter::GetNonProfileDependentFilterFrom(Filter f return CompilerFilter::kSpace; case CompilerFilter::kSpeedProfile: - case CompilerFilter::kLayoutProfile: return CompilerFilter::kSpeed; case CompilerFilter::kEverythingProfile: @@ -171,7 +165,6 @@ std::string CompilerFilter::NameOfFilter(Filter filter) { case CompilerFilter::kTime: return "time"; case CompilerFilter::kSpeedProfile: return "speed-profile"; case CompilerFilter::kSpeed: return "speed"; - case CompilerFilter::kLayoutProfile: return "layout-profile"; case CompilerFilter::kEverythingProfile: return "everything-profile"; case CompilerFilter::kEverything: return "everything"; } @@ -199,8 +192,6 @@ bool CompilerFilter::ParseCompilerFilter(const char* option, Filter* filter) { *filter = kSpeed; } else if (strcmp(option, "speed-profile") == 0) { *filter = kSpeedProfile; - } else if (strcmp(option, "layout-profile") == 0) { - *filter = kLayoutProfile; } else if (strcmp(option, "everything") == 0) { *filter = kEverything; } else if (strcmp(option, "everything-profile") == 0) { diff --git a/runtime/compiler_filter.h b/runtime/compiler_filter.h index 7eb5f9a90f..796f4aad0c 100644 --- a/runtime/compiler_filter.h +++ b/runtime/compiler_filter.h @@ -39,7 +39,6 @@ class CompilerFilter FINAL { kSpace, // Maximize space savings. kBalanced, // Good performance return on compilation investment. kSpeedProfile, // Maximize runtime performance based on profile. - kLayoutProfile, // Temporary filter for dexlayout. Will be merged with kSpeedProfile. kSpeed, // Maximize runtime performance. kEverythingProfile, // Compile everything capable of being compiled based on profile. kEverything, // Compile everything capable of being compiled. diff --git a/runtime/debugger.cc b/runtime/debugger.cc index 22a31635a6..1a0cec075c 100644 --- a/runtime/debugger.cc +++ b/runtime/debugger.cc @@ -588,29 +588,6 @@ bool Dbg::RequiresDeoptimization() { return !Runtime::Current()->GetInstrumentation()->IsForcedInterpretOnly(); } -// Used to patch boot image method entry point to interpreter bridge. -class UpdateEntryPointsClassVisitor : public ClassVisitor { - public: - explicit UpdateEntryPointsClassVisitor(instrumentation::Instrumentation* instrumentation) - : instrumentation_(instrumentation) {} - - bool operator()(ObjPtr<mirror::Class> klass) OVERRIDE REQUIRES(Locks::mutator_lock_) { - auto pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize(); - for (auto& m : klass->GetMethods(pointer_size)) { - const void* code = m.GetEntryPointFromQuickCompiledCode(); - if (Runtime::Current()->GetHeap()->IsInBootImageOatFile(code) && - !m.IsNative() && - !m.IsProxyMethod()) { - instrumentation_->UpdateMethodsCodeFromDebugger(&m, GetQuickToInterpreterBridge()); - } - } - return true; - } - - private: - instrumentation::Instrumentation* const instrumentation_; -}; - void Dbg::GoActive() { // Enable all debugging features, including scans for breakpoints. // This is a no-op if we're already active. @@ -639,14 +616,16 @@ void Dbg::GoActive() { } Runtime* runtime = Runtime::Current(); - // Since boot image code may be AOT compiled as not debuggable, we need to patch - // entry points of methods in boot image to interpreter bridge. - // However, the performance cost of this is non-negligible during native-debugging due to the + // Best effort deoptimization if the runtime is non-Java debuggable. This happens when + // ro.debuggable is set, but the application is not debuggable, or when a standalone + // dalvikvm invocation is not passed the debuggable option (-Xcompiler-option --debuggable). + // + // The performance cost of this is non-negligible during native-debugging due to the // forced JIT, so we keep the AOT code in that case in exchange for limited native debugging. - if (!runtime->GetInstrumentation()->IsForcedInterpretOnly() && !runtime->IsNativeDebuggable()) { - ScopedObjectAccess soa(self); - UpdateEntryPointsClassVisitor visitor(runtime->GetInstrumentation()); - runtime->GetClassLinker()->VisitClasses(&visitor); + if (!runtime->IsJavaDebuggable() && + !runtime->GetInstrumentation()->IsForcedInterpretOnly() && + !runtime->IsNativeDebuggable()) { + runtime->DeoptimizeBootImage(); } ScopedSuspendAll ssa(__FUNCTION__); diff --git a/runtime/dex2oat_environment_test.h b/runtime/dex2oat_environment_test.h index 7ae9f03c83..8b0c51c998 100644 --- a/runtime/dex2oat_environment_test.h +++ b/runtime/dex2oat_environment_test.h @@ -25,6 +25,7 @@ #include "common_runtime_test.h" #include "compiler_callbacks.h" +#include "exec_utils.h" #include "gc/heap.h" #include "gc/space/image_space.h" #include "oat_file_assistant.h" diff --git a/runtime/dex_cache_resolved_classes.h b/runtime/dex_cache_resolved_classes.h index f53ca4acb6..bebdf0dbfe 100644 --- a/runtime/dex_cache_resolved_classes.h +++ b/runtime/dex_cache_resolved_classes.h @@ -44,6 +44,10 @@ class DexCacheResolvedClasses { return dex_location_.compare(other.dex_location_); } + bool AddClass(dex::TypeIndex index) const { + return classes_.insert(index).second; + } + template <class InputIt> void AddClasses(InputIt begin, InputIt end) const { classes_.insert(begin, end); diff --git a/runtime/dex_file_test.cc b/runtime/dex_file_test.cc index 0fec856865..9dca4c0621 100644 --- a/runtime/dex_file_test.cc +++ b/runtime/dex_file_test.cc @@ -338,13 +338,16 @@ TEST_F(DexFileTest, ClassDefs) { ScopedObjectAccess soa(Thread::Current()); std::unique_ptr<const DexFile> raw(OpenTestDexFile("Nested")); ASSERT_TRUE(raw.get() != nullptr); - EXPECT_EQ(2U, raw->NumClassDefs()); + EXPECT_EQ(3U, raw->NumClassDefs()); const DexFile::ClassDef& c0 = raw->GetClassDef(0); - EXPECT_STREQ("LNested$Inner;", raw->GetClassDescriptor(c0)); + EXPECT_STREQ("LNested$1;", raw->GetClassDescriptor(c0)); const DexFile::ClassDef& c1 = raw->GetClassDef(1); - EXPECT_STREQ("LNested;", raw->GetClassDescriptor(c1)); + EXPECT_STREQ("LNested$Inner;", raw->GetClassDescriptor(c1)); + + const DexFile::ClassDef& c2 = raw->GetClassDef(2); + EXPECT_STREQ("LNested;", raw->GetClassDescriptor(c2)); } TEST_F(DexFileTest, GetMethodSignature) { diff --git a/runtime/dexopt_test.cc b/runtime/dexopt_test.cc new file mode 100644 index 0000000000..69c6151d9e --- /dev/null +++ b/runtime/dexopt_test.cc @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2017 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 <string> +#include <vector> + +#include <backtrace/BacktraceMap.h> +#include <gtest/gtest.h> + +#include "common_runtime_test.h" +#include "compiler_callbacks.h" +#include "dex2oat_environment_test.h" +#include "dexopt_test.h" +#include "gc/space/image_space.h" +#include "mem_map.h" + +namespace art { +void DexoptTest::SetUp() { + ReserveImageSpace(); + Dex2oatEnvironmentTest::SetUp(); +} + +void DexoptTest::PreRuntimeCreate() { + std::string error_msg; + ASSERT_TRUE(PreRelocateImage(GetImageLocation(), &error_msg)) << error_msg; + ASSERT_TRUE(PreRelocateImage(GetImageLocation2(), &error_msg)) << error_msg; + UnreserveImageSpace(); +} + +void DexoptTest::PostRuntimeCreate() { + ReserveImageSpace(); +} + +void DexoptTest::GenerateOatForTest(const std::string& dex_location, + const std::string& oat_location, + CompilerFilter::Filter filter, + bool relocate, + bool pic, + bool with_alternate_image) { + std::string dalvik_cache = GetDalvikCache(GetInstructionSetString(kRuntimeISA)); + std::string dalvik_cache_tmp = dalvik_cache + ".redirected"; + + if (!relocate) { + // Temporarily redirect the dalvik cache so dex2oat doesn't find the + // relocated image file. + ASSERT_EQ(0, rename(dalvik_cache.c_str(), dalvik_cache_tmp.c_str())) << strerror(errno); + } + + std::vector<std::string> args; + args.push_back("--dex-file=" + dex_location); + args.push_back("--oat-file=" + oat_location); + args.push_back("--compiler-filter=" + CompilerFilter::NameOfFilter(filter)); + args.push_back("--runtime-arg"); + + // Use -Xnorelocate regardless of the relocate argument. + // We control relocation by redirecting the dalvik cache when needed + // rather than use this flag. + args.push_back("-Xnorelocate"); + + if (pic) { + args.push_back("--compile-pic"); + } + + std::string image_location = GetImageLocation(); + if (with_alternate_image) { + args.push_back("--boot-image=" + GetImageLocation2()); + } + + std::string error_msg; + ASSERT_TRUE(OatFileAssistant::Dex2Oat(args, &error_msg)) << error_msg; + + if (!relocate) { + // Restore the dalvik cache if needed. + ASSERT_EQ(0, rename(dalvik_cache_tmp.c_str(), dalvik_cache.c_str())) << strerror(errno); + } + + // Verify the odex file was generated as expected. + std::unique_ptr<OatFile> odex_file(OatFile::Open(oat_location.c_str(), + oat_location.c_str(), + nullptr, + nullptr, + false, + /*low_4gb*/false, + dex_location.c_str(), + &error_msg)); + ASSERT_TRUE(odex_file.get() != nullptr) << error_msg; + EXPECT_EQ(pic, odex_file->IsPic()); + EXPECT_EQ(filter, odex_file->GetCompilerFilter()); + + std::unique_ptr<ImageHeader> image_header( + gc::space::ImageSpace::ReadImageHeader(image_location.c_str(), + kRuntimeISA, + &error_msg)); + ASSERT_TRUE(image_header != nullptr) << error_msg; + const OatHeader& oat_header = odex_file->GetOatHeader(); + uint32_t combined_checksum = OatFileAssistant::CalculateCombinedImageChecksum(); + + if (CompilerFilter::DependsOnImageChecksum(filter)) { + if (with_alternate_image) { + EXPECT_NE(combined_checksum, oat_header.GetImageFileLocationOatChecksum()); + } else { + EXPECT_EQ(combined_checksum, oat_header.GetImageFileLocationOatChecksum()); + } + } + + if (!with_alternate_image) { + if (CompilerFilter::IsBytecodeCompilationEnabled(filter)) { + if (relocate) { + EXPECT_EQ(reinterpret_cast<uintptr_t>(image_header->GetOatDataBegin()), + oat_header.GetImageFileLocationOatDataBegin()); + EXPECT_EQ(image_header->GetPatchDelta(), oat_header.GetImagePatchDelta()); + } else { + EXPECT_NE(reinterpret_cast<uintptr_t>(image_header->GetOatDataBegin()), + oat_header.GetImageFileLocationOatDataBegin()); + EXPECT_NE(image_header->GetPatchDelta(), oat_header.GetImagePatchDelta()); + } + } + } +} + +void DexoptTest::GenerateOdexForTest(const std::string& dex_location, + const std::string& odex_location, + CompilerFilter::Filter filter) { + GenerateOatForTest(dex_location, + odex_location, + filter, + /*relocate*/false, + /*pic*/false, + /*with_alternate_image*/false); +} + +void DexoptTest::GeneratePicOdexForTest(const std::string& dex_location, + const std::string& odex_location, + CompilerFilter::Filter filter) { + GenerateOatForTest(dex_location, + odex_location, + filter, + /*relocate*/false, + /*pic*/true, + /*with_alternate_image*/false); +} + +void DexoptTest::GenerateOatForTest(const char* dex_location, + CompilerFilter::Filter filter, + bool relocate, + bool pic, + bool with_alternate_image) { + std::string oat_location; + std::string error_msg; + ASSERT_TRUE(OatFileAssistant::DexLocationToOatFilename( + dex_location, kRuntimeISA, &oat_location, &error_msg)) << error_msg; + GenerateOatForTest(dex_location, + oat_location, + filter, + relocate, + pic, + with_alternate_image); +} + +void DexoptTest::GenerateOatForTest(const char* dex_location, CompilerFilter::Filter filter) { + GenerateOatForTest(dex_location, + filter, + /*relocate*/true, + /*pic*/false, + /*with_alternate_image*/false); +} + +bool DexoptTest::PreRelocateImage(const std::string& image_location, std::string* error_msg) { + std::string image; + if (!GetCachedImageFile(image_location, &image, error_msg)) { + return false; + } + + std::string patchoat = GetAndroidRoot(); + patchoat += kIsDebugBuild ? "/bin/patchoatd" : "/bin/patchoat"; + + std::vector<std::string> argv; + argv.push_back(patchoat); + argv.push_back("--input-image-location=" + image_location); + argv.push_back("--output-image-file=" + image); + argv.push_back("--instruction-set=" + std::string(GetInstructionSetString(kRuntimeISA))); + argv.push_back("--base-offset-delta=0x00008000"); + return Exec(argv, error_msg); +} + +void DexoptTest::ReserveImageSpace() { + MemMap::Init(); + + // Ensure a chunk of memory is reserved for the image space. + // The reservation_end includes room for the main space that has to come + // right after the image in case of the GSS collector. + uintptr_t reservation_start = ART_BASE_ADDRESS; + uintptr_t reservation_end = ART_BASE_ADDRESS + 384 * MB; + + std::unique_ptr<BacktraceMap> map(BacktraceMap::Create(getpid(), true)); + ASSERT_TRUE(map.get() != nullptr) << "Failed to build process map"; + for (BacktraceMap::const_iterator it = map->begin(); + reservation_start < reservation_end && it != map->end(); ++it) { + ReserveImageSpaceChunk(reservation_start, std::min(it->start, reservation_end)); + reservation_start = std::max(reservation_start, it->end); + } + ReserveImageSpaceChunk(reservation_start, reservation_end); +} + +void DexoptTest::ReserveImageSpaceChunk(uintptr_t start, uintptr_t end) { + if (start < end) { + std::string error_msg; + image_reservation_.push_back(std::unique_ptr<MemMap>( + MemMap::MapAnonymous("image reservation", + reinterpret_cast<uint8_t*>(start), end - start, + PROT_NONE, false, false, &error_msg))); + ASSERT_TRUE(image_reservation_.back().get() != nullptr) << error_msg; + LOG(INFO) << "Reserved space for image " << + reinterpret_cast<void*>(image_reservation_.back()->Begin()) << "-" << + reinterpret_cast<void*>(image_reservation_.back()->End()); + } +} + +void DexoptTest::UnreserveImageSpace() { + image_reservation_.clear(); +} + +} // namespace art diff --git a/runtime/dexopt_test.h b/runtime/dexopt_test.h new file mode 100644 index 0000000000..5f0eafd8eb --- /dev/null +++ b/runtime/dexopt_test.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2017 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_DEXOPT_TEST_H_ +#define ART_RUNTIME_DEXOPT_TEST_H_ + +#include <string> +#include <vector> + +#include "dex2oat_environment_test.h" + +namespace art { + +class DexoptTest : public Dex2oatEnvironmentTest { + public: + virtual void SetUp() OVERRIDE; + + virtual void PreRuntimeCreate(); + + virtual void PostRuntimeCreate() OVERRIDE; + + // Generate an oat file for the purposes of test. + // The oat file will be generated for dex_location in the given oat_location + // with the following configuration: + // filter - controls the compilation filter + // pic - whether or not the code will be PIC + // relocate - if true, the oat file will be relocated with respect to the + // boot image. Otherwise the oat file will not be relocated. + // with_alternate_image - if true, the oat file will be generated with an + // image checksum different than the current image checksum. + void GenerateOatForTest(const std::string& dex_location, + const std::string& oat_location, + CompilerFilter::Filter filter, + bool relocate, + bool pic, + bool with_alternate_image); + + // Generate a non-PIC odex file for the purposes of test. + // The generated odex file will be un-relocated. + void GenerateOdexForTest(const std::string& dex_location, + const std::string& odex_location, + CompilerFilter::Filter filter); + + void GeneratePicOdexForTest(const std::string& dex_location, + const std::string& odex_location, + CompilerFilter::Filter filter); + + // Generate an oat file for the given dex location in its oat location (under + // the dalvik cache). + void GenerateOatForTest(const char* dex_location, + CompilerFilter::Filter filter, + bool relocate, + bool pic, + bool with_alternate_image); + + // Generate a standard oat file in the oat location. + void GenerateOatForTest(const char* dex_location, CompilerFilter::Filter filter); + + private: + // Pre-Relocate the image to a known non-zero offset so we don't have to + // deal with the runtime randomly relocating the image by 0 and messing up + // the expected results of the tests. + bool PreRelocateImage(const std::string& image_location, std::string* error_msg); + + // Reserve memory around where the image will be loaded so other memory + // won't conflict when it comes time to load the image. + // This can be called with an already loaded image to reserve the space + // around it. + void ReserveImageSpace(); + + // Reserve a chunk of memory for the image space in the given range. + // Only has effect for chunks with a positive number of bytes. + void ReserveImageSpaceChunk(uintptr_t start, uintptr_t end); + + // Unreserve any memory reserved by ReserveImageSpace. This should be called + // before the image is loaded. + void UnreserveImageSpace(); + + std::vector<std::unique_ptr<MemMap>> image_reservation_; +}; + +} // namespace art + +#endif // ART_RUNTIME_DEXOPT_TEST_H_ diff --git a/runtime/entrypoints/entrypoint_utils-inl.h b/runtime/entrypoints/entrypoint_utils-inl.h index ac0ce36016..28aca6c905 100644 --- a/runtime/entrypoints/entrypoint_utils-inl.h +++ b/runtime/entrypoints/entrypoint_utils-inl.h @@ -76,6 +76,10 @@ inline ArtMethod* GetResolvedMethod(ArtMethod* outer_method, // Lookup the declaring class of the inlined method. const DexFile* dex_file = caller->GetDexFile(); const DexFile::MethodId& method_id = dex_file->GetMethodId(method_index); + ArtMethod* inlined_method = caller->GetDexCacheResolvedMethod(method_index, kRuntimePointerSize); + if (inlined_method != nullptr && !inlined_method->IsRuntimeMethod()) { + return inlined_method; + } const char* descriptor = dex_file->StringByTypeIdx(method_id.class_idx_); ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); Thread* self = Thread::Current(); @@ -92,8 +96,7 @@ inline ArtMethod* GetResolvedMethod(ArtMethod* outer_method, const char* method_name = dex_file->GetMethodName(method_id); const Signature signature = dex_file->GetMethodSignature(method_id); - ArtMethod* inlined_method = - klass->FindDeclaredDirectMethod(method_name, signature, kRuntimePointerSize); + inlined_method = klass->FindDeclaredDirectMethod(method_name, signature, kRuntimePointerSize); if (inlined_method == nullptr) { inlined_method = klass->FindDeclaredVirtualMethod(method_name, signature, kRuntimePointerSize); if (inlined_method == nullptr) { @@ -103,6 +106,7 @@ inline ArtMethod* GetResolvedMethod(ArtMethod* outer_method, << "This must be due to duplicate classes or playing wrongly with class loaders"; } } + caller->SetDexCacheResolvedMethod(method_index, inlined_method, kRuntimePointerSize); return inlined_method; } diff --git a/runtime/entrypoints/entrypoint_utils.cc b/runtime/entrypoints/entrypoint_utils.cc index 25fd727968..06c11f5101 100644 --- a/runtime/entrypoints/entrypoint_utils.cc +++ b/runtime/entrypoints/entrypoint_utils.cc @@ -254,5 +254,10 @@ CallerAndOuterMethod GetCalleeSaveMethodCallerAndOuterMethod(Thread* self, return result; } +ArtMethod* GetCalleeSaveOuterMethod(Thread* self, Runtime::CalleeSaveType type) { + ScopedAssertNoThreadSuspension ants(__FUNCTION__); + ArtMethod** sp = self->GetManagedStack()->GetTopQuickFrame(); + return DoGetCalleeSaveMethodOuterCallerAndPc(sp, type).first; +} } // namespace art diff --git a/runtime/entrypoints/entrypoint_utils.h b/runtime/entrypoints/entrypoint_utils.h index 6a04f20f3a..69ee3ebe75 100644 --- a/runtime/entrypoints/entrypoint_utils.h +++ b/runtime/entrypoints/entrypoint_utils.h @@ -191,6 +191,9 @@ CallerAndOuterMethod GetCalleeSaveMethodCallerAndOuterMethod(Thread* self, Runtime::CalleeSaveType type) REQUIRES_SHARED(Locks::mutator_lock_); +ArtMethod* GetCalleeSaveOuterMethod(Thread* self, Runtime::CalleeSaveType type) + REQUIRES_SHARED(Locks::mutator_lock_); + } // namespace art #endif // ART_RUNTIME_ENTRYPOINTS_ENTRYPOINT_UTILS_H_ diff --git a/runtime/entrypoints/quick/quick_alloc_entrypoints.cc b/runtime/entrypoints/quick/quick_alloc_entrypoints.cc index e9f09b2bd9..582f0cff48 100644 --- a/runtime/entrypoints/quick/quick_alloc_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_alloc_entrypoints.cc @@ -129,29 +129,34 @@ GENERATE_ENTRYPOINTS_FOR_ALLOCATOR(RegionTLAB, gc::kAllocatorTypeRegionTLAB) #define GENERATE_ENTRYPOINTS(suffix) \ extern "C" void* art_quick_alloc_array_resolved##suffix(mirror::Class* klass, int32_t); \ +extern "C" void* art_quick_alloc_array_resolved8##suffix(mirror::Class* klass, int32_t); \ +extern "C" void* art_quick_alloc_array_resolved16##suffix(mirror::Class* klass, int32_t); \ +extern "C" void* art_quick_alloc_array_resolved32##suffix(mirror::Class* klass, int32_t); \ +extern "C" void* art_quick_alloc_array_resolved64##suffix(mirror::Class* klass, int32_t); \ extern "C" void* art_quick_alloc_object_resolved##suffix(mirror::Class* klass); \ extern "C" void* art_quick_alloc_object_initialized##suffix(mirror::Class* klass); \ extern "C" void* art_quick_alloc_object_with_checks##suffix(mirror::Class* klass); \ -extern "C" void* art_quick_check_and_alloc_array##suffix(uint32_t, int32_t, ArtMethod* ref); \ -extern "C" void* art_quick_check_and_alloc_array_with_access_check##suffix(uint32_t, int32_t, ArtMethod* ref); \ extern "C" void* art_quick_alloc_string_from_bytes##suffix(void*, int32_t, int32_t, int32_t); \ extern "C" void* art_quick_alloc_string_from_chars##suffix(int32_t, int32_t, void*); \ extern "C" void* art_quick_alloc_string_from_string##suffix(void*); \ -extern "C" void* art_quick_alloc_array##suffix##_instrumented(uint32_t, int32_t, ArtMethod* ref); \ extern "C" void* art_quick_alloc_array_resolved##suffix##_instrumented(mirror::Class* klass, int32_t); \ -extern "C" void* art_quick_alloc_array_with_access_check##suffix##_instrumented(uint32_t, int32_t, ArtMethod* ref); \ -extern "C" void* art_quick_alloc_object##suffix##_instrumented(uint32_t type_idx, ArtMethod* ref); \ +extern "C" void* art_quick_alloc_array_resolved8##suffix##_instrumented(mirror::Class* klass, int32_t); \ +extern "C" void* art_quick_alloc_array_resolved16##suffix##_instrumented(mirror::Class* klass, int32_t); \ +extern "C" void* art_quick_alloc_array_resolved32##suffix##_instrumented(mirror::Class* klass, int32_t); \ +extern "C" void* art_quick_alloc_array_resolved64##suffix##_instrumented(mirror::Class* klass, int32_t); \ extern "C" void* art_quick_alloc_object_resolved##suffix##_instrumented(mirror::Class* klass); \ extern "C" void* art_quick_alloc_object_initialized##suffix##_instrumented(mirror::Class* klass); \ extern "C" void* art_quick_alloc_object_with_checks##suffix##_instrumented(mirror::Class* klass); \ -extern "C" void* art_quick_check_and_alloc_array##suffix##_instrumented(uint32_t, int32_t, ArtMethod* ref); \ -extern "C" void* art_quick_check_and_alloc_array_with_access_check##suffix##_instrumented(uint32_t, int32_t, ArtMethod* ref); \ extern "C" void* art_quick_alloc_string_from_bytes##suffix##_instrumented(void*, int32_t, int32_t, int32_t); \ extern "C" void* art_quick_alloc_string_from_chars##suffix##_instrumented(int32_t, int32_t, void*); \ extern "C" void* art_quick_alloc_string_from_string##suffix##_instrumented(void*); \ void SetQuickAllocEntryPoints##suffix(QuickEntryPoints* qpoints, bool instrumented) { \ if (instrumented) { \ qpoints->pAllocArrayResolved = art_quick_alloc_array_resolved##suffix##_instrumented; \ + qpoints->pAllocArrayResolved8 = art_quick_alloc_array_resolved8##suffix##_instrumented; \ + qpoints->pAllocArrayResolved16 = art_quick_alloc_array_resolved16##suffix##_instrumented; \ + qpoints->pAllocArrayResolved32 = art_quick_alloc_array_resolved32##suffix##_instrumented; \ + qpoints->pAllocArrayResolved64 = art_quick_alloc_array_resolved64##suffix##_instrumented; \ qpoints->pAllocObjectResolved = art_quick_alloc_object_resolved##suffix##_instrumented; \ qpoints->pAllocObjectInitialized = art_quick_alloc_object_initialized##suffix##_instrumented; \ qpoints->pAllocObjectWithChecks = art_quick_alloc_object_with_checks##suffix##_instrumented; \ @@ -160,6 +165,10 @@ void SetQuickAllocEntryPoints##suffix(QuickEntryPoints* qpoints, bool instrument qpoints->pAllocStringFromString = art_quick_alloc_string_from_string##suffix##_instrumented; \ } else { \ qpoints->pAllocArrayResolved = art_quick_alloc_array_resolved##suffix; \ + qpoints->pAllocArrayResolved8 = art_quick_alloc_array_resolved8##suffix; \ + qpoints->pAllocArrayResolved16 = art_quick_alloc_array_resolved16##suffix; \ + qpoints->pAllocArrayResolved32 = art_quick_alloc_array_resolved32##suffix; \ + qpoints->pAllocArrayResolved64 = art_quick_alloc_array_resolved64##suffix; \ qpoints->pAllocObjectResolved = art_quick_alloc_object_resolved##suffix; \ qpoints->pAllocObjectInitialized = art_quick_alloc_object_initialized##suffix; \ qpoints->pAllocObjectWithChecks = art_quick_alloc_object_with_checks##suffix; \ diff --git a/runtime/entrypoints/quick/quick_entrypoints_list.h b/runtime/entrypoints/quick/quick_entrypoints_list.h index 22b0f921c7..e0a2e3cf8f 100644 --- a/runtime/entrypoints/quick/quick_entrypoints_list.h +++ b/runtime/entrypoints/quick/quick_entrypoints_list.h @@ -21,6 +21,10 @@ #define QUICK_ENTRYPOINT_LIST(V) \ V(AllocArrayResolved, void*, mirror::Class*, int32_t) \ + V(AllocArrayResolved8, void*, mirror::Class*, int32_t) \ + V(AllocArrayResolved16, void*, mirror::Class*, int32_t) \ + V(AllocArrayResolved32, void*, mirror::Class*, int32_t) \ + V(AllocArrayResolved64, void*, mirror::Class*, int32_t) \ V(AllocObjectResolved, void*, mirror::Class*) \ V(AllocObjectInitialized, void*, mirror::Class*) \ V(AllocObjectWithChecks, void*, mirror::Class*) \ diff --git a/runtime/entrypoints/quick/quick_field_entrypoints.cc b/runtime/entrypoints/quick/quick_field_entrypoints.cc index 6d17000dd8..4544aef148 100644 --- a/runtime/entrypoints/quick/quick_field_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_field_entrypoints.cc @@ -55,261 +55,207 @@ ALWAYS_INLINE static inline ArtField* FindInstanceField(uint32_t field_idx, return field; } -extern "C" ssize_t artGetByteStaticFromCode(uint32_t field_idx, ArtMethod* referrer, Thread* self) - REQUIRES_SHARED(Locks::mutator_lock_) { - ScopedQuickEntrypointChecks sqec(self); - ArtField* field = FindFieldFast(field_idx, referrer, StaticPrimitiveRead, sizeof(int8_t)); - if (LIKELY(field != nullptr)) { - return field->GetByte(field->GetDeclaringClass()); - } - field = FindFieldFromCode<StaticPrimitiveRead, true>(field_idx, referrer, self, sizeof(int8_t)); - if (LIKELY(field != nullptr)) { - return field->GetByte(field->GetDeclaringClass()); - } - return 0; // Will throw exception by checking with Thread::Current. -} - -extern "C" size_t artGetBooleanStaticFromCode(uint32_t field_idx, ArtMethod* referrer, Thread* self) - REQUIRES_SHARED(Locks::mutator_lock_) { - ScopedQuickEntrypointChecks sqec(self); - ArtField* field = FindFieldFast(field_idx, referrer, StaticPrimitiveRead, sizeof(int8_t)); - if (LIKELY(field != nullptr)) { - return field->GetBoolean(field->GetDeclaringClass()); - } - field = FindFieldFromCode<StaticPrimitiveRead, true>(field_idx, referrer, self, sizeof(int8_t)); - if (LIKELY(field != nullptr)) { - return field->GetBoolean(field->GetDeclaringClass()); - } - return 0; // Will throw exception by checking with Thread::Current. -} - -extern "C" ssize_t artGetShortStaticFromCode(uint32_t field_idx, ArtMethod* referrer, Thread* self) - REQUIRES_SHARED(Locks::mutator_lock_) { - ScopedQuickEntrypointChecks sqec(self); - ArtField* field = FindFieldFast(field_idx, referrer, StaticPrimitiveRead, sizeof(int16_t)); - if (LIKELY(field != nullptr)) { - return field->GetShort(field->GetDeclaringClass()); - } - field = FindFieldFromCode<StaticPrimitiveRead, true>(field_idx, referrer, self, sizeof(int16_t)); - if (LIKELY(field != nullptr)) { - return field->GetShort(field->GetDeclaringClass()); - } - return 0; // Will throw exception by checking with Thread::Current. -} - -extern "C" size_t artGetCharStaticFromCode(uint32_t field_idx, ArtMethod* referrer, Thread* self) - REQUIRES_SHARED(Locks::mutator_lock_) { - ScopedQuickEntrypointChecks sqec(self); - ArtField* field = FindFieldFast(field_idx, referrer, StaticPrimitiveRead, sizeof(int16_t)); - if (LIKELY(field != nullptr)) { - return field->GetChar(field->GetDeclaringClass()); - } - field = FindFieldFromCode<StaticPrimitiveRead, true>(field_idx, referrer, self, sizeof(int16_t)); - if (LIKELY(field != nullptr)) { - return field->GetChar(field->GetDeclaringClass()); - } - return 0; // Will throw exception by checking with Thread::Current. -} - -extern "C" size_t artGet32StaticFromCode(uint32_t field_idx, ArtMethod* referrer, Thread* self) - REQUIRES_SHARED(Locks::mutator_lock_) { - ScopedQuickEntrypointChecks sqec(self); - ArtField* field = FindFieldFast(field_idx, referrer, StaticPrimitiveRead, sizeof(int32_t)); - if (LIKELY(field != nullptr)) { - return field->Get32(field->GetDeclaringClass()); - } - field = FindFieldFromCode<StaticPrimitiveRead, true>(field_idx, referrer, self, sizeof(int32_t)); - if (LIKELY(field != nullptr)) { - return field->Get32(field->GetDeclaringClass()); - } - return 0; // Will throw exception by checking with Thread::Current. -} - -extern "C" uint64_t artGet64StaticFromCode(uint32_t field_idx, - ArtMethod* referrer, - Thread* self) - REQUIRES_SHARED(Locks::mutator_lock_) { - ScopedQuickEntrypointChecks sqec(self); - ArtField* field = FindFieldFast(field_idx, referrer, StaticPrimitiveRead, sizeof(int64_t)); - if (LIKELY(field != nullptr)) { - return field->Get64(field->GetDeclaringClass()); - } - field = FindFieldFromCode<StaticPrimitiveRead, true>(field_idx, referrer, self, sizeof(int64_t)); - if (LIKELY(field != nullptr)) { - return field->Get64(field->GetDeclaringClass()); +static ArtMethod* GetReferrer(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) { + if (kIsDebugBuild) { + // stub_test doesn't call this code with a proper frame, so get the outer, and if + // it does not have compiled code return it. + ArtMethod* outer = GetCalleeSaveOuterMethod(self, Runtime::kSaveRefsOnly); + if (outer->GetEntryPointFromQuickCompiledCode() == nullptr) { + return outer; + } } - return 0; // Will throw exception by checking with Thread::Current. + return GetCalleeSaveMethodCallerAndOuterMethod(self, Runtime::kSaveRefsOnly).caller; } -extern "C" mirror::Object* artGetObjStaticFromCode(uint32_t field_idx, - ArtMethod* referrer, - Thread* self) +#define ART_GET_FIELD_FROM_CODE(Kind, PrimitiveType, RetType, SetType, \ + PrimitiveOrObject, IsObject, Ptr) \ + extern "C" RetType artGet ## Kind ## StaticFromCode(uint32_t field_idx, \ + ArtMethod* referrer, \ + Thread* self) \ + REQUIRES_SHARED(Locks::mutator_lock_) { \ + ScopedQuickEntrypointChecks sqec(self); \ + ArtField* field = FindFieldFast( \ + field_idx, referrer, Static ## PrimitiveOrObject ## Read, \ + sizeof(PrimitiveType)); \ + if (LIKELY(field != nullptr)) { \ + return field->Get ## Kind (field->GetDeclaringClass())Ptr; \ + } \ + field = FindFieldFromCode<Static ## PrimitiveOrObject ## Read, true>( \ + field_idx, referrer, self, sizeof(PrimitiveType)); \ + if (LIKELY(field != nullptr)) { \ + return field->Get ## Kind (field->GetDeclaringClass())Ptr; \ + } \ + /* Will throw exception by checking with Thread::Current. */ \ + return 0; \ + } \ + \ + extern "C" RetType artGet ## Kind ## InstanceFromCode(uint32_t field_idx, \ + mirror::Object* obj, \ + ArtMethod* referrer, \ + Thread* self) \ + REQUIRES_SHARED(Locks::mutator_lock_) { \ + ScopedQuickEntrypointChecks sqec(self); \ + ArtField* field = FindFieldFast( \ + field_idx, referrer, Instance ## PrimitiveOrObject ## Read, \ + sizeof(PrimitiveType)); \ + if (LIKELY(field != nullptr) && obj != nullptr) { \ + return field->Get ## Kind (obj)Ptr; \ + } \ + field = FindInstanceField<Instance ## PrimitiveOrObject ## Read, true>( \ + field_idx, referrer, self, sizeof(PrimitiveType), &obj); \ + if (LIKELY(field != nullptr)) { \ + return field->Get ## Kind (obj)Ptr; \ + } \ + /* Will throw exception by checking with Thread::Current. */ \ + return 0; \ + } \ + \ + extern "C" int artSet ## Kind ## StaticFromCode(uint32_t field_idx, \ + SetType new_value, \ + ArtMethod* referrer, \ + Thread* self) \ + REQUIRES_SHARED(Locks::mutator_lock_) { \ + ScopedQuickEntrypointChecks sqec(self); \ + ArtField* field = FindFieldFast( \ + field_idx, referrer, Static ## PrimitiveOrObject ## Write, \ + sizeof(PrimitiveType)); \ + if (LIKELY(field != nullptr)) { \ + field->Set ## Kind <false>(field->GetDeclaringClass(), new_value); \ + return 0; \ + } \ + if (IsObject) { \ + StackHandleScope<1> hs(self); \ + HandleWrapper<mirror::Object> h_obj(hs.NewHandleWrapper( \ + reinterpret_cast<mirror::Object**>(&new_value))); \ + field = FindFieldFromCode<Static ## PrimitiveOrObject ## Write, true>( \ + field_idx, referrer, self, sizeof(PrimitiveType)); \ + } else { \ + field = FindFieldFromCode<Static ## PrimitiveOrObject ## Write, true>( \ + field_idx, referrer, self, sizeof(PrimitiveType)); \ + } \ + if (LIKELY(field != nullptr)) { \ + field->Set ## Kind <false>(field->GetDeclaringClass(), new_value); \ + return 0; \ + } \ + return -1; \ + } \ + \ + extern "C" int artSet ## Kind ## InstanceFromCode(uint32_t field_idx, \ + mirror::Object* obj, \ + SetType new_value, \ + ArtMethod* referrer, \ + Thread* self) \ + REQUIRES_SHARED(Locks::mutator_lock_) { \ + ScopedQuickEntrypointChecks sqec(self); \ + ArtField* field = FindFieldFast( \ + field_idx, referrer, Instance ## PrimitiveOrObject ## Write, \ + sizeof(PrimitiveType)); \ + if (LIKELY(field != nullptr && obj != nullptr)) { \ + field->Set ## Kind <false>(obj, new_value); \ + return 0; \ + } \ + if (IsObject) { \ + StackHandleScope<1> hs(self); \ + HandleWrapper<mirror::Object> h_obj(hs.NewHandleWrapper( \ + reinterpret_cast<mirror::Object**>(&new_value))); \ + field = FindInstanceField<Instance ## PrimitiveOrObject ## Write, true>( \ + field_idx, \ + referrer, \ + self, \ + sizeof(PrimitiveType), \ + &obj); \ + } else { \ + field = FindInstanceField<Instance ## PrimitiveOrObject ## Write, true>( \ + field_idx, \ + referrer, \ + self, \ + sizeof(PrimitiveType), \ + &obj); \ + } \ + if (LIKELY(field != nullptr)) { \ + field->Set ## Kind<false>(obj, new_value); \ + return 0; \ + } \ + return -1; \ + } \ + \ + extern "C" RetType artGet ## Kind ## StaticFromCompiledCode( \ + uint32_t field_idx, \ + Thread* self) \ + REQUIRES_SHARED(Locks::mutator_lock_) { \ + return artGet ## Kind ## StaticFromCode( \ + field_idx, GetReferrer(self), self); \ + } \ + \ + extern "C" RetType artGet ## Kind ## InstanceFromCompiledCode( \ + uint32_t field_idx, \ + mirror::Object* obj, \ + Thread* self) \ + REQUIRES_SHARED(Locks::mutator_lock_) { \ + return artGet ## Kind ## InstanceFromCode( \ + field_idx, obj, GetReferrer(self), self); \ + } \ + \ + extern "C" int artSet ## Kind ## StaticFromCompiledCode( \ + uint32_t field_idx, \ + SetType new_value, \ + Thread* self) \ + REQUIRES_SHARED(Locks::mutator_lock_) { \ + return artSet ## Kind ## StaticFromCode( \ + field_idx, new_value, GetReferrer(self), self); \ + } \ + \ + extern "C" int artSet ## Kind ## InstanceFromCompiledCode( \ + uint32_t field_idx, \ + mirror::Object* obj, \ + SetType new_value, \ + Thread* self) \ + REQUIRES_SHARED(Locks::mutator_lock_) { \ + return artSet ## Kind ## InstanceFromCode( \ + field_idx, obj, new_value, GetReferrer(self), self); \ + } + +ART_GET_FIELD_FROM_CODE(Byte, int8_t, ssize_t, uint32_t, Primitive, false, ) +ART_GET_FIELD_FROM_CODE(Boolean, int8_t, size_t, uint32_t, Primitive, false, ) +ART_GET_FIELD_FROM_CODE(Short, int16_t, ssize_t, uint16_t, Primitive, false, ) +ART_GET_FIELD_FROM_CODE(Char, int16_t, size_t, uint16_t, Primitive, false, ) +ART_GET_FIELD_FROM_CODE(32, int32_t, size_t, uint32_t, Primitive, false, ) +ART_GET_FIELD_FROM_CODE(64, int64_t, uint64_t, uint64_t, Primitive, false, ) +ART_GET_FIELD_FROM_CODE(Obj, mirror::HeapReference<mirror::Object>, mirror::Object*, + mirror::Object*, Object, true, .Ptr()) + + +// To cut on the number of entrypoints, we have shared entries for +// byte/boolean and char/short for setting an instance or static field. We just +// forward those to the unsigned variant. +extern "C" int artSet8StaticFromCompiledCode(uint32_t field_idx, + uint32_t new_value, + Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) { - ScopedQuickEntrypointChecks sqec(self); - ArtField* field = FindFieldFast(field_idx, - referrer, - StaticObjectRead, - sizeof(mirror::HeapReference<mirror::Object>)); - if (LIKELY(field != nullptr)) { - return field->GetObj(field->GetDeclaringClass()).Ptr(); - } - field = FindFieldFromCode<StaticObjectRead, true>(field_idx, - referrer, - self, - sizeof(mirror::HeapReference<mirror::Object>)); - if (LIKELY(field != nullptr)) { - return field->GetObj(field->GetDeclaringClass()).Ptr(); - } - return nullptr; // Will throw exception by checking with Thread::Current. + return artSetBooleanStaticFromCode(field_idx, new_value, GetReferrer(self), self); } -extern "C" ssize_t artGetByteInstanceFromCode(uint32_t field_idx, - mirror::Object* obj, - ArtMethod* referrer, +extern "C" int artSet16StaticFromCompiledCode(uint32_t field_idx, + uint16_t new_value, Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) { - ScopedQuickEntrypointChecks sqec(self); - ArtField* field = FindFieldFast(field_idx, referrer, InstancePrimitiveRead, sizeof(int8_t)); - if (LIKELY(field != nullptr && obj != nullptr)) { - return field->GetByte(obj); - } - field = FindInstanceField<InstancePrimitiveRead, true>(field_idx, - referrer, - self, - sizeof(int8_t), - &obj); - if (LIKELY(field != nullptr)) { - return field->GetByte(obj); - } - return 0; // Will throw exception by checking with Thread::Current. + return artSetCharStaticFromCode(field_idx, new_value, GetReferrer(self), self); } -extern "C" size_t artGetBooleanInstanceFromCode(uint32_t field_idx, - mirror::Object* obj, - ArtMethod* referrer, - Thread* self) - REQUIRES_SHARED(Locks::mutator_lock_) { - ScopedQuickEntrypointChecks sqec(self); - ArtField* field = FindFieldFast(field_idx, referrer, InstancePrimitiveRead, sizeof(int8_t)); - if (LIKELY(field != nullptr && obj != nullptr)) { - return field->GetBoolean(obj); - } - field = FindInstanceField<InstancePrimitiveRead, true>(field_idx, - referrer, - self, - sizeof(int8_t), - &obj); - if (LIKELY(field != nullptr)) { - return field->GetBoolean(obj); - } - return 0; // Will throw exception by checking with Thread::Current. -} -extern "C" ssize_t artGetShortInstanceFromCode(uint32_t field_idx, +extern "C" int artSet8InstanceFromCompiledCode(uint32_t field_idx, mirror::Object* obj, - ArtMethod* referrer, + uint8_t new_value, Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) { - ScopedQuickEntrypointChecks sqec(self); - ArtField* field = FindFieldFast(field_idx, referrer, InstancePrimitiveRead, sizeof(int16_t)); - if (LIKELY(field != nullptr && obj != nullptr)) { - return field->GetShort(obj); - } - field = FindInstanceField<InstancePrimitiveRead, true>(field_idx, - referrer, - self, - sizeof(int16_t), - &obj); - if (LIKELY(field != nullptr)) { - return field->GetShort(obj); - } - return 0; // Will throw exception by checking with Thread::Current. -} - -extern "C" size_t artGetCharInstanceFromCode(uint32_t field_idx, - mirror::Object* obj, - ArtMethod* referrer, - Thread* self) - REQUIRES_SHARED(Locks::mutator_lock_) { - ScopedQuickEntrypointChecks sqec(self); - ArtField* field = FindFieldFast(field_idx, referrer, InstancePrimitiveRead, sizeof(int16_t)); - if (LIKELY(field != nullptr && obj != nullptr)) { - return field->GetChar(obj); - } - field = FindInstanceField<InstancePrimitiveRead, true>(field_idx, - referrer, - self, - sizeof(int16_t), - &obj); - if (LIKELY(field != nullptr)) { - return field->GetChar(obj); - } - return 0; // Will throw exception by checking with Thread::Current. + return artSetBooleanInstanceFromCode(field_idx, obj, new_value, GetReferrer(self), self); } -extern "C" size_t artGet32InstanceFromCode(uint32_t field_idx, - mirror::Object* obj, - ArtMethod* referrer, - Thread* self) - REQUIRES_SHARED(Locks::mutator_lock_) { - ScopedQuickEntrypointChecks sqec(self); - ArtField* field = FindFieldFast(field_idx, referrer, InstancePrimitiveRead, sizeof(int32_t)); - if (LIKELY(field != nullptr && obj != nullptr)) { - return field->Get32(obj); - } - field = FindInstanceField<InstancePrimitiveRead, true>(field_idx, - referrer, - self, - sizeof(int32_t), - &obj); - if (LIKELY(field != nullptr)) { - return field->Get32(obj); - } - return 0; // Will throw exception by checking with Thread::Current. -} - -extern "C" uint64_t artGet64InstanceFromCode(uint32_t field_idx, - mirror::Object* obj, - ArtMethod* referrer, - Thread* self) - REQUIRES_SHARED(Locks::mutator_lock_) { - ScopedQuickEntrypointChecks sqec(self); - ArtField* field = FindFieldFast(field_idx, referrer, InstancePrimitiveRead, sizeof(int64_t)); - if (LIKELY(field != nullptr && obj != nullptr)) { - return field->Get64(obj); - } - field = FindInstanceField<InstancePrimitiveRead, true>(field_idx, - referrer, - self, - sizeof(int64_t), - &obj); - if (LIKELY(field != nullptr)) { - return field->Get64(obj); - } - return 0; // Will throw exception by checking with Thread::Current. -} - -extern "C" mirror::Object* artGetObjInstanceFromCode(uint32_t field_idx, - mirror::Object* obj, - ArtMethod* referrer, - Thread* self) +extern "C" int artSet16InstanceFromCompiledCode(uint32_t field_idx, + mirror::Object* obj, + uint16_t new_value, + Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) { - ScopedQuickEntrypointChecks sqec(self); - ArtField* field = FindFieldFast(field_idx, - referrer, - InstanceObjectRead, - sizeof(mirror::HeapReference<mirror::Object>)); - if (LIKELY(field != nullptr && obj != nullptr)) { - return field->GetObj(obj).Ptr(); - } - field = FindInstanceField<InstanceObjectRead, true>(field_idx, - referrer, - self, - sizeof(mirror::HeapReference<mirror::Object>), - &obj); - if (LIKELY(field != nullptr)) { - return field->GetObj(obj).Ptr(); - } - return nullptr; // Will throw exception by checking with Thread::Current. + return artSetCharInstanceFromCode(field_idx, obj, new_value, GetReferrer(self), self); } extern "C" int artSet8StaticFromCode(uint32_t field_idx, @@ -317,32 +263,7 @@ extern "C" int artSet8StaticFromCode(uint32_t field_idx, ArtMethod* referrer, Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) { - ScopedQuickEntrypointChecks sqec(self); - ArtField* field = FindFieldFast(field_idx, referrer, StaticPrimitiveWrite, sizeof(int8_t)); - if (LIKELY(field != nullptr)) { - Primitive::Type type = field->GetTypeAsPrimitiveType(); - // Compiled code can't use transactional mode. - if (type == Primitive::kPrimBoolean) { - field->SetBoolean<false>(field->GetDeclaringClass(), new_value); - } else { - DCHECK_EQ(Primitive::kPrimByte, type); - field->SetByte<false>(field->GetDeclaringClass(), new_value); - } - return 0; // success - } - field = FindFieldFromCode<StaticPrimitiveWrite, true>(field_idx, referrer, self, sizeof(int8_t)); - if (LIKELY(field != nullptr)) { - Primitive::Type type = field->GetTypeAsPrimitiveType(); - // Compiled code can't use transactional mode. - if (type == Primitive::kPrimBoolean) { - field->SetBoolean<false>(field->GetDeclaringClass(), new_value); - } else { - DCHECK_EQ(Primitive::kPrimByte, type); - field->SetByte<false>(field->GetDeclaringClass(), new_value); - } - return 0; // success - } - return -1; // failure + return artSetBooleanStaticFromCode(field_idx, new_value, referrer, self); } extern "C" int artSet16StaticFromCode(uint32_t field_idx, @@ -350,108 +271,7 @@ extern "C" int artSet16StaticFromCode(uint32_t field_idx, ArtMethod* referrer, Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) { - ScopedQuickEntrypointChecks sqec(self); - ArtField* field = FindFieldFast(field_idx, referrer, StaticPrimitiveWrite, sizeof(int16_t)); - if (LIKELY(field != nullptr)) { - Primitive::Type type = field->GetTypeAsPrimitiveType(); - // Compiled code can't use transactional mode. - if (type == Primitive::kPrimChar) { - field->SetChar<false>(field->GetDeclaringClass(), new_value); - } else { - DCHECK_EQ(Primitive::kPrimShort, type); - field->SetShort<false>(field->GetDeclaringClass(), new_value); - } - return 0; // success - } - field = FindFieldFromCode<StaticPrimitiveWrite, true>(field_idx, referrer, self, sizeof(int16_t)); - if (LIKELY(field != nullptr)) { - Primitive::Type type = field->GetTypeAsPrimitiveType(); - // Compiled code can't use transactional mode. - if (type == Primitive::kPrimChar) { - field->SetChar<false>(field->GetDeclaringClass(), new_value); - } else { - DCHECK_EQ(Primitive::kPrimShort, type); - field->SetShort<false>(field->GetDeclaringClass(), new_value); - } - return 0; // success - } - return -1; // failure -} - -extern "C" int artSet32StaticFromCode(uint32_t field_idx, - uint32_t new_value, - ArtMethod* referrer, - Thread* self) - REQUIRES_SHARED(Locks::mutator_lock_) { - ScopedQuickEntrypointChecks sqec(self); - ArtField* field = FindFieldFast(field_idx, referrer, StaticPrimitiveWrite, sizeof(int32_t)); - if (LIKELY(field != nullptr)) { - // Compiled code can't use transactional mode. - field->Set32<false>(field->GetDeclaringClass(), new_value); - return 0; // success - } - field = FindFieldFromCode<StaticPrimitiveWrite, true>(field_idx, referrer, self, sizeof(int32_t)); - if (LIKELY(field != nullptr)) { - // Compiled code can't use transactional mode. - field->Set32<false>(field->GetDeclaringClass(), new_value); - return 0; // success - } - return -1; // failure -} - -extern "C" int artSet64StaticFromCode(uint32_t field_idx, - ArtMethod* referrer, - uint64_t new_value, - Thread* self) - REQUIRES_SHARED(Locks::mutator_lock_) { - ScopedQuickEntrypointChecks sqec(self); - ArtField* field = FindFieldFast(field_idx, referrer, StaticPrimitiveWrite, sizeof(int64_t)); - if (LIKELY(field != nullptr)) { - // Compiled code can't use transactional mode. - field->Set64<false>(field->GetDeclaringClass(), new_value); - return 0; // success - } - field = FindFieldFromCode<StaticPrimitiveWrite, true>(field_idx, referrer, self, sizeof(int64_t)); - if (LIKELY(field != nullptr)) { - // Compiled code can't use transactional mode. - field->Set64<false>(field->GetDeclaringClass(), new_value); - return 0; // success - } - return -1; // failure -} - -extern "C" int artSetObjStaticFromCode(uint32_t field_idx, - mirror::Object* new_value, - ArtMethod* referrer, - Thread* self) - REQUIRES_SHARED(Locks::mutator_lock_) { - ScopedQuickEntrypointChecks sqec(self); - ArtField* field = FindFieldFast(field_idx, - referrer, - StaticObjectWrite, - sizeof(mirror::HeapReference<mirror::Object>)); - if (LIKELY(field != nullptr)) { - if (LIKELY(!field->IsPrimitiveType())) { - // Compiled code can't use transactional mode. - field->SetObj<false>(field->GetDeclaringClass(), new_value); - return 0; // success - } - } - { - StackHandleScope<1> hs(self); - HandleWrapper<mirror::Object> h_obj(hs.NewHandleWrapper(&new_value)); - field = FindFieldFromCode<StaticObjectWrite, true>( - field_idx, - referrer, - self, - sizeof(mirror::HeapReference<mirror::Object>)); - } - if (LIKELY(field != nullptr)) { - // Compiled code can't use transactional mode. - field->SetObj<false>(field->GetDeclaringClass(), new_value); - return 0; // success - } - return -1; // failure + return artSetCharStaticFromCode(field_idx, new_value, referrer, self); } extern "C" int artSet8InstanceFromCode(uint32_t field_idx, @@ -460,35 +280,7 @@ extern "C" int artSet8InstanceFromCode(uint32_t field_idx, ArtMethod* referrer, Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) { - ScopedQuickEntrypointChecks sqec(self); - ArtField* field = FindFieldFast(field_idx, referrer, InstancePrimitiveWrite, sizeof(int8_t)); - if (LIKELY(field != nullptr && obj != nullptr)) { - Primitive::Type type = field->GetTypeAsPrimitiveType(); - // Compiled code can't use transactional mode. - if (type == Primitive::kPrimBoolean) { - field->SetBoolean<false>(obj, new_value); - } else { - DCHECK_EQ(Primitive::kPrimByte, type); - field->SetByte<false>(obj, new_value); - } - return 0; // success - } - field = FindInstanceField<InstancePrimitiveWrite, true>(field_idx, - referrer, - self, - sizeof(int8_t), - &obj); - if (LIKELY(field != nullptr)) { - Primitive::Type type = field->GetTypeAsPrimitiveType(); - // Compiled code can't use transactional mode. - if (type == Primitive::kPrimBoolean) { - field->SetBoolean<false>(obj, new_value); - } else { - field->SetByte<false>(obj, new_value); - } - return 0; // success - } - return -1; // failure + return artSetBooleanInstanceFromCode(field_idx, obj, new_value, referrer, self); } extern "C" int artSet16InstanceFromCode(uint32_t field_idx, @@ -497,126 +289,7 @@ extern "C" int artSet16InstanceFromCode(uint32_t field_idx, ArtMethod* referrer, Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) { - ScopedQuickEntrypointChecks sqec(self); - ArtField* field = FindFieldFast(field_idx, referrer, InstancePrimitiveWrite, sizeof(int16_t)); - if (LIKELY(field != nullptr && obj != nullptr)) { - Primitive::Type type = field->GetTypeAsPrimitiveType(); - // Compiled code can't use transactional mode. - if (type == Primitive::kPrimChar) { - field->SetChar<false>(obj, new_value); - } else { - DCHECK_EQ(Primitive::kPrimShort, type); - field->SetShort<false>(obj, new_value); - } - return 0; // success - } - field = FindInstanceField<InstancePrimitiveWrite, true>(field_idx, - referrer, - self, - sizeof(int16_t), - &obj); - if (LIKELY(field != nullptr)) { - Primitive::Type type = field->GetTypeAsPrimitiveType(); - // Compiled code can't use transactional mode. - if (type == Primitive::kPrimChar) { - field->SetChar<false>(obj, new_value); - } else { - DCHECK_EQ(Primitive::kPrimShort, type); - field->SetShort<false>(obj, new_value); - } - return 0; // success - } - return -1; // failure -} - -extern "C" int artSet32InstanceFromCode(uint32_t field_idx, - mirror::Object* obj, - uint32_t new_value, - ArtMethod* referrer, - Thread* self) - REQUIRES_SHARED(Locks::mutator_lock_) { - ScopedQuickEntrypointChecks sqec(self); - ArtField* field = FindFieldFast(field_idx, referrer, InstancePrimitiveWrite, sizeof(int32_t)); - if (LIKELY(field != nullptr && obj != nullptr)) { - // Compiled code can't use transactional mode. - field->Set32<false>(obj, new_value); - return 0; // success - } - field = FindInstanceField<InstancePrimitiveWrite, true>(field_idx, - referrer, - self, - sizeof(int32_t), - &obj); - if (LIKELY(field != nullptr)) { - // Compiled code can't use transactional mode. - field->Set32<false>(obj, new_value); - return 0; // success - } - return -1; // failure -} - -extern "C" int artSet64InstanceFromCode(uint32_t field_idx, - mirror::Object* obj, - uint64_t new_value, - ArtMethod* referrer, - Thread* self) - REQUIRES_SHARED(Locks::mutator_lock_) { - ScopedQuickEntrypointChecks sqec(self); - ArtField* field = FindFieldFast(field_idx, referrer, InstancePrimitiveWrite, sizeof(int64_t)); - if (LIKELY(field != nullptr && obj != nullptr)) { - // Compiled code can't use transactional mode. - field->Set64<false>(obj, new_value); - return 0; // success - } - field = FindInstanceField<InstancePrimitiveWrite, true>(field_idx, - referrer, - self, - sizeof(int64_t), - &obj); - if (LIKELY(field != nullptr)) { - // Compiled code can't use transactional mode. - field->Set64<false>(obj, new_value); - return 0; - } - return -1; // failure -} - -extern "C" int artSetObjInstanceFromCode(uint32_t field_idx, - mirror::Object* obj, - mirror::Object* new_value, - ArtMethod* referrer, - Thread* self) - REQUIRES_SHARED(Locks::mutator_lock_) { - ScopedQuickEntrypointChecks sqec(self); - ArtField* field = FindFieldFast(field_idx, - referrer, - InstanceObjectWrite, - sizeof(mirror::HeapReference<mirror::Object>)); - if (LIKELY(field != nullptr && obj != nullptr)) { - // Compiled code can't use transactional mode. - field->SetObj<false>(obj, new_value); - return 0; // success - } - { - StackHandleScope<2> hs(self); - HandleWrapper<mirror::Object> h_obj(hs.NewHandleWrapper(&obj)); - HandleWrapper<mirror::Object> h_new_value(hs.NewHandleWrapper(&new_value)); - field = FindFieldFromCode<InstanceObjectWrite, true>( - field_idx, - referrer, - self, - sizeof(mirror::HeapReference<mirror::Object>)); - } - if (LIKELY(field != nullptr)) { - if (UNLIKELY(obj == nullptr)) { - ThrowNullPointerExceptionForFieldAccess(field, false); - } else { - // Compiled code can't use transactional mode. - field->SetObj<false>(obj, new_value); - return 0; // success - } - } - return -1; // failure + return artSetCharInstanceFromCode(field_idx, obj, new_value, referrer, self); } extern "C" mirror::Object* artReadBarrierMark(mirror::Object* obj) { diff --git a/runtime/entrypoints/quick/quick_jni_entrypoints.cc b/runtime/entrypoints/quick/quick_jni_entrypoints.cc index 670dadcd4d..158c1d6348 100644 --- a/runtime/entrypoints/quick/quick_jni_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_jni_entrypoints.cc @@ -20,7 +20,7 @@ #include "indirect_reference_table.h" #include "mirror/object-inl.h" #include "thread-inl.h" -#include "verify_object-inl.h" +#include "verify_object.h" namespace art { diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc index eb76fb6b88..bde9009f7b 100644 --- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc @@ -781,15 +781,19 @@ extern "C" uint64_t artQuickToInterpreterBridge(ArtMethod* method, Thread* self, // 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( - result, shorty[0] == 'L', /* from_code */ false, self->GetException()); - - // Set special exception to cause deoptimization. - self->SetException(Thread::GetDeoptimizationException()); + Dbg::IsForcedInterpreterNeededForUpcall(self, caller))) { + if (!Runtime::Current()->IsAsyncDeoptimizeable(caller_pc)) { + LOG(WARNING) << "Got a deoptimization request on un-deoptimizable method " + << caller->PrettyMethod(); + } else { + // Push the context of the deoptimization stack so we can restore the return value and the + // exception before executing the deoptimized frames. + self->PushDeoptimizationContext( + result, shorty[0] == 'L', /* from_code */ false, self->GetException()); + + // Set special exception to cause deoptimization. + self->SetException(Thread::GetDeoptimizationException()); + } } // No need to restore the args since the method has already been run by the interpreter. diff --git a/runtime/entrypoints_order_test.cc b/runtime/entrypoints_order_test.cc index 8e84d76e83..d0687ce7b0 100644 --- a/runtime/entrypoints_order_test.cc +++ b/runtime/entrypoints_order_test.cc @@ -152,7 +152,15 @@ class EntrypointsOrderTest : public CommonRuntimeTest { void CheckQuickEntryPoints() { CHECKED(OFFSETOF_MEMBER(QuickEntryPoints, pAllocArrayResolved) == 0, QuickEntryPoints_start_with_allocarray_resoved); - EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pAllocArrayResolved, pAllocObjectResolved, + EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pAllocArrayResolved, pAllocArrayResolved8, + sizeof(void*)); + EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pAllocArrayResolved8, pAllocArrayResolved16, + sizeof(void*)); + EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pAllocArrayResolved16, pAllocArrayResolved32, + sizeof(void*)); + EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pAllocArrayResolved32, pAllocArrayResolved64, + sizeof(void*)); + EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pAllocArrayResolved64, pAllocObjectResolved, sizeof(void*)); EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pAllocObjectResolved, pAllocObjectInitialized, sizeof(void*)); diff --git a/runtime/exec_utils.cc b/runtime/exec_utils.cc new file mode 100644 index 0000000000..9efb1a353c --- /dev/null +++ b/runtime/exec_utils.cc @@ -0,0 +1,102 @@ +/* + * 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. + */ + +#include "exec_utils.h" + +#include <sys/types.h> +#include <sys/wait.h> +#include <string> +#include <vector> + +#include "android-base/stringprintf.h" +#include "android-base/strings.h" + +#include "runtime.h" + +namespace art { + +using android::base::StringAppendF; +using android::base::StringPrintf; + +int ExecAndReturnCode(std::vector<std::string>& arg_vector, std::string* error_msg) { + const std::string command_line(android::base::Join(arg_vector, ' ')); + CHECK_GE(arg_vector.size(), 1U) << command_line; + + // Convert the args to char pointers. + const char* program = arg_vector[0].c_str(); + std::vector<char*> args; + for (size_t i = 0; i < arg_vector.size(); ++i) { + const std::string& arg = arg_vector[i]; + char* arg_str = const_cast<char*>(arg.c_str()); + CHECK(arg_str != nullptr) << i; + args.push_back(arg_str); + } + args.push_back(nullptr); + + // fork and exec + pid_t pid = fork(); + if (pid == 0) { + // no allocation allowed between fork and exec + + // change process groups, so we don't get reaped by ProcessManager + setpgid(0, 0); + + // (b/30160149): protect subprocesses from modifications to LD_LIBRARY_PATH, etc. + // Use the snapshot of the environment from the time the runtime was created. + char** envp = (Runtime::Current() == nullptr) ? nullptr : Runtime::Current()->GetEnvSnapshot(); + if (envp == nullptr) { + execv(program, &args[0]); + } else { + execve(program, &args[0], envp); + } + PLOG(ERROR) << "Failed to execve(" << command_line << ")"; + // _exit to avoid atexit handlers in child. + _exit(1); + } else { + if (pid == -1) { + *error_msg = StringPrintf("Failed to execv(%s) because fork failed: %s", + command_line.c_str(), strerror(errno)); + return -1; + } + + // wait for subprocess to finish + int status = -1; + pid_t got_pid = TEMP_FAILURE_RETRY(waitpid(pid, &status, 0)); + if (got_pid != pid) { + *error_msg = StringPrintf("Failed after fork for execv(%s) because waitpid failed: " + "wanted %d, got %d: %s", + command_line.c_str(), pid, got_pid, strerror(errno)); + return -1; + } + if (WIFEXITED(status)) { + return WEXITSTATUS(status); + } + return -1; + } +} + +bool Exec(std::vector<std::string>& arg_vector, std::string* error_msg) { + int status = ExecAndReturnCode(arg_vector, error_msg); + if (status != 0) { + const std::string command_line(android::base::Join(arg_vector, ' ')); + *error_msg = StringPrintf("Failed execv(%s) because non-0 exit status", + command_line.c_str()); + return false; + } + return true; +} + +} // namespace art diff --git a/runtime/exec_utils.h b/runtime/exec_utils.h new file mode 100644 index 0000000000..093f7b8d80 --- /dev/null +++ b/runtime/exec_utils.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2017 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_EXEC_UTILS_H_ +#define ART_RUNTIME_EXEC_UTILS_H_ + +#include <string> +#include <vector> + +namespace art { + +// Wrapper on fork/execv to run a command in a subprocess. +// Both of these spawn child processes using the environment as it was set when the single instance +// of the runtime (Runtime::Current()) was started. If no instance of the runtime was started, it +// will use the current environment settings. +bool Exec(std::vector<std::string>& arg_vector, std::string* error_msg); +int ExecAndReturnCode(std::vector<std::string>& arg_vector, std::string* error_msg); + +} // namespace art + +#endif // ART_RUNTIME_EXEC_UTILS_H_ diff --git a/runtime/experimental_flags.h b/runtime/experimental_flags.h index 5ddb9fa27a..0471c969f9 100644 --- a/runtime/experimental_flags.h +++ b/runtime/experimental_flags.h @@ -26,8 +26,6 @@ struct ExperimentalFlags { // The actual flag values. enum { kNone = 0x0000, - kAgents = 0x0001, // 0b00000001 - kRuntimePlugins = 0x0002, // 0b00000010 kMethodHandles = 0x0004, // 0b00000100 }; @@ -67,14 +65,6 @@ struct ExperimentalFlags { inline std::ostream& operator<<(std::ostream& stream, const ExperimentalFlags& e) { bool started = false; - if (e & ExperimentalFlags::kAgents) { - stream << (started ? "|" : "") << "kAgents"; - started = true; - } - if (e & ExperimentalFlags::kRuntimePlugins) { - stream << (started ? "|" : "") << "kRuntimePlugins"; - started = true; - } if (e & ExperimentalFlags::kMethodHandles) { stream << (started ? "|" : "") << "kMethodHandles"; started = true; diff --git a/runtime/gc/collector/concurrent_copying-inl.h b/runtime/gc/collector/concurrent_copying-inl.h index 7c649525e4..854d0a58ff 100644 --- a/runtime/gc/collector/concurrent_copying-inl.h +++ b/runtime/gc/collector/concurrent_copying-inl.h @@ -22,6 +22,7 @@ #include "gc/accounting/space_bitmap-inl.h" #include "gc/heap.h" #include "gc/space/region_space.h" +#include "mirror/object-inl.h" #include "lock_word.h" namespace art { diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc index 6044053b4f..f12ad8058d 100644 --- a/runtime/gc/collector/concurrent_copying.cc +++ b/runtime/gc/collector/concurrent_copying.cc @@ -25,6 +25,7 @@ #include "gc/accounting/heap_bitmap-inl.h" #include "gc/accounting/mod_union_table-inl.h" #include "gc/accounting/space_bitmap-inl.h" +#include "gc/gc_pause_listener.h" #include "gc/reference_processor.h" #include "gc/space/image_space.h" #include "gc/space/space-inl.h" @@ -139,7 +140,7 @@ void ConcurrentCopying::RunPhases() { // Verify no from space refs. This causes a pause. if (kEnableNoFromSpaceRefsVerification || kIsDebugBuild) { TimingLogger::ScopedTiming split("(Paused)VerifyNoFromSpaceReferences", GetTimings()); - ScopedPause pause(this); + ScopedPause pause(this, false); CheckEmptyMarkStack(); if (kVerboseMode) { LOG(INFO) << "Verifying no from-space refs"; @@ -439,8 +440,27 @@ void ConcurrentCopying::FlipThreadRoots() { gc_barrier_->Init(self, 0); ThreadFlipVisitor thread_flip_visitor(this, heap_->use_tlab_); FlipCallback flip_callback(this); + + // This is the point where Concurrent-Copying will pause all threads. We report a pause here, if + // necessary. This is slightly over-reporting, as this includes the time to actually suspend + // threads. + { + GcPauseListener* pause_listener = GetHeap()->GetGcPauseListener(); + if (pause_listener != nullptr) { + pause_listener->StartPause(); + } + } + size_t barrier_count = Runtime::Current()->FlipThreadRoots( &thread_flip_visitor, &flip_callback, this); + + { + GcPauseListener* pause_listener = GetHeap()->GetGcPauseListener(); + if (pause_listener != nullptr) { + pause_listener->EndPause(); + } + } + { ScopedThreadStateChange tsc(self, kWaitingForCheckPointsToRun); gc_barrier_->Increment(self, barrier_count); @@ -857,7 +877,10 @@ void ConcurrentCopying::IssueEmptyCheckpoint() { thread->ReadFlag(kEmptyCheckpointRequest)) { // Found a runnable thread that hasn't responded to the empty checkpoint request. // Assume it's stuck and safe to dump its stack. - thread->Dump(LOG_STREAM(FATAL_WITHOUT_ABORT)); + thread->Dump(LOG_STREAM(FATAL_WITHOUT_ABORT), + /*dump_native_stack*/ true, + /*backtrace_map*/ nullptr, + /*force_dump_stack*/ true); } } } @@ -1852,8 +1875,10 @@ class ConcurrentCopying::RefFieldsVisitor { // Scan ref fields of an object. inline void ConcurrentCopying::Scan(mirror::Object* to_ref) { - if (kDisallowReadBarrierDuringScan) { + if (kDisallowReadBarrierDuringScan && !Runtime::Current()->IsActiveTransaction()) { // Avoid all read barriers during visit references to help performance. + // Don't do this in transaction mode because we may read the old value of an field which may + // trigger read barriers. Thread::Current()->ModifyDebugDisallowReadBarrier(1); } DCHECK(!region_space_->IsInFromSpace(to_ref)); @@ -1862,7 +1887,7 @@ inline void ConcurrentCopying::Scan(mirror::Object* to_ref) { // Disable the read barrier for a performance reason. to_ref->VisitReferences</*kVisitNativeRoots*/true, kDefaultVerifyFlags, kWithoutReadBarrier>( visitor, visitor); - if (kDisallowReadBarrierDuringScan) { + if (kDisallowReadBarrierDuringScan && !Runtime::Current()->IsActiveTransaction()) { Thread::Current()->ModifyDebugDisallowReadBarrier(-1); } } diff --git a/runtime/gc/collector/garbage_collector.cc b/runtime/gc/collector/garbage_collector.cc index 01bcb7df19..14fd332b57 100644 --- a/runtime/gc/collector/garbage_collector.cc +++ b/runtime/gc/collector/garbage_collector.cc @@ -158,22 +158,26 @@ void GarbageCollector::ResetMeasurements() { total_freed_bytes_ = 0; } -GarbageCollector::ScopedPause::ScopedPause(GarbageCollector* collector) - : start_time_(NanoTime()), collector_(collector) { +GarbageCollector::ScopedPause::ScopedPause(GarbageCollector* collector, bool with_reporting) + : start_time_(NanoTime()), collector_(collector), with_reporting_(with_reporting) { Runtime* runtime = Runtime::Current(); runtime->GetThreadList()->SuspendAll(__FUNCTION__); - GcPauseListener* pause_listener = runtime->GetHeap()->GetGcPauseListener(); - if (pause_listener != nullptr) { - pause_listener->StartPause(); + if (with_reporting) { + GcPauseListener* pause_listener = runtime->GetHeap()->GetGcPauseListener(); + if (pause_listener != nullptr) { + pause_listener->StartPause(); + } } } GarbageCollector::ScopedPause::~ScopedPause() { collector_->RegisterPause(NanoTime() - start_time_); Runtime* runtime = Runtime::Current(); - GcPauseListener* pause_listener = runtime->GetHeap()->GetGcPauseListener(); - if (pause_listener != nullptr) { - pause_listener->EndPause(); + if (with_reporting_) { + GcPauseListener* pause_listener = runtime->GetHeap()->GetGcPauseListener(); + if (pause_listener != nullptr) { + pause_listener->EndPause(); + } } runtime->GetThreadList()->ResumeAll(); } diff --git a/runtime/gc/collector/garbage_collector.h b/runtime/gc/collector/garbage_collector.h index 0177e2a1ad..95601d736d 100644 --- a/runtime/gc/collector/garbage_collector.h +++ b/runtime/gc/collector/garbage_collector.h @@ -126,12 +126,14 @@ class GarbageCollector : public RootVisitor, public IsMarkedVisitor, public Mark public: class SCOPED_LOCKABLE ScopedPause { public: - explicit ScopedPause(GarbageCollector* collector) EXCLUSIVE_LOCK_FUNCTION(Locks::mutator_lock_); + explicit ScopedPause(GarbageCollector* collector, bool with_reporting = true) + EXCLUSIVE_LOCK_FUNCTION(Locks::mutator_lock_); ~ScopedPause() UNLOCK_FUNCTION(); private: const uint64_t start_time_; GarbageCollector* const collector_; + bool with_reporting_; }; GarbageCollector(Heap* heap, const std::string& name); diff --git a/runtime/gc/heap-inl.h b/runtime/gc/heap-inl.h index 54f221056a..394e541fd8 100644 --- a/runtime/gc/heap-inl.h +++ b/runtime/gc/heap-inl.h @@ -34,7 +34,7 @@ #include "handle_scope-inl.h" #include "thread-inl.h" #include "utils.h" -#include "verify_object-inl.h" +#include "verify_object.h" namespace art { namespace gc { diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc index 268cca0cfd..051f3f7b00 100644 --- a/runtime/gc/heap.cc +++ b/runtime/gc/heap.cc @@ -78,6 +78,7 @@ #include "scoped_thread_state_change-inl.h" #include "handle_scope-inl.h" #include "thread_list.h" +#include "verify_object-inl.h" #include "well_known_classes.h" namespace art { @@ -127,8 +128,6 @@ static constexpr uint32_t kAllocSpaceBeginForDeterministicAoT = 0x40000000; // Dump the rosalloc stats on SIGQUIT. static constexpr bool kDumpRosAllocStatsOnSigQuit = false; -static constexpr size_t kNativeAllocationHistogramBuckets = 16; - // Extra added to the heap growth multiplier. Used to adjust the GC ergonomics for the read barrier // config. static constexpr double kExtraHeapGrowthMultiplier = kUseReadBarrier ? 1.0 : 0.0; @@ -194,18 +193,12 @@ Heap::Heap(size_t initial_size, capacity_(capacity), growth_limit_(growth_limit), max_allowed_footprint_(initial_size), - native_footprint_gc_watermark_(initial_size), - native_need_to_run_finalization_(false), concurrent_start_bytes_(std::numeric_limits<size_t>::max()), total_bytes_freed_ever_(0), 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), + new_native_bytes_allocated_(0), + old_native_bytes_allocated_(0), num_bytes_freed_revoke_(0), verify_missing_card_marks_(false), verify_system_weaks_(false), @@ -293,8 +286,13 @@ Heap::Heap(size_t initial_size, if (foreground_collector_type_ == kCollectorTypeCC) { // Need to use a low address so that we can allocate a contiguous // 2 * Xmx space when there's no image (dex2oat for target). +#if defined(__LP64__) CHECK_GE(300 * MB, non_moving_space_capacity); requested_alloc_space_begin = reinterpret_cast<uint8_t*>(300 * MB) - non_moving_space_capacity; +#else + // For 32-bit, use 0x20000000 because asan reserves 0x04000000 - 0x20000000. + requested_alloc_space_begin = reinterpret_cast<uint8_t*>(0x20000000); +#endif } // Load image space(s). @@ -369,7 +367,12 @@ Heap::Heap(size_t initial_size, &error_str)); CHECK(non_moving_space_mem_map != nullptr) << error_str; // Try to reserve virtual memory at a lower address if we have a separate non moving space. +#if defined(__LP64__) request_begin = reinterpret_cast<uint8_t*>(300 * MB); +#else + // For 32-bit, use 0x20000000 because asan reserves 0x04000000 - 0x20000000. + request_begin = reinterpret_cast<uint8_t*>(0x20000000) + non_moving_space_capacity; +#endif } // Attempt to create 2 mem maps at or after the requested begin. if (foreground_collector_type_ != kCollectorTypeCC) { @@ -534,6 +537,12 @@ Heap::Heap(size_t initial_size, gc_complete_lock_ = new Mutex("GC complete lock"); gc_complete_cond_.reset(new ConditionVariable("GC complete condition variable", *gc_complete_lock_)); + native_blocking_gc_lock_ = new Mutex("Native blocking GC lock"); + native_blocking_gc_cond_.reset(new ConditionVariable("Native blocking GC condition variable", + *native_blocking_gc_lock_)); + native_blocking_gc_in_progress_ = false; + native_blocking_gcs_finished_ = 0; + thread_flip_lock_ = new Mutex("GC thread flip lock"); thread_flip_cond_.reset(new ConditionVariable("GC thread flip condition variable", *thread_flip_lock_)); @@ -1101,19 +1110,9 @@ 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"; - } - } + os << "Registered native bytes allocated: " + << old_native_bytes_allocated_.LoadRelaxed() + new_native_bytes_allocated_.LoadRelaxed() + << "\n"; BaseMutex::DumpAll(os); } @@ -1198,6 +1197,7 @@ Heap::~Heap() { STLDeleteElements(&continuous_spaces_); STLDeleteElements(&discontinuous_spaces_); delete gc_complete_lock_; + delete native_blocking_gc_lock_; delete thread_flip_lock_; delete pending_task_lock_; delete backtrace_lock_; @@ -2645,6 +2645,13 @@ collector::GcType Heap::CollectGarbageInternal(collector::GcType gc_type, // Approximate heap size. ATRACE_INT("Heap size (KB)", bytes_allocated_before_gc / KB); + if (gc_type == NonStickyGcType()) { + // Move all bytes from new_native_bytes_allocated_ to + // old_native_bytes_allocated_ now that GC has been triggered, resetting + // new_native_bytes_allocated_ to zero in the process. + old_native_bytes_allocated_.FetchAndAddRelaxed(new_native_bytes_allocated_.ExchangeRelaxed(0)); + } + DCHECK_LT(gc_type, collector::kGcTypeMax); DCHECK_NE(gc_type, collector::kGcTypeNone); @@ -3352,7 +3359,7 @@ void Heap::PreGcVerificationPaused(collector::GarbageCollector* gc) { void Heap::PreGcVerification(collector::GarbageCollector* gc) { if (verify_pre_gc_heap_ || verify_missing_card_marks_ || verify_mod_union_table_) { - collector::GarbageCollector::ScopedPause pause(gc); + collector::GarbageCollector::ScopedPause pause(gc, false); PreGcVerificationPaused(gc); } } @@ -3420,7 +3427,7 @@ void Heap::PostGcVerificationPaused(collector::GarbageCollector* gc) { void Heap::PostGcVerification(collector::GarbageCollector* gc) { if (verify_system_weaks_ || verify_post_gc_rosalloc_ || verify_post_gc_heap_) { - collector::GarbageCollector::ScopedPause pause(gc); + collector::GarbageCollector::ScopedPause pause(gc, false); PostGcVerificationPaused(gc); } } @@ -3504,18 +3511,6 @@ bool Heap::IsMovableObject(ObjPtr<mirror::Object> obj) const { return false; } -void Heap::UpdateMaxNativeFootprint() { - size_t native_size = native_bytes_allocated_.LoadRelaxed(); - // TODO: Tune the native heap utilization to be a value other than the java heap utilization. - size_t target_size = native_size / GetTargetHeapUtilization(); - if (target_size > native_size + max_free_) { - target_size = native_size + max_free_; - } else if (target_size < native_size + min_free_) { - target_size = native_size + min_free_; - } - native_footprint_gc_watermark_ = std::min(growth_limit_, target_size); -} - collector::GarbageCollector* Heap::FindCollectorByGcType(collector::GcType gc_type) { for (const auto& collector : garbage_collectors_) { if (collector->GetCollectorType() == collector_type_ && @@ -3543,8 +3538,11 @@ void Heap::GrowForUtilization(collector::GarbageCollector* collector_ran, collector::GcType gc_type = collector_ran->GetGcType(); const double multiplier = HeapGrowthMultiplier(); // Use the multiplier to grow more for // foreground. - const uint64_t adjusted_min_free = static_cast<uint64_t>(min_free_ * multiplier); - const uint64_t adjusted_max_free = static_cast<uint64_t>(max_free_ * multiplier); + // Ensure at least 2.5 MB to temporarily fix excessive GC caused by TLAB ergonomics. + const uint64_t adjusted_min_free = std::max(static_cast<uint64_t>(min_free_ * multiplier), + static_cast<uint64_t>(5 * MB / 2)); + const uint64_t adjusted_max_free = std::max(static_cast<uint64_t>(max_free_ * multiplier), + static_cast<uint64_t>(5 * MB / 2)); if (gc_type != collector::kGcTypeSticky) { // Grow the heap for non sticky GC. ssize_t delta = bytes_allocated / GetTargetHeapUtilization() - bytes_allocated; @@ -3552,11 +3550,9 @@ void Heap::GrowForUtilization(collector::GarbageCollector* collector_ran, target_size = bytes_allocated + delta * multiplier; target_size = std::min(target_size, bytes_allocated + adjusted_max_free); target_size = std::max(target_size, bytes_allocated + adjusted_min_free); - native_need_to_run_finalization_ = true; next_gc_type_ = collector::kGcTypeSticky; } else { - collector::GcType non_sticky_gc_type = - HasZygoteSpace() ? collector::kGcTypePartial : collector::kGcTypeFull; + collector::GcType non_sticky_gc_type = NonStickyGcType(); // Find what the next non sticky collector will be. collector::GarbageCollector* non_sticky_collector = FindCollectorByGcType(non_sticky_gc_type); // If the throughput of the current sticky GC >= throughput of the non sticky collector, then @@ -3707,7 +3703,7 @@ void Heap::ConcurrentGC(Thread* self, bool force_full) { collector::GcType next_gc_type = next_gc_type_; // If forcing full and next gc type is sticky, override with a non-sticky type. if (force_full && next_gc_type == collector::kGcTypeSticky) { - next_gc_type = HasZygoteSpace() ? collector::kGcTypePartial : collector::kGcTypeFull; + next_gc_type = NonStickyGcType(); } if (CollectGarbageInternal(next_gc_type, kGcCauseBackground, false) == collector::kGcTypeNone) { @@ -3864,70 +3860,79 @@ 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(); - native_need_to_run_finalization_ = false; - } - // Total number of native bytes allocated. - size_t new_native_bytes_allocated = native_bytes_allocated_.FetchAndAddSequentiallyConsistent(bytes); - new_native_bytes_allocated += bytes; - if (new_native_bytes_allocated > native_footprint_gc_watermark_) { - collector::GcType gc_type = HasZygoteSpace() ? collector::kGcTypePartial : - collector::kGcTypeFull; - - // The second watermark is higher than the gc watermark. If you hit this it means you are - // allocating native objects faster than the GC can keep up with. - if (new_native_bytes_allocated > growth_limit_) { - if (WaitForGcToComplete(kGcCauseForNativeAlloc, self) != collector::kGcTypeNone) { - // Just finished a GC, attempt to run finalizers. - RunFinalization(env, kNativeAllocationFinalizeTimeout); - CHECK(!env->ExceptionCheck()); - // Native bytes allocated may be updated by finalization, refresh it. - new_native_bytes_allocated = native_bytes_allocated_.LoadRelaxed(); - } - // If we still are over the watermark, attempt a GC for alloc and run finalizers. - if (new_native_bytes_allocated > growth_limit_) { - CollectGarbageInternal(gc_type, kGcCauseForNativeAlloc, false); - RunFinalization(env, kNativeAllocationFinalizeTimeout); - native_need_to_run_finalization_ = false; - CHECK(!env->ExceptionCheck()); + // See the REDESIGN section of go/understanding-register-native-allocation + // for an explanation of how RegisterNativeAllocation works. + size_t new_value = bytes + new_native_bytes_allocated_.FetchAndAddRelaxed(bytes); + if (new_value > NativeAllocationBlockingGcWatermark()) { + // Wait for a new GC to finish and finalizers to run, because the + // allocation rate is too high. + Thread* self = ThreadForEnv(env); + + bool run_gc = false; + { + MutexLock mu(self, *native_blocking_gc_lock_); + uint32_t initial_gcs_finished = native_blocking_gcs_finished_; + if (native_blocking_gc_in_progress_) { + // A native blocking GC is in progress from the last time the native + // allocation blocking GC watermark was exceeded. Wait for that GC to + // finish before addressing the fact that we exceeded the blocking + // watermark again. + do { + native_blocking_gc_cond_->Wait(self); + } while (native_blocking_gcs_finished_ == initial_gcs_finished); + initial_gcs_finished++; } - // We have just run finalizers, update the native watermark since it is very likely that - // finalizers released native managed allocations. - UpdateMaxNativeFootprint(); - } else if (!IsGCRequestPending()) { - if (IsGcConcurrent()) { - RequestConcurrentGC(self, true); // Request non-sticky type. + + // It's possible multiple threads have seen that we exceeded the + // blocking watermark. Ensure that only one of those threads runs the + // blocking GC. The rest of the threads should instead wait for the + // blocking GC to complete. + if (native_blocking_gc_in_progress_) { + do { + native_blocking_gc_cond_->Wait(self); + } while (native_blocking_gcs_finished_ == initial_gcs_finished); } else { - CollectGarbageInternal(gc_type, kGcCauseForNativeAlloc, false); + native_blocking_gc_in_progress_ = true; + run_gc = true; } } + + if (run_gc) { + CollectGarbageInternal(NonStickyGcType(), kGcCauseForNativeAlloc, false); + RunFinalization(env, kNativeAllocationFinalizeTimeout); + CHECK(!env->ExceptionCheck()); + + MutexLock mu(self, *native_blocking_gc_lock_); + native_blocking_gc_in_progress_ = false; + native_blocking_gcs_finished_++; + native_blocking_gc_cond_->Broadcast(self); + } + } else if (new_value > NativeAllocationGcWatermark() && !IsGCRequestPending()) { + // Trigger another GC because there have been enough native bytes + // allocated since the last GC. + if (IsGcConcurrent()) { + RequestConcurrentGC(ThreadForEnv(env), /*force_full*/true); + } else { + CollectGarbageInternal(NonStickyGcType(), kGcCauseForNativeAlloc, false); + } } } -void Heap::RegisterNativeFree(JNIEnv* env, size_t bytes) { - size_t expected_size; - { - MutexLock mu(Thread::Current(), native_histogram_lock_); - native_free_histogram_.AddValue(bytes); - } +void Heap::RegisterNativeFree(JNIEnv*, size_t bytes) { + // Take the bytes freed out of new_native_bytes_allocated_ first. If + // new_native_bytes_allocated_ reaches zero, take the remaining bytes freed + // out of old_native_bytes_allocated_ to ensure all freed bytes are + // accounted for. + size_t allocated; + size_t new_freed_bytes; do { - expected_size = native_bytes_allocated_.LoadRelaxed(); - if (UNLIKELY(bytes > expected_size)) { - ScopedObjectAccess soa(env); - env->ThrowNew(WellKnownClasses::java_lang_RuntimeException, - StringPrintf("Attempted to free %zd native bytes with only %zd native bytes " - "registered as allocated", bytes, expected_size).c_str()); - break; - } - } while (!native_bytes_allocated_.CompareExchangeWeakRelaxed(expected_size, - expected_size - bytes)); + allocated = new_native_bytes_allocated_.LoadRelaxed(); + new_freed_bytes = std::min(allocated, bytes); + } while (!new_native_bytes_allocated_.CompareExchangeWeakRelaxed(allocated, + allocated - new_freed_bytes)); + if (new_freed_bytes < bytes) { + old_native_bytes_allocated_.FetchAndSubRelaxed(bytes - new_freed_bytes); + } } size_t Heap::GetTotalMemory() const { diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h index 3a8e29b08a..a4d300b110 100644 --- a/runtime/gc/heap.h +++ b/runtime/gc/heap.h @@ -260,9 +260,8 @@ class Heap { REQUIRES_SHARED(Locks::mutator_lock_); void RegisterNativeAllocation(JNIEnv* env, size_t bytes) - REQUIRES(!*gc_complete_lock_, !*pending_task_lock_, !native_histogram_lock_); - void RegisterNativeFree(JNIEnv* env, size_t bytes) - REQUIRES(!*gc_complete_lock_, !*pending_task_lock_, !native_histogram_lock_); + REQUIRES(!*gc_complete_lock_, !*pending_task_lock_, !*native_blocking_gc_lock_); + void RegisterNativeFree(JNIEnv* env, size_t bytes); // Change the allocator, updates entrypoints. void ChangeAllocator(AllocatorType allocator) @@ -562,7 +561,7 @@ class Heap { space::Space* FindSpaceFromAddress(const void* ptr) const REQUIRES_SHARED(Locks::mutator_lock_); - void DumpForSigQuit(std::ostream& os) REQUIRES(!*gc_complete_lock_, !native_histogram_lock_); + void DumpForSigQuit(std::ostream& os) REQUIRES(!*gc_complete_lock_); // Do a pending collector transition. void DoPendingCollectorTransition() REQUIRES(!*gc_complete_lock_, !*pending_task_lock_); @@ -679,7 +678,7 @@ class Heap { // GC performance measuring void DumpGcPerformanceInfo(std::ostream& os) - REQUIRES(!*gc_complete_lock_, !native_histogram_lock_); + REQUIRES(!*gc_complete_lock_); void ResetGcPerformanceInfo() REQUIRES(!*gc_complete_lock_); // Thread pool. @@ -979,10 +978,6 @@ class Heap { void PostGcVerificationPaused(collector::GarbageCollector* gc) REQUIRES(Locks::mutator_lock_, !*gc_complete_lock_); - // Update the watermark for the native allocated bytes based on the current number of native - // bytes allocated and the target utilization ratio. - void UpdateMaxNativeFootprint(); - // Find a collector based on GC type. collector::GarbageCollector* FindCollectorByGcType(collector::GcType gc_type); @@ -1066,6 +1061,31 @@ class Heap { REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!*gc_complete_lock_, !*pending_task_lock_, !*backtrace_lock_); + collector::GcType NonStickyGcType() const { + return HasZygoteSpace() ? collector::kGcTypePartial : collector::kGcTypeFull; + } + + // How large new_native_bytes_allocated_ can grow before we trigger a new + // GC. + ALWAYS_INLINE size_t NativeAllocationGcWatermark() const { + // Reuse max_free_ for the native allocation gc watermark, so that the + // native heap is treated in the same way as the Java heap in the case + // where the gc watermark update would exceed max_free_. Using max_free_ + // instead of the target utilization means the watermark doesn't depend on + // the current number of registered native allocations. + return max_free_; + } + + // How large new_native_bytes_allocated_ can grow while GC is in progress + // before we block the allocating thread to allow GC to catch up. + ALWAYS_INLINE size_t NativeAllocationBlockingGcWatermark() const { + // Historically the native allocations were bounded by growth_limit_. This + // uses that same value, dividing growth_limit_ by 2 to account for + // the fact that now the bound is relative to the number of retained + // registered native allocations rather than absolute. + return growth_limit_ / 2; + } + // All-known continuous spaces, where objects lie within fixed bounds. std::vector<space::ContinuousSpace*> continuous_spaces_ GUARDED_BY(Locks::mutator_lock_); @@ -1184,12 +1204,6 @@ class Heap { // a GC should be triggered. size_t max_allowed_footprint_; - // The watermark at which a concurrent GC is requested by registerNativeAllocation. - size_t native_footprint_gc_watermark_; - - // Whether or not we need to run finalizers in the next native allocation. - bool native_need_to_run_finalization_; - // When num_bytes_allocated_ exceeds this amount then a concurrent GC should be requested so that // it completes ahead of an allocation failing. size_t concurrent_start_bytes_; @@ -1203,13 +1217,25 @@ class Heap { // Number of bytes allocated. Adjusted after each allocation and free. Atomic<size_t> num_bytes_allocated_; - // 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 registered native bytes allocated since the last time GC was + // triggered. Adjusted after each RegisterNativeAllocation and + // RegisterNativeFree. Used to determine when to trigger GC for native + // allocations. + // See the REDESIGN section of go/understanding-register-native-allocation. + Atomic<size_t> new_native_bytes_allocated_; + + // Number of registered native bytes allocated prior to the last time GC was + // triggered, for debugging purposes. The current number of registered + // native bytes is determined by taking the sum of + // old_native_bytes_allocated_ and new_native_bytes_allocated_. + Atomic<size_t> old_native_bytes_allocated_; + + // Used for synchronization of blocking GCs triggered by + // RegisterNativeAllocation. + Mutex* native_blocking_gc_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; + std::unique_ptr<ConditionVariable> native_blocking_gc_cond_ GUARDED_BY(native_blocking_gc_lock_); + bool native_blocking_gc_in_progress_ GUARDED_BY(native_blocking_gc_lock_); + uint32_t native_blocking_gcs_finished_ GUARDED_BY(native_blocking_gc_lock_); // Number of bytes freed by thread local buffer revokes. This will // cancel out the ahead-of-time bulk counting of bytes allocated in diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc index e03958d717..ffbca525d9 100644 --- a/runtime/gc/space/image_space.cc +++ b/runtime/gc/space/image_space.cc @@ -32,6 +32,7 @@ #include "base/scoped_flock.h" #include "base/systrace.h" #include "base/time_utils.h" +#include "exec_utils.h" #include "gc/accounting/space_bitmap-inl.h" #include "image-inl.h" #include "image_space_fs.h" diff --git a/runtime/handle_scope-inl.h b/runtime/handle_scope-inl.h index b212d095cb..077f45e8f3 100644 --- a/runtime/handle_scope-inl.h +++ b/runtime/handle_scope-inl.h @@ -23,7 +23,7 @@ #include "handle.h" #include "obj_ptr-inl.h" #include "thread-inl.h" -#include "verify_object-inl.h" +#include "verify_object.h" namespace art { diff --git a/runtime/handle_scope_test.cc b/runtime/handle_scope_test.cc index aab1d9c224..f888482ae5 100644 --- a/runtime/handle_scope_test.cc +++ b/runtime/handle_scope_test.cc @@ -17,10 +17,12 @@ #include <type_traits> #include "base/enums.h" +#include "class_linker-inl.h" #include "common_runtime_test.h" #include "gtest/gtest.h" #include "handle.h" #include "handle_scope-inl.h" +#include "mirror/class-inl.h" #include "mirror/object.h" #include "scoped_thread_state_change-inl.h" #include "thread.h" diff --git a/runtime/hprof/hprof.cc b/runtime/hprof/hprof.cc index fe6a6e9140..3d3ad593b3 100644 --- a/runtime/hprof/hprof.cc +++ b/runtime/hprof/hprof.cc @@ -1169,7 +1169,7 @@ void Hprof::DumpHeapObject(mirror::Object* obj) { } void Hprof::DumpHeapClass(mirror::Class* klass) { - if (!klass->IsResolved() && !klass->IsErroneous()) { + if (!klass->IsResolved()) { // Class is allocated but not yet resolved: we cannot access its fields or super class. return; } diff --git a/runtime/image.cc b/runtime/image.cc index 6d8889532f..54b099eb14 100644 --- a/runtime/image.cc +++ b/runtime/image.cc @@ -25,7 +25,7 @@ namespace art { const uint8_t ImageHeader::kImageMagic[] = { 'a', 'r', 't', '\n' }; -const uint8_t ImageHeader::kImageVersion[] = { '0', '3', '5', '\0' }; // ArtMethod update +const uint8_t ImageHeader::kImageVersion[] = { '0', '3', '6', '\0' }; // Erroneous resolved class. ImageHeader::ImageHeader(uint32_t image_begin, uint32_t image_size, diff --git a/runtime/indirect_reference_table-inl.h b/runtime/indirect_reference_table-inl.h index 0e66ae96b5..24ee22759c 100644 --- a/runtime/indirect_reference_table-inl.h +++ b/runtime/indirect_reference_table-inl.h @@ -25,7 +25,7 @@ #include "gc_root-inl.h" #include "obj_ptr-inl.h" #include "runtime-inl.h" -#include "verify_object-inl.h" +#include "verify_object.h" namespace art { namespace mirror { diff --git a/runtime/indirect_reference_table.cc b/runtime/indirect_reference_table.cc index c7371191b7..9fbb2e9930 100644 --- a/runtime/indirect_reference_table.cc +++ b/runtime/indirect_reference_table.cc @@ -25,7 +25,6 @@ #include "scoped_thread_state_change-inl.h" #include "thread.h" #include "utils.h" -#include "verify_object-inl.h" #include <cstdlib> diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc index 4ea1130947..f11e2cba10 100644 --- a/runtime/instrumentation.cc +++ b/runtime/instrumentation.cc @@ -88,11 +88,11 @@ Instrumentation::Instrumentation() } void Instrumentation::InstallStubsForClass(mirror::Class* klass) { - if (klass->IsErroneous()) { - // We can't execute code in a erroneous class: do nothing. - } else if (!klass->IsResolved()) { + if (!klass->IsResolved()) { // We need the class to be resolved to install/uninstall stubs. Otherwise its methods // could not be initialized or linked with regards to class inheritance. + } else if (klass->IsErroneousResolved()) { + // We can't execute code in a erroneous class: do nothing. } else { for (ArtMethod& method : klass->GetMethods(kRuntimePointerSize)) { InstallStubsForMethod(&method); @@ -105,10 +105,9 @@ static void UpdateEntrypoints(ArtMethod* method, const void* quick_code) method->SetEntryPointFromQuickCompiledCode(quick_code); } -bool Instrumentation::NeedDebugVersionForBootImageCode(ArtMethod* method, const void* code) const - REQUIRES_SHARED(Locks::mutator_lock_) { +bool Instrumentation::NeedDebugVersionFor(ArtMethod* method) const REQUIRES_SHARED(Locks::mutator_lock_) { return Dbg::IsDebuggerActive() && - Runtime::Current()->GetHeap()->IsInBootImageOatFile(code) && + Runtime::Current()->IsJavaDebuggable() && !method->IsNative() && !method->IsProxyMethod(); } @@ -132,9 +131,10 @@ void Instrumentation::InstallStubsForMethod(ArtMethod* method) { if ((forced_interpret_only_ || IsDeoptimized(method)) && !method->IsNative()) { new_quick_code = GetQuickToInterpreterBridge(); } else if (is_class_initialized || !method->IsStatic() || method->IsConstructor()) { - new_quick_code = class_linker->GetQuickOatCodeFor(method); - if (NeedDebugVersionForBootImageCode(method, new_quick_code)) { + if (NeedDebugVersionFor(method)) { new_quick_code = GetQuickToInterpreterBridge(); + } else { + new_quick_code = class_linker->GetQuickOatCodeFor(method); } } else { new_quick_code = GetQuickResolutionStub(); @@ -148,13 +148,14 @@ void Instrumentation::InstallStubsForMethod(ArtMethod* method) { // class, all its static methods code will be set to the instrumentation entry point. // For more details, see ClassLinker::FixupStaticTrampolines. if (is_class_initialized || !method->IsStatic() || method->IsConstructor()) { - new_quick_code = class_linker->GetQuickOatCodeFor(method); - if (NeedDebugVersionForBootImageCode(method, new_quick_code)) { + if (NeedDebugVersionFor(method)) { // Oat code should not be used. Don't install instrumentation stub and // use interpreter for instrumentation. new_quick_code = GetQuickToInterpreterBridge(); } else if (entry_exit_stubs_installed_) { new_quick_code = GetQuickInstrumentationEntryPoint(); + } else { + new_quick_code = class_linker->GetQuickOatCodeFor(method); } } else { new_quick_code = GetQuickResolutionStub(); @@ -557,10 +558,8 @@ void Instrumentation::RemoveListener(InstrumentationListener* listener, uint32_t } Instrumentation::InstrumentationLevel Instrumentation::GetCurrentInstrumentationLevel() const { - if (interpreter_stubs_installed_ && interpret_only_) { + if (interpreter_stubs_installed_) { return InstrumentationLevel::kInstrumentWithInterpreter; - } else if (interpreter_stubs_installed_) { - return InstrumentationLevel::kInstrumentWithInterpreterAndJit; } else if (entry_exit_stubs_installed_) { return InstrumentationLevel::kInstrumentWithInstrumentationStubs; } else { @@ -569,11 +568,8 @@ Instrumentation::InstrumentationLevel Instrumentation::GetCurrentInstrumentation } bool Instrumentation::RequiresInstrumentationInstallation(InstrumentationLevel new_level) const { - // We need to reinstall instrumentation if we go to a different level or if the current level is - // kInstrumentWithInterpreterAndJit since that level does not force all code to always use the - // interpreter and so we might have started running optimized code again. - return new_level == InstrumentationLevel::kInstrumentWithInterpreterAndJit || - GetCurrentInstrumentationLevel() != new_level; + // We need to reinstall instrumentation if we go to a different level. + return GetCurrentInstrumentationLevel() != new_level; } void Instrumentation::ConfigureStubs(const char* key, InstrumentationLevel desired_level) { @@ -604,7 +600,7 @@ void Instrumentation::ConfigureStubs(const char* key, InstrumentationLevel desir Locks::mutator_lock_->AssertExclusiveHeld(self); Locks::thread_list_lock_->AssertNotHeld(self); if (requested_level > InstrumentationLevel::kInstrumentNothing) { - if (requested_level >= InstrumentationLevel::kInstrumentWithInterpreterAndJit) { + if (requested_level == InstrumentationLevel::kInstrumentWithInterpreter) { interpreter_stubs_installed_ = true; entry_exit_stubs_installed_ = true; } else { @@ -731,10 +727,12 @@ void Instrumentation::UpdateMethodsCode(ArtMethod* method, const void* quick_cod 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. +void Instrumentation::UpdateMethodsCodeForJavaDebuggable(ArtMethod* method, + const void* quick_code) { + // When the runtime is set to Java debuggable, 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, so we bypass the DCHECK in + // UpdateMethodsCode. UpdateMethodsCodeImpl(method, quick_code); } @@ -819,10 +817,9 @@ void Instrumentation::Undeoptimize(ArtMethod* method) { !method->GetDeclaringClass()->IsInitialized()) { UpdateEntrypoints(method, GetQuickResolutionStub()); } else { - const void* quick_code = class_linker->GetQuickOatCodeFor(method); - if (NeedDebugVersionForBootImageCode(method, quick_code)) { - quick_code = GetQuickToInterpreterBridge(); - } + const void* quick_code = NeedDebugVersionFor(method) + ? GetQuickToInterpreterBridge() + : class_linker->GetQuickOatCodeFor(method); UpdateEntrypoints(method, quick_code); } @@ -879,14 +876,6 @@ bool Instrumentation::ShouldNotifyMethodEnterExitEvents() const { return !deoptimization_enabled_ && !interpreter_stubs_installed_; } -// TODO we don't check deoptimization_enabled_ because currently there isn't really any support for -// multiple users of instrumentation. Since this is just a temporary state anyway pending work to -// ensure that the current_method doesn't get kept across suspend points this should be okay. -// TODO Remove once b/33630159 is resolved. -void Instrumentation::ReJitEverything(const char* key) { - ConfigureStubs(key, InstrumentationLevel::kInstrumentWithInterpreterAndJit); -} - void Instrumentation::DeoptimizeEverything(const char* key) { CHECK(deoptimization_enabled_); ConfigureStubs(key, InstrumentationLevel::kInstrumentWithInterpreter); @@ -1114,7 +1103,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 && Runtime::Current()->IsDeoptimizeable(*return_pc)) { + if (deoptimize && Runtime::Current()->IsAsyncDeoptimizeable(*return_pc)) { if (kVerboseInstrumentation) { LOG(INFO) << "Deoptimizing " << visitor.caller->PrettyMethod() @@ -1132,6 +1121,10 @@ TwoWordReturn Instrumentation::PopInstrumentationStackFrame(Thread* self, uintpt return GetTwoWordSuccessValue(*return_pc, reinterpret_cast<uintptr_t>(GetQuickDeoptimizationEntryPoint())); } else { + if (deoptimize && !Runtime::Current()->IsAsyncDeoptimizeable(*return_pc)) { + LOG(WARNING) << "Got a deoptimization request on un-deoptimizable " << method->PrettyMethod() + << " at PC " << reinterpret_cast<void*>(*return_pc); + } if (kVerboseInstrumentation) { LOG(INFO) << "Returning from " << method->PrettyMethod() << " to PC " << reinterpret_cast<void*>(*return_pc); diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h index 05c0aaa081..01071a541f 100644 --- a/runtime/instrumentation.h +++ b/runtime/instrumentation.h @@ -133,9 +133,6 @@ class Instrumentation { enum class InstrumentationLevel { kInstrumentNothing, // execute without instrumentation kInstrumentWithInstrumentationStubs, // execute with instrumentation entry/exit stubs - kInstrumentWithInterpreterAndJit, // execute with interpreter initially and later the JIT - // (if it is enabled). This level is special in that it - // always requires re-instrumentation. kInstrumentWithInterpreter // execute with interpreter }; @@ -166,13 +163,6 @@ class Instrumentation { } bool ShouldNotifyMethodEnterExitEvents() const REQUIRES_SHARED(Locks::mutator_lock_); - // Executes everything with the interpreter/jit (if available). - void ReJitEverything(const char* key) - REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_) - REQUIRES(!Locks::thread_list_lock_, - !Locks::classlinker_classes_lock_, - !deoptimized_methods_lock_); - // Executes everything with interpreter. void DeoptimizeEverything(const char* key) REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_) @@ -239,7 +229,7 @@ class Instrumentation { REQUIRES_SHARED(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) + void UpdateMethodsCodeForJavaDebuggable(ArtMethod* method, const void* quick_code) REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!deoptimized_methods_lock_); // Get the quick code for the given method. More efficient than asking the class linker as it @@ -264,7 +254,7 @@ class Instrumentation { // Code is in boot image oat file which isn't compiled as debuggable. // Need debug version (interpreter or jitted) if that's the case. - bool NeedDebugVersionForBootImageCode(ArtMethod* method, const void* code) const + bool NeedDebugVersionFor(ArtMethod* method) const REQUIRES_SHARED(Locks::mutator_lock_); bool AreExitStubsInstalled() const { diff --git a/runtime/interpreter/interpreter_common.h b/runtime/interpreter/interpreter_common.h index aeb438f05f..7ef3508164 100644 --- a/runtime/interpreter/interpreter_common.h +++ b/runtime/interpreter/interpreter_common.h @@ -255,17 +255,11 @@ static inline ObjPtr<mirror::String> ResolveString(Thread* self, } } ArtMethod* method = shadow_frame.GetMethod(); - // MethodVerifier refuses methods with string_idx out of bounds. - DCHECK_LT(string_idx.index_ % mirror::DexCache::kDexCacheStringCacheSize, - method->GetDexFile()->NumStringIds()); - ObjPtr<mirror::String> string_ptr = - mirror::StringDexCachePair::Lookup(method->GetDexCache()->GetStrings(), - string_idx.index_, - mirror::DexCache::kDexCacheStringCacheSize).Read(); + ObjPtr<mirror::String> string_ptr = method->GetDexCache()->GetResolvedString(string_idx); if (UNLIKELY(string_ptr == nullptr)) { StackHandleScope<1> hs(self); Handle<mirror::DexCache> dex_cache(hs.NewHandle(method->GetDexCache())); - string_ptr = Runtime::Current()->GetClassLinker()->ResolveString(*method->GetDexFile(), + string_ptr = Runtime::Current()->GetClassLinker()->ResolveString(*dex_cache->GetDexFile(), string_idx, dex_cache); } diff --git a/runtime/interpreter/mterp/mips64/bincmp.S b/runtime/interpreter/mterp/mips64/bincmp.S index 07b12100fd..c2bca91ebf 100644 --- a/runtime/interpreter/mterp/mips64/bincmp.S +++ b/runtime/interpreter/mterp/mips64/bincmp.S @@ -6,7 +6,6 @@ * For: if-eq, if-ne, if-lt, if-ge, if-gt, if-le */ /* if-cmp vA, vB, +CCCC */ - .extern MterpProfileBranch ext a2, rINST, 8, 4 # a2 <- A ext a3, rINST, 12, 4 # a3 <- B lh rINST, 2(rPC) # rINST <- offset (sign-extended CCCC) diff --git a/runtime/interpreter/mterp/mips64/op_packed_switch.S b/runtime/interpreter/mterp/mips64/op_packed_switch.S index 27ce580642..44e77a41d8 100644 --- a/runtime/interpreter/mterp/mips64/op_packed_switch.S +++ b/runtime/interpreter/mterp/mips64/op_packed_switch.S @@ -10,7 +10,6 @@ */ /* op vAA, +BBBBBBBB */ .extern $func - .extern MterpProfileBranch lh a0, 2(rPC) # a0 <- bbbb (lo) lh a1, 4(rPC) # a1 <- BBBB (hi) srl a3, rINST, 8 # a3 <- AA diff --git a/runtime/interpreter/mterp/mterp.cc b/runtime/interpreter/mterp/mterp.cc index 369c2614a7..75ab91acba 100644 --- a/runtime/interpreter/mterp/mterp.cc +++ b/runtime/interpreter/mterp/mterp.cc @@ -768,38 +768,32 @@ extern "C" ssize_t MterpAddHotnessBatch(ArtMethod* method, return MterpSetUpHotnessCountdown(method, shadow_frame); } -// TUNING: Unused by arm/arm64/x86/x86_64. Remove when mips/mips64 mterps support batch updates. -extern "C" size_t MterpProfileBranch(Thread* self, ShadowFrame* shadow_frame, int32_t offset) - REQUIRES_SHARED(Locks::mutator_lock_) { - ArtMethod* method = shadow_frame->GetMethod(); - JValue* result = shadow_frame->GetResultRegister(); - uint32_t dex_pc = shadow_frame->GetDexPC(); - jit::Jit* jit = Runtime::Current()->GetJit(); - if ((jit != nullptr) && (offset <= 0)) { - jit->AddSamples(self, method, 1, /*with_backedges*/ true); - } - int16_t countdown_value = MterpSetUpHotnessCountdown(method, shadow_frame); - if (countdown_value == jit::kJitCheckForOSR) { - return jit::Jit::MaybeDoOnStackReplacement(self, method, dex_pc, offset, result); - } else { - return false; - } -} - extern "C" size_t MterpMaybeDoOnStackReplacement(Thread* self, ShadowFrame* shadow_frame, int32_t offset) REQUIRES_SHARED(Locks::mutator_lock_) { - ArtMethod* method = shadow_frame->GetMethod(); - JValue* result = shadow_frame->GetResultRegister(); - uint32_t dex_pc = shadow_frame->GetDexPC(); - jit::Jit* jit = Runtime::Current()->GetJit(); - if (offset <= 0) { - // Keep updating hotness in case a compilation request was dropped. Eventually it will retry. - jit->AddSamples(self, method, 1, /*with_backedges*/ true); + int16_t osr_countdown = shadow_frame->GetCachedHotnessCountdown() - 1; + bool did_osr = false; + /* + * To reduce the cost of polling the compiler to determine whether the requested OSR + * compilation has completed, only check every Nth time. NOTE: the "osr_countdown <= 0" + * condition is satisfied either by the decrement below or the initial setting of + * the cached countdown field to kJitCheckForOSR, which elsewhere is asserted to be -1. + */ + if (osr_countdown <= 0) { + ArtMethod* method = shadow_frame->GetMethod(); + JValue* result = shadow_frame->GetResultRegister(); + uint32_t dex_pc = shadow_frame->GetDexPC(); + jit::Jit* jit = Runtime::Current()->GetJit(); + osr_countdown = jit::Jit::kJitRecheckOSRThreshold; + if (offset <= 0) { + // Keep updating hotness in case a compilation request was dropped. Eventually it will retry. + jit->AddSamples(self, method, osr_countdown, /*with_backedges*/ true); + } + did_osr = jit::Jit::MaybeDoOnStackReplacement(self, method, dex_pc, offset, result); } - // Assumes caller has already determined that an OSR check is appropriate. - return jit::Jit::MaybeDoOnStackReplacement(self, method, dex_pc, offset, result); + shadow_frame->SetCachedHotnessCountdown(osr_countdown); + return did_osr; } } // namespace interpreter diff --git a/runtime/interpreter/mterp/out/mterp_mips64.S b/runtime/interpreter/mterp/out/mterp_mips64.S index bf096664df..013bb32e8f 100644 --- a/runtime/interpreter/mterp/out/mterp_mips64.S +++ b/runtime/interpreter/mterp/out/mterp_mips64.S @@ -1174,7 +1174,6 @@ artMterpAsmInstructionStart = .L_op_nop */ /* op vAA, +BBBBBBBB */ .extern MterpDoPackedSwitch - .extern MterpProfileBranch lh a0, 2(rPC) # a0 <- bbbb (lo) lh a1, 4(rPC) # a1 <- BBBB (hi) srl a3, rINST, 8 # a3 <- AA @@ -1201,7 +1200,6 @@ artMterpAsmInstructionStart = .L_op_nop */ /* op vAA, +BBBBBBBB */ .extern MterpDoSparseSwitch - .extern MterpProfileBranch lh a0, 2(rPC) # a0 <- bbbb (lo) lh a1, 4(rPC) # a1 <- BBBB (hi) srl a3, rINST, 8 # a3 <- AA @@ -1396,7 +1394,6 @@ artMterpAsmInstructionStart = .L_op_nop * For: if-eq, if-ne, if-lt, if-ge, if-gt, if-le */ /* if-cmp vA, vB, +CCCC */ - .extern MterpProfileBranch ext a2, rINST, 8, 4 # a2 <- A ext a3, rINST, 12, 4 # a3 <- B lh rINST, 2(rPC) # rINST <- offset (sign-extended CCCC) @@ -1423,7 +1420,6 @@ artMterpAsmInstructionStart = .L_op_nop * For: if-eq, if-ne, if-lt, if-ge, if-gt, if-le */ /* if-cmp vA, vB, +CCCC */ - .extern MterpProfileBranch ext a2, rINST, 8, 4 # a2 <- A ext a3, rINST, 12, 4 # a3 <- B lh rINST, 2(rPC) # rINST <- offset (sign-extended CCCC) @@ -1450,7 +1446,6 @@ artMterpAsmInstructionStart = .L_op_nop * For: if-eq, if-ne, if-lt, if-ge, if-gt, if-le */ /* if-cmp vA, vB, +CCCC */ - .extern MterpProfileBranch ext a2, rINST, 8, 4 # a2 <- A ext a3, rINST, 12, 4 # a3 <- B lh rINST, 2(rPC) # rINST <- offset (sign-extended CCCC) @@ -1477,7 +1472,6 @@ artMterpAsmInstructionStart = .L_op_nop * For: if-eq, if-ne, if-lt, if-ge, if-gt, if-le */ /* if-cmp vA, vB, +CCCC */ - .extern MterpProfileBranch ext a2, rINST, 8, 4 # a2 <- A ext a3, rINST, 12, 4 # a3 <- B lh rINST, 2(rPC) # rINST <- offset (sign-extended CCCC) @@ -1504,7 +1498,6 @@ artMterpAsmInstructionStart = .L_op_nop * For: if-eq, if-ne, if-lt, if-ge, if-gt, if-le */ /* if-cmp vA, vB, +CCCC */ - .extern MterpProfileBranch ext a2, rINST, 8, 4 # a2 <- A ext a3, rINST, 12, 4 # a3 <- B lh rINST, 2(rPC) # rINST <- offset (sign-extended CCCC) @@ -1531,7 +1524,6 @@ artMterpAsmInstructionStart = .L_op_nop * For: if-eq, if-ne, if-lt, if-ge, if-gt, if-le */ /* if-cmp vA, vB, +CCCC */ - .extern MterpProfileBranch ext a2, rINST, 8, 4 # a2 <- A ext a3, rINST, 12, 4 # a3 <- B lh rINST, 2(rPC) # rINST <- offset (sign-extended CCCC) diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc index feb6e0857a..371e2f1e65 100644 --- a/runtime/interpreter/unstarted_runtime.cc +++ b/runtime/interpreter/unstarted_runtime.cc @@ -401,6 +401,25 @@ void UnstartedRuntime::UnstartedClassGetDeclaredConstructor( result->SetL(constructor); } +void UnstartedRuntime::UnstartedClassGetDeclaringClass( + 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)))); + if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) { + result->SetL(nullptr); + return; + } + // Return null for anonymous classes. + JValue is_anon_result; + UnstartedClassIsAnonymousClass(self, shadow_frame, &is_anon_result, arg_offset); + if (is_anon_result.GetZ() != 0) { + result->SetL(nullptr); + return; + } + result->SetL(annotations::GetDeclaringClass(klass)); +} + void UnstartedRuntime::UnstartedClassGetEnclosingClass( Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) { StackHandleScope<1> hs(self); @@ -420,6 +439,23 @@ void UnstartedRuntime::UnstartedClassGetInnerClassFlags( result->SetI(mirror::Class::GetInnerClassFlags(klass, default_value)); } +void UnstartedRuntime::UnstartedClassIsAnonymousClass( + 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)))); + if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) { + result->SetZ(false); + return; + } + mirror::String* class_name = nullptr; + if (!annotations::GetInnerClass(klass, &class_name)) { + result->SetZ(false); + return; + } + result->SetZ(class_name == nullptr); +} + static std::unique_ptr<MemMap> FindAndExtractEntry(const std::string& jar_file, const char* entry_name, size_t* size, diff --git a/runtime/interpreter/unstarted_runtime_list.h b/runtime/interpreter/unstarted_runtime_list.h index b8553b5771..96b35e4e9c 100644 --- a/runtime/interpreter/unstarted_runtime_list.h +++ b/runtime/interpreter/unstarted_runtime_list.h @@ -28,8 +28,10 @@ V(ClassGetDeclaredField, "java.lang.reflect.Field java.lang.Class.getDeclaredField(java.lang.String)") \ 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(ClassGetDeclaringClass, "java.lang.Class java.lang.Class.getDeclaringClass()") \ V(ClassGetEnclosingClass, "java.lang.Class java.lang.Class.getEnclosingClass()") \ V(ClassGetInnerClassFlags, "int java.lang.Class.getInnerClassFlags(int)") \ + V(ClassIsAnonymousClass, "boolean java.lang.Class.isAnonymousClass()") \ 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()") \ diff --git a/runtime/interpreter/unstarted_runtime_test.cc b/runtime/interpreter/unstarted_runtime_test.cc index b190c81aff..ae55f4c2ef 100644 --- a/runtime/interpreter/unstarted_runtime_test.cc +++ b/runtime/interpreter/unstarted_runtime_test.cc @@ -885,5 +885,64 @@ TEST_F(UnstartedRuntimeTest, Pow) { ShadowFrame::DeleteDeoptimizedFrame(tmp); } +TEST_F(UnstartedRuntimeTest, IsAnonymousClass) { + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + + JValue result; + ShadowFrame* shadow_frame = ShadowFrame::CreateDeoptimizedFrame(10, nullptr, nullptr, 0); + + mirror::Class* class_klass = mirror::Class::GetJavaLangClass(); + shadow_frame->SetVRegReference(0, class_klass); + UnstartedClassIsAnonymousClass(self, shadow_frame, &result, 0); + EXPECT_EQ(result.GetZ(), 0); + + jobject class_loader = LoadDex("Nested"); + StackHandleScope<1> hs(soa.Self()); + Handle<mirror::ClassLoader> loader( + hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader))); + mirror::Class* c = class_linker_->FindClass(soa.Self(), "LNested$1;", loader); + ASSERT_TRUE(c != nullptr); + shadow_frame->SetVRegReference(0, c); + UnstartedClassIsAnonymousClass(self, shadow_frame, &result, 0); + EXPECT_EQ(result.GetZ(), 1); + + ShadowFrame::DeleteDeoptimizedFrame(shadow_frame); +} + +TEST_F(UnstartedRuntimeTest, GetDeclaringClass) { + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + + JValue result; + ShadowFrame* shadow_frame = ShadowFrame::CreateDeoptimizedFrame(10, nullptr, nullptr, 0); + + jobject class_loader = LoadDex("Nested"); + StackHandleScope<4> hs(self); + Handle<mirror::ClassLoader> loader( + hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader))); + + Handle<mirror::Class> nested_klass(hs.NewHandle( + class_linker_->FindClass(soa.Self(), "LNested;", loader))); + Handle<mirror::Class> inner_klass(hs.NewHandle( + class_linker_->FindClass(soa.Self(), "LNested$Inner;", loader))); + Handle<mirror::Class> anon_klass(hs.NewHandle( + class_linker_->FindClass(soa.Self(), "LNested$1;", loader))); + + shadow_frame->SetVRegReference(0, nested_klass.Get()); + UnstartedClassGetDeclaringClass(self, shadow_frame, &result, 0); + EXPECT_EQ(result.GetL(), nullptr); + + shadow_frame->SetVRegReference(0, inner_klass.Get()); + UnstartedClassGetDeclaringClass(self, shadow_frame, &result, 0); + EXPECT_EQ(result.GetL(), nested_klass.Get()); + + shadow_frame->SetVRegReference(0, anon_klass.Get()); + UnstartedClassGetDeclaringClass(self, shadow_frame, &result, 0); + EXPECT_EQ(result.GetL(), nullptr); + + ShadowFrame::DeleteDeoptimizedFrame(shadow_frame); +} + } // namespace interpreter } // namespace art diff --git a/runtime/jdwp/object_registry.cc b/runtime/jdwp/object_registry.cc index 170887e397..4615574947 100644 --- a/runtime/jdwp/object_registry.cc +++ b/runtime/jdwp/object_registry.cc @@ -19,6 +19,7 @@ #include "handle_scope-inl.h" #include "jni_internal.h" #include "mirror/class.h" +#include "mirror/throwable.h" #include "obj_ptr-inl.h" #include "scoped_thread_state_change-inl.h" diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc index 6deb03dc41..fec3c4f7b8 100644 --- a/runtime/jit/jit.cc +++ b/runtime/jit/jit.cc @@ -145,7 +145,12 @@ 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) {} + use_jit_compilation_(true), + hot_method_threshold_(0), + warm_method_threshold_(0), + osr_method_threshold_(0), + priority_thread_weight_(0), + invoke_transition_weight_(0) {} Jit* Jit::Create(JitOptions* options, std::string* error_msg) { DCHECK(options->UseJitCompilation() || options->GetProfileSaverOptions().IsEnabled()); @@ -289,7 +294,11 @@ bool Jit::CompileMethod(ArtMethod* method, Thread* self, bool osr) { void Jit::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)); + + // We need peers as we may report the JIT thread, e.g., in the debugger. + constexpr bool kJitPoolNeedsPeers = true; + thread_pool_.reset(new ThreadPool("Jit thread pool", 1, kJitPoolNeedsPeers)); + thread_pool_->SetPthreadPriority(kJitPoolThreadPthreadPriority); Start(); } diff --git a/runtime/jit/jit.h b/runtime/jit/jit.h index 4112142a4f..d566799340 100644 --- a/runtime/jit/jit.h +++ b/runtime/jit/jit.h @@ -54,6 +54,8 @@ class Jit { static constexpr size_t kDefaultCompileThreshold = kStressMode ? 2 : 10000; static constexpr size_t kDefaultPriorityThreadWeightRatio = 1000; static constexpr size_t kDefaultInvokeTransitionWeightRatio = 500; + // How frequently should the interpreter check to see if OSR compilation is ready. + static constexpr int16_t kJitRecheckOSRThreshold = 100; virtual ~Jit(); static Jit* Create(JitOptions* options, std::string* error_msg); diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc index 6336cddc07..f5151b588a 100644 --- a/runtime/jit/jit_code_cache.cc +++ b/runtime/jit/jit_code_cache.cc @@ -594,7 +594,7 @@ uint8_t* JitCodeCache::CommitCodeInternal(Thread* self, VLOG(jit) << "JIT discarded jitted code due to invalid single-implementation assumptions."; return nullptr; } - DCHECK(cha_single_implementation_list.empty() || !Runtime::Current()->IsDebuggable()) + DCHECK(cha_single_implementation_list.empty() || !Runtime::Current()->IsJavaDebuggable()) << "Should not be using cha on debuggable apps/runs!"; for (ArtMethod* single_impl : cha_single_implementation_list) { @@ -1141,8 +1141,17 @@ OatQuickMethodHeader* JitCodeCache::LookupMethodHeader(uintptr_t pc, ArtMethod* return nullptr; } if (kIsDebugBuild && method != nullptr) { - DCHECK_EQ(it->second, method) - << ArtMethod::PrettyMethod(method) << " " << ArtMethod::PrettyMethod(it->second) << " " + // When we are walking the stack to redefine classes and creating obsolete methods it is + // possible that we might have updated the method_code_map by making this method obsolete in a + // previous frame. Therefore we should just check that the non-obsolete version of this method + // is the one we expect. We change to the non-obsolete versions in the error message since the + // obsolete version of the method might not be fully initialized yet. This situation can only + // occur when we are in the process of allocating and setting up obsolete methods. Otherwise + // method and it->second should be identical. (See runtime/openjdkjvmti/ti_redefine.cc for more + // information.) + DCHECK_EQ(it->second->GetNonObsoleteMethod(), method->GetNonObsoleteMethod()) + << ArtMethod::PrettyMethod(method->GetNonObsoleteMethod()) << " " + << ArtMethod::PrettyMethod(it->second->GetNonObsoleteMethod()) << " " << std::hex << pc; } return method_header; diff --git a/runtime/memory_region.cc b/runtime/memory_region.cc index a5c70c3b26..13cc5c99bc 100644 --- a/runtime/memory_region.cc +++ b/runtime/memory_region.cc @@ -29,8 +29,39 @@ void MemoryRegion::CopyFrom(size_t offset, const MemoryRegion& from) const { CHECK_GT(from.size(), 0U); CHECK_GE(this->size(), from.size()); CHECK_LE(offset, this->size() - from.size()); - memmove(reinterpret_cast<void*>(start() + offset), - from.pointer(), from.size()); + memmove(reinterpret_cast<void*>(begin() + offset), from.pointer(), from.size()); +} + +void MemoryRegion::StoreBits(uintptr_t bit_offset, uint32_t value, size_t length) { + DCHECK_LE(value, MaxInt<uint32_t>(length)); + DCHECK_LE(length, BitSizeOf<uint32_t>()); + DCHECK_LE(bit_offset + length, size_in_bits()); + if (length == 0) { + return; + } + // Bits are stored in this order {7 6 5 4 3 2 1 0}. + // How many remaining bits in current byte is (bit_offset % kBitsPerByte) + 1. + uint8_t* out = ComputeInternalPointer<uint8_t>(bit_offset >> kBitsPerByteLog2); + size_t orig_len = length; + uint32_t orig_value = value; + uintptr_t bit_remainder = bit_offset % kBitsPerByte; + while (true) { + const uintptr_t remaining_bits = kBitsPerByte - bit_remainder; + if (length <= remaining_bits) { + // Length is smaller than all of remainder bits. + size_t mask = ((1 << length) - 1) << bit_remainder; + *out = (*out & ~mask) | (value << bit_remainder); + break; + } + // Copy remaining bits in current byte. + size_t value_mask = (1 << remaining_bits) - 1; + *out = (*out & ~(value_mask << bit_remainder)) | ((value & value_mask) << bit_remainder); + value >>= remaining_bits; + bit_remainder = 0; + length -= remaining_bits; + ++out; + } + DCHECK_EQ(LoadBits(bit_offset, orig_len), orig_value) << bit_offset << " " << orig_len; } } // namespace art diff --git a/runtime/memory_region.h b/runtime/memory_region.h index fe3f917c76..7cf5d49d70 100644 --- a/runtime/memory_region.h +++ b/runtime/memory_region.h @@ -35,6 +35,12 @@ namespace art { // of the region. class MemoryRegion FINAL : public ValueObject { public: + struct ContentEquals { + constexpr bool operator()(const MemoryRegion& lhs, const MemoryRegion& rhs) const { + return lhs.size() == rhs.size() && memcmp(lhs.begin(), rhs.begin(), lhs.size()) == 0; + } + }; + MemoryRegion() : pointer_(nullptr), size_(0) {} MemoryRegion(void* pointer_in, uintptr_t size_in) : pointer_(pointer_in), size_(size_in) {} @@ -46,8 +52,8 @@ class MemoryRegion FINAL : public ValueObject { return OFFSETOF_MEMBER(MemoryRegion, pointer_); } - uint8_t* start() const { return reinterpret_cast<uint8_t*>(pointer_); } - uint8_t* end() const { return start() + size_; } + uint8_t* begin() const { return reinterpret_cast<uint8_t*>(pointer_); } + uint8_t* end() const { return begin() + size_; } // Load value of type `T` at `offset`. The memory address corresponding // to `offset` should be word-aligned (on ARM, this is a requirement). @@ -124,11 +130,35 @@ class MemoryRegion FINAL : public ValueObject { // The bit at the smallest offset is the least significant bit in the // loaded value. `length` must not be larger than the number of bits // contained in the return value (32). - uint32_t LoadBits(uintptr_t bit_offset, size_t length) const { - CHECK_LE(length, sizeof(uint32_t) * kBitsPerByte); - uint32_t value = 0u; + ALWAYS_INLINE uint32_t LoadBits(uintptr_t bit_offset, size_t length) const { + DCHECK_LE(length, BitSizeOf<uint32_t>()); + DCHECK_LE(bit_offset + length, size_in_bits()); + if (UNLIKELY(length == 0)) { + // Do not touch any memory if the range is empty. + return 0; + } + const uint8_t* address = begin() + bit_offset / kBitsPerByte; + const uint32_t shift = bit_offset & (kBitsPerByte - 1); + // Load the value (reading only the strictly needed bytes). + const uint32_t load_bit_count = shift + length; + uint32_t value = address[0] >> shift; + if (load_bit_count > 8) { + value |= static_cast<uint32_t>(address[1]) << (8 - shift); + if (load_bit_count > 16) { + value |= static_cast<uint32_t>(address[2]) << (16 - shift); + if (load_bit_count > 24) { + value |= static_cast<uint32_t>(address[3]) << (24 - shift); + if (load_bit_count > 32) { + value |= static_cast<uint32_t>(address[4]) << (32 - shift); + } + } + } + } + // Clear unwanted most significant bits. + uint32_t clear_bit_count = BitSizeOf(value) - length; + value = (value << clear_bit_count) >> clear_bit_count; for (size_t i = 0; i < length; ++i) { - value |= LoadBit(bit_offset + i) << i; + DCHECK_EQ((value >> i) & 1, LoadBit(bit_offset + i)); } return value; } @@ -137,21 +167,22 @@ class MemoryRegion FINAL : public ValueObject { // `bit_offset`. The bit at the smallest offset is the least significant // bit of the stored `value`. `value` must not be larger than `length` // bits. - void StoreBits(uintptr_t bit_offset, uint32_t value, size_t length) { - CHECK_LE(value, MaxInt<uint32_t>(length)); - for (size_t i = 0; i < length; ++i) { - bool ith_bit = value & (1 << i); - StoreBit(bit_offset + i, ith_bit); - } - } + void StoreBits(uintptr_t bit_offset, uint32_t value, size_t length); void CopyFrom(size_t offset, const MemoryRegion& from) const; + template<class Vector> + void CopyFromVector(size_t offset, Vector& vector) const { + if (!vector.empty()) { + CopyFrom(offset, MemoryRegion(vector.data(), vector.size())); + } + } + // Compute a sub memory region based on an existing one. ALWAYS_INLINE MemoryRegion Subregion(uintptr_t offset, uintptr_t size_in) const { CHECK_GE(this->size(), size_in); CHECK_LE(offset, this->size() - size_in); - return MemoryRegion(reinterpret_cast<void*>(start() + offset), size_in); + return MemoryRegion(reinterpret_cast<void*>(begin() + offset), size_in); } // Compute an extended memory region based on an existing one. @@ -165,7 +196,7 @@ class MemoryRegion FINAL : public ValueObject { ALWAYS_INLINE T* ComputeInternalPointer(size_t offset) const { CHECK_GE(size(), sizeof(T)); CHECK_LE(offset, size() - sizeof(T)); - return reinterpret_cast<T*>(start() + offset); + return reinterpret_cast<T*>(begin() + offset); } // Locate the bit with the given offset. Returns a pointer to the byte diff --git a/runtime/memory_region_test.cc b/runtime/memory_region_test.cc index 72e03a485a..6634c60193 100644 --- a/runtime/memory_region_test.cc +++ b/runtime/memory_region_test.cc @@ -14,6 +14,7 @@ * limitations under the License. */ +#include "bit_memory_region.h" #include "memory_region.h" #include "gtest/gtest.h" @@ -55,4 +56,35 @@ TEST(MemoryRegion, StoreUnaligned) { } } +TEST(MemoryRegion, TestBits) { + const size_t n = 8; + uint8_t data[n] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + MemoryRegion region(&data, n); + uint32_t value = 0xDEADBEEF; + // Try various offsets and lengths. + for (size_t bit_offset = 0; bit_offset < 2 * kBitsPerByte; ++bit_offset) { + for (size_t length = 0; length < 2 * kBitsPerByte; ++length) { + const uint32_t length_mask = (1 << length) - 1; + uint32_t masked_value = value & length_mask; + BitMemoryRegion bmr(region, bit_offset, length); + region.StoreBits(bit_offset, masked_value, length); + EXPECT_EQ(region.LoadBits(bit_offset, length), masked_value); + EXPECT_EQ(bmr.LoadBits(0, length), masked_value); + // Check adjacent bits to make sure they were not incorrectly cleared. + EXPECT_EQ(region.LoadBits(0, bit_offset), (1u << bit_offset) - 1); + EXPECT_EQ(region.LoadBits(bit_offset + length, length), length_mask); + region.StoreBits(bit_offset, length_mask, length); + // Store with bit memory region. + bmr.StoreBits(0, masked_value, length); + EXPECT_EQ(bmr.LoadBits(0, length), masked_value); + // Check adjacent bits to make sure they were not incorrectly cleared. + EXPECT_EQ(region.LoadBits(0, bit_offset), (1u << bit_offset) - 1); + EXPECT_EQ(region.LoadBits(bit_offset + length, length), length_mask); + region.StoreBits(bit_offset, length_mask, length); + // Flip the value to try different edge bit combinations. + value = ~value; + } + } +} + } // namespace art diff --git a/runtime/mirror/array-inl.h b/runtime/mirror/array-inl.h index a5db0c0a8a..f56226bd98 100644 --- a/runtime/mirror/array-inl.h +++ b/runtime/mirror/array-inl.h @@ -207,6 +207,19 @@ inline void PrimitiveArray<T>::VisitRoots(RootVisitor* visitor) { } template<typename T> +inline PrimitiveArray<T>* PrimitiveArray<T>::AllocateAndFill(Thread* self, + const T* data, + size_t length) { + StackHandleScope<1> hs(self); + Handle<PrimitiveArray<T>> arr(hs.NewHandle(PrimitiveArray<T>::Alloc(self, length))); + if (!arr.IsNull()) { + // Copy it in. Just skip if it's null + memcpy(arr->GetData(), data, sizeof(T) * length); + } + return arr.Get(); +} + +template<typename T> inline PrimitiveArray<T>* PrimitiveArray<T>::Alloc(Thread* self, size_t length) { Array* raw_array = Array::Alloc<true>(self, GetArrayClass(), diff --git a/runtime/mirror/array.h b/runtime/mirror/array.h index 19d300e1f4..16cf30f1e2 100644 --- a/runtime/mirror/array.h +++ b/runtime/mirror/array.h @@ -119,6 +119,10 @@ class MANAGED PrimitiveArray : public Array { static PrimitiveArray<T>* Alloc(Thread* self, size_t length) REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Roles::uninterruptible_); + static PrimitiveArray<T>* AllocateAndFill(Thread* self, const T* data, size_t length) + REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Roles::uninterruptible_); + + const T* GetData() const ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_) { return reinterpret_cast<const T*>(GetRawData(sizeof(T), 0)); } diff --git a/runtime/mirror/class-inl.h b/runtime/mirror/class-inl.h index 2fb8d28de9..2cff47e8b4 100644 --- a/runtime/mirror/class-inl.h +++ b/runtime/mirror/class-inl.h @@ -65,15 +65,27 @@ inline Class* Class::GetSuperClass() { OFFSET_OF_OBJECT_MEMBER(Class, super_class_)); } +inline void Class::SetSuperClass(ObjPtr<Class> new_super_class) { + // Super class is assigned once, except during class linker initialization. + if (kIsDebugBuild) { + ObjPtr<Class> old_super_class = + GetFieldObject<Class>(OFFSET_OF_OBJECT_MEMBER(Class, super_class_)); + DCHECK(old_super_class == nullptr || old_super_class == new_super_class); + } + DCHECK(new_super_class != nullptr); + SetFieldObject<false>(OFFSET_OF_OBJECT_MEMBER(Class, super_class_), new_super_class); +} + template<VerifyObjectFlags kVerifyFlags, ReadBarrierOption kReadBarrierOption> inline ClassLoader* Class::GetClassLoader() { return GetFieldObject<ClassLoader, kVerifyFlags, kReadBarrierOption>( OFFSET_OF_OBJECT_MEMBER(Class, class_loader_)); } -template<VerifyObjectFlags kVerifyFlags> +template<VerifyObjectFlags kVerifyFlags, ReadBarrierOption kReadBarrierOption> inline DexCache* Class::GetDexCache() { - return GetFieldObject<DexCache, kVerifyFlags>(OFFSET_OF_OBJECT_MEMBER(Class, dex_cache_)); + return GetFieldObject<DexCache, kVerifyFlags, kReadBarrierOption>( + OFFSET_OF_OBJECT_MEMBER(Class, dex_cache_)); } inline uint32_t Class::GetCopiedMethodsStartOffset() { @@ -634,23 +646,6 @@ inline void Class::SetClinitThreadId(pid_t new_clinit_thread_id) { } } -template<VerifyObjectFlags kVerifyFlags> -inline uint32_t Class::GetAccessFlags() { - // Check class is loaded/retired or this is java.lang.String that has a - // circularity issue during loading the names of its members - DCHECK(IsIdxLoaded<kVerifyFlags>() || IsRetired<kVerifyFlags>() || - IsErroneous<static_cast<VerifyObjectFlags>(kVerifyFlags & ~kVerifyThis)>() || - this == String::GetJavaLangString()) - << "IsIdxLoaded=" << IsIdxLoaded<kVerifyFlags>() - << " IsRetired=" << IsRetired<kVerifyFlags>() - << " IsErroneous=" << - IsErroneous<static_cast<VerifyObjectFlags>(kVerifyFlags & ~kVerifyThis)>() - << " IsString=" << (this == String::GetJavaLangString()) - << " status= " << GetStatus<kVerifyFlags>() - << " descriptor=" << PrettyDescriptor(); - return GetField32<kVerifyFlags>(AccessFlagsOffset()); -} - inline String* Class::GetName() { return GetFieldObject<String>(OFFSET_OF_OBJECT_MEMBER(Class, name_)); } diff --git a/runtime/mirror/class.cc b/runtime/mirror/class.cc index 9964b73c98..1b8f3f83e7 100644 --- a/runtime/mirror/class.cc +++ b/runtime/mirror/class.cc @@ -115,7 +115,9 @@ void Class::SetStatus(Handle<Class> h_this, Status new_status, Thread* self) { ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); bool class_linker_initialized = class_linker != nullptr && class_linker->IsInitialized(); if (LIKELY(class_linker_initialized)) { - if (UNLIKELY(new_status <= old_status && new_status != kStatusError && + if (UNLIKELY(new_status <= old_status && + new_status != kStatusErrorUnresolved && + new_status != kStatusErrorResolved && new_status != kStatusRetired)) { LOG(FATAL) << "Unexpected change back of class status for " << h_this->PrettyClass() << " " << old_status << " -> " << new_status; @@ -127,10 +129,12 @@ void Class::SetStatus(Handle<Class> h_this, Status new_status, Thread* self) { << h_this->PrettyClass() << " " << old_status << " -> " << new_status; } } - if (UNLIKELY(new_status == kStatusError)) { - CHECK_NE(h_this->GetStatus(), kStatusError) + if (UNLIKELY(IsErroneous(new_status))) { + CHECK(!h_this->IsErroneous()) << "Attempt to set as erroneous an already erroneous class " - << h_this->PrettyClass(); + << h_this->PrettyClass() + << " old_status: " << old_status << " new_status: " << new_status; + CHECK_EQ(new_status == kStatusErrorResolved, old_status >= kStatusResolved); if (VLOG_IS_ON(class_linker)) { LOG(ERROR) << "Setting " << h_this->PrettyDescriptor() << " to erroneous."; if (self->IsExceptionPending()) { @@ -177,7 +181,7 @@ void Class::SetStatus(Handle<Class> h_this, Status new_status, Thread* self) { // Class is a temporary one, ensure that waiters for resolution get notified of retirement // so that they can grab the new version of the class from the class linker's table. CHECK_LT(new_status, kStatusResolved) << h_this->PrettyDescriptor(); - if (new_status == kStatusRetired || new_status == kStatusError) { + if (new_status == kStatusRetired || new_status == kStatusErrorUnresolved) { h_this->NotifyAll(self); } } else { @@ -305,7 +309,7 @@ void Class::DumpClass(std::ostream& os, int flags) { } if (h_this->NumStaticFields() > 0) { os << " static fields (" << h_this->NumStaticFields() << " entries):\n"; - if (h_this->IsResolved() || h_this->IsErroneous()) { + if (h_this->IsResolved()) { for (size_t i = 0; i < h_this->NumStaticFields(); ++i) { os << StringPrintf(" %2zd: %s\n", i, ArtField::PrettyField(h_this->GetStaticField(i)).c_str()); @@ -316,7 +320,7 @@ void Class::DumpClass(std::ostream& os, int flags) { } if (h_this->NumInstanceFields() > 0) { os << " instance fields (" << h_this->NumInstanceFields() << " entries):\n"; - if (h_this->IsResolved() || h_this->IsErroneous()) { + if (h_this->IsResolved()) { for (size_t i = 0; i < h_this->NumInstanceFields(); ++i) { os << StringPrintf(" %2zd: %s\n", i, ArtField::PrettyField(h_this->GetInstanceField(i)).c_str()); @@ -1341,5 +1345,26 @@ std::string Class::PrettyClassAndClassLoader() { return result; } +template<VerifyObjectFlags kVerifyFlags> void Class::GetAccessFlagsDCheck() { + // Check class is loaded/retired or this is java.lang.String that has a + // circularity issue during loading the names of its members + DCHECK(IsIdxLoaded<kVerifyFlags>() || IsRetired<kVerifyFlags>() || + IsErroneous<static_cast<VerifyObjectFlags>(kVerifyFlags & ~kVerifyThis)>() || + this == String::GetJavaLangString()) + << "IsIdxLoaded=" << IsIdxLoaded<kVerifyFlags>() + << " IsRetired=" << IsRetired<kVerifyFlags>() + << " IsErroneous=" << + IsErroneous<static_cast<VerifyObjectFlags>(kVerifyFlags & ~kVerifyThis)>() + << " IsString=" << (this == String::GetJavaLangString()) + << " status= " << GetStatus<kVerifyFlags>() + << " descriptor=" << PrettyDescriptor(); +} +// Instantiate the common cases. +template void Class::GetAccessFlagsDCheck<kVerifyNone>(); +template void Class::GetAccessFlagsDCheck<kVerifyThis>(); +template void Class::GetAccessFlagsDCheck<kVerifyReads>(); +template void Class::GetAccessFlagsDCheck<kVerifyWrites>(); +template void Class::GetAccessFlagsDCheck<kVerifyAll>(); + } // namespace mirror } // namespace art diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h index fb2792a316..d34f09c721 100644 --- a/runtime/mirror/class.h +++ b/runtime/mirror/class.h @@ -84,6 +84,13 @@ class MANAGED Class FINAL : public Object { // will be gc'ed once all refs to the class point to the newly // cloned version. // + // kStatusErrorUnresolved, kStatusErrorResolved: Class is erroneous. We need + // to distinguish between classes that have been resolved and classes that + // have not. This is important because the const-class instruction needs to + // return a previously resolved class even if its subsequent initialization + // failed. We also need this to decide whether to wrap a previous + // initialization failure in ClassDefNotFound error or not. + // // kStatusNotReady: If a Class cannot be found in the class table by // FindClass, it allocates an new one with AllocClass in the // kStatusNotReady and calls LoadClass. Note if it does find a @@ -119,8 +126,9 @@ class MANAGED Class FINAL : public Object { // // TODO: Explain the other states enum Status { - kStatusRetired = -2, // Retired, should not be used. Use the newly cloned one instead. - kStatusError = -1, + kStatusRetired = -3, // Retired, should not be used. Use the newly cloned one instead. + kStatusErrorResolved = -2, + kStatusErrorUnresolved = -1, kStatusNotReady = 0, kStatusIdx = 1, // Loaded, DEX idx in super_class_type_idx_ and interfaces_type_idx_. kStatusLoaded = 2, // DEX idx values resolved. @@ -158,8 +166,25 @@ class MANAGED Class FINAL : public Object { // Returns true if the class has failed to link. template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> + bool IsErroneousUnresolved() REQUIRES_SHARED(Locks::mutator_lock_) { + return GetStatus<kVerifyFlags>() == kStatusErrorUnresolved; + } + + // Returns true if the class has failed to initialize. + template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> + bool IsErroneousResolved() REQUIRES_SHARED(Locks::mutator_lock_) { + return GetStatus<kVerifyFlags>() == kStatusErrorResolved; + } + + // Returns true if the class status indicets that the class has failed to link or initialize. + static bool IsErroneous(Status status) { + return status == kStatusErrorUnresolved || status == kStatusErrorResolved; + } + + // Returns true if the class has failed to link or initialize. + template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> bool IsErroneous() REQUIRES_SHARED(Locks::mutator_lock_) { - return GetStatus<kVerifyFlags>() == kStatusError; + return IsErroneous(GetStatus<kVerifyFlags>()); } // Returns true if the class has been loaded. @@ -177,7 +202,8 @@ class MANAGED Class FINAL : public Object { // Returns true if the class has been linked. template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> bool IsResolved() REQUIRES_SHARED(Locks::mutator_lock_) { - return GetStatus<kVerifyFlags>() >= kStatusResolved; + Status status = GetStatus<kVerifyFlags>(); + return status >= kStatusResolved || status == kStatusErrorResolved; } // Returns true if the class was compile-time verified. @@ -205,7 +231,13 @@ class MANAGED Class FINAL : public Object { } template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> - ALWAYS_INLINE uint32_t GetAccessFlags() REQUIRES_SHARED(Locks::mutator_lock_); + ALWAYS_INLINE uint32_t GetAccessFlags() REQUIRES_SHARED(Locks::mutator_lock_) { + if (kIsDebugBuild) { + GetAccessFlagsDCheck<kVerifyFlags>(); + } + return GetField32<kVerifyFlags>(AccessFlagsOffset()); + } + static MemberOffset AccessFlagsOffset() { return OFFSET_OF_OBJECT_MEMBER(Class, access_flags_); } @@ -345,7 +377,7 @@ class MANAGED Class FINAL : public Object { // be replaced with a class with the right size for embedded imt/vtable. bool IsTemp() REQUIRES_SHARED(Locks::mutator_lock_) { Status s = GetStatus(); - return s < Status::kStatusResolving && ShouldHaveEmbeddedVTable(); + return s < Status::kStatusResolving && s != kStatusErrorResolved && ShouldHaveEmbeddedVTable(); } String* GetName() REQUIRES_SHARED(Locks::mutator_lock_); // Returns the cached name. @@ -657,14 +689,7 @@ class MANAGED Class FINAL : public Object { // `This` and `klass` must be classes. ObjPtr<Class> GetCommonSuperClass(Handle<Class> klass) REQUIRES_SHARED(Locks::mutator_lock_); - void SetSuperClass(ObjPtr<Class> new_super_class) REQUIRES_SHARED(Locks::mutator_lock_) { - // Super class is assigned once, except during class linker initialization. - ObjPtr<Class> old_super_class = - GetFieldObject<Class>(OFFSET_OF_OBJECT_MEMBER(Class, super_class_)); - DCHECK(old_super_class == nullptr || old_super_class == new_super_class); - DCHECK(new_super_class != nullptr); - SetFieldObject<false>(OFFSET_OF_OBJECT_MEMBER(Class, super_class_), new_super_class); - } + void SetSuperClass(ObjPtr<Class> new_super_class) REQUIRES_SHARED(Locks::mutator_lock_); bool HasSuperClass() REQUIRES_SHARED(Locks::mutator_lock_) { return GetSuperClass() != nullptr; @@ -696,7 +721,8 @@ class MANAGED Class FINAL : public Object { void DumpClass(std::ostream& os, int flags) REQUIRES_SHARED(Locks::mutator_lock_); - template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> + template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags, + ReadBarrierOption kReadBarrierOption = kWithReadBarrier> DexCache* GetDexCache() REQUIRES_SHARED(Locks::mutator_lock_); // Also updates the dex_cache_strings_ variable from new_dex_cache. @@ -1017,7 +1043,7 @@ class MANAGED Class FINAL : public Object { // Returns the number of instance fields containing reference types. Does not count fields in any // super classes. uint32_t NumReferenceInstanceFields() REQUIRES_SHARED(Locks::mutator_lock_) { - DCHECK(IsResolved() || IsErroneous()); + DCHECK(IsResolved()); return GetField32(OFFSET_OF_OBJECT_MEMBER(Class, num_reference_instance_fields_)); } @@ -1045,7 +1071,7 @@ class MANAGED Class FINAL : public Object { // Returns the number of static fields containing reference types. uint32_t NumReferenceStaticFields() REQUIRES_SHARED(Locks::mutator_lock_) { - DCHECK(IsResolved() || IsErroneous()); + DCHECK(IsResolved()); return GetField32(OFFSET_OF_OBJECT_MEMBER(Class, num_reference_static_fields_)); } @@ -1370,6 +1396,9 @@ class MANAGED Class FINAL : public Object { bool ProxyDescriptorEquals(const char* match) REQUIRES_SHARED(Locks::mutator_lock_); + template<VerifyObjectFlags kVerifyFlags> + void GetAccessFlagsDCheck() REQUIRES_SHARED(Locks::mutator_lock_); + // Check that the pointer size matches the one in the class linker. ALWAYS_INLINE static void CheckPointerSize(PointerSize pointer_size); diff --git a/runtime/mirror/method_handle_impl.h b/runtime/mirror/method_handle_impl.h index abe999a5ac..53d267b52c 100644 --- a/runtime/mirror/method_handle_impl.h +++ b/runtime/mirror/method_handle_impl.h @@ -19,12 +19,13 @@ #include "class.h" #include "gc_root.h" -#include "object.h" +#include "object-inl.h" #include "method_handles.h" #include "method_type.h" namespace art { +struct MethodHandleOffsets; struct MethodHandleImplOffsets; namespace mirror { @@ -84,10 +85,12 @@ class MANAGED MethodHandle : public Object { static mirror::Class* StaticClass() REQUIRES_SHARED(Locks::mutator_lock_); private: + // NOTE: cached_spread_invoker_ isn't used by the runtime. + HeapReference<mirror::MethodHandle> cached_spread_invoker_; HeapReference<mirror::MethodType> nominal_type_; HeapReference<mirror::MethodType> method_type_; - uint64_t art_field_or_method_; uint32_t handle_kind_; + uint64_t art_field_or_method_; private: static MemberOffset NominalTypeOffset() { @@ -103,7 +106,7 @@ class MANAGED MethodHandle : public Object { return MemberOffset(OFFSETOF_MEMBER(MethodHandle, handle_kind_)); } - friend struct art::MethodHandleImplOffsets; // for verifying offset information + friend struct art::MethodHandleOffsets; // for verifying offset information DISALLOW_IMPLICIT_CONSTRUCTORS(MethodHandle); }; @@ -119,6 +122,11 @@ class MANAGED MethodHandleImpl : public MethodHandle { static void VisitRoots(RootVisitor* visitor) REQUIRES_SHARED(Locks::mutator_lock_); private: + static MemberOffset InfoOffset() { + return MemberOffset(OFFSETOF_MEMBER(MethodHandleImpl, info_)); + } + + HeapReference<mirror::Object> info_; // Unused by the runtime. static GcRoot<mirror::Class> static_class_; // java.lang.invoke.MethodHandleImpl.class friend struct art::MethodHandleImplOffsets; // for verifying offset information diff --git a/runtime/mirror/method_type_test.cc b/runtime/mirror/method_type_test.cc index 03ab93069c..637bafd75e 100644 --- a/runtime/mirror/method_type_test.cc +++ b/runtime/mirror/method_type_test.cc @@ -19,12 +19,13 @@ #include <string> #include <vector> +#include "class-inl.h" #include "class_linker.h" +#include "class_loader.h" #include "common_runtime_test.h" #include "handle_scope-inl.h" -#include "runtime/mirror/class.h" -#include "runtime/mirror/class_loader.h" -#include "scoped_thread_state_change.h" +#include "object_array-inl.h" +#include "scoped_thread_state_change-inl.h" namespace art { namespace mirror { diff --git a/runtime/mirror/object-inl.h b/runtime/mirror/object-inl.h index 354410e6bf..8e591e4434 100644 --- a/runtime/mirror/object-inl.h +++ b/runtime/mirror/object-inl.h @@ -142,8 +142,10 @@ inline void Object::Wait(Thread* self, int64_t ms, int32_t ns) { } inline uint32_t Object::GetReadBarrierState(uintptr_t* fake_address_dependency) { -#ifdef USE_BAKER_READ_BARRIER - CHECK(kUseBakerReadBarrier); + if (!kUseBakerReadBarrier) { + LOG(FATAL) << "Unreachable"; + UNREACHABLE(); + } #if defined(__arm__) uintptr_t obj = reinterpret_cast<uintptr_t>(this); uintptr_t result; @@ -190,37 +192,29 @@ inline uint32_t Object::GetReadBarrierState(uintptr_t* fake_address_dependency) UNREACHABLE(); UNUSED(fake_address_dependency); #endif -#else // !USE_BAKER_READ_BARRIER - LOG(FATAL) << "Unreachable"; - UNREACHABLE(); - UNUSED(fake_address_dependency); -#endif } inline uint32_t Object::GetReadBarrierState() { -#ifdef USE_BAKER_READ_BARRIER + if (!kUseBakerReadBarrier) { + LOG(FATAL) << "Unreachable"; + UNREACHABLE(); + } DCHECK(kUseBakerReadBarrier); LockWord lw(GetField<uint32_t, /*kIsVolatile*/false>(OFFSET_OF_OBJECT_MEMBER(Object, monitor_))); uint32_t rb_state = lw.ReadBarrierState(); DCHECK(ReadBarrier::IsValidReadBarrierState(rb_state)) << rb_state; return rb_state; -#else - LOG(FATAL) << "Unreachable"; - UNREACHABLE(); -#endif } inline uint32_t Object::GetReadBarrierStateAcquire() { -#ifdef USE_BAKER_READ_BARRIER - DCHECK(kUseBakerReadBarrier); + if (!kUseBakerReadBarrier) { + LOG(FATAL) << "Unreachable"; + UNREACHABLE(); + } LockWord lw(GetFieldAcquire<uint32_t>(OFFSET_OF_OBJECT_MEMBER(Object, monitor_))); uint32_t rb_state = lw.ReadBarrierState(); DCHECK(ReadBarrier::IsValidReadBarrierState(rb_state)) << rb_state; return rb_state; -#else - LOG(FATAL) << "Unreachable"; - UNREACHABLE(); -#endif } inline uint32_t Object::GetMarkBit() { @@ -233,23 +227,22 @@ inline uint32_t Object::GetMarkBit() { } inline void Object::SetReadBarrierState(uint32_t rb_state) { -#ifdef USE_BAKER_READ_BARRIER - DCHECK(kUseBakerReadBarrier); + if (!kUseBakerReadBarrier) { + LOG(FATAL) << "Unreachable"; + UNREACHABLE(); + } DCHECK(ReadBarrier::IsValidReadBarrierState(rb_state)) << rb_state; LockWord lw = GetLockWord(false); lw.SetReadBarrierState(rb_state); SetLockWord(lw, false); -#else - LOG(FATAL) << "Unreachable"; - UNREACHABLE(); - UNUSED(rb_state); -#endif } template<bool kCasRelease> inline bool Object::AtomicSetReadBarrierState(uint32_t expected_rb_state, uint32_t rb_state) { -#ifdef USE_BAKER_READ_BARRIER - DCHECK(kUseBakerReadBarrier); + if (!kUseBakerReadBarrier) { + LOG(FATAL) << "Unreachable"; + UNREACHABLE(); + } DCHECK(ReadBarrier::IsValidReadBarrierState(expected_rb_state)) << expected_rb_state; DCHECK(ReadBarrier::IsValidReadBarrierState(rb_state)) << rb_state; LockWord expected_lw; @@ -272,11 +265,6 @@ inline bool Object::AtomicSetReadBarrierState(uint32_t expected_rb_state, uint32 CasLockWordWeakRelease(expected_lw, new_lw) : CasLockWordWeakRelaxed(expected_lw, new_lw))); return true; -#else - UNUSED(expected_rb_state, rb_state); - LOG(FATAL) << "Unreachable"; - UNREACHABLE(); -#endif } inline bool Object::AtomicSetMarkBit(uint32_t expected_mark_bit, uint32_t mark_bit) { @@ -691,19 +679,6 @@ inline void Object::SetFieldShortVolatile(MemberOffset field_offset, int16_t new field_offset, new_value); } -template<VerifyObjectFlags kVerifyFlags, bool kIsVolatile> -inline int32_t Object::GetField32(MemberOffset field_offset) { - if (kVerifyFlags & kVerifyThis) { - VerifyObject(this); - } - return GetField<int32_t, kIsVolatile>(field_offset); -} - -template<VerifyObjectFlags kVerifyFlags> -inline int32_t Object::GetField32Volatile(MemberOffset field_offset) { - return GetField32<kVerifyFlags, true>(field_offset); -} - template<bool kTransactionActive, bool kCheckTransaction, VerifyObjectFlags kVerifyFlags, bool kIsVolatile> inline void Object::SetField32(MemberOffset field_offset, int32_t new_value) { @@ -854,28 +829,6 @@ inline void Object::SetField64Volatile(MemberOffset field_offset, int64_t new_va new_value); } -template<typename kSize, bool kIsVolatile> -inline void Object::SetField(MemberOffset field_offset, kSize new_value) { - uint8_t* raw_addr = reinterpret_cast<uint8_t*>(this) + field_offset.Int32Value(); - kSize* addr = reinterpret_cast<kSize*>(raw_addr); - if (kIsVolatile) { - reinterpret_cast<Atomic<kSize>*>(addr)->StoreSequentiallyConsistent(new_value); - } else { - reinterpret_cast<Atomic<kSize>*>(addr)->StoreJavaData(new_value); - } -} - -template<typename kSize, bool kIsVolatile> -inline kSize Object::GetField(MemberOffset field_offset) { - const uint8_t* raw_addr = reinterpret_cast<const uint8_t*>(this) + field_offset.Int32Value(); - const kSize* addr = reinterpret_cast<const kSize*>(raw_addr); - if (kIsVolatile) { - return reinterpret_cast<const Atomic<kSize>*>(addr)->LoadSequentiallyConsistent(); - } else { - return reinterpret_cast<const Atomic<kSize>*>(addr)->LoadJavaData(); - } -} - template<typename kSize> inline kSize Object::GetFieldAcquire(MemberOffset field_offset) { const uint8_t* raw_addr = reinterpret_cast<const uint8_t*>(this) + field_offset.Int32Value(); diff --git a/runtime/mirror/object.h b/runtime/mirror/object.h index db58a60994..4541ce2a42 100644 --- a/runtime/mirror/object.h +++ b/runtime/mirror/object.h @@ -17,6 +17,7 @@ #ifndef ART_RUNTIME_MIRROR_OBJECT_H_ #define ART_RUNTIME_MIRROR_OBJECT_H_ +#include "atomic.h" #include "base/casts.h" #include "base/enums.h" #include "globals.h" @@ -432,11 +433,18 @@ class MANAGED LOCKABLE Object { template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags, bool kIsVolatile = false> ALWAYS_INLINE int32_t GetField32(MemberOffset field_offset) - REQUIRES_SHARED(Locks::mutator_lock_); + REQUIRES_SHARED(Locks::mutator_lock_) { + if (kVerifyFlags & kVerifyThis) { + VerifyObject(this); + } + return GetField<int32_t, kIsVolatile>(field_offset); + } template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> ALWAYS_INLINE int32_t GetField32Volatile(MemberOffset field_offset) - REQUIRES_SHARED(Locks::mutator_lock_); + REQUIRES_SHARED(Locks::mutator_lock_) { + return GetField32<kVerifyFlags, true>(field_offset); + } template<bool kTransactionActive, bool kCheckTransaction = true, VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags, bool kIsVolatile = false> @@ -611,10 +619,28 @@ class MANAGED LOCKABLE Object { private: template<typename kSize, bool kIsVolatile> ALWAYS_INLINE void SetField(MemberOffset field_offset, kSize new_value) - REQUIRES_SHARED(Locks::mutator_lock_); + REQUIRES_SHARED(Locks::mutator_lock_) { + uint8_t* raw_addr = reinterpret_cast<uint8_t*>(this) + field_offset.Int32Value(); + kSize* addr = reinterpret_cast<kSize*>(raw_addr); + if (kIsVolatile) { + reinterpret_cast<Atomic<kSize>*>(addr)->StoreSequentiallyConsistent(new_value); + } else { + reinterpret_cast<Atomic<kSize>*>(addr)->StoreJavaData(new_value); + } + } + template<typename kSize, bool kIsVolatile> ALWAYS_INLINE kSize GetField(MemberOffset field_offset) - REQUIRES_SHARED(Locks::mutator_lock_); + REQUIRES_SHARED(Locks::mutator_lock_) { + const uint8_t* raw_addr = reinterpret_cast<const uint8_t*>(this) + field_offset.Int32Value(); + const kSize* addr = reinterpret_cast<const kSize*>(raw_addr); + if (kIsVolatile) { + return reinterpret_cast<const Atomic<kSize>*>(addr)->LoadSequentiallyConsistent(); + } else { + return reinterpret_cast<const Atomic<kSize>*>(addr)->LoadJavaData(); + } + } + // Get a field with acquire semantics. template<typename kSize> ALWAYS_INLINE kSize GetFieldAcquire(MemberOffset field_offset) diff --git a/runtime/monitor.cc b/runtime/monitor.cc index 071b0e2c75..a32003e81b 100644 --- a/runtime/monitor.cc +++ b/runtime/monitor.cc @@ -303,6 +303,7 @@ std::string Monitor::PrettyContentionInfo(const std::string& owner_name, ArtMethod* owners_method, uint32_t owners_dex_pc, size_t num_waiters) { + Locks::mutator_lock_->AssertSharedHeld(Thread::Current()); const char* owners_filename; int32_t owners_line_number = 0; if (owners_method != nullptr) { @@ -355,36 +356,44 @@ void Monitor::Lock(Thread* self) { // Do this before releasing the lock so that we don't get deflated. size_t num_waiters = num_waiters_; ++num_waiters_; + + // If systrace logging is enabled, first look at the lock owner. Acquiring the monitor's + // lock and then re-acquiring the mutator lock can deadlock. + bool started_trace = false; + if (ATRACE_ENABLED()) { + if (owner_ != nullptr) { // Did the owner_ give the lock up? + 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 " + << ArtMethod::PrettyMethod(m) << "(" << (filename != nullptr ? filename : "null") + << ":" << line_number << ")"; + ATRACE_BEGIN(oss.str().c_str()); + started_trace = true; + } + } + monitor_lock_.Unlock(self); // Let go of locks in order. self->SetMonitorEnterObject(GetObject()); { + ScopedThreadSuspension tsc(self, kBlocked); // Change to blocked and give up mutator_lock_. 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? 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 " - << ArtMethod::PrettyMethod(m) << "(" << (filename != nullptr ? filename : "null") - << ":" << line_number << ")"; - ATRACE_BEGIN(oss.str().c_str()); - } monitor_contenders_.Wait(self); // Still contended so wait. } } @@ -414,6 +423,8 @@ void Monitor::Lock(Thread* self) { sample_percent = 100 * wait_ms / lock_profiling_threshold_; } if (sample_percent != 0 && (static_cast<uint32_t>(rand() % 100) < sample_percent)) { + // Reacquire mutator_lock_ for logging. + ScopedObjectAccess soa(self); if (wait_ms > kLongWaitMs && owners_method != nullptr) { uint32_t pc; ArtMethod* m = self->GetCurrentMethod(&pc); @@ -441,9 +452,11 @@ void Monitor::Lock(Thread* self) { } } } - ATRACE_END(); } } + if (started_trace) { + ATRACE_END(); + } self->SetMonitorEnterObject(nullptr); monitor_lock_.Lock(self); // Reacquire locks in order. --num_waiters_; diff --git a/runtime/native/dalvik_system_VMDebug.cc b/runtime/native/dalvik_system_VMDebug.cc index 67b2e1c503..0d24587998 100644 --- a/runtime/native/dalvik_system_VMDebug.cc +++ b/runtime/native/dalvik_system_VMDebug.cc @@ -90,7 +90,8 @@ static void VMDebug_startMethodTracingDdmsImpl(JNIEnv*, jclass, jint bufferSize, static void VMDebug_startMethodTracingFd(JNIEnv* env, jclass, jstring javaTraceFilename, jobject javaFd, jint bufferSize, jint flags, - jboolean samplingEnabled, jint intervalUs) { + jboolean samplingEnabled, jint intervalUs, + jboolean streamingOutput) { int originalFd = jniGetFDFromFileDescriptor(env, javaFd); if (originalFd < 0) { return; @@ -108,7 +109,10 @@ static void VMDebug_startMethodTracingFd(JNIEnv* env, jclass, jstring javaTraceF if (traceFilename.c_str() == nullptr) { return; } - Trace::Start(traceFilename.c_str(), fd, bufferSize, flags, Trace::TraceOutputMode::kFile, + Trace::TraceOutputMode outputMode = streamingOutput + ? Trace::TraceOutputMode::kStreaming + : Trace::TraceOutputMode::kFile; + Trace::Start(traceFilename.c_str(), fd, bufferSize, flags, outputMode, samplingEnabled ? Trace::TraceMode::kSampling : Trace::TraceMode::kMethodTracing, intervalUs); } @@ -547,7 +551,7 @@ static JNINativeMethod gMethods[] = { NATIVE_METHOD(VMDebug, startEmulatorTracing, "()V"), NATIVE_METHOD(VMDebug, startInstructionCounting, "()V"), NATIVE_METHOD(VMDebug, startMethodTracingDdmsImpl, "(IIZI)V"), - NATIVE_METHOD(VMDebug, startMethodTracingFd, "(Ljava/lang/String;Ljava/io/FileDescriptor;IIZI)V"), + NATIVE_METHOD(VMDebug, startMethodTracingFd, "(Ljava/lang/String;Ljava/io/FileDescriptor;IIZIZ)V"), NATIVE_METHOD(VMDebug, startMethodTracingFilename, "(Ljava/lang/String;IIZI)V"), NATIVE_METHOD(VMDebug, stopAllocCounting, "()V"), NATIVE_METHOD(VMDebug, stopEmulatorTracing, "()V"), diff --git a/runtime/native/dalvik_system_ZygoteHooks.cc b/runtime/native/dalvik_system_ZygoteHooks.cc index 10fc90bc27..fd22d9e646 100644 --- a/runtime/native/dalvik_system_ZygoteHooks.cc +++ b/runtime/native/dalvik_system_ZygoteHooks.cc @@ -71,7 +71,7 @@ static void EnableDebugger() { static void EnableDebugFeatures(uint32_t debug_flags) { // Must match values in com.android.internal.os.Zygote. enum { - DEBUG_ENABLE_DEBUGGER = 1, + DEBUG_ENABLE_JDWP = 1, DEBUG_ENABLE_CHECKJNI = 1 << 1, DEBUG_ENABLE_ASSERT = 1 << 2, DEBUG_ENABLE_SAFEMODE = 1 << 3, @@ -79,6 +79,7 @@ static void EnableDebugFeatures(uint32_t debug_flags) { DEBUG_GENERATE_DEBUG_INFO = 1 << 5, DEBUG_ALWAYS_JIT = 1 << 6, DEBUG_NATIVE_DEBUGGABLE = 1 << 7, + DEBUG_JAVA_DEBUGGABLE = 1 << 8, }; Runtime* const runtime = Runtime::Current(); @@ -100,11 +101,11 @@ static void EnableDebugFeatures(uint32_t debug_flags) { debug_flags &= ~DEBUG_ENABLE_JNI_LOGGING; } - Dbg::SetJdwpAllowed((debug_flags & DEBUG_ENABLE_DEBUGGER) != 0); - if ((debug_flags & DEBUG_ENABLE_DEBUGGER) != 0) { + Dbg::SetJdwpAllowed((debug_flags & DEBUG_ENABLE_JDWP) != 0); + if ((debug_flags & DEBUG_ENABLE_JDWP) != 0) { EnableDebugger(); } - debug_flags &= ~DEBUG_ENABLE_DEBUGGER; + debug_flags &= ~DEBUG_ENABLE_JDWP; const bool safe_mode = (debug_flags & DEBUG_ENABLE_SAFEMODE) != 0; if (safe_mode) { @@ -130,6 +131,14 @@ static void EnableDebugFeatures(uint32_t debug_flags) { debug_flags &= ~DEBUG_ALWAYS_JIT; } + if ((debug_flags & DEBUG_JAVA_DEBUGGABLE) != 0) { + runtime->AddCompilerOption("--debuggable"); + runtime->SetJavaDebuggable(true); + // Deoptimize the boot image as it may be non-debuggable. + runtime->DeoptimizeBootImage(); + debug_flags &= ~DEBUG_JAVA_DEBUGGABLE; + } + if ((debug_flags & DEBUG_NATIVE_DEBUGGABLE) != 0) { runtime->AddCompilerOption("--debuggable"); runtime->AddCompilerOption("--generate-debug-info"); diff --git a/runtime/native/java_lang_String.cc b/runtime/native/java_lang_String.cc index ea266d131d..f1d6ff5f70 100644 --- a/runtime/native/java_lang_String.cc +++ b/runtime/native/java_lang_String.cc @@ -25,7 +25,7 @@ #include "scoped_fast_native_object_access-inl.h" #include "scoped_thread_state_change-inl.h" #include "ScopedLocalRef.h" -#include "verify_object-inl.h" +#include "verify_object.h" namespace art { diff --git a/runtime/native/java_lang_Thread.cc b/runtime/native/java_lang_Thread.cc index fcb017545a..195091f8ab 100644 --- a/runtime/native/java_lang_Thread.cc +++ b/runtime/native/java_lang_Thread.cc @@ -25,7 +25,7 @@ #include "ScopedUtfChars.h" #include "thread.h" #include "thread_list.h" -#include "verify_object-inl.h" +#include "verify_object.h" namespace art { diff --git a/runtime/native/java_lang_VMClassLoader.cc b/runtime/native/java_lang_VMClassLoader.cc index 284d2d10c5..a8fa7db688 100644 --- a/runtime/native/java_lang_VMClassLoader.cc +++ b/runtime/native/java_lang_VMClassLoader.cc @@ -81,14 +81,12 @@ static jclass VMClassLoader_findLoadedClass(JNIEnv* env, jclass, jobject javaLoa if (c != nullptr && c->IsErroneous()) { cl->ThrowEarlierClassFailure(c.Ptr()); Thread* self = soa.Self(); - ObjPtr<mirror::Class> eiie_class = - self->DecodeJObject(WellKnownClasses::java_lang_ExceptionInInitializerError)->AsClass(); ObjPtr<mirror::Class> iae_class = self->DecodeJObject(WellKnownClasses::java_lang_IllegalAccessError)->AsClass(); ObjPtr<mirror::Class> ncdfe_class = self->DecodeJObject(WellKnownClasses::java_lang_NoClassDefFoundError)->AsClass(); ObjPtr<mirror::Class> exception = self->GetException()->GetClass(); - if (exception == eiie_class || exception == iae_class || exception == ncdfe_class) { + if (exception == iae_class || exception == ncdfe_class) { self->ThrowNewWrappedException("Ljava/lang/ClassNotFoundException;", c->PrettyDescriptor().c_str()); } diff --git a/runtime/native/java_lang_invoke_MethodHandleImpl.cc b/runtime/native/java_lang_invoke_MethodHandleImpl.cc new file mode 100644 index 0000000000..72a37f875d --- /dev/null +++ b/runtime/native/java_lang_invoke_MethodHandleImpl.cc @@ -0,0 +1,76 @@ +/* + * 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. + */ + +#include "java_lang_invoke_MethodHandleImpl.h" + +#include "art_method.h" +#include "handle_scope-inl.h" +#include "jni_internal.h" +#include "mirror/field.h" +#include "mirror/method.h" +#include "mirror/method_handle_impl.h" +#include "runtime.h" +#include "scoped_thread_state_change-inl.h" + +namespace art { + +static jobject MethodHandleImpl_getMemberInternal(JNIEnv* env, jobject thiz) { + ScopedObjectAccess soa(env); + StackHandleScope<2> hs(soa.Self()); + Handle<mirror::MethodHandleImpl> handle = hs.NewHandle( + soa.Decode<mirror::MethodHandleImpl>(thiz)); + + // Check the handle kind, we need to materialize a Field for field accessors, + // a Method for method invokers and a Constructor for constructors. + const mirror::MethodHandle::Kind handle_kind = handle->GetHandleKind(); + + // We check this here because we pass false to CreateFromArtField and + // CreateFromArtMethod. + DCHECK(!Runtime::Current()->IsActiveTransaction()); + + MutableHandle<mirror::Object> h_object(hs.NewHandle<mirror::Object>(nullptr)); + if (handle_kind >= mirror::MethodHandle::kFirstAccessorKind) { + ArtField* const field = handle->GetTargetField(); + h_object.Assign(mirror::Field::CreateFromArtField<kRuntimePointerSize, false>( + soa.Self(), field, false /* force_resolve */)); + } else { + ArtMethod* const method = handle->GetTargetMethod(); + if (method->IsConstructor()) { + h_object.Assign(mirror::Constructor::CreateFromArtMethod<kRuntimePointerSize, false>( + soa.Self(), method)); + } else { + h_object.Assign(mirror::Method::CreateFromArtMethod<kRuntimePointerSize, false>( + soa.Self(), method)); + } + } + + if (UNLIKELY(h_object.Get() == nullptr)) { + soa.Self()->AssertPendingOOMException(); + return nullptr; + } + + return soa.AddLocalReference<jobject>(h_object.Get()); +} + +static JNINativeMethod gMethods[] = { + NATIVE_METHOD(MethodHandleImpl, getMemberInternal, "()Ljava/lang/reflect/Member;"), +}; + +void register_java_lang_invoke_MethodHandleImpl(JNIEnv* env) { + REGISTER_NATIVE_METHODS("java/lang/invoke/MethodHandleImpl"); +} + +} // namespace art diff --git a/runtime/native/java_lang_invoke_MethodHandleImpl.h b/runtime/native/java_lang_invoke_MethodHandleImpl.h new file mode 100644 index 0000000000..0e50371697 --- /dev/null +++ b/runtime/native/java_lang_invoke_MethodHandleImpl.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2017 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_NATIVE_JAVA_LANG_INVOKE_METHODHANDLEIMPL_H_ +#define ART_RUNTIME_NATIVE_JAVA_LANG_INVOKE_METHODHANDLEIMPL_H_ + +#include <jni.h> + +namespace art { + +void register_java_lang_invoke_MethodHandleImpl(JNIEnv* env); + +} // namespace art + +#endif // ART_RUNTIME_NATIVE_JAVA_LANG_INVOKE_METHODHANDLEIMPL_H_ diff --git a/runtime/native/java_lang_reflect_Proxy.cc b/runtime/native/java_lang_reflect_Proxy.cc index ece0338c93..70cd6aaae2 100644 --- a/runtime/native/java_lang_reflect_Proxy.cc +++ b/runtime/native/java_lang_reflect_Proxy.cc @@ -22,7 +22,7 @@ #include "mirror/object_array.h" #include "mirror/string.h" #include "scoped_fast_native_object_access-inl.h" -#include "verify_object-inl.h" +#include "verify_object.h" namespace art { diff --git a/runtime/oat.h b/runtime/oat.h index 29821a2eea..532c9681c3 100644 --- a/runtime/oat.h +++ b/runtime/oat.h @@ -32,7 +32,7 @@ class InstructionSetFeatures; class PACKED(4) OatHeader { public: static constexpr uint8_t kOatMagic[] = { 'o', 'a', 't', '\n' }; - static constexpr uint8_t kOatVersion[] = { '1', '0', '3', '\0' }; // Native pc change + static constexpr uint8_t kOatVersion[] = { '1', '0', '9', '\0' }; // Register mask change. static constexpr const char* kImageLocationKey = "image-location"; static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline"; diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc index d47f1b5611..31eb1ccdc8 100644 --- a/runtime/oat_file.cc +++ b/runtime/oat_file.cc @@ -193,7 +193,7 @@ bool OatFileBase::LoadVdex(const std::string& vdex_filename, bool writable, bool low_4gb, std::string* error_msg) { - vdex_.reset(VdexFile::Open(vdex_filename, writable, low_4gb, error_msg)); + vdex_ = VdexFile::Open(vdex_filename, writable, low_4gb, error_msg); if (vdex_.get() == nullptr) { *error_msg = StringPrintf("Failed to load vdex file '%s' %s", vdex_filename.c_str(), diff --git a/runtime/oat_file.h b/runtime/oat_file.h index 62d99fb51c..111755e7a1 100644 --- a/runtime/oat_file.h +++ b/runtime/oat_file.h @@ -201,8 +201,12 @@ class OatFile { // A representation of an invalid OatClass, used when an OatClass can't be found. // See FindOatClass(). static OatClass Invalid() { - return OatClass(nullptr, mirror::Class::kStatusError, kOatClassNoneCompiled, 0, nullptr, - nullptr); + return OatClass(/* oat_file */ nullptr, + mirror::Class::kStatusErrorUnresolved, + kOatClassNoneCompiled, + /* bitmap_size */ 0, + /* bitmap_pointer */ nullptr, + /* methods_pointer */ nullptr); } private: diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc index 8a23457470..77cdd28d3a 100644 --- a/runtime/oat_file_assistant.cc +++ b/runtime/oat_file_assistant.cc @@ -25,6 +25,7 @@ #include "base/logging.h" #include "compiler_filter.h" #include "class_linker.h" +#include "exec_utils.h" #include "gc/heap.h" #include "gc/space/image_space.h" #include "image.h" @@ -315,6 +316,11 @@ OatFileAssistant::OatStatus OatFileAssistant::OatFileStatus() { } bool OatFileAssistant::DexChecksumUpToDate(const VdexFile& file, std::string* error_msg) { + if (file.GetHeader().GetNumberOfDexFiles() <= 0) { + VLOG(oat) << "Vdex does not contain any dex files"; + return false; + } + // TODO: Use GetRequiredDexChecksum to get secondary checksums as well, not // just the primary. Because otherwise we may fail to see a secondary // checksum failure in the case when the original (multidex) files are @@ -601,7 +607,7 @@ bool OatFileAssistant::Dex2Oat(const std::vector<std::string>& args, class_path = OatFile::kSpecialSharedLibrary; } argv.push_back(class_path); - if (runtime->IsDebuggable()) { + if (runtime->IsJavaDebuggable()) { argv.push_back("--debuggable"); } runtime->AddCurrentRuntimeFeaturesAsDex2OatArguments(&argv); @@ -851,10 +857,10 @@ OatFileAssistant::OatStatus OatFileAssistant::OatFileInfo::Status() { // Check to see if there is a vdex file we can make use of. std::string error_msg; std::string vdex_filename = ReplaceFileExtension(filename_, "vdex"); - std::unique_ptr<VdexFile> vdex(VdexFile::Open(vdex_filename, - /*writeable*/false, - /*low_4gb*/false, - &error_msg)); + std::unique_ptr<VdexFile> vdex = VdexFile::Open(vdex_filename, + /*writeable*/false, + /*low_4gb*/false, + &error_msg); if (vdex == nullptr) { status_ = kOatCannotOpen; VLOG(oat) << "unable to open vdex file " << vdex_filename << ": " << error_msg; diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc index 9669dabe2e..f777340cfd 100644 --- a/runtime/oat_file_assistant_test.cc +++ b/runtime/oat_file_assistant_test.cc @@ -14,23 +14,16 @@ * limitations under the License. */ -#include <algorithm> -#include <fstream> #include <string> #include <vector> #include <sys/param.h> #include "android-base/strings.h" -#include <backtrace/BacktraceMap.h> #include <gtest/gtest.h> #include "art_field-inl.h" #include "class_linker-inl.h" -#include "common_runtime_test.h" -#include "compiler_callbacks.h" -#include "dex2oat_environment_test.h" -#include "gc/space/image_space.h" -#include "mem_map.h" +#include "dexopt_test.h" #include "oat_file_assistant.h" #include "oat_file_manager.h" #include "os.h" @@ -40,242 +33,17 @@ namespace art { -class OatFileAssistantTest : public Dex2oatEnvironmentTest { - public: - virtual void SetUp() OVERRIDE { - ReserveImageSpace(); - Dex2oatEnvironmentTest::SetUp(); - } - - // Pre-Relocate the image to a known non-zero offset so we don't have to - // deal with the runtime randomly relocating the image by 0 and messing up - // the expected results of the tests. - bool PreRelocateImage(const std::string& image_location, std::string* error_msg) { - std::string image; - if (!GetCachedImageFile(image_location, &image, error_msg)) { - return false; - } - - std::string patchoat = GetAndroidRoot(); - patchoat += kIsDebugBuild ? "/bin/patchoatd" : "/bin/patchoat"; - - std::vector<std::string> argv; - argv.push_back(patchoat); - argv.push_back("--input-image-location=" + image_location); - argv.push_back("--output-image-file=" + image); - argv.push_back("--instruction-set=" + std::string(GetInstructionSetString(kRuntimeISA))); - argv.push_back("--base-offset-delta=0x00008000"); - return Exec(argv, error_msg); - } - - virtual void PreRuntimeCreate() { - std::string error_msg; - ASSERT_TRUE(PreRelocateImage(GetImageLocation(), &error_msg)) << error_msg; - ASSERT_TRUE(PreRelocateImage(GetImageLocation2(), &error_msg)) << error_msg; - UnreserveImageSpace(); - } - - virtual void PostRuntimeCreate() OVERRIDE { - ReserveImageSpace(); - } - - // Generate an oat file for the purposes of test. - void GenerateOatForTest(const std::string& dex_location, - const std::string& oat_location, - CompilerFilter::Filter filter, - bool relocate, - bool pic, - bool with_alternate_image) { - std::string dalvik_cache = GetDalvikCache(GetInstructionSetString(kRuntimeISA)); - std::string dalvik_cache_tmp = dalvik_cache + ".redirected"; - - if (!relocate) { - // Temporarily redirect the dalvik cache so dex2oat doesn't find the - // relocated image file. - ASSERT_EQ(0, rename(dalvik_cache.c_str(), dalvik_cache_tmp.c_str())) << strerror(errno); - } - - std::vector<std::string> args; - args.push_back("--dex-file=" + dex_location); - args.push_back("--oat-file=" + oat_location); - args.push_back("--compiler-filter=" + CompilerFilter::NameOfFilter(filter)); - args.push_back("--runtime-arg"); - - // Use -Xnorelocate regardless of the relocate argument. - // We control relocation by redirecting the dalvik cache when needed - // rather than use this flag. - args.push_back("-Xnorelocate"); - - if (pic) { - args.push_back("--compile-pic"); - } - - std::string image_location = GetImageLocation(); - if (with_alternate_image) { - args.push_back("--boot-image=" + GetImageLocation2()); - } - - std::string error_msg; - ASSERT_TRUE(OatFileAssistant::Dex2Oat(args, &error_msg)) << error_msg; - - if (!relocate) { - // Restore the dalvik cache if needed. - ASSERT_EQ(0, rename(dalvik_cache_tmp.c_str(), dalvik_cache.c_str())) << strerror(errno); - } - - // Verify the odex file was generated as expected. - std::unique_ptr<OatFile> odex_file(OatFile::Open(oat_location.c_str(), - oat_location.c_str(), - nullptr, - nullptr, - false, - /*low_4gb*/false, - dex_location.c_str(), - &error_msg)); - ASSERT_TRUE(odex_file.get() != nullptr) << error_msg; - EXPECT_EQ(pic, odex_file->IsPic()); - EXPECT_EQ(filter, odex_file->GetCompilerFilter()); - - std::unique_ptr<ImageHeader> image_header( - gc::space::ImageSpace::ReadImageHeader(image_location.c_str(), - kRuntimeISA, - &error_msg)); - ASSERT_TRUE(image_header != nullptr) << error_msg; - const OatHeader& oat_header = odex_file->GetOatHeader(); - uint32_t combined_checksum = OatFileAssistant::CalculateCombinedImageChecksum(); - - if (CompilerFilter::DependsOnImageChecksum(filter)) { - if (with_alternate_image) { - EXPECT_NE(combined_checksum, oat_header.GetImageFileLocationOatChecksum()); - } else { - EXPECT_EQ(combined_checksum, oat_header.GetImageFileLocationOatChecksum()); - } - } - - if (!with_alternate_image) { - if (CompilerFilter::IsBytecodeCompilationEnabled(filter)) { - if (relocate) { - EXPECT_EQ(reinterpret_cast<uintptr_t>(image_header->GetOatDataBegin()), - oat_header.GetImageFileLocationOatDataBegin()); - EXPECT_EQ(image_header->GetPatchDelta(), oat_header.GetImagePatchDelta()); - } else { - EXPECT_NE(reinterpret_cast<uintptr_t>(image_header->GetOatDataBegin()), - oat_header.GetImageFileLocationOatDataBegin()); - EXPECT_NE(image_header->GetPatchDelta(), oat_header.GetImagePatchDelta()); - } - } - } - } +class OatFileAssistantTest : public DexoptTest {}; - // Generate a non-PIC odex file for the purposes of test. - // The generated odex file will be un-relocated. - void GenerateOdexForTest(const std::string& dex_location, - const std::string& odex_location, - CompilerFilter::Filter filter) { - GenerateOatForTest(dex_location, - odex_location, - filter, - /*relocate*/false, - /*pic*/false, - /*with_alternate_image*/false); - } - - void GeneratePicOdexForTest(const std::string& dex_location, - const std::string& odex_location, - CompilerFilter::Filter filter) { - GenerateOatForTest(dex_location, - odex_location, - filter, - /*relocate*/false, - /*pic*/true, - /*with_alternate_image*/false); - } - - // Generate an oat file in the oat location. - void GenerateOatForTest(const char* dex_location, - CompilerFilter::Filter filter, - bool relocate, - bool pic, - bool with_alternate_image) { - std::string oat_location; - std::string error_msg; - ASSERT_TRUE(OatFileAssistant::DexLocationToOatFilename( - dex_location, kRuntimeISA, &oat_location, &error_msg)) << error_msg; - GenerateOatForTest(dex_location, - oat_location, - filter, - relocate, - pic, - with_alternate_image); - } - - // Generate a standard oat file in the oat location. - void GenerateOatForTest(const char* dex_location, CompilerFilter::Filter filter) { - GenerateOatForTest(dex_location, - filter, - /*relocate*/true, - /*pic*/false, - /*with_alternate_image*/false); - } - - private: - // Reserve memory around where the image will be loaded so other memory - // won't conflict when it comes time to load the image. - // This can be called with an already loaded image to reserve the space - // around it. - void ReserveImageSpace() { - MemMap::Init(); - - // Ensure a chunk of memory is reserved for the image space. - // The reservation_end includes room for the main space that has to come - // right after the image in case of the GSS collector. - uintptr_t reservation_start = ART_BASE_ADDRESS; - uintptr_t reservation_end = ART_BASE_ADDRESS + 384 * MB; - - std::unique_ptr<BacktraceMap> map(BacktraceMap::Create(getpid(), true)); - ASSERT_TRUE(map.get() != nullptr) << "Failed to build process map"; - for (BacktraceMap::const_iterator it = map->begin(); - reservation_start < reservation_end && it != map->end(); ++it) { - ReserveImageSpaceChunk(reservation_start, std::min(it->start, reservation_end)); - reservation_start = std::max(reservation_start, it->end); - } - ReserveImageSpaceChunk(reservation_start, reservation_end); - } - - // Reserve a chunk of memory for the image space in the given range. - // Only has effect for chunks with a positive number of bytes. - void ReserveImageSpaceChunk(uintptr_t start, uintptr_t end) { - if (start < end) { - std::string error_msg; - image_reservation_.push_back(std::unique_ptr<MemMap>( - MemMap::MapAnonymous("image reservation", - reinterpret_cast<uint8_t*>(start), end - start, - PROT_NONE, false, false, &error_msg))); - ASSERT_TRUE(image_reservation_.back().get() != nullptr) << error_msg; - LOG(INFO) << "Reserved space for image " << - reinterpret_cast<void*>(image_reservation_.back()->Begin()) << "-" << - reinterpret_cast<void*>(image_reservation_.back()->End()); - } - } - - - // Unreserve any memory reserved by ReserveImageSpace. This should be called - // before the image is loaded. - void UnreserveImageSpace() { - image_reservation_.clear(); - } - - std::vector<std::unique_ptr<MemMap>> image_reservation_; -}; - -class OatFileAssistantNoDex2OatTest : public OatFileAssistantTest { +class OatFileAssistantNoDex2OatTest : public DexoptTest { public: virtual void SetUpRuntimeOptions(RuntimeOptions* options) { - OatFileAssistantTest::SetUpRuntimeOptions(options); + DexoptTest::SetUpRuntimeOptions(options); options->push_back(std::make_pair("-Xnodex2oat", nullptr)); } }; + // Case: We have a DEX file, but no OAT file for it. // Expect: The status is kDex2OatNeeded. TEST_F(OatFileAssistantTest, DexNoOat) { @@ -379,6 +147,21 @@ TEST_F(OatFileAssistantTest, VdexUpToDateNoOdex) { oat_file_assistant.GetStatusDump(); } +// Case: We have a DEX file and empty VDEX and ODEX files. +TEST_F(OatFileAssistantTest, EmptyVdexOdex) { + std::string dex_location = GetScratchDir() + "/EmptyVdexOdex.jar"; + std::string odex_location = GetOdexDir() + "/EmptyVdexOdex.oat"; + std::string vdex_location = GetOdexDir() + "/EmptyVdexOdex.vdex"; + + Copy(GetDexSrc1(), dex_location); + ScratchFile vdex_file(vdex_location.c_str()); + ScratchFile odex_file(odex_location.c_str()); + + OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false); + EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch, + oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed)); +} + // Case: We have a DEX file and up-to-date (OAT) VDEX file for it, but no OAT // file. TEST_F(OatFileAssistantTest, VdexUpToDateNoOat) { diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc index 33bd0f311d..a46b47075c 100644 --- a/runtime/oat_file_manager.cc +++ b/runtime/oat_file_manager.cc @@ -22,6 +22,7 @@ #include "android-base/stringprintf.h" +#include "art_field-inl.h" #include "base/logging.h" #include "base/stl_util.h" #include "base/systrace.h" @@ -32,11 +33,13 @@ #include "handle_scope-inl.h" #include "jni_internal.h" #include "mirror/class_loader.h" +#include "mirror/object-inl.h" #include "oat_file_assistant.h" #include "obj_ptr-inl.h" #include "scoped_thread_state_change-inl.h" #include "thread-inl.h" #include "thread_list.h" +#include "well_known_classes.h" namespace art { diff --git a/runtime/openjdkjvm/OpenjdkJvm.cc b/runtime/openjdkjvm/OpenjdkJvm.cc index 2f51e27b2d..bdaad20d7e 100644 --- a/runtime/openjdkjvm/OpenjdkJvm.cc +++ b/runtime/openjdkjvm/OpenjdkJvm.cc @@ -46,7 +46,7 @@ #include "scoped_thread_state_change-inl.h" #include "ScopedUtfChars.h" #include "mirror/class_loader.h" -#include "verify_object-inl.h" +#include "verify_object.h" #include "base/logging.h" #include "base/macros.h" #include "../../libcore/ojluni/src/main/native/jvm.h" // TODO(narayan): fix it diff --git a/runtime/openjdkjvmti/Android.bp b/runtime/openjdkjvmti/Android.bp index 976a1e7902..c01e3f4152 100644 --- a/runtime/openjdkjvmti/Android.bp +++ b/runtime/openjdkjvmti/Android.bp @@ -22,6 +22,7 @@ cc_defaults { "OpenjdkJvmTi.cc", "ti_class.cc", "ti_class_definition.cc", + "ti_class_loader.cc", "ti_dump.cc", "ti_field.cc", "ti_heap.cc", diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc index 417d1041a8..a815a603a7 100644 --- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc +++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc @@ -138,6 +138,7 @@ class JvmtiFunctions { } static jvmtiError SuspendThread(jvmtiEnv* env, jthread thread) { + ENSURE_HAS_CAP(env, can_suspend); return ERR(NOT_IMPLEMENTED); } @@ -145,10 +146,12 @@ class JvmtiFunctions { jint request_count, const jthread* request_list, jvmtiError* results) { + ENSURE_HAS_CAP(env, can_suspend); return ERR(NOT_IMPLEMENTED); } static jvmtiError ResumeThread(jvmtiEnv* env, jthread thread) { + ENSURE_HAS_CAP(env, can_suspend); return ERR(NOT_IMPLEMENTED); } @@ -156,14 +159,17 @@ class JvmtiFunctions { jint request_count, const jthread* request_list, jvmtiError* results) { + ENSURE_HAS_CAP(env, can_suspend); return ERR(NOT_IMPLEMENTED); } static jvmtiError StopThread(jvmtiEnv* env, jthread thread, jobject exception) { + ENSURE_HAS_CAP(env, can_signal_thread); return ERR(NOT_IMPLEMENTED); } static jvmtiError InterruptThread(jvmtiEnv* env, jthread thread) { + ENSURE_HAS_CAP(env, can_signal_thread); return ERR(NOT_IMPLEMENTED); } @@ -175,6 +181,7 @@ class JvmtiFunctions { jthread thread, jint* owned_monitor_count_ptr, jobject** owned_monitors_ptr) { + ENSURE_HAS_CAP(env, can_get_owned_monitor_info); return ERR(NOT_IMPLEMENTED); } @@ -182,12 +189,14 @@ class JvmtiFunctions { jthread thread, jint* monitor_info_count_ptr, jvmtiMonitorStackDepthInfo** monitor_info_ptr) { + ENSURE_HAS_CAP(env, can_get_owned_monitor_stack_depth_info); return ERR(NOT_IMPLEMENTED); } static jvmtiError GetCurrentContendedMonitor(jvmtiEnv* env, jthread thread, jobject* monitor_ptr) { + ENSURE_HAS_CAP(env, can_get_current_contended_monitor); return ERR(NOT_IMPLEMENTED); } @@ -271,6 +280,7 @@ class JvmtiFunctions { } static jvmtiError PopFrame(jvmtiEnv* env, jthread thread) { + ENSURE_HAS_CAP(env, can_pop_frame); return ERR(NOT_IMPLEMENTED); } @@ -283,30 +293,37 @@ class JvmtiFunctions { } static jvmtiError NotifyFramePop(jvmtiEnv* env, jthread thread, jint depth) { + ENSURE_HAS_CAP(env, can_generate_frame_pop_events); return ERR(NOT_IMPLEMENTED); } static jvmtiError ForceEarlyReturnObject(jvmtiEnv* env, jthread thread, jobject value) { + ENSURE_HAS_CAP(env, can_force_early_return); return ERR(NOT_IMPLEMENTED); } static jvmtiError ForceEarlyReturnInt(jvmtiEnv* env, jthread thread, jint value) { + ENSURE_HAS_CAP(env, can_force_early_return); return ERR(NOT_IMPLEMENTED); } static jvmtiError ForceEarlyReturnLong(jvmtiEnv* env, jthread thread, jlong value) { + ENSURE_HAS_CAP(env, can_force_early_return); return ERR(NOT_IMPLEMENTED); } static jvmtiError ForceEarlyReturnFloat(jvmtiEnv* env, jthread thread, jfloat value) { + ENSURE_HAS_CAP(env, can_force_early_return); return ERR(NOT_IMPLEMENTED); } static jvmtiError ForceEarlyReturnDouble(jvmtiEnv* env, jthread thread, jdouble value) { + ENSURE_HAS_CAP(env, can_force_early_return); return ERR(NOT_IMPLEMENTED); } static jvmtiError ForceEarlyReturnVoid(jvmtiEnv* env, jthread thread) { + ENSURE_HAS_CAP(env, can_force_early_return); return ERR(NOT_IMPLEMENTED); } @@ -316,6 +333,7 @@ class JvmtiFunctions { jobject initial_object, const jvmtiHeapCallbacks* callbacks, const void* user_data) { + ENSURE_HAS_CAP(env, can_tag_objects); HeapUtil heap_util(&gObjectTagTable); return heap_util.FollowReferences(env, heap_filter, @@ -402,6 +420,7 @@ class JvmtiFunctions { jobject object, jvmtiObjectReferenceCallback object_reference_callback, const void* user_data) { + ENSURE_HAS_CAP(env, can_tag_objects); return ERR(NOT_IMPLEMENTED); } @@ -410,6 +429,7 @@ class JvmtiFunctions { jvmtiStackReferenceCallback stack_ref_callback, jvmtiObjectReferenceCallback object_ref_callback, const void* user_data) { + ENSURE_HAS_CAP(env, can_tag_objects); return ERR(NOT_IMPLEMENTED); } @@ -417,6 +437,7 @@ class JvmtiFunctions { jvmtiHeapObjectFilter object_filter, jvmtiHeapObjectCallback heap_object_callback, const void* user_data) { + ENSURE_HAS_CAP(env, can_tag_objects); return ERR(NOT_IMPLEMENTED); } @@ -425,6 +446,7 @@ class JvmtiFunctions { jvmtiHeapObjectFilter object_filter, jvmtiHeapObjectCallback heap_object_callback, const void* user_data) { + ENSURE_HAS_CAP(env, can_tag_objects); return ERR(NOT_IMPLEMENTED); } @@ -433,6 +455,7 @@ class JvmtiFunctions { jint depth, jint slot, jobject* value_ptr) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -440,6 +463,7 @@ class JvmtiFunctions { jthread thread, jint depth, jobject* value_ptr) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -448,6 +472,7 @@ class JvmtiFunctions { jint depth, jint slot, jint* value_ptr) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -456,6 +481,7 @@ class JvmtiFunctions { jint depth, jint slot, jlong* value_ptr) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -464,6 +490,7 @@ class JvmtiFunctions { jint depth, jint slot, jfloat* value_ptr) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -472,6 +499,7 @@ class JvmtiFunctions { jint depth, jint slot, jdouble* value_ptr) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -480,6 +508,7 @@ class JvmtiFunctions { jint depth, jint slot, jobject value) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -488,6 +517,7 @@ class JvmtiFunctions { jint depth, jint slot, jint value) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -496,6 +526,7 @@ class JvmtiFunctions { jint depth, jint slot, jlong value) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -504,6 +535,7 @@ class JvmtiFunctions { jint depth, jint slot, jfloat value) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -512,30 +544,37 @@ class JvmtiFunctions { jint depth, jint slot, jdouble value) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } static jvmtiError SetBreakpoint(jvmtiEnv* env, jmethodID method, jlocation location) { + ENSURE_HAS_CAP(env, can_generate_breakpoint_events); return ERR(NOT_IMPLEMENTED); } static jvmtiError ClearBreakpoint(jvmtiEnv* env, jmethodID method, jlocation location) { + ENSURE_HAS_CAP(env, can_generate_breakpoint_events); return ERR(NOT_IMPLEMENTED); } static jvmtiError SetFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field) { + ENSURE_HAS_CAP(env, can_generate_field_access_events); return ERR(NOT_IMPLEMENTED); } static jvmtiError ClearFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field) { + ENSURE_HAS_CAP(env, can_generate_field_access_events); return ERR(NOT_IMPLEMENTED); } static jvmtiError SetFieldModificationWatch(jvmtiEnv* env, jclass klass, jfieldID field) { + ENSURE_HAS_CAP(env, can_generate_field_modification_events); return ERR(NOT_IMPLEMENTED); } static jvmtiError ClearFieldModificationWatch(jvmtiEnv* env, jclass klass, jfieldID field) { + ENSURE_HAS_CAP(env, can_generate_field_modification_events); return ERR(NOT_IMPLEMENTED); } @@ -563,6 +602,7 @@ class JvmtiFunctions { } static jvmtiError GetSourceFileName(jvmtiEnv* env, jclass klass, char** source_name_ptr) { + ENSURE_HAS_CAP(env, can_get_source_file_name); return ERR(NOT_IMPLEMENTED); } @@ -603,6 +643,7 @@ class JvmtiFunctions { jint* constant_pool_count_ptr, jint* constant_pool_byte_count_ptr, unsigned char** constant_pool_bytes_ptr) { + ENSURE_HAS_CAP(env, can_get_constant_pool); return ERR(NOT_IMPLEMENTED); } @@ -629,10 +670,12 @@ class JvmtiFunctions { static jvmtiError GetSourceDebugExtension(jvmtiEnv* env, jclass klass, char** source_debug_extension_ptr) { + ENSURE_HAS_CAP(env, can_get_source_debug_extension); return ERR(NOT_IMPLEMENTED); } static jvmtiError RetransformClasses(jvmtiEnv* env, jint class_count, const jclass* classes) { + ENSURE_HAS_CAP(env, can_retransform_classes); std::string error_msg; jvmtiError res = Transformer::RetransformClasses(ArtJvmTiEnv::AsArtJvmTiEnv(env), art::Runtime::Current(), @@ -649,6 +692,7 @@ class JvmtiFunctions { static jvmtiError RedefineClasses(jvmtiEnv* env, jint class_count, const jvmtiClassDefinition* class_definitions) { + ENSURE_HAS_CAP(env, can_redefine_classes); std::string error_msg; jvmtiError res = Redefiner::RedefineClasses(ArtJvmTiEnv::AsArtJvmTiEnv(env), art::Runtime::Current(), @@ -673,6 +717,7 @@ class JvmtiFunctions { static jvmtiError GetObjectMonitorUsage(jvmtiEnv* env, jobject object, jvmtiMonitorUsage* info_ptr) { + ENSURE_HAS_CAP(env, can_get_monitor_info); return ERR(NOT_IMPLEMENTED); } @@ -703,6 +748,7 @@ class JvmtiFunctions { jclass klass, jfieldID field, jboolean* is_synthetic_ptr) { + ENSURE_HAS_CAP(env, can_get_synthetic_attribute); return FieldUtil::IsFieldSynthetic(env, klass, field, is_synthetic_ptr); } @@ -742,6 +788,7 @@ class JvmtiFunctions { jmethodID method, jint* entry_count_ptr, jvmtiLineNumberEntry** table_ptr) { + ENSURE_HAS_CAP(env, can_get_line_numbers); return MethodUtil::GetLineNumberTable(env, method, entry_count_ptr, table_ptr); } @@ -756,6 +803,7 @@ class JvmtiFunctions { jmethodID method, jint* entry_count_ptr, jvmtiLocalVariableEntry** table_ptr) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -763,6 +811,7 @@ class JvmtiFunctions { jmethodID method, jint* bytecode_count_ptr, unsigned char** bytecodes_ptr) { + ENSURE_HAS_CAP(env, can_get_bytecodes); return ERR(NOT_IMPLEMENTED); } @@ -771,6 +820,7 @@ class JvmtiFunctions { } static jvmtiError IsMethodSynthetic(jvmtiEnv* env, jmethodID method, jboolean* is_synthetic_ptr) { + ENSURE_HAS_CAP(env, can_get_synthetic_attribute); return MethodUtil::IsMethodSynthetic(env, method, is_synthetic_ptr); } @@ -779,10 +829,12 @@ class JvmtiFunctions { } static jvmtiError SetNativeMethodPrefix(jvmtiEnv* env, const char* prefix) { + ENSURE_HAS_CAP(env, can_set_native_method_prefix); return ERR(NOT_IMPLEMENTED); } static jvmtiError SetNativeMethodPrefixes(jvmtiEnv* env, jint prefix_count, char** prefixes) { + ENSURE_HAS_CAP(env, can_set_native_method_prefix); return ERR(NOT_IMPLEMENTED); } @@ -855,7 +907,6 @@ class JvmtiFunctions { jthread event_thread, ...) { ENSURE_VALID_ENV(env); - // TODO: Check for capabilities. art::Thread* art_thread = nullptr; if (event_thread != nullptr) { // TODO: Need non-aborting call here, to return JVMTI_ERROR_INVALID_THREAD. @@ -1053,18 +1104,22 @@ class JvmtiFunctions { } static jvmtiError GetCurrentThreadCpuTimerInfo(jvmtiEnv* env, jvmtiTimerInfo* info_ptr) { + ENSURE_HAS_CAP(env, can_get_current_thread_cpu_time); return ERR(NOT_IMPLEMENTED); } static jvmtiError GetCurrentThreadCpuTime(jvmtiEnv* env, jlong* nanos_ptr) { + ENSURE_HAS_CAP(env, can_get_current_thread_cpu_time); return ERR(NOT_IMPLEMENTED); } static jvmtiError GetThreadCpuTimerInfo(jvmtiEnv* env, jvmtiTimerInfo* info_ptr) { + ENSURE_HAS_CAP(env, can_get_thread_cpu_time); return ERR(NOT_IMPLEMENTED); } static jvmtiError GetThreadCpuTime(jvmtiEnv* env, jthread thread, jlong* nanos_ptr) { + ENSURE_HAS_CAP(env, can_get_thread_cpu_time); return ERR(NOT_IMPLEMENTED); } @@ -1313,6 +1368,7 @@ extern "C" bool ArtPlugin_Initialize() { ThreadUtil::Register(&gEventHandler); ClassUtil::Register(&gEventHandler); DumpUtil::Register(&gEventHandler); + SearchUtil::Register(); runtime->GetJavaVM()->AddEnvironmentHook(GetEnvHandler); runtime->AddSystemWeakHolder(&gObjectTagTable); @@ -1325,6 +1381,7 @@ extern "C" bool ArtPlugin_Deinitialize() { ThreadUtil::Unregister(); ClassUtil::Unregister(); DumpUtil::Unregister(); + SearchUtil::Unregister(); return true; } diff --git a/runtime/openjdkjvmti/art_jvmti.h b/runtime/openjdkjvmti/art_jvmti.h index 256c3a6cec..106165c38b 100644 --- a/runtime/openjdkjvmti/art_jvmti.h +++ b/runtime/openjdkjvmti/art_jvmti.h @@ -114,6 +114,21 @@ static inline JvmtiUniquePtr MakeJvmtiUniquePtr(jvmtiEnv* env, T* mem) { } ALWAYS_INLINE +static inline jvmtiError CopyDataIntoJvmtiBuffer(ArtJvmTiEnv* env, + const unsigned char* source, + jint len, + /*out*/unsigned char** dest) { + jvmtiError res = env->Allocate(len, dest); + if (res != OK) { + return res; + } + memcpy(reinterpret_cast<void*>(*dest), + reinterpret_cast<const void*>(source), + len); + return OK; +} + +ALWAYS_INLINE static inline jvmtiError CopyString(jvmtiEnv* env, const char* src, unsigned char** copy) { size_t len = strlen(src) + 1; unsigned char* buf; @@ -131,7 +146,7 @@ const jvmtiCapabilities kPotentialCapabilities = { .can_generate_field_modification_events = 0, .can_generate_field_access_events = 0, .can_get_bytecodes = 0, - .can_get_synthetic_attribute = 0, + .can_get_synthetic_attribute = 1, .can_get_owned_monitor_info = 0, .can_get_current_contended_monitor = 0, .can_get_monitor_info = 0, @@ -139,7 +154,7 @@ const jvmtiCapabilities kPotentialCapabilities = { .can_redefine_classes = 1, .can_signal_thread = 0, .can_get_source_file_name = 0, - .can_get_line_numbers = 0, + .can_get_line_numbers = 1, .can_get_source_debug_extension = 0, .can_access_local_variables = 0, .can_maintain_original_method_order = 0, @@ -156,10 +171,10 @@ const jvmtiCapabilities kPotentialCapabilities = { .can_generate_all_class_hook_events = 0, .can_generate_compiled_method_load_events = 0, .can_generate_monitor_events = 0, - .can_generate_vm_object_alloc_events = 0, + .can_generate_vm_object_alloc_events = 1, .can_generate_native_method_bind_events = 0, - .can_generate_garbage_collection_events = 0, - .can_generate_object_free_events = 0, + .can_generate_garbage_collection_events = 1, + .can_generate_object_free_events = 1, .can_force_early_return = 0, .can_get_owned_monitor_stack_depth_info = 0, .can_get_constant_pool = 0, diff --git a/runtime/openjdkjvmti/events-inl.h b/runtime/openjdkjvmti/events-inl.h index 21ec731ba5..4f5eb0c33f 100644 --- a/runtime/openjdkjvmti/events-inl.h +++ b/runtime/openjdkjvmti/events-inl.h @@ -37,96 +37,84 @@ static inline ArtJvmtiEvent GetArtJvmtiEvent(ArtJvmTiEnv* env, jvmtiEvent e) { } } -template <typename FnType> -ALWAYS_INLINE static inline FnType* GetCallback(ArtJvmTiEnv* env, ArtJvmtiEvent event) { - if (env->event_callbacks == nullptr) { - return nullptr; - } +namespace impl { - // TODO: Add a type check. Can be done, for example, by an explicitly instantiated template - // function. +// Infrastructure to achieve type safety for event dispatch. - switch (event) { - case ArtJvmtiEvent::kVmInit: - return reinterpret_cast<FnType*>(env->event_callbacks->VMInit); - case ArtJvmtiEvent::kVmDeath: - return reinterpret_cast<FnType*>(env->event_callbacks->VMDeath); - case ArtJvmtiEvent::kThreadStart: - return reinterpret_cast<FnType*>(env->event_callbacks->ThreadStart); - case ArtJvmtiEvent::kThreadEnd: - return reinterpret_cast<FnType*>(env->event_callbacks->ThreadEnd); - case ArtJvmtiEvent::kClassFileLoadHookRetransformable: - case ArtJvmtiEvent::kClassFileLoadHookNonRetransformable: - return reinterpret_cast<FnType*>(env->event_callbacks->ClassFileLoadHook); - case ArtJvmtiEvent::kClassLoad: - return reinterpret_cast<FnType*>(env->event_callbacks->ClassLoad); - case ArtJvmtiEvent::kClassPrepare: - return reinterpret_cast<FnType*>(env->event_callbacks->ClassPrepare); - case ArtJvmtiEvent::kVmStart: - return reinterpret_cast<FnType*>(env->event_callbacks->VMStart); - case ArtJvmtiEvent::kException: - return reinterpret_cast<FnType*>(env->event_callbacks->Exception); - case ArtJvmtiEvent::kExceptionCatch: - return reinterpret_cast<FnType*>(env->event_callbacks->ExceptionCatch); - case ArtJvmtiEvent::kSingleStep: - return reinterpret_cast<FnType*>(env->event_callbacks->SingleStep); - case ArtJvmtiEvent::kFramePop: - return reinterpret_cast<FnType*>(env->event_callbacks->FramePop); - case ArtJvmtiEvent::kBreakpoint: - return reinterpret_cast<FnType*>(env->event_callbacks->Breakpoint); - case ArtJvmtiEvent::kFieldAccess: - return reinterpret_cast<FnType*>(env->event_callbacks->FieldAccess); - case ArtJvmtiEvent::kFieldModification: - return reinterpret_cast<FnType*>(env->event_callbacks->FieldModification); - case ArtJvmtiEvent::kMethodEntry: - return reinterpret_cast<FnType*>(env->event_callbacks->MethodEntry); - case ArtJvmtiEvent::kMethodExit: - return reinterpret_cast<FnType*>(env->event_callbacks->MethodExit); - case ArtJvmtiEvent::kNativeMethodBind: - return reinterpret_cast<FnType*>(env->event_callbacks->NativeMethodBind); - case ArtJvmtiEvent::kCompiledMethodLoad: - return reinterpret_cast<FnType*>(env->event_callbacks->CompiledMethodLoad); - case ArtJvmtiEvent::kCompiledMethodUnload: - return reinterpret_cast<FnType*>(env->event_callbacks->CompiledMethodUnload); - case ArtJvmtiEvent::kDynamicCodeGenerated: - return reinterpret_cast<FnType*>(env->event_callbacks->DynamicCodeGenerated); - case ArtJvmtiEvent::kDataDumpRequest: - return reinterpret_cast<FnType*>(env->event_callbacks->DataDumpRequest); - case ArtJvmtiEvent::kMonitorWait: - return reinterpret_cast<FnType*>(env->event_callbacks->MonitorWait); - case ArtJvmtiEvent::kMonitorWaited: - return reinterpret_cast<FnType*>(env->event_callbacks->MonitorWaited); - case ArtJvmtiEvent::kMonitorContendedEnter: - return reinterpret_cast<FnType*>(env->event_callbacks->MonitorContendedEnter); - case ArtJvmtiEvent::kMonitorContendedEntered: - return reinterpret_cast<FnType*>(env->event_callbacks->MonitorContendedEntered); - case ArtJvmtiEvent::kResourceExhausted: - return reinterpret_cast<FnType*>(env->event_callbacks->ResourceExhausted); - case ArtJvmtiEvent::kGarbageCollectionStart: - return reinterpret_cast<FnType*>(env->event_callbacks->GarbageCollectionStart); - case ArtJvmtiEvent::kGarbageCollectionFinish: - return reinterpret_cast<FnType*>(env->event_callbacks->GarbageCollectionFinish); - case ArtJvmtiEvent::kObjectFree: - return reinterpret_cast<FnType*>(env->event_callbacks->ObjectFree); - case ArtJvmtiEvent::kVmObjectAlloc: - return reinterpret_cast<FnType*>(env->event_callbacks->VMObjectAlloc); - } - return nullptr; -} +#define FORALL_EVENT_TYPES(fn) \ + fn(VMInit, ArtJvmtiEvent::kVmInit) \ + fn(VMDeath, ArtJvmtiEvent::kVmDeath) \ + fn(ThreadStart, ArtJvmtiEvent::kThreadStart) \ + fn(ThreadEnd, ArtJvmtiEvent::kThreadEnd) \ + fn(ClassFileLoadHook, ArtJvmtiEvent::kClassFileLoadHookRetransformable) \ + fn(ClassFileLoadHook, ArtJvmtiEvent::kClassFileLoadHookNonRetransformable) \ + fn(ClassLoad, ArtJvmtiEvent::kClassLoad) \ + fn(ClassPrepare, ArtJvmtiEvent::kClassPrepare) \ + fn(VMStart, ArtJvmtiEvent::kVmStart) \ + fn(Exception, ArtJvmtiEvent::kException) \ + fn(ExceptionCatch, ArtJvmtiEvent::kExceptionCatch) \ + fn(SingleStep, ArtJvmtiEvent::kSingleStep) \ + fn(FramePop, ArtJvmtiEvent::kFramePop) \ + fn(Breakpoint, ArtJvmtiEvent::kBreakpoint) \ + fn(FieldAccess, ArtJvmtiEvent::kFieldAccess) \ + fn(FieldModification, ArtJvmtiEvent::kFieldModification) \ + fn(MethodEntry, ArtJvmtiEvent::kMethodEntry) \ + fn(MethodExit, ArtJvmtiEvent::kMethodExit) \ + fn(NativeMethodBind, ArtJvmtiEvent::kNativeMethodBind) \ + fn(CompiledMethodLoad, ArtJvmtiEvent::kCompiledMethodLoad) \ + fn(CompiledMethodUnload, ArtJvmtiEvent::kCompiledMethodUnload) \ + fn(DynamicCodeGenerated, ArtJvmtiEvent::kDynamicCodeGenerated) \ + fn(DataDumpRequest, ArtJvmtiEvent::kDataDumpRequest) \ + fn(MonitorWait, ArtJvmtiEvent::kMonitorWait) \ + fn(MonitorWaited, ArtJvmtiEvent::kMonitorWaited) \ + fn(MonitorContendedEnter, ArtJvmtiEvent::kMonitorContendedEnter) \ + fn(MonitorContendedEntered, ArtJvmtiEvent::kMonitorContendedEntered) \ + fn(ResourceExhausted, ArtJvmtiEvent::kResourceExhausted) \ + fn(GarbageCollectionStart, ArtJvmtiEvent::kGarbageCollectionStart) \ + fn(GarbageCollectionFinish, ArtJvmtiEvent::kGarbageCollectionFinish) \ + fn(ObjectFree, ArtJvmtiEvent::kObjectFree) \ + fn(VMObjectAlloc, ArtJvmtiEvent::kVmObjectAlloc) + +template <ArtJvmtiEvent kEvent> +struct EventFnType { +}; + +#define EVENT_FN_TYPE(name, enum_name) \ +template <> \ +struct EventFnType<enum_name> { \ + using type = decltype(jvmtiEventCallbacks().name); \ +}; + +FORALL_EVENT_TYPES(EVENT_FN_TYPE) -template <typename ...Args> -inline void EventHandler::DispatchClassFileLoadHookEvent(art::Thread*, - ArtJvmtiEvent event, - Args... args ATTRIBUTE_UNUSED) const { - CHECK(event == ArtJvmtiEvent::kClassFileLoadHookRetransformable || - event == ArtJvmtiEvent::kClassFileLoadHookNonRetransformable); - LOG(FATAL) << "Incorrect arguments to ClassFileLoadHook!"; +#undef EVENT_FN_TYPE + +template <ArtJvmtiEvent kEvent> +ALWAYS_INLINE inline typename EventFnType<kEvent>::type GetCallback(ArtJvmTiEnv* env); + +#define GET_CALLBACK(name, enum_name) \ +template <> \ +ALWAYS_INLINE inline EventFnType<enum_name>::type GetCallback<enum_name>( \ + ArtJvmTiEnv* env) { \ + if (env->event_callbacks == nullptr) { \ + return nullptr; \ + } \ + return env->event_callbacks->name; \ } +FORALL_EVENT_TYPES(GET_CALLBACK) + +#undef GET_CALLBACK + +#undef FORALL_EVENT_TYPES + +} // namespace impl + +// C++ does not allow partial template function specialization. The dispatch for our separated +// ClassFileLoadHook event types is the same, so use this helper for code deduplication. // TODO Locking of some type! -template <> +template <ArtJvmtiEvent kEvent> inline void EventHandler::DispatchClassFileLoadHookEvent(art::Thread* thread, - ArtJvmtiEvent event, JNIEnv* jnienv, jclass class_being_redefined, jobject loader, @@ -136,26 +124,16 @@ inline void EventHandler::DispatchClassFileLoadHookEvent(art::Thread* thread, const unsigned char* class_data, jint* new_class_data_len, unsigned char** new_class_data) const { - CHECK(event == ArtJvmtiEvent::kClassFileLoadHookRetransformable || - event == ArtJvmtiEvent::kClassFileLoadHookNonRetransformable); - using FnType = void(jvmtiEnv* /* jvmti_env */, - JNIEnv* /* jnienv */, - jclass /* class_being_redefined */, - jobject /* loader */, - const char* /* name */, - jobject /* protection_domain */, - jint /* class_data_len */, - const unsigned char* /* class_data */, - jint* /* new_class_data_len */, - unsigned char** /* new_class_data */); + static_assert(kEvent == ArtJvmtiEvent::kClassFileLoadHookRetransformable || + kEvent == ArtJvmtiEvent::kClassFileLoadHookNonRetransformable, "Unsupported event"); jint current_len = class_data_len; unsigned char* current_class_data = const_cast<unsigned char*>(class_data); ArtJvmTiEnv* last_env = nullptr; for (ArtJvmTiEnv* env : envs) { - if (ShouldDispatch(event, env, thread)) { - jint new_len; - unsigned char* new_data; - FnType* callback = GetCallback<FnType>(env, event); + if (ShouldDispatch<kEvent>(env, thread)) { + jint new_len = 0; + unsigned char* new_data = nullptr; + auto callback = impl::GetCallback<kEvent>(env); callback(env, jnienv, class_being_redefined, @@ -186,28 +164,16 @@ inline void EventHandler::DispatchClassFileLoadHookEvent(art::Thread* thread, } } -template <typename ...Args> +// Our goal for DispatchEvent: Do not allow implicit type conversion. Types of ...args must match +// exactly the argument types of the corresponding Jvmti kEvent function pointer. + +template <ArtJvmtiEvent kEvent, typename ...Args> inline void EventHandler::DispatchEvent(art::Thread* thread, - ArtJvmtiEvent event, Args... args) const { - switch (event) { - case ArtJvmtiEvent::kClassFileLoadHookRetransformable: - case ArtJvmtiEvent::kClassFileLoadHookNonRetransformable: - return DispatchClassFileLoadHookEvent(thread, event, args...); - default: - return GenericDispatchEvent(thread, event, args...); - } -} - -// TODO Locking of some type! -template <typename ...Args> -inline void EventHandler::GenericDispatchEvent(art::Thread* thread, - ArtJvmtiEvent event, - Args... args) const { using FnType = void(jvmtiEnv*, Args...); for (ArtJvmTiEnv* env : envs) { - if (ShouldDispatch(event, env, thread)) { - FnType* callback = GetCallback<FnType>(env, event); + if (ShouldDispatch<kEvent>(env, thread)) { + FnType* callback = impl::GetCallback<kEvent>(env); if (callback != nullptr) { (*callback)(env, args...); } @@ -215,14 +181,66 @@ inline void EventHandler::GenericDispatchEvent(art::Thread* thread, } } -inline bool EventHandler::ShouldDispatch(ArtJvmtiEvent event, - ArtJvmTiEnv* env, +// C++ does not allow partial template function specialization. The dispatch for our separated +// ClassFileLoadHook event types is the same, and in the DispatchClassFileLoadHookEvent helper. +// The following two DispatchEvent specializations dispatch to it. +template <> +inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookRetransformable>( + art::Thread* thread, + JNIEnv* jnienv, + jclass class_being_redefined, + jobject loader, + const char* name, + jobject protection_domain, + jint class_data_len, + const unsigned char* class_data, + jint* new_class_data_len, + unsigned char** new_class_data) const { + return DispatchClassFileLoadHookEvent<ArtJvmtiEvent::kClassFileLoadHookRetransformable>( + thread, + jnienv, + class_being_redefined, + loader, + name, + protection_domain, + class_data_len, + class_data, + new_class_data_len, + new_class_data); +} +template <> +inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookNonRetransformable>( + art::Thread* thread, + JNIEnv* jnienv, + jclass class_being_redefined, + jobject loader, + const char* name, + jobject protection_domain, + jint class_data_len, + const unsigned char* class_data, + jint* new_class_data_len, + unsigned char** new_class_data) const { + return DispatchClassFileLoadHookEvent<ArtJvmtiEvent::kClassFileLoadHookNonRetransformable>( + thread, + jnienv, + class_being_redefined, + loader, + name, + protection_domain, + class_data_len, + class_data, + new_class_data_len, + new_class_data); +} + +template <ArtJvmtiEvent kEvent> +inline bool EventHandler::ShouldDispatch(ArtJvmTiEnv* env, art::Thread* thread) { - bool dispatch = env->event_masks.global_event_mask.Test(event); + bool dispatch = env->event_masks.global_event_mask.Test(kEvent); - if (!dispatch && thread != nullptr && env->event_masks.unioned_thread_event_mask.Test(event)) { + if (!dispatch && thread != nullptr && env->event_masks.unioned_thread_event_mask.Test(kEvent)) { EventMask* mask = env->event_masks.GetEventMaskOrNull(thread); - dispatch = mask != nullptr && mask->Test(event); + dispatch = mask != nullptr && mask->Test(kEvent); } return dispatch; } diff --git a/runtime/openjdkjvmti/events.cc b/runtime/openjdkjvmti/events.cc index d3f8001d69..34492a91fd 100644 --- a/runtime/openjdkjvmti/events.cc +++ b/runtime/openjdkjvmti/events.cc @@ -206,13 +206,12 @@ class JvmtiAllocationListener : public art::gc::AllocationListener { ScopedLocalRef<jclass> klass( jni_env, jni_env->AddLocalReference<jclass>(obj->Ptr()->GetClass())); - handler_->DispatchEvent(self, - ArtJvmtiEvent::kVmObjectAlloc, - jni_env, - thread.get(), - object.get(), - klass.get(), - static_cast<jlong>(byte_count)); + handler_->DispatchEvent<ArtJvmtiEvent::kVmObjectAlloc>(self, + reinterpret_cast<JNIEnv*>(jni_env), + thread.get(), + object.get(), + klass.get(), + static_cast<jlong>(byte_count)); } } @@ -241,11 +240,11 @@ class JvmtiGcPauseListener : public art::gc::GcPauseListener { finish_enabled_(false) {} void StartPause() OVERRIDE { - handler_->DispatchEvent(nullptr, ArtJvmtiEvent::kGarbageCollectionStart); + handler_->DispatchEvent<ArtJvmtiEvent::kGarbageCollectionStart>(nullptr); } void EndPause() OVERRIDE { - handler_->DispatchEvent(nullptr, ArtJvmtiEvent::kGarbageCollectionFinish); + handler_->DispatchEvent<ArtJvmtiEvent::kGarbageCollectionFinish>(nullptr); } bool IsEnabled() { @@ -303,6 +302,64 @@ void EventHandler::HandleEventType(ArtJvmtiEvent event, bool enable) { } } +// Checks to see if the env has the capabilities associated with the given event. +static bool HasAssociatedCapability(ArtJvmTiEnv* env, + ArtJvmtiEvent event) { + jvmtiCapabilities caps = env->capabilities; + switch (event) { + case ArtJvmtiEvent::kBreakpoint: + return caps.can_generate_breakpoint_events == 1; + + case ArtJvmtiEvent::kCompiledMethodLoad: + case ArtJvmtiEvent::kCompiledMethodUnload: + return caps.can_generate_compiled_method_load_events == 1; + + case ArtJvmtiEvent::kException: + case ArtJvmtiEvent::kExceptionCatch: + return caps.can_generate_exception_events == 1; + + case ArtJvmtiEvent::kFieldAccess: + return caps.can_generate_field_access_events == 1; + + case ArtJvmtiEvent::kFieldModification: + return caps.can_generate_field_modification_events == 1; + + case ArtJvmtiEvent::kFramePop: + return caps.can_generate_frame_pop_events == 1; + + case ArtJvmtiEvent::kGarbageCollectionStart: + case ArtJvmtiEvent::kGarbageCollectionFinish: + return caps.can_generate_garbage_collection_events == 1; + + case ArtJvmtiEvent::kMethodEntry: + return caps.can_generate_method_entry_events == 1; + + case ArtJvmtiEvent::kMethodExit: + return caps.can_generate_method_exit_events == 1; + + case ArtJvmtiEvent::kMonitorContendedEnter: + case ArtJvmtiEvent::kMonitorContendedEntered: + case ArtJvmtiEvent::kMonitorWait: + case ArtJvmtiEvent::kMonitorWaited: + return caps.can_generate_monitor_events == 1; + + case ArtJvmtiEvent::kNativeMethodBind: + return caps.can_generate_native_method_bind_events == 1; + + case ArtJvmtiEvent::kObjectFree: + return caps.can_generate_object_free_events == 1; + + case ArtJvmtiEvent::kSingleStep: + return caps.can_generate_single_step_events == 1; + + case ArtJvmtiEvent::kVmObjectAlloc: + return caps.can_generate_vm_object_alloc_events == 1; + + default: + return true; + } +} + jvmtiError EventHandler::SetEvent(ArtJvmTiEnv* env, art::Thread* thread, ArtJvmtiEvent event, @@ -319,8 +376,6 @@ jvmtiError EventHandler::SetEvent(ArtJvmTiEnv* env, } } - // TODO: Capability check. - if (mode != JVMTI_ENABLE && mode != JVMTI_DISABLE) { return ERR(ILLEGAL_ARGUMENT); } @@ -329,6 +384,10 @@ jvmtiError EventHandler::SetEvent(ArtJvmTiEnv* env, return ERR(INVALID_EVENT_TYPE); } + if (!HasAssociatedCapability(env, event)) { + return ERR(MUST_POSSESS_CAPABILITY); + } + bool old_state = global_mask.Test(event); if (mode == JVMTI_ENABLE) { diff --git a/runtime/openjdkjvmti/events.h b/runtime/openjdkjvmti/events.h index 8e246ded8e..4e20d1776b 100644 --- a/runtime/openjdkjvmti/events.h +++ b/runtime/openjdkjvmti/events.h @@ -156,9 +156,9 @@ class EventHandler { ArtJvmtiEvent event, jvmtiEventMode mode); - template <typename ...Args> + template <ArtJvmtiEvent kEvent, typename ...Args> ALWAYS_INLINE - inline void DispatchEvent(art::Thread* thread, ArtJvmtiEvent event, Args... args) const; + inline void DispatchEvent(art::Thread* thread, Args... args) const; // Tell the event handler capabilities were added/lost so it can adjust the sent events.If // caps_added is true then caps is all the newly set capabilities of the jvmtiEnv. If it is false @@ -169,8 +169,9 @@ class EventHandler { bool added); private: + template <ArtJvmtiEvent kEvent> ALWAYS_INLINE - static inline bool ShouldDispatch(ArtJvmtiEvent event, ArtJvmTiEnv* env, art::Thread* thread); + static inline bool ShouldDispatch(ArtJvmTiEnv* env, art::Thread* thread); ALWAYS_INLINE inline bool NeedsEventUpdate(ArtJvmTiEnv* env, @@ -181,14 +182,17 @@ class EventHandler { ALWAYS_INLINE inline void RecalculateGlobalEventMask(ArtJvmtiEvent event); - template <typename ...Args> - ALWAYS_INLINE inline void GenericDispatchEvent(art::Thread* thread, - ArtJvmtiEvent event, - Args... args) const; - template <typename ...Args> + template <ArtJvmtiEvent kEvent> ALWAYS_INLINE inline void DispatchClassFileLoadHookEvent(art::Thread* thread, - ArtJvmtiEvent event, - Args... args) const; + JNIEnv* jnienv, + jclass class_being_redefined, + jobject loader, + const char* name, + jobject protection_domain, + jint class_data_len, + const unsigned char* class_data, + jint* new_class_data_len, + unsigned char** new_class_data) const; void HandleEventType(ArtJvmtiEvent event, bool enable); diff --git a/runtime/openjdkjvmti/object_tagging.cc b/runtime/openjdkjvmti/object_tagging.cc index 94cb46a428..b27c2a3834 100644 --- a/runtime/openjdkjvmti/object_tagging.cc +++ b/runtime/openjdkjvmti/object_tagging.cc @@ -207,7 +207,7 @@ void ObjectTagTable::SweepImpl(art::IsMarkedVisitor* visitor) { } void ObjectTagTable::HandleNullSweep(jlong tag) { - event_handler_->DispatchEvent(nullptr, ArtJvmtiEvent::kObjectFree, tag); + event_handler_->DispatchEvent<ArtJvmtiEvent::kObjectFree>(nullptr, tag); } template <typename T, ObjectTagTable::TableUpdateNullTarget kTargetNull> diff --git a/runtime/openjdkjvmti/ti_class.cc b/runtime/openjdkjvmti/ti_class.cc index 450f75a813..c14fd84264 100644 --- a/runtime/openjdkjvmti/ti_class.cc +++ b/runtime/openjdkjvmti/ti_class.cc @@ -31,6 +31,8 @@ #include "ti_class.h" +#include "android-base/stringprintf.h" + #include <mutex> #include <unordered_set> @@ -38,34 +40,226 @@ #include "base/macros.h" #include "class_table-inl.h" #include "class_linker.h" +#include "common_throws.h" #include "events-inl.h" #include "handle.h" #include "jni_env_ext-inl.h" #include "jni_internal.h" +#include "mirror/array-inl.h" +#include "mirror/class-inl.h" +#include "mirror/class_ext.h" #include "runtime.h" #include "runtime_callbacks.h" #include "ScopedLocalRef.h" #include "scoped_thread_state_change-inl.h" #include "thread-inl.h" #include "thread_list.h" +#include "ti_class_loader.h" +#include "ti_redefine.h" +#include "utils.h" namespace openjdkjvmti { +using android::base::StringPrintf; + +static std::unique_ptr<const art::DexFile> MakeSingleDexFile(art::Thread* self, + const char* descriptor, + const std::string& orig_location, + jint final_len, + const unsigned char* final_dex_data) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + // Make the mmap + std::string error_msg; + std::unique_ptr<art::MemMap> map(Redefiner::MoveDataToMemMap(orig_location, + final_len, + final_dex_data, + &error_msg)); + if (map.get() == nullptr) { + LOG(WARNING) << "Unable to allocate mmap for redefined dex file! Error was: " << error_msg; + self->ThrowOutOfMemoryError(StringPrintf( + "Unable to allocate dex file for transformation of %s", descriptor).c_str()); + return nullptr; + } + + // Make a dex-file + if (map->Size() < sizeof(art::DexFile::Header)) { + LOG(WARNING) << "Could not read dex file header because dex_data was too short"; + art::ThrowClassFormatError(nullptr, + "Unable to read transformed dex file of %s", + descriptor); + return nullptr; + } + uint32_t checksum = reinterpret_cast<const art::DexFile::Header*>(map->Begin())->checksum_; + std::unique_ptr<const art::DexFile> dex_file(art::DexFile::Open(map->GetName(), + checksum, + std::move(map), + /*verify*/true, + /*verify_checksum*/true, + &error_msg)); + if (dex_file.get() == nullptr) { + LOG(WARNING) << "Unable to load modified dex file for " << descriptor << ": " << error_msg; + art::ThrowClassFormatError(nullptr, + "Unable to read transformed dex file of %s because %s", + descriptor, + error_msg.c_str()); + return nullptr; + } + if (dex_file->NumClassDefs() != 1) { + LOG(WARNING) << "Dex file contains more than 1 class_def. Ignoring."; + // TODO Throw some other sort of error here maybe? + art::ThrowClassFormatError( + nullptr, + "Unable to use transformed dex file of %s because it contained too many classes", + descriptor); + return nullptr; + } + return dex_file; +} + struct ClassCallback : public art::ClassLoadCallback { + void ClassPreDefine(const char* descriptor, + art::Handle<art::mirror::Class> klass, + art::Handle<art::mirror::ClassLoader> class_loader, + const art::DexFile& initial_dex_file, + const art::DexFile::ClassDef& initial_class_def ATTRIBUTE_UNUSED, + /*out*/art::DexFile const** final_dex_file, + /*out*/art::DexFile::ClassDef const** final_class_def) + OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) { + bool is_enabled = + event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kClassFileLoadHookRetransformable) || + event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kClassFileLoadHookNonRetransformable); + if (!is_enabled) { + return; + } + if (descriptor[0] != 'L') { + // It is a primitive or array. Just return + return; + } + std::string name(std::string(descriptor).substr(1, strlen(descriptor) - 2)); + + art::Thread* self = art::Thread::Current(); + art::JNIEnvExt* env = self->GetJniEnv(); + ScopedLocalRef<jobject> loader( + env, class_loader.IsNull() ? nullptr : env->AddLocalReference<jobject>(class_loader.Get())); + // Go back to native. + art::ScopedThreadSuspension sts(self, art::ThreadState::kNative); + // Call all Non-retransformable agents. + jint post_no_redefine_len = 0; + unsigned char* post_no_redefine_dex_data = nullptr; + std::unique_ptr<const unsigned char> post_no_redefine_unique_ptr(nullptr); + event_handler->DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookNonRetransformable>( + self, + static_cast<JNIEnv*>(env), + static_cast<jclass>(nullptr), // The class doesn't really exist yet so send null. + loader.get(), + name.c_str(), + static_cast<jobject>(nullptr), // Android doesn't seem to have protection domains + static_cast<jint>(initial_dex_file.Size()), + static_cast<const unsigned char*>(initial_dex_file.Begin()), + static_cast<jint*>(&post_no_redefine_len), + static_cast<unsigned char**>(&post_no_redefine_dex_data)); + if (post_no_redefine_dex_data == nullptr) { + DCHECK_EQ(post_no_redefine_len, 0); + post_no_redefine_dex_data = const_cast<unsigned char*>(initial_dex_file.Begin()); + post_no_redefine_len = initial_dex_file.Size(); + } else { + post_no_redefine_unique_ptr = std::unique_ptr<const unsigned char>(post_no_redefine_dex_data); + DCHECK_GT(post_no_redefine_len, 0); + } + // Call all retransformable agents. + jint final_len = 0; + unsigned char* final_dex_data = nullptr; + std::unique_ptr<const unsigned char> final_dex_unique_ptr(nullptr); + event_handler->DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookRetransformable>( + self, + static_cast<JNIEnv*>(env), + static_cast<jclass>(nullptr), // The class doesn't really exist yet so send null. + loader.get(), + name.c_str(), + static_cast<jobject>(nullptr), // Android doesn't seem to have protection domains + static_cast<jint>(post_no_redefine_len), + static_cast<const unsigned char*>(post_no_redefine_dex_data), + static_cast<jint*>(&final_len), + static_cast<unsigned char**>(&final_dex_data)); + if (final_dex_data == nullptr) { + DCHECK_EQ(final_len, 0); + final_dex_data = post_no_redefine_dex_data; + final_len = post_no_redefine_len; + } else { + final_dex_unique_ptr = std::unique_ptr<const unsigned char>(final_dex_data); + DCHECK_GT(final_len, 0); + } + + if (final_dex_data != initial_dex_file.Begin()) { + LOG(WARNING) << "Changing class " << descriptor; + art::ScopedObjectAccess soa(self); + art::StackHandleScope<2> hs(self); + // Save the results of all the non-retransformable agents. + // First allocate the ClassExt + art::Handle<art::mirror::ClassExt> ext(hs.NewHandle(klass->EnsureExtDataPresent(self))); + // Make sure we have a ClassExt. This is fine even though we are a temporary since it will + // get copied. + if (ext.IsNull()) { + // We will just return failure if we fail to allocate + LOG(WARNING) << "Could not allocate ext-data for class '" << descriptor << "'. " + << "Aborting transformation since we will be unable to store it."; + self->AssertPendingOOMException(); + return; + } + + // Allocate the byte array to store the dex file bytes in. + art::Handle<art::mirror::ByteArray> arr(hs.NewHandle( + art::mirror::ByteArray::AllocateAndFill( + self, + reinterpret_cast<const signed char*>(post_no_redefine_dex_data), + post_no_redefine_len))); + if (arr.IsNull()) { + LOG(WARNING) << "Unable to allocate byte array for initial dex-file bytes. Aborting " + << "transformation"; + self->AssertPendingOOMException(); + return; + } + + std::unique_ptr<const art::DexFile> dex_file(MakeSingleDexFile(self, + descriptor, + initial_dex_file.GetLocation(), + final_len, + final_dex_data)); + if (dex_file.get() == nullptr) { + return; + } + + // TODO Check Redefined dex file for all invariants. + LOG(WARNING) << "Dex file created by class-definition time transformation of " + << descriptor << " is not checked for all retransformation invariants."; + + if (!ClassLoaderHelper::AddToClassLoader(self, class_loader, dex_file.get())) { + LOG(ERROR) << "Unable to add " << descriptor << " to class loader!"; + return; + } + + // Actually set the ClassExt's original bytes once we have actually succeeded. + ext->SetOriginalDexFileBytes(arr.Get()); + // Set the return values + *final_class_def = &dex_file->GetClassDef(0); + *final_dex_file = dex_file.release(); + } + } + void ClassLoad(art::Handle<art::mirror::Class> klass) REQUIRES_SHARED(art::Locks::mutator_lock_) { if (event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kClassLoad)) { art::Thread* thread = art::Thread::Current(); ScopedLocalRef<jclass> jklass(thread->GetJniEnv(), thread->GetJniEnv()->AddLocalReference<jclass>(klass.Get())); - ScopedLocalRef<jclass> jthread( - thread->GetJniEnv(), thread->GetJniEnv()->AddLocalReference<jclass>(thread->GetPeer())); + ScopedLocalRef<jthread> thread_jni( + thread->GetJniEnv(), thread->GetJniEnv()->AddLocalReference<jthread>(thread->GetPeer())); { art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative); - event_handler->DispatchEvent(thread, - ArtJvmtiEvent::kClassLoad, - reinterpret_cast<JNIEnv*>(thread->GetJniEnv()), - jthread.get(), - jklass.get()); + event_handler->DispatchEvent<ArtJvmtiEvent::kClassLoad>( + thread, + static_cast<JNIEnv*>(thread->GetJniEnv()), + thread_jni.get(), + jklass.get()); } AddTempClass(thread, jklass.get()); } @@ -78,14 +272,14 @@ struct ClassCallback : public art::ClassLoadCallback { art::Thread* thread = art::Thread::Current(); ScopedLocalRef<jclass> jklass(thread->GetJniEnv(), thread->GetJniEnv()->AddLocalReference<jclass>(klass.Get())); - ScopedLocalRef<jclass> jthread( - thread->GetJniEnv(), thread->GetJniEnv()->AddLocalReference<jclass>(thread->GetPeer())); + ScopedLocalRef<jthread> thread_jni( + thread->GetJniEnv(), thread->GetJniEnv()->AddLocalReference<jthread>(thread->GetPeer())); art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative); - event_handler->DispatchEvent(thread, - ArtJvmtiEvent::kClassPrepare, - reinterpret_cast<JNIEnv*>(thread->GetJniEnv()), - jthread.get(), - jklass.get()); + event_handler->DispatchEvent<ArtJvmtiEvent::kClassPrepare>( + thread, + static_cast<JNIEnv*>(thread->GetJniEnv()), + thread_jni.get(), + jklass.get()); } } diff --git a/runtime/openjdkjvmti/ti_class_loader.cc b/runtime/openjdkjvmti/ti_class_loader.cc new file mode 100644 index 0000000000..afec0bfac0 --- /dev/null +++ b/runtime/openjdkjvmti/ti_class_loader.cc @@ -0,0 +1,211 @@ +/* Copyright (C) 2017 The Android Open Source Project + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This file implements interfaces from the file jvmti.h. This implementation + * is licensed under the same terms as the file jvmti.h. The + * copyright and license information for the file jvmti.h follows. + * + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "ti_class_loader.h" + +#include <limits> + +#include "android-base/stringprintf.h" + +#include "art_jvmti.h" +#include "base/array_slice.h" +#include "base/logging.h" +#include "dex_file.h" +#include "dex_file_types.h" +#include "events-inl.h" +#include "gc/allocation_listener.h" +#include "gc/heap.h" +#include "instrumentation.h" +#include "jit/jit.h" +#include "jit/jit_code_cache.h" +#include "jni_env_ext-inl.h" +#include "jvmti_allocator.h" +#include "mirror/class.h" +#include "mirror/class_ext.h" +#include "mirror/object.h" +#include "object_lock.h" +#include "runtime.h" +#include "ScopedLocalRef.h" +#include "transform.h" + +namespace openjdkjvmti { + +bool ClassLoaderHelper::AddToClassLoader(art::Thread* self, + art::Handle<art::mirror::ClassLoader> loader, + const art::DexFile* dex_file) { + art::ScopedObjectAccessUnchecked soa(self); + art::StackHandleScope<3> hs(self); + if (art::ClassLinker::IsBootClassLoader(soa, loader.Get())) { + art::Runtime::Current()->GetClassLinker()->AppendToBootClassPath(self, *dex_file); + return true; + } + art::Handle<art::mirror::Object> java_dex_file_obj( + hs.NewHandle(FindSourceDexFileObject(self, loader))); + if (java_dex_file_obj.IsNull()) { + return false; + } + art::Handle<art::mirror::LongArray> old_cookie(hs.NewHandle(GetDexFileCookie(java_dex_file_obj))); + art::Handle<art::mirror::LongArray> cookie(hs.NewHandle( + AllocateNewDexFileCookie(self, old_cookie, dex_file))); + if (cookie.IsNull()) { + return false; + } + art::ScopedAssertNoThreadSuspension nts("Replacing cookie fields in j.l.DexFile object"); + UpdateJavaDexFile(java_dex_file_obj.Get(), cookie.Get()); + return true; +} + +void ClassLoaderHelper::UpdateJavaDexFile(art::ObjPtr<art::mirror::Object> java_dex_file, + art::ObjPtr<art::mirror::LongArray> new_cookie) { + art::ArtField* internal_cookie_field = java_dex_file->GetClass()->FindDeclaredInstanceField( + "mInternalCookie", "Ljava/lang/Object;"); + art::ArtField* cookie_field = java_dex_file->GetClass()->FindDeclaredInstanceField( + "mCookie", "Ljava/lang/Object;"); + CHECK(internal_cookie_field != nullptr); + art::ObjPtr<art::mirror::LongArray> orig_internal_cookie( + internal_cookie_field->GetObject(java_dex_file)->AsLongArray()); + art::ObjPtr<art::mirror::LongArray> orig_cookie( + cookie_field->GetObject(java_dex_file)->AsLongArray()); + internal_cookie_field->SetObject<false>(java_dex_file, new_cookie); + if (!orig_cookie.IsNull()) { + cookie_field->SetObject<false>(java_dex_file, new_cookie); + } +} + +art::ObjPtr<art::mirror::LongArray> ClassLoaderHelper::GetDexFileCookie( + art::Handle<art::mirror::Object> java_dex_file_obj) { + // mCookie is nulled out if the DexFile has been closed but mInternalCookie sticks around until + // the object is finalized. Since they always point to the same array if mCookie is not null we + // just use the mInternalCookie field. We will update one or both of these fields later. + // TODO Should I get the class from the classloader or directly? + art::ArtField* internal_cookie_field = java_dex_file_obj->GetClass()->FindDeclaredInstanceField( + "mInternalCookie", "Ljava/lang/Object;"); + // TODO Add check that mCookie is either null or same as mInternalCookie + CHECK(internal_cookie_field != nullptr); + return internal_cookie_field->GetObject(java_dex_file_obj.Get())->AsLongArray(); +} + +// TODO Really wishing I had that mirror of java.lang.DexFile now. +art::ObjPtr<art::mirror::LongArray> ClassLoaderHelper::AllocateNewDexFileCookie( + art::Thread* self, + art::Handle<art::mirror::LongArray> cookie, + const art::DexFile* dex_file) { + art::StackHandleScope<1> hs(self); + CHECK(cookie.Get() != nullptr); + CHECK_GE(cookie->GetLength(), 1); + art::Handle<art::mirror::LongArray> new_cookie( + hs.NewHandle(art::mirror::LongArray::Alloc(self, cookie->GetLength() + 1))); + if (new_cookie.Get() == nullptr) { + self->AssertPendingOOMException(); + return nullptr; + } + // Copy the oat-dex field at the start. + // TODO Should I clear this field? + // TODO This is a really crappy thing here with the first element being different. + new_cookie->SetWithoutChecks<false>(0, cookie->GetWithoutChecks(0)); + // This must match the casts in runtime/native/dalvik_system_DexFile.cc:ConvertDexFilesToJavaArray + new_cookie->SetWithoutChecks<false>( + 1, static_cast<int64_t>(reinterpret_cast<uintptr_t>(dex_file))); + new_cookie->Memcpy(2, cookie.Get(), 1, cookie->GetLength() - 1); + return new_cookie.Get(); +} + +// TODO This should return the actual source java.lang.DexFile object for the klass being loaded. +art::ObjPtr<art::mirror::Object> ClassLoaderHelper::FindSourceDexFileObject( + art::Thread* self, art::Handle<art::mirror::ClassLoader> loader) { + const char* dex_path_list_element_array_name = "[Ldalvik/system/DexPathList$Element;"; + const char* dex_path_list_element_name = "Ldalvik/system/DexPathList$Element;"; + const char* dex_file_name = "Ldalvik/system/DexFile;"; + const char* dex_path_list_name = "Ldalvik/system/DexPathList;"; + const char* dex_class_loader_name = "Ldalvik/system/BaseDexClassLoader;"; + + CHECK(!self->IsExceptionPending()); + art::StackHandleScope<5> hs(self); + art::ClassLinker* class_linker = art::Runtime::Current()->GetClassLinker(); + + art::Handle<art::mirror::ClassLoader> null_loader(hs.NewHandle<art::mirror::ClassLoader>( + nullptr)); + art::Handle<art::mirror::Class> base_dex_loader_class(hs.NewHandle(class_linker->FindClass( + self, dex_class_loader_name, null_loader))); + + // Get all the ArtFields so we can look in the BaseDexClassLoader + art::ArtField* path_list_field = base_dex_loader_class->FindDeclaredInstanceField( + "pathList", dex_path_list_name); + CHECK(path_list_field != nullptr); + + art::ArtField* dex_path_list_element_field = + class_linker->FindClass(self, dex_path_list_name, null_loader) + ->FindDeclaredInstanceField("dexElements", dex_path_list_element_array_name); + CHECK(dex_path_list_element_field != nullptr); + + art::ArtField* element_dex_file_field = + class_linker->FindClass(self, dex_path_list_element_name, null_loader) + ->FindDeclaredInstanceField("dexFile", dex_file_name); + CHECK(element_dex_file_field != nullptr); + + // Check if loader is a BaseDexClassLoader + art::Handle<art::mirror::Class> loader_class(hs.NewHandle(loader->GetClass())); + // Currently only base_dex_loader is allowed to actually define classes but if this changes in the + // future we should make sure to support all class loader types. + if (!loader_class->IsSubClass(base_dex_loader_class.Get())) { + LOG(ERROR) << "The classloader is not a BaseDexClassLoader which is currently the only " + << "supported class loader type!"; + return nullptr; + } + // Start navigating the fields of the loader (now known to be a BaseDexClassLoader derivative) + art::Handle<art::mirror::Object> path_list( + hs.NewHandle(path_list_field->GetObject(loader.Get()))); + CHECK(path_list.Get() != nullptr); + CHECK(!self->IsExceptionPending()); + art::Handle<art::mirror::ObjectArray<art::mirror::Object>> dex_elements_list(hs.NewHandle( + dex_path_list_element_field->GetObject(path_list.Get())-> + AsObjectArray<art::mirror::Object>())); + CHECK(!self->IsExceptionPending()); + CHECK(dex_elements_list.Get() != nullptr); + size_t num_elements = dex_elements_list->GetLength(); + // Iterate over the DexPathList$Element to find the right one + for (size_t i = 0; i < num_elements; i++) { + art::ObjPtr<art::mirror::Object> current_element = dex_elements_list->Get(i); + CHECK(!current_element.IsNull()); + // TODO It would be cleaner to put the art::DexFile into the dalvik.system.DexFile the class + // comes from but it is more annoying because we would need to find this class. It is not + // necessary for proper function since we just need to be in front of the classes old dex file + // in the path. + art::ObjPtr<art::mirror::Object> first_dex_file( + element_dex_file_field->GetObject(current_element)); + if (!first_dex_file.IsNull()) { + return first_dex_file; + } + } + return nullptr; +} + +} // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_class_loader.h b/runtime/openjdkjvmti/ti_class_loader.h new file mode 100644 index 0000000000..1ac49886cb --- /dev/null +++ b/runtime/openjdkjvmti/ti_class_loader.h @@ -0,0 +1,99 @@ +/* Copyright (C) 2017 The Android Open Source Project + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This file implements interfaces from the file jvmti.h. This implementation + * is licensed under the same terms as the file jvmti.h. The + * copyright and license information for the file jvmti.h follows. + * + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_LOADER_H_ +#define ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_LOADER_H_ + +#include <string> + +#include <jni.h> + +#include "art_jvmti.h" +#include "art_method.h" +#include "base/array_slice.h" +#include "class_linker.h" +#include "dex_file.h" +#include "gc_root-inl.h" +#include "globals.h" +#include "jni_env_ext-inl.h" +#include "jvmti.h" +#include "linear_alloc.h" +#include "mem_map.h" +#include "mirror/array-inl.h" +#include "mirror/array.h" +#include "mirror/class-inl.h" +#include "mirror/class.h" +#include "mirror/class_loader-inl.h" +#include "mirror/string-inl.h" +#include "oat_file.h" +#include "obj_ptr.h" +#include "scoped_thread_state_change-inl.h" +#include "stack.h" +#include "ti_class_definition.h" +#include "thread_list.h" +#include "transform.h" +#include "utf.h" +#include "utils/dex_cache_arrays_layout-inl.h" + +namespace openjdkjvmti { + +// Class that can redefine a single class's methods. +// TODO We should really make this be driven by an outside class so we can do multiple classes at +// the same time and have less required cleanup. +class ClassLoaderHelper { + public: + static bool AddToClassLoader(art::Thread* self, + art::Handle<art::mirror::ClassLoader> loader, + const art::DexFile* dex_file) + REQUIRES_SHARED(art::Locks::mutator_lock_); + + // Finds a java.lang.DexFile object that is associated with the given ClassLoader. Each of these + // j.l.DexFile objects holds several art::DexFile*s in it. + // TODO This should return the actual source java.lang.DexFile object for the klass being loaded. + static art::ObjPtr<art::mirror::Object> FindSourceDexFileObject( + art::Thread* self, art::Handle<art::mirror::ClassLoader> loader) + REQUIRES_SHARED(art::Locks::mutator_lock_); + + static art::ObjPtr<art::mirror::LongArray> GetDexFileCookie( + art::Handle<art::mirror::Object> java_dex_file) REQUIRES_SHARED(art::Locks::mutator_lock_); + + static art::ObjPtr<art::mirror::LongArray> AllocateNewDexFileCookie( + art::Thread* self, + art::Handle<art::mirror::LongArray> old_dex_file_cookie, + const art::DexFile* new_dex_file) REQUIRES_SHARED(art::Locks::mutator_lock_); + + static void UpdateJavaDexFile(art::ObjPtr<art::mirror::Object> java_dex_file, + art::ObjPtr<art::mirror::LongArray> new_cookie) + REQUIRES(art::Roles::uninterruptible_) REQUIRES_SHARED(art::Locks::mutator_lock_); +}; + +} // namespace openjdkjvmti +#endif // ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_LOADER_H_ diff --git a/runtime/openjdkjvmti/ti_dump.cc b/runtime/openjdkjvmti/ti_dump.cc index 2ee5c409f6..d9e3ef1bcf 100644 --- a/runtime/openjdkjvmti/ti_dump.cc +++ b/runtime/openjdkjvmti/ti_dump.cc @@ -48,7 +48,7 @@ struct DumpCallback : public art::RuntimeSigQuitCallback { void SigQuit() OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) { art::Thread* thread = art::Thread::Current(); art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative); - event_handler->DispatchEvent(nullptr, ArtJvmtiEvent::kDataDumpRequest); + event_handler->DispatchEvent<ArtJvmtiEvent::kDataDumpRequest>(nullptr); } EventHandler* event_handler = nullptr; diff --git a/runtime/openjdkjvmti/ti_heap.cc b/runtime/openjdkjvmti/ti_heap.cc index 7b2521d63a..fe3e52b0c1 100644 --- a/runtime/openjdkjvmti/ti_heap.cc +++ b/runtime/openjdkjvmti/ti_heap.cc @@ -303,11 +303,11 @@ class FollowReferencesHelper FINAL { art::Thread* thread = FindThread(info); if (thread != nullptr) { - art::mirror::Object* thread_obj = thread->GetPeer(); + art::mirror::Object* thread_obj; if (thread->IsStillStarting()) { thread_obj = nullptr; } else { - thread_obj = thread->GetPeer(); + thread_obj = thread->GetPeerFromOtherThread(); } if (thread_obj != nullptr) { ref_info->jni_local.thread_tag = tag_table_->GetTagOrZero(thread_obj); @@ -333,11 +333,11 @@ class FollowReferencesHelper FINAL { art::Thread* thread = FindThread(info); if (thread != nullptr) { - art::mirror::Object* thread_obj = thread->GetPeer(); + art::mirror::Object* thread_obj; if (thread->IsStillStarting()) { thread_obj = nullptr; } else { - thread_obj = thread->GetPeer(); + thread_obj = thread->GetPeerFromOtherThread(); } if (thread_obj != nullptr) { ref_info->stack_local.thread_tag = tag_table_->GetTagOrZero(thread_obj); diff --git a/runtime/openjdkjvmti/ti_monitor.cc b/runtime/openjdkjvmti/ti_monitor.cc index b82768397b..645faea41b 100644 --- a/runtime/openjdkjvmti/ti_monitor.cc +++ b/runtime/openjdkjvmti/ti_monitor.cc @@ -54,7 +54,7 @@ class JvmtiMonitor { JvmtiMonitor() : owner_(nullptr), count_(0) { } - static bool Destroy(art::Thread* self, JvmtiMonitor* monitor) { + static bool Destroy(art::Thread* self, JvmtiMonitor* monitor) NO_THREAD_SAFETY_ANALYSIS { // Check whether this thread holds the monitor, or nobody does. art::Thread* owner_thread = monitor->owner_.load(std::memory_order_relaxed); if (owner_thread != nullptr && self != owner_thread) { @@ -71,7 +71,7 @@ class JvmtiMonitor { return true; } - void MonitorEnter(art::Thread* self) { + void MonitorEnter(art::Thread* self) NO_THREAD_SAFETY_ANALYSIS { // Check for recursive enter. if (IsOwner(self)) { count_++; @@ -86,7 +86,7 @@ class JvmtiMonitor { count_ = 1; } - bool MonitorExit(art::Thread* self) { + bool MonitorExit(art::Thread* self) NO_THREAD_SAFETY_ANALYSIS { if (!IsOwner(self)) { return false; } diff --git a/runtime/openjdkjvmti/ti_phase.cc b/runtime/openjdkjvmti/ti_phase.cc index 154406a5db..60371cfafe 100644 --- a/runtime/openjdkjvmti/ti_phase.cc +++ b/runtime/openjdkjvmti/ti_phase.cc @@ -64,7 +64,7 @@ struct PhaseUtil::PhaseCallback : public art::RuntimePhaseCallback { case RuntimePhase::kStart: { art::ScopedThreadSuspension sts(art::Thread::Current(), art::ThreadState::kNative); - event_handler->DispatchEvent(nullptr, ArtJvmtiEvent::kVmStart, GetJniEnv()); + event_handler->DispatchEvent<ArtJvmtiEvent::kVmStart>(nullptr, GetJniEnv()); PhaseUtil::current_phase_ = JVMTI_PHASE_START; } break; @@ -72,17 +72,14 @@ struct PhaseUtil::PhaseCallback : public art::RuntimePhaseCallback { { ScopedLocalRef<jthread> thread(GetJniEnv(), GetCurrentJThread()); art::ScopedThreadSuspension sts(art::Thread::Current(), art::ThreadState::kNative); - event_handler->DispatchEvent(nullptr, - ArtJvmtiEvent::kVmInit, - GetJniEnv(), - thread.get()); + event_handler->DispatchEvent<ArtJvmtiEvent::kVmInit>(nullptr, GetJniEnv(), thread.get()); PhaseUtil::current_phase_ = JVMTI_PHASE_LIVE; } break; case RuntimePhase::kDeath: { art::ScopedThreadSuspension sts(art::Thread::Current(), art::ThreadState::kNative); - event_handler->DispatchEvent(nullptr, ArtJvmtiEvent::kVmDeath, GetJniEnv()); + event_handler->DispatchEvent<ArtJvmtiEvent::kVmDeath>(nullptr, GetJniEnv()); PhaseUtil::current_phase_ = JVMTI_PHASE_DEAD; } // TODO: Block events now. @@ -139,4 +136,8 @@ void PhaseUtil::Unregister() { art::Runtime::Current()->GetRuntimeCallbacks()->RemoveRuntimePhaseCallback(&gPhaseCallback); } +jvmtiPhase PhaseUtil::GetPhaseUnchecked() { + return PhaseUtil::current_phase_; +} + } // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_phase.h b/runtime/openjdkjvmti/ti_phase.h index bd15fa6891..851fc27de5 100644 --- a/runtime/openjdkjvmti/ti_phase.h +++ b/runtime/openjdkjvmti/ti_phase.h @@ -57,6 +57,8 @@ class PhaseUtil { struct PhaseCallback; + static jvmtiPhase GetPhaseUnchecked(); + private: static jvmtiPhase current_phase_; }; diff --git a/runtime/openjdkjvmti/ti_redefine.cc b/runtime/openjdkjvmti/ti_redefine.cc index 34efc502e1..058b93a042 100644 --- a/runtime/openjdkjvmti/ti_redefine.cc +++ b/runtime/openjdkjvmti/ti_redefine.cc @@ -48,12 +48,13 @@ #include "jit/jit_code_cache.h" #include "jni_env_ext-inl.h" #include "jvmti_allocator.h" -#include "mirror/class.h" +#include "mirror/class-inl.h" #include "mirror/class_ext.h" #include "mirror/object.h" #include "object_lock.h" #include "runtime.h" #include "ScopedLocalRef.h" +#include "ti_class_loader.h" #include "transform.h" namespace openjdkjvmti { @@ -74,9 +75,7 @@ class ObsoleteMethodStackVisitor : public art::StackVisitor { StackVisitor::StackWalkKind::kIncludeInlinedFrames), allocator_(allocator), obsoleted_methods_(obsoleted_methods), - obsolete_maps_(obsolete_maps), - is_runtime_frame_(false) { - } + obsolete_maps_(obsolete_maps) { } ~ObsoleteMethodStackVisitor() OVERRIDE {} @@ -99,21 +98,7 @@ class ObsoleteMethodStackVisitor : public art::StackVisitor { bool VisitFrame() OVERRIDE REQUIRES(art::Locks::mutator_lock_) { art::ArtMethod* old_method = GetMethod(); - // TODO REMOVE once either current_method doesn't stick around through suspend points or deopt - // works through runtime methods. - bool prev_was_runtime_frame_ = is_runtime_frame_; - is_runtime_frame_ = old_method->IsRuntimeMethod(); if (obsoleted_methods_.find(old_method) != obsoleted_methods_.end()) { - // The check below works since when we deoptimize we set shadow frames for all frames until a - // native/runtime transition and for those set the return PC to a function that will complete - // the deoptimization. This does leave us with the unfortunate side-effect that frames just - // below runtime frames cannot be deoptimized at the moment. - // TODO REMOVE once either current_method doesn't stick around through suspend points or deopt - // works through runtime methods. - // TODO b/33616143 - if (!IsShadowFrame() && prev_was_runtime_frame_) { - LOG(FATAL) << "Deoptimization failed due to runtime method in stack. See b/33616143"; - } // We cannot ensure that the right dex file is used in inlined frames so we don't support // redefining them. DCHECK(!IsInInlinedFrame()) << "Inlined frames are not supported when using redefinition"; @@ -136,6 +121,7 @@ class ObsoleteMethodStackVisitor : public art::StackVisitor { new_obsolete_method->CopyFrom(old_method, ptr_size); DCHECK_EQ(new_obsolete_method->GetDeclaringClass(), old_method->GetDeclaringClass()); new_obsolete_method->SetIsObsolete(); + new_obsolete_method->SetDontCompile(); obsolete_maps_->insert({old_method, new_obsolete_method}); // Update JIT Data structures to point to the new method. art::jit::Jit* jit = art::Runtime::Current()->GetJit(); @@ -162,9 +148,6 @@ class ObsoleteMethodStackVisitor : public art::StackVisitor { // values in this map must be added to the obsolete_methods_ (and obsolete_dex_caches_) fields of // the redefined classes ClassExt by the caller. std::unordered_map<art::ArtMethod*, art::ArtMethod*>* obsolete_maps_; - // TODO REMOVE once either current_method doesn't stick around through suspend points or deopt - // works through runtime methods. - bool is_runtime_frame_; }; jvmtiError Redefiner::IsModifiableClass(jvmtiEnv* env ATTRIBUTE_UNUSED, @@ -386,85 +369,6 @@ jvmtiError Redefiner::AddRedefinition(ArtJvmTiEnv* env, const ArtClassDefinition return OK; } -// TODO *MAJOR* This should return the actual source java.lang.DexFile object for the klass. -// TODO Make mirror of DexFile and associated types to make this less hellish. -// TODO Make mirror of BaseDexClassLoader and associated types to make this less hellish. -art::mirror::Object* Redefiner::ClassRedefinition::FindSourceDexFileObject( - art::Handle<art::mirror::ClassLoader> loader) { - const char* dex_path_list_element_array_name = "[Ldalvik/system/DexPathList$Element;"; - const char* dex_path_list_element_name = "Ldalvik/system/DexPathList$Element;"; - const char* dex_file_name = "Ldalvik/system/DexFile;"; - const char* dex_path_list_name = "Ldalvik/system/DexPathList;"; - const char* dex_class_loader_name = "Ldalvik/system/BaseDexClassLoader;"; - - CHECK(!driver_->self_->IsExceptionPending()); - art::StackHandleScope<11> hs(driver_->self_); - art::ClassLinker* class_linker = driver_->runtime_->GetClassLinker(); - - art::Handle<art::mirror::ClassLoader> null_loader(hs.NewHandle<art::mirror::ClassLoader>( - nullptr)); - art::Handle<art::mirror::Class> base_dex_loader_class(hs.NewHandle(class_linker->FindClass( - driver_->self_, dex_class_loader_name, null_loader))); - - // Get all the ArtFields so we can look in the BaseDexClassLoader - art::ArtField* path_list_field = base_dex_loader_class->FindDeclaredInstanceField( - "pathList", dex_path_list_name); - CHECK(path_list_field != nullptr); - - art::ArtField* dex_path_list_element_field = - class_linker->FindClass(driver_->self_, dex_path_list_name, null_loader) - ->FindDeclaredInstanceField("dexElements", dex_path_list_element_array_name); - CHECK(dex_path_list_element_field != nullptr); - - art::ArtField* element_dex_file_field = - class_linker->FindClass(driver_->self_, dex_path_list_element_name, null_loader) - ->FindDeclaredInstanceField("dexFile", dex_file_name); - CHECK(element_dex_file_field != nullptr); - - // Check if loader is a BaseDexClassLoader - art::Handle<art::mirror::Class> loader_class(hs.NewHandle(loader->GetClass())); - if (!loader_class->IsSubClass(base_dex_loader_class.Get())) { - LOG(ERROR) << "The classloader is not a BaseDexClassLoader which is currently the only " - << "supported class loader type!"; - return nullptr; - } - // Start navigating the fields of the loader (now known to be a BaseDexClassLoader derivative) - art::Handle<art::mirror::Object> path_list( - hs.NewHandle(path_list_field->GetObject(loader.Get()))); - CHECK(path_list.Get() != nullptr); - CHECK(!driver_->self_->IsExceptionPending()); - art::Handle<art::mirror::ObjectArray<art::mirror::Object>> dex_elements_list(hs.NewHandle( - dex_path_list_element_field->GetObject(path_list.Get())-> - AsObjectArray<art::mirror::Object>())); - CHECK(!driver_->self_->IsExceptionPending()); - CHECK(dex_elements_list.Get() != nullptr); - size_t num_elements = dex_elements_list->GetLength(); - art::MutableHandle<art::mirror::Object> current_element( - hs.NewHandle<art::mirror::Object>(nullptr)); - art::MutableHandle<art::mirror::Object> first_dex_file( - hs.NewHandle<art::mirror::Object>(nullptr)); - // Iterate over the DexPathList$Element to find the right one - // TODO Or not ATM just return the first one. - for (size_t i = 0; i < num_elements; i++) { - current_element.Assign(dex_elements_list->Get(i)); - CHECK(current_element.Get() != nullptr); - CHECK(!driver_->self_->IsExceptionPending()); - CHECK(dex_elements_list.Get() != nullptr); - CHECK_EQ(current_element->GetClass(), class_linker->FindClass(driver_->self_, - dex_path_list_element_name, - null_loader)); - // TODO It would be cleaner to put the art::DexFile into the dalvik.system.DexFile the class - // comes from but it is more annoying because we would need to find this class. It is not - // necessary for proper function since we just need to be in front of the classes old dex file - // in the path. - first_dex_file.Assign(element_dex_file_field->GetObject(current_element.Get())); - if (first_dex_file.Get() != nullptr) { - return first_dex_file.Get(); - } - } - return nullptr; -} - art::mirror::Class* Redefiner::ClassRedefinition::GetMirrorClass() { return driver_->self_->DecodeJObject(klass_)->AsClass(); } @@ -478,39 +382,6 @@ art::mirror::DexCache* Redefiner::ClassRedefinition::CreateNewDexCache( return driver_->runtime_->GetClassLinker()->RegisterDexFile(*dex_file_, loader.Get()); } -// TODO Really wishing I had that mirror of java.lang.DexFile now. -art::mirror::LongArray* Redefiner::ClassRedefinition::AllocateDexFileCookie( - art::Handle<art::mirror::Object> java_dex_file_obj) { - art::StackHandleScope<2> hs(driver_->self_); - // mCookie is nulled out if the DexFile has been closed but mInternalCookie sticks around until - // the object is finalized. Since they always point to the same array if mCookie is not null we - // just use the mInternalCookie field. We will update one or both of these fields later. - // TODO Should I get the class from the classloader or directly? - art::ArtField* internal_cookie_field = java_dex_file_obj->GetClass()->FindDeclaredInstanceField( - "mInternalCookie", "Ljava/lang/Object;"); - // TODO Add check that mCookie is either null or same as mInternalCookie - CHECK(internal_cookie_field != nullptr); - art::Handle<art::mirror::LongArray> cookie( - hs.NewHandle(internal_cookie_field->GetObject(java_dex_file_obj.Get())->AsLongArray())); - // TODO Maybe make these non-fatal. - CHECK(cookie.Get() != nullptr); - CHECK_GE(cookie->GetLength(), 1); - art::Handle<art::mirror::LongArray> new_cookie( - hs.NewHandle(art::mirror::LongArray::Alloc(driver_->self_, cookie->GetLength() + 1))); - if (new_cookie.Get() == nullptr) { - driver_->self_->AssertPendingOOMException(); - return nullptr; - } - // Copy the oat-dex field at the start. - // TODO Should I clear this field? - // TODO This is a really crappy thing here with the first element being different. - new_cookie->SetWithoutChecks<false>(0, cookie->GetWithoutChecks(0)); - new_cookie->SetWithoutChecks<false>( - 1, static_cast<int64_t>(reinterpret_cast<intptr_t>(dex_file_.get()))); - new_cookie->Memcpy(2, cookie.Get(), 1, cookie->GetLength() - 1); - return new_cookie.Get(); -} - void Redefiner::RecordFailure(jvmtiError result, const std::string& class_sig, const std::string& error_msg) { @@ -520,28 +391,13 @@ void Redefiner::RecordFailure(jvmtiError result, result_ = result; } -// Allocates a ByteArray big enough to store the given number of bytes and copies them from the -// bytes pointer. -static art::mirror::ByteArray* AllocateAndFillBytes(art::Thread* self, - const uint8_t* bytes, - int32_t num_bytes) - REQUIRES_SHARED(art::Locks::mutator_lock_) { - art::StackHandleScope<1> hs(self); - art::Handle<art::mirror::ByteArray> arr(hs.NewHandle( - art::mirror::ByteArray::Alloc(self, num_bytes))); - if (!arr.IsNull()) { - // Copy it in. Just skip if it's null - memcpy(arr->GetData(), bytes, num_bytes); - } - return arr.Get(); -} - art::mirror::ByteArray* Redefiner::ClassRedefinition::AllocateOrGetOriginalDexFileBytes() { // If we have been specifically given a new set of bytes use that if (original_dex_file_.size() != 0) { - return AllocateAndFillBytes(driver_->self_, - &original_dex_file_.At(0), - original_dex_file_.size()); + return art::mirror::ByteArray::AllocateAndFill( + driver_->self_, + reinterpret_cast<const signed char*>(&original_dex_file_.At(0)), + original_dex_file_.size()); } // See if we already have one set. @@ -561,7 +417,10 @@ art::mirror::ByteArray* Redefiner::ClassRedefinition::AllocateOrGetOriginalDexFi LOG(WARNING) << "Current dex file has more than one class in it. Calling RetransformClasses " << "on this class might fail if no transformations are applied to it!"; } - return AllocateAndFillBytes(driver_->self_, current_dex_file.Begin(), current_dex_file.Size()); + return art::mirror::ByteArray::AllocateAndFill( + driver_->self_, + reinterpret_cast<const signed char*>(current_dex_file.Begin()), + current_dex_file.Size()); } struct CallbackCtx { @@ -587,7 +446,8 @@ void Redefiner::ClassRedefinition::FindAndAllocateObsoleteMethods(art::mirror::C art::ScopedAssertNoThreadSuspension ns("No thread suspension during thread stack walking"); art::mirror::ClassExt* ext = art_klass->GetExtData(); CHECK(ext->GetObsoleteMethods() != nullptr); - CallbackCtx ctx(art_klass->GetClassLoader()->GetAllocator()); + art::ClassLinker* linker = driver_->runtime_->GetClassLinker(); + CallbackCtx ctx(linker->GetAllocatorForClassLoader(art_klass->GetClassLoader())); // Add all the declared methods to the map for (auto& m : art_klass->GetDeclaredMethods(art::kRuntimePointerSize)) { ctx.obsolete_methods.insert(&m); @@ -631,16 +491,141 @@ void Redefiner::ClassRedefinition::FillObsoleteMethodMap( } } -// TODO It should be possible to only deoptimize the specific obsolete methods. -// TODO ReJitEverything can (sort of) fail. In certain cases it will skip deoptimizing some frames. -// If one of these frames is an obsolete method we have a problem. b/33616143 -// TODO This shouldn't be necessary once we can ensure that the current method is not kept in -// registers across suspend points. -// TODO Pending b/33630159 -void Redefiner::EnsureObsoleteMethodsAreDeoptimized() { - art::ScopedAssertNoThreadSuspension nts("Deoptimizing everything!"); - art::instrumentation::Instrumentation* i = runtime_->GetInstrumentation(); - i->ReJitEverything("libOpenJkdJvmti - Class Redefinition"); +// Try and get the declared method. First try to get a virtual method then a direct method if that's +// not found. +static art::ArtMethod* FindMethod(art::Handle<art::mirror::Class> klass, + const char* name, + art::Signature sig) REQUIRES_SHARED(art::Locks::mutator_lock_) { + art::ArtMethod* m = klass->FindDeclaredVirtualMethod(name, sig, art::kRuntimePointerSize); + if (m == nullptr) { + m = klass->FindDeclaredDirectMethod(name, sig, art::kRuntimePointerSize); + } + return m; +} + +bool Redefiner::ClassRedefinition::CheckSameMethods() { + art::StackHandleScope<1> hs(driver_->self_); + art::Handle<art::mirror::Class> h_klass(hs.NewHandle(GetMirrorClass())); + DCHECK_EQ(dex_file_->NumClassDefs(), 1u); + + art::ClassDataItemIterator new_iter(*dex_file_, + dex_file_->GetClassData(dex_file_->GetClassDef(0))); + + // Make sure we have the same number of methods. + uint32_t num_new_method = new_iter.NumVirtualMethods() + new_iter.NumDirectMethods(); + uint32_t num_old_method = h_klass->GetDeclaredMethodsSlice(art::kRuntimePointerSize).size(); + if (num_new_method != num_old_method) { + bool bigger = num_new_method > num_old_method; + RecordFailure(bigger ? ERR(UNSUPPORTED_REDEFINITION_METHOD_ADDED) + : ERR(UNSUPPORTED_REDEFINITION_METHOD_DELETED), + StringPrintf("Total number of declared methods changed from %d to %d", + num_old_method, num_new_method)); + return false; + } + + // Skip all of the fields. We should have already checked this. + while (new_iter.HasNextStaticField() || new_iter.HasNextInstanceField()) { + new_iter.Next(); + } + // Check each of the methods. NB we don't need to specifically check for removals since the 2 dex + // files have the same number of methods, which means there must be an equal amount of additions + // and removals. + for (; new_iter.HasNextVirtualMethod() || new_iter.HasNextDirectMethod(); new_iter.Next()) { + // Get the data on the method we are searching for + const art::DexFile::MethodId& new_method_id = dex_file_->GetMethodId(new_iter.GetMemberIndex()); + const char* new_method_name = dex_file_->GetMethodName(new_method_id); + art::Signature new_method_signature = dex_file_->GetMethodSignature(new_method_id); + art::ArtMethod* old_method = FindMethod(h_klass, new_method_name, new_method_signature); + // If we got past the check for the same number of methods above that means there must be at + // least one added and one removed method. We will return the ADDED failure message since it is + // easier to get a useful error report for it. + if (old_method == nullptr) { + RecordFailure(ERR(UNSUPPORTED_REDEFINITION_METHOD_ADDED), + StringPrintf("Unknown method '%s' (sig: %s) was added!", + new_method_name, + new_method_signature.ToString().c_str())); + return false; + } + // Since direct methods have different flags than virtual ones (specifically direct methods must + // have kAccPrivate or kAccStatic or kAccConstructor flags) we can tell if a method changes from + // virtual to direct. + uint32_t new_flags = new_iter.GetMethodAccessFlags(); + if (new_flags != (old_method->GetAccessFlags() & art::kAccValidMethodFlags)) { + RecordFailure(ERR(UNSUPPORTED_REDEFINITION_METHOD_MODIFIERS_CHANGED), + StringPrintf("method '%s' (sig: %s) had different access flags", + new_method_name, + new_method_signature.ToString().c_str())); + return false; + } + } + return true; +} + +bool Redefiner::ClassRedefinition::CheckSameFields() { + art::StackHandleScope<1> hs(driver_->self_); + art::Handle<art::mirror::Class> h_klass(hs.NewHandle(GetMirrorClass())); + DCHECK_EQ(dex_file_->NumClassDefs(), 1u); + art::ClassDataItemIterator new_iter(*dex_file_, + dex_file_->GetClassData(dex_file_->GetClassDef(0))); + const art::DexFile& old_dex_file = h_klass->GetDexFile(); + art::ClassDataItemIterator old_iter(old_dex_file, + old_dex_file.GetClassData(*h_klass->GetClassDef())); + // Instance and static fields can be differentiated by their flags so no need to check them + // separately. + while (new_iter.HasNextInstanceField() || new_iter.HasNextStaticField()) { + // Get the data on the method we are searching for + const art::DexFile::FieldId& new_field_id = dex_file_->GetFieldId(new_iter.GetMemberIndex()); + const char* new_field_name = dex_file_->GetFieldName(new_field_id); + const char* new_field_type = dex_file_->GetFieldTypeDescriptor(new_field_id); + + if (!(old_iter.HasNextInstanceField() || old_iter.HasNextStaticField())) { + // We are missing the old version of this method! + RecordFailure(ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED), + StringPrintf("Unknown field '%s' (type: %s) added!", + new_field_name, + new_field_type)); + return false; + } + + const art::DexFile::FieldId& old_field_id = old_dex_file.GetFieldId(old_iter.GetMemberIndex()); + const char* old_field_name = old_dex_file.GetFieldName(old_field_id); + const char* old_field_type = old_dex_file.GetFieldTypeDescriptor(old_field_id); + + // Check name and type. + if (strcmp(old_field_name, new_field_name) != 0 || + strcmp(old_field_type, new_field_type) != 0) { + RecordFailure(ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED), + StringPrintf("Field changed from '%s' (sig: %s) to '%s' (sig: %s)!", + old_field_name, + old_field_type, + new_field_name, + new_field_type)); + return false; + } + + // Since static fields have different flags than instance ones (specifically static fields must + // have the kAccStatic flag) we can tell if a field changes from static to instance. + if (new_iter.GetFieldAccessFlags() != old_iter.GetFieldAccessFlags()) { + RecordFailure(ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED), + StringPrintf("Field '%s' (sig: %s) had different access flags", + new_field_name, + new_field_type)); + return false; + } + + new_iter.Next(); + old_iter.Next(); + } + if (old_iter.HasNextInstanceField() || old_iter.HasNextStaticField()) { + RecordFailure(ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED), + StringPrintf("field '%s' (sig: %s) is missing!", + old_dex_file.GetFieldName(old_dex_file.GetFieldId( + old_iter.GetMemberIndex())), + old_dex_file.GetFieldTypeDescriptor(old_dex_file.GetFieldId( + old_iter.GetMemberIndex())))); + return false; + } + return true; } bool Redefiner::ClassRedefinition::CheckClass() { @@ -854,31 +839,85 @@ class RedefinitionDataHolder { DISALLOW_COPY_AND_ASSIGN(RedefinitionDataHolder); }; +// Looks through the previously allocated cookies to see if we need to update them with another new +// dexfile. This is so that even if multiple classes with the same classloader are redefined at +// once they are all added to the classloader. +bool Redefiner::ClassRedefinition::AllocateAndRememberNewDexFileCookie( + int32_t klass_index, + art::Handle<art::mirror::ClassLoader> source_class_loader, + art::Handle<art::mirror::Object> dex_file_obj, + /*out*/RedefinitionDataHolder* holder) { + art::StackHandleScope<2> hs(driver_->self_); + art::MutableHandle<art::mirror::LongArray> old_cookie( + hs.NewHandle<art::mirror::LongArray>(nullptr)); + bool has_older_cookie = false; + // See if we already have a cookie that a previous redefinition got from the same classloader. + for (int32_t i = 0; i < klass_index; i++) { + if (holder->GetSourceClassLoader(i) == source_class_loader.Get()) { + // Since every instance of this classloader should have the same cookie associated with it we + // can stop looking here. + has_older_cookie = true; + old_cookie.Assign(holder->GetNewDexFileCookie(i)); + break; + } + } + if (old_cookie.IsNull()) { + // No older cookie. Get it directly from the dex_file_obj + // We should not have seen this classloader elsewhere. + CHECK(!has_older_cookie); + old_cookie.Assign(ClassLoaderHelper::GetDexFileCookie(dex_file_obj)); + } + // Use the old cookie to generate the new one with the new DexFile* added in. + art::Handle<art::mirror::LongArray> + new_cookie(hs.NewHandle(ClassLoaderHelper::AllocateNewDexFileCookie(driver_->self_, + old_cookie, + dex_file_.get()))); + // Make sure the allocation worked. + if (new_cookie.IsNull()) { + return false; + } + + // Save the cookie. + holder->SetNewDexFileCookie(klass_index, new_cookie.Get()); + // If there are other copies of this same classloader we need to make sure that we all have the + // same cookie. + if (has_older_cookie) { + for (int32_t i = 0; i < klass_index; i++) { + // We will let the GC take care of the cookie we allocated for this one. + if (holder->GetSourceClassLoader(i) == source_class_loader.Get()) { + holder->SetNewDexFileCookie(i, new_cookie.Get()); + } + } + } + + return true; +} + bool Redefiner::ClassRedefinition::FinishRemainingAllocations( int32_t klass_index, /*out*/RedefinitionDataHolder* holder) { + art::ScopedObjectAccessUnchecked soa(driver_->self_); art::StackHandleScope<2> hs(driver_->self_); holder->SetMirrorClass(klass_index, GetMirrorClass()); // This shouldn't allocate art::Handle<art::mirror::ClassLoader> loader(hs.NewHandle(GetClassLoader())); - holder->SetSourceClassLoader(klass_index, loader.Get()); - if (loader.Get() == nullptr) { - // TODO Better error msg. - RecordFailure(ERR(INTERNAL), "Unable to find class loader!"); - return false; - } - art::Handle<art::mirror::Object> dex_file_obj(hs.NewHandle(FindSourceDexFileObject(loader))); - holder->SetJavaDexFile(klass_index, dex_file_obj.Get()); - if (dex_file_obj.Get() == nullptr) { - // TODO Better error msg. - RecordFailure(ERR(INTERNAL), "Unable to find class loader!"); - return false; - } - holder->SetNewDexFileCookie(klass_index, AllocateDexFileCookie(dex_file_obj)); - if (holder->GetNewDexFileCookie(klass_index) == nullptr) { - driver_->self_->AssertPendingOOMException(); - driver_->self_->ClearException(); - RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate dex file array for class loader"); - return false; + // The bootclasspath is handled specially so it doesn't have a j.l.DexFile. + if (!art::ClassLinker::IsBootClassLoader(soa, loader.Get())) { + holder->SetSourceClassLoader(klass_index, loader.Get()); + art::Handle<art::mirror::Object> dex_file_obj(hs.NewHandle( + ClassLoaderHelper::FindSourceDexFileObject(driver_->self_, loader))); + holder->SetJavaDexFile(klass_index, dex_file_obj.Get()); + if (dex_file_obj.Get() == nullptr) { + // TODO Better error msg. + RecordFailure(ERR(INTERNAL), "Unable to find dex file!"); + return false; + } + // Allocate the new dex file cookie. + if (!AllocateAndRememberNewDexFileCookie(klass_index, loader, dex_file_obj, holder)) { + driver_->self_->AssertPendingOOMException(); + driver_->self_->ClearException(); + RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate dex file array for class loader"); + return false; + } } holder->SetNewDexCache(klass_index, CreateNewDexCache(loader)); if (holder->GetNewDexCache(klass_index) == nullptr) { @@ -965,6 +1004,13 @@ jvmtiError Redefiner::Run() { // cleaned up by the GC eventually. return result_; } + int32_t counter = 0; + for (Redefiner::ClassRedefinition& redef : redefinitions_) { + if (holder.GetSourceClassLoader(counter) == nullptr) { + runtime_->GetClassLinker()->AppendToBootClassPath(self_, redef.GetDexFile()); + } + counter++; + } // Disable GC and wait for it to be done if we are a moving GC. This is fine since we are done // allocating so no deadlocks. art::gc::Heap* heap = runtime_->GetHeap(); @@ -983,24 +1029,20 @@ jvmtiError Redefiner::Run() { // TODO We need to update all debugger MethodIDs so they note the method they point to is // obsolete or implement some other well defined semantics. // TODO We need to decide on & implement semantics for JNI jmethodids when we redefine methods. - int32_t cnt = 0; + counter = 0; for (Redefiner::ClassRedefinition& redef : redefinitions_) { - art::mirror::Class* klass = holder.GetMirrorClass(cnt); - redef.UpdateJavaDexFile(holder.GetJavaDexFile(cnt), holder.GetNewDexFileCookie(cnt)); + art::ScopedAssertNoThreadSuspension nts("Updating runtime objects for redefinition"); + if (holder.GetSourceClassLoader(counter) != nullptr) { + ClassLoaderHelper::UpdateJavaDexFile(holder.GetJavaDexFile(counter), + holder.GetNewDexFileCookie(counter)); + } + art::mirror::Class* klass = holder.GetMirrorClass(counter); // TODO Rewrite so we don't do a stack walk for each and every class. redef.FindAndAllocateObsoleteMethods(klass); - redef.UpdateClass(klass, holder.GetNewDexCache(cnt), holder.GetOriginalDexFileBytes(cnt)); - cnt++; + redef.UpdateClass(klass, holder.GetNewDexCache(counter), + holder.GetOriginalDexFileBytes(counter)); + counter++; } - // Ensure that obsolete methods are deoptimized. This is needed since optimized methods may have - // pointers to their ArtMethod's stashed in registers that they then use to attempt to hit the - // DexCache. (b/33630159) - // TODO This can fail (leave some methods optimized) near runtime methods (including - // quick-to-interpreter transition function). - // TODO We probably don't need this at all once we have a way to ensure that the - // current_art_method is never stashed in a (physical) register by the JIT and lost to the - // stack-walker. - EnsureObsoleteMethodsAreDeoptimized(); // TODO Verify the new Class. // TODO Shrink the obsolete method maps if possible? // TODO find appropriate class loader. @@ -1102,24 +1144,6 @@ void Redefiner::ClassRedefinition::UpdateClass( ext->SetOriginalDexFileBytes(original_dex_file); } -void Redefiner::ClassRedefinition::UpdateJavaDexFile( - art::ObjPtr<art::mirror::Object> java_dex_file, - art::ObjPtr<art::mirror::LongArray> new_cookie) { - art::ArtField* internal_cookie_field = java_dex_file->GetClass()->FindDeclaredInstanceField( - "mInternalCookie", "Ljava/lang/Object;"); - art::ArtField* cookie_field = java_dex_file->GetClass()->FindDeclaredInstanceField( - "mCookie", "Ljava/lang/Object;"); - CHECK(internal_cookie_field != nullptr); - art::ObjPtr<art::mirror::LongArray> orig_internal_cookie( - internal_cookie_field->GetObject(java_dex_file)->AsLongArray()); - art::ObjPtr<art::mirror::LongArray> orig_cookie( - cookie_field->GetObject(java_dex_file)->AsLongArray()); - internal_cookie_field->SetObject<false>(java_dex_file, new_cookie); - if (!orig_cookie.IsNull()) { - cookie_field->SetObject<false>(java_dex_file, new_cookie); - } -} - // This function does all (java) allocations we need to do for the Class being redefined. // TODO Change this name maybe? bool Redefiner::ClassRedefinition::EnsureClassAllocationsFinished() { diff --git a/runtime/openjdkjvmti/ti_redefine.h b/runtime/openjdkjvmti/ti_redefine.h index 29a7e1f3ac..3209abbe64 100644 --- a/runtime/openjdkjvmti/ti_redefine.h +++ b/runtime/openjdkjvmti/ti_redefine.h @@ -96,6 +96,11 @@ class Redefiner { static jvmtiError IsModifiableClass(jvmtiEnv* env, jclass klass, jboolean* is_redefinable); + static std::unique_ptr<art::MemMap> MoveDataToMemMap(const std::string& original_location, + jint data_len, + const unsigned char* dex_data, + std::string* error_msg); + private: class ClassRedefinition { public: @@ -122,18 +127,13 @@ class Redefiner { art::mirror::Class* GetMirrorClass() REQUIRES_SHARED(art::Locks::mutator_lock_); art::mirror::ClassLoader* GetClassLoader() REQUIRES_SHARED(art::Locks::mutator_lock_); - // This finds the java.lang.DexFile we will add the native DexFile to as part of the classpath. - // TODO Make sure the DexFile object returned is the one that the klass_ actually comes from. - art::mirror::Object* FindSourceDexFileObject(art::Handle<art::mirror::ClassLoader> loader) - REQUIRES_SHARED(art::Locks::mutator_lock_); + const art::DexFile& GetDexFile() { + return *dex_file_; + } art::mirror::DexCache* CreateNewDexCache(art::Handle<art::mirror::ClassLoader> loader) REQUIRES_SHARED(art::Locks::mutator_lock_); - // Allocates and fills the new DexFileCookie - art::mirror::LongArray* AllocateDexFileCookie(art::Handle<art::mirror::Object> j_dex_file_obj) - REQUIRES_SHARED(art::Locks::mutator_lock_); - // This may return nullptr with a OOME pending if allocation fails. art::mirror::ByteArray* AllocateOrGetOriginalDexFileBytes() REQUIRES_SHARED(art::Locks::mutator_lock_); @@ -145,6 +145,13 @@ class Redefiner { bool FinishRemainingAllocations(int32_t klass_index, /*out*/RedefinitionDataHolder* holder) REQUIRES_SHARED(art::Locks::mutator_lock_); + bool AllocateAndRememberNewDexFileCookie( + int32_t klass_index, + art::Handle<art::mirror::ClassLoader> source_class_loader, + art::Handle<art::mirror::Object> dex_file_obj, + /*out*/RedefinitionDataHolder* holder) + REQUIRES_SHARED(art::Locks::mutator_lock_); + void FindAndAllocateObsoleteMethods(art::mirror::Class* art_klass) REQUIRES(art::Locks::mutator_lock_); @@ -170,17 +177,11 @@ class Redefiner { // Checks that the class can even be redefined. bool CheckRedefinable() REQUIRES_SHARED(art::Locks::mutator_lock_); - // Checks that the dex file does not add/remove methods. - bool CheckSameMethods() REQUIRES_SHARED(art::Locks::mutator_lock_) { - LOG(WARNING) << "methods are not checked for modification currently"; - return true; - } + // Checks that the dex file does not add/remove methods, or change their modifiers or types. + bool CheckSameMethods() REQUIRES_SHARED(art::Locks::mutator_lock_); - // Checks that the dex file does not modify fields - bool CheckSameFields() REQUIRES_SHARED(art::Locks::mutator_lock_) { - LOG(WARNING) << "Fields are not checked for modification currently"; - return true; - } + // Checks that the dex file does not modify fields types or modifiers. + bool CheckSameFields() REQUIRES_SHARED(art::Locks::mutator_lock_); void UpdateJavaDexFile(art::ObjPtr<art::mirror::Object> java_dex_file, art::ObjPtr<art::mirror::LongArray> new_cookie) @@ -234,11 +235,6 @@ class Redefiner { /*out*/std::string* error_msg) REQUIRES_SHARED(art::Locks::mutator_lock_); - static std::unique_ptr<art::MemMap> MoveDataToMemMap(const std::string& original_location, - jint data_len, - const unsigned char* dex_data, - std::string* error_msg); - // TODO Put on all the lock qualifiers. jvmtiError Run() REQUIRES_SHARED(art::Locks::mutator_lock_); @@ -248,14 +244,6 @@ class Redefiner { REQUIRES_SHARED(art::Locks::mutator_lock_); void ReleaseAllDexFiles() REQUIRES_SHARED(art::Locks::mutator_lock_); - // Ensure that obsolete methods are deoptimized. This is needed since optimized methods may have - // pointers to their ArtMethods stashed in registers that they then use to attempt to hit the - // DexCache. - void EnsureObsoleteMethodsAreDeoptimized() - REQUIRES(art::Locks::mutator_lock_) - REQUIRES(!art::Locks::thread_list_lock_, - !art::Locks::classlinker_classes_lock_); - void RecordFailure(jvmtiError result, const std::string& class_sig, const std::string& error_msg); void RecordFailure(jvmtiError result, const std::string& error_msg) { RecordFailure(result, "NO CLASS", error_msg); diff --git a/runtime/openjdkjvmti/ti_search.cc b/runtime/openjdkjvmti/ti_search.cc index 913d2b6a74..df80f85ed8 100644 --- a/runtime/openjdkjvmti/ti_search.cc +++ b/runtime/openjdkjvmti/ti_search.cc @@ -34,15 +34,177 @@ #include "jni.h" #include "art_jvmti.h" +#include "base/enums.h" #include "base/macros.h" #include "class_linker.h" #include "dex_file.h" +#include "jni_internal.h" +#include "mirror/class-inl.h" +#include "mirror/object.h" +#include "mirror/string.h" +#include "obj_ptr-inl.h" #include "runtime.h" +#include "runtime_callbacks.h" #include "scoped_thread_state_change-inl.h" #include "ScopedLocalRef.h" +#include "ti_phase.h" +#include "thread-inl.h" +#include "thread_list.h" namespace openjdkjvmti { +static std::vector<std::string> gSystemOnloadSegments; + +static art::ObjPtr<art::mirror::Object> GetSystemProperties(art::Thread* self, + art::ClassLinker* class_linker) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + art::ObjPtr<art::mirror::Class> system_class = + class_linker->LookupClass(self, "Ljava/lang/System;", nullptr); + DCHECK(system_class != nullptr); + DCHECK(system_class->IsInitialized()); + + art::ArtField* props_field = + system_class->FindDeclaredStaticField("props", "Ljava/util/Properties;"); + DCHECK(props_field != nullptr); + + art::ObjPtr<art::mirror::Object> props_obj = props_field->GetObject(system_class); + DCHECK(props_obj != nullptr); + + return props_obj; +} + +static void Update() REQUIRES_SHARED(art::Locks::mutator_lock_) { + if (gSystemOnloadSegments.empty()) { + return; + } + + // In the on-load phase we have to modify java.class.path to influence the system classloader. + // As this is an unmodifiable system property, we have to access the "defaults" field. + art::ClassLinker* class_linker = art::Runtime::Current()->GetClassLinker(); + DCHECK(class_linker != nullptr); + art::Thread* self = art::Thread::Current(); + + // Prepare: collect classes, fields and methods. + art::ObjPtr<art::mirror::Class> properties_class = + class_linker->LookupClass(self, "Ljava/util/Properties;", nullptr); + DCHECK(properties_class != nullptr); + + ScopedLocalRef<jobject> defaults_jobj(self->GetJniEnv(), nullptr); + { + art::ObjPtr<art::mirror::Object> props_obj = GetSystemProperties(self, class_linker); + + art::ArtField* defaults_field = + properties_class->FindDeclaredInstanceField("defaults", "Ljava/util/Properties;"); + DCHECK(defaults_field != nullptr); + + art::ObjPtr<art::mirror::Object> defaults_obj = defaults_field->GetObject(props_obj); + DCHECK(defaults_obj != nullptr); + defaults_jobj.reset(self->GetJniEnv()->AddLocalReference<jobject>(defaults_obj)); + } + + art::ArtMethod* get_property = + properties_class->FindDeclaredVirtualMethod( + "getProperty", + "(Ljava/lang/String;)Ljava/lang/String;", + art::kRuntimePointerSize); + DCHECK(get_property != nullptr); + art::ArtMethod* set_property = + properties_class->FindDeclaredVirtualMethod( + "setProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;", + art::kRuntimePointerSize); + DCHECK(set_property != nullptr); + + // This is an allocation. Do this late to avoid the need for handles. + ScopedLocalRef<jobject> cp_jobj(self->GetJniEnv(), nullptr); + { + art::ObjPtr<art::mirror::Object> cp_key = + art::mirror::String::AllocFromModifiedUtf8(self, "java.class.path"); + if (cp_key == nullptr) { + self->AssertPendingOOMException(); + self->ClearException(); + return; + } + cp_jobj.reset(self->GetJniEnv()->AddLocalReference<jobject>(cp_key)); + } + + // OK, now get the current value. + std::string str_value; + { + ScopedLocalRef<jobject> old_value(self->GetJniEnv(), + self->GetJniEnv()->CallObjectMethod( + defaults_jobj.get(), + art::jni::EncodeArtMethod(get_property), + cp_jobj.get())); + DCHECK(old_value.get() != nullptr); + + str_value = self->DecodeJObject(old_value.get())->AsString()->ToModifiedUtf8(); + self->GetJniEnv()->DeleteLocalRef(old_value.release()); + } + + // Update the value by appending the new segments. + for (const std::string& segment : gSystemOnloadSegments) { + if (!str_value.empty()) { + str_value += ":"; + } + str_value += segment; + } + gSystemOnloadSegments.clear(); + + // Create the new value object. + ScopedLocalRef<jobject> new_val_jobj(self->GetJniEnv(), nullptr); + { + art::ObjPtr<art::mirror::Object> new_value = + art::mirror::String::AllocFromModifiedUtf8(self, str_value.c_str()); + if (new_value == nullptr) { + self->AssertPendingOOMException(); + self->ClearException(); + return; + } + + new_val_jobj.reset(self->GetJniEnv()->AddLocalReference<jobject>(new_value)); + } + + // Write to the defaults. + ScopedLocalRef<jobject> res_obj(self->GetJniEnv(), + self->GetJniEnv()->CallObjectMethod(defaults_jobj.get(), + art::jni::EncodeArtMethod(set_property), + cp_jobj.get(), + new_val_jobj.get())); + if (self->IsExceptionPending()) { + self->ClearException(); + return; + } +} + +struct SearchCallback : public art::RuntimePhaseCallback { + void NextRuntimePhase(RuntimePhase phase) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) { + if (phase == RuntimePhase::kStart) { + // It's time to update the system properties. + Update(); + } + } +}; + +static SearchCallback gSearchCallback; + +void SearchUtil::Register() { + art::Runtime* runtime = art::Runtime::Current(); + + art::ScopedThreadStateChange stsc(art::Thread::Current(), + art::ThreadState::kWaitingForDebuggerToAttach); + art::ScopedSuspendAll ssa("Add search callback"); + runtime->GetRuntimeCallbacks()->AddRuntimePhaseCallback(&gSearchCallback); +} + +void SearchUtil::Unregister() { + art::ScopedThreadStateChange stsc(art::Thread::Current(), + art::ThreadState::kWaitingForDebuggerToAttach); + art::ScopedSuspendAll ssa("Remove search callback"); + art::Runtime* runtime = art::Runtime::Current(); + runtime->GetRuntimeCallbacks()->RemoveRuntimePhaseCallback(&gSearchCallback); +} + jvmtiError SearchUtil::AddToBootstrapClassLoaderSearch(jvmtiEnv* env ATTRIBUTE_UNUSED, const char* segment) { art::Runtime* current = art::Runtime::Current(); @@ -78,14 +240,21 @@ jvmtiError SearchUtil::AddToSystemClassLoaderSearch(jvmtiEnv* jvmti_env ATTRIBUT return ERR(NULL_POINTER); } - art::Runtime* current = art::Runtime::Current(); - if (current == nullptr) { + jvmtiPhase phase = PhaseUtil::GetPhaseUnchecked(); + + if (phase == jvmtiPhase::JVMTI_PHASE_ONLOAD) { + // We could try and see whether it is a valid path. We could also try to allocate Java + // objects to avoid later OOME. + gSystemOnloadSegments.push_back(segment); + return ERR(NONE); + } else if (phase != jvmtiPhase::JVMTI_PHASE_LIVE) { return ERR(WRONG_PHASE); } - jobject sys_class_loader = current->GetSystemClassLoader(); + + jobject sys_class_loader = art::Runtime::Current()->GetSystemClassLoader(); if (sys_class_loader == nullptr) { - // TODO: Support classpath change in OnLoad. - return ERR(WRONG_PHASE); + // This is unexpected. + return ERR(INTERNAL); } // We'll use BaseDexClassLoader.addDexPath, as it takes care of array resizing etc. As a downside, diff --git a/runtime/openjdkjvmti/ti_search.h b/runtime/openjdkjvmti/ti_search.h index 6a52e80405..cd7b4be28c 100644 --- a/runtime/openjdkjvmti/ti_search.h +++ b/runtime/openjdkjvmti/ti_search.h @@ -32,12 +32,17 @@ #ifndef ART_RUNTIME_OPENJDKJVMTI_TI_SEARCH_H_ #define ART_RUNTIME_OPENJDKJVMTI_TI_SEARCH_H_ +#include <vector> + #include "jvmti.h" namespace openjdkjvmti { class SearchUtil { public: + static void Register(); + static void Unregister(); + static jvmtiError AddToBootstrapClassLoaderSearch(jvmtiEnv* env, const char* segment); static jvmtiError AddToSystemClassLoaderSearch(jvmtiEnv* env, const char* segment); diff --git a/runtime/openjdkjvmti/ti_stack.cc b/runtime/openjdkjvmti/ti_stack.cc index 4cf55a6a98..b5a6c6e1ee 100644 --- a/runtime/openjdkjvmti/ti_stack.cc +++ b/runtime/openjdkjvmti/ti_stack.cc @@ -377,7 +377,8 @@ jvmtiError StackUtil::GetAllStackTraces(jvmtiEnv* env, jvmtiStackInfo& old_stack_info = stack_info_array.get()[i]; jvmtiStackInfo& new_stack_info = stack_info[i]; - jthread thread_peer = current->GetJniEnv()->AddLocalReference<jthread>(threads[i]->GetPeer()); + jthread thread_peer = current->GetJniEnv()->AddLocalReference<jthread>( + threads[i]->GetPeerFromOtherThread()); new_stack_info.thread = thread_peer; if (old_stack_info.frame_count > 0) { @@ -453,7 +454,7 @@ jvmtiError StackUtil::GetThreadListStackTraces(jvmtiEnv* env, } // Get the peer, and check whether we know it. - art::ObjPtr<art::mirror::Object> peer = thread->GetPeer(); + art::ObjPtr<art::mirror::Object> peer = thread->GetPeerFromOtherThread(); for (size_t index = 0; index != handles.size(); ++index) { if (peer == handles[index].Get()) { // Found the thread. diff --git a/runtime/openjdkjvmti/ti_thread.cc b/runtime/openjdkjvmti/ti_thread.cc index 9f81d6ba97..00d4144415 100644 --- a/runtime/openjdkjvmti/ti_thread.cc +++ b/runtime/openjdkjvmti/ti_thread.cc @@ -61,11 +61,14 @@ struct ThreadCallback : public art::ThreadLifecycleCallback, public art::Runtime } return self->GetJniEnv()->AddLocalReference<jthread>(self->GetPeer()); } - void Post(art::Thread* self, ArtJvmtiEvent type) REQUIRES_SHARED(art::Locks::mutator_lock_) { + template <ArtJvmtiEvent kEvent> + void Post(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_) { DCHECK_EQ(self, art::Thread::Current()); ScopedLocalRef<jthread> thread(self->GetJniEnv(), GetThreadObject(self)); art::ScopedThreadSuspension sts(self, art::ThreadState::kNative); - event_handler->DispatchEvent(self, type, self->GetJniEnv(), thread.get()); + event_handler->DispatchEvent<kEvent>(self, + reinterpret_cast<JNIEnv*>(self->GetJniEnv()), + thread.get()); } void ThreadStart(art::Thread* self) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) { @@ -81,11 +84,11 @@ struct ThreadCallback : public art::ThreadLifecycleCallback, public art::Runtime } return; } - Post(self, ArtJvmtiEvent::kThreadStart); + Post<ArtJvmtiEvent::kThreadStart>(self); } void ThreadDeath(art::Thread* self) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) { - Post(self, ArtJvmtiEvent::kThreadEnd); + Post<ArtJvmtiEvent::kThreadEnd>(self); } void NextRuntimePhase(RuntimePhase phase) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) { @@ -93,7 +96,7 @@ struct ThreadCallback : public art::ThreadLifecycleCallback, public art::Runtime // We moved to VMInit. Report the main thread as started (it was attached early, and must // not be reported until Init. started = true; - Post(art::Thread::Current(), ArtJvmtiEvent::kThreadStart); + Post<ArtJvmtiEvent::kThreadStart>(art::Thread::Current()); } } @@ -197,7 +200,7 @@ jvmtiError ThreadUtil::GetThreadInfo(jvmtiEnv* env, jthread thread, jvmtiThreadI info_ptr->is_daemon = self->IsDaemon(); - art::ObjPtr<art::mirror::Object> peer = self->GetPeer(); + art::ObjPtr<art::mirror::Object> peer = self->GetPeerFromOtherThread(); // ThreadGroup. if (peer != nullptr) { @@ -455,7 +458,7 @@ jvmtiError ThreadUtil::GetAllThreads(jvmtiEnv* env, continue; } - art::ObjPtr<art::mirror::Object> peer = thread->GetPeer(); + art::ObjPtr<art::mirror::Object> peer = thread->GetPeerFromOtherThread(); if (peer != nullptr) { peers.push_back(peer); } diff --git a/runtime/openjdkjvmti/ti_threadgroup.cc b/runtime/openjdkjvmti/ti_threadgroup.cc index 35b1bfd920..e63ce6576a 100644 --- a/runtime/openjdkjvmti/ti_threadgroup.cc +++ b/runtime/openjdkjvmti/ti_threadgroup.cc @@ -174,7 +174,7 @@ static void GetThreads(art::Handle<art::mirror::Object> thread_group, if (t->IsStillStarting()) { continue; } - art::ObjPtr<art::mirror::Object> peer = t->GetPeer(); + art::ObjPtr<art::mirror::Object> peer = t->GetPeerFromOtherThread(); if (peer == nullptr) { continue; } diff --git a/runtime/openjdkjvmti/transform.cc b/runtime/openjdkjvmti/transform.cc index af4fb7187a..2fec631c00 100644 --- a/runtime/openjdkjvmti/transform.cc +++ b/runtime/openjdkjvmti/transform.cc @@ -68,19 +68,17 @@ jvmtiError Transformer::RetransformClassesDirect( for (ArtClassDefinition& def : *definitions) { jint new_len = -1; unsigned char* new_data = nullptr; - // Static casts are so that we get the right template initialization for the special event - // handling code required by the ClassFileLoadHooks. - gEventHandler.DispatchEvent(self, - ArtJvmtiEvent::kClassFileLoadHookRetransformable, - GetJniEnv(env), - static_cast<jclass>(def.klass), - static_cast<jobject>(def.loader), - static_cast<const char*>(def.name.c_str()), - static_cast<jobject>(def.protection_domain), - static_cast<jint>(def.dex_len), - static_cast<const unsigned char*>(def.dex_data.get()), - static_cast<jint*>(&new_len), - static_cast<unsigned char**>(&new_data)); + gEventHandler.DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookRetransformable>( + self, + GetJniEnv(env), + def.klass, + def.loader, + def.name.c_str(), + def.protection_domain, + def.dex_len, + static_cast<const unsigned char*>(def.dex_data.get()), + &new_len, + &new_data); def.SetNewDexData(env, new_len, new_data); } return OK; @@ -139,20 +137,6 @@ jvmtiError GetClassLocation(ArtJvmTiEnv* env, jclass klass, /*out*/std::string* return OK; } -static jvmtiError CopyDataIntoJvmtiBuffer(ArtJvmTiEnv* env, - const unsigned char* source, - jint len, - /*out*/unsigned char** dest) { - jvmtiError res = env->Allocate(len, dest); - if (res != OK) { - return res; - } - memcpy(reinterpret_cast<void*>(*dest), - reinterpret_cast<const void*>(source), - len); - return OK; -} - jvmtiError Transformer::GetDexDataForRetransformation(ArtJvmTiEnv* env, art::Handle<art::mirror::Class> klass, /*out*/jint* dex_data_len, @@ -195,7 +179,9 @@ jvmtiError Transformer::FillInTransformationData(ArtJvmTiEnv* env, } def->klass = klass; def->loader = soa.AddLocalReference<jobject>(hs_klass->GetClassLoader()); - def->name = art::mirror::Class::ComputeName(hs_klass)->ToModifiedUtf8(); + std::string descriptor_store; + std::string descriptor(hs_klass->GetDescriptor(&descriptor_store)); + def->name = descriptor.substr(1, descriptor.size() - 2); // TODO is this always null? def->protection_domain = nullptr; if (def->dex_data.get() == nullptr) { diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc index d1ad77c910..9113f83cd4 100644 --- a/runtime/parsed_options.cc +++ b/runtime/parsed_options.cc @@ -300,8 +300,6 @@ std::unique_ptr<RuntimeParser> ParsedOptions::MakeParser(bool ignore_unrecognize .Define("-Xplugin:_") .WithType<std::vector<Plugin>>().AppendValues() .IntoKey(M::Plugins) - .Define("-Xfully-deoptable") - .IntoKey(M::FullyDeoptable) .Define("-XX:ThreadSuspendTimeout=_") // in ms .WithType<MillisecondsToNanoseconds>() // store as ns .IntoKey(M::ThreadSuspendTimeout) @@ -599,42 +597,6 @@ bool ParsedOptions::DoParse(const RuntimeOptions& options, args.Set(M::HeapGrowthLimit, args.GetOrDefault(M::MemoryMaximumSize)); } - if (args.GetOrDefault(M::Experimental) & ExperimentalFlags::kRuntimePlugins) { - LOG(WARNING) << "Experimental runtime plugin support has been enabled. No guarantees are made " - << "about stability or usage of this plugin support. Use at your own risk. Do " - << "not attempt to write shipping code that relies on the implementation of " - << "runtime plugins."; - } else if (!args.GetOrDefault(M::Plugins).empty()) { - LOG(WARNING) << "Experimental runtime plugin support has not been enabled. Ignored options: "; - for (const auto& op : args.GetOrDefault(M::Plugins)) { - LOG(WARNING) << " -plugin:" << op.GetLibrary(); - } - } - - if (args.GetOrDefault(M::Experimental) & ExperimentalFlags::kAgents) { - LOG(WARNING) << "Experimental runtime agent support has been enabled. No guarantees are made " - << "the completeness, accuracy, reliability, or stability of the agent " - << "implementation. Use at your own risk. Do not attempt to write shipping code " - << "that relies on the implementation of any part of this api."; - } else if (!args.GetOrDefault(M::AgentLib).empty() || !args.GetOrDefault(M::AgentPath).empty()) { - LOG(WARNING) << "agent support has not been enabled. Enable experimental agent " - << " support with '-XExperimental:agent'. Ignored options are:"; - for (const auto& op : args.GetOrDefault(M::AgentLib)) { - if (op.HasArgs()) { - LOG(WARNING) << " -agentlib:" << op.GetName() << "=" << op.GetArgs(); - } else { - LOG(WARNING) << " -agentlib:" << op.GetName(); - } - } - for (const auto& op : args.GetOrDefault(M::AgentPath)) { - if (op.HasArgs()) { - LOG(WARNING) << " -agentpath:" << op.GetName() << "=" << op.GetArgs(); - } else { - LOG(WARNING) << " -agentpath:" << op.GetName(); - } - } - } - *runtime_options = std::move(args); return true; } diff --git a/runtime/quick_exception_handler.cc b/runtime/quick_exception_handler.cc index b809c3eb56..bf995095de 100644 --- a/runtime/quick_exception_handler.cc +++ b/runtime/quick_exception_handler.cc @@ -347,9 +347,11 @@ class DeoptimizeStackVisitor FINAL : public StackVisitor { callee_method_ = method; return true; } else if (!single_frame_deopt_ && - !Runtime::Current()->IsDeoptimizeable(GetCurrentQuickFramePc())) { + !Runtime::Current()->IsAsyncDeoptimizeable(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. + LOG(WARNING) << "Got request to deoptimize un-deoptimizable method " + << method->PrettyMethod(); FinishStackWalk(); return false; // End stack walk. } else { @@ -405,7 +407,8 @@ class DeoptimizeStackVisitor FINAL : public StackVisitor { CodeInfoEncoding encoding = code_info.ExtractEncoding(); StackMap stack_map = code_info.GetStackMapForNativePcOffset(native_pc_offset, encoding); const size_t number_of_vregs = m->GetCodeItem()->registers_size_; - uint32_t register_mask = stack_map.GetRegisterMask(encoding.stack_map_encoding); + uint32_t register_mask = code_info.GetRegisterMaskOf(encoding, stack_map); + BitMemoryRegion stack_mask = code_info.GetStackMaskOf(encoding, stack_map); DexRegisterMap vreg_map = IsInInlinedFrame() ? code_info.GetDexRegisterMapAtDepth(GetCurrentInliningDepth() - 1, code_info.GetInlineInfoOf(stack_map, encoding), @@ -438,8 +441,7 @@ class DeoptimizeStackVisitor FINAL : public StackVisitor { const uint8_t* addr = reinterpret_cast<const uint8_t*>(GetCurrentQuickFrame()) + offset; value = *reinterpret_cast<const uint32_t*>(addr); uint32_t bit = (offset >> 2); - if (stack_map.GetNumberOfStackMaskBits(encoding.stack_map_encoding) > bit && - stack_map.GetStackMaskBit(encoding.stack_map_encoding, bit)) { + if (bit < encoding.stack_mask_size_in_bits && stack_mask.LoadBit(bit)) { is_reference = true; } break; diff --git a/runtime/runtime.cc b/runtime/runtime.cc index 06cd7ff4b5..9609bee022 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -114,6 +114,7 @@ #include "native/java_lang_Thread.h" #include "native/java_lang_Throwable.h" #include "native/java_lang_VMClassLoader.h" +#include "native/java_lang_invoke_MethodHandleImpl.h" #include "native/java_lang_ref_FinalizerReference.h" #include "native/java_lang_ref_Reference.h" #include "native/java_lang_reflect_Array.h" @@ -244,7 +245,7 @@ Runtime::Runtime() force_native_bridge_(false), is_native_bridge_loaded_(false), is_native_debuggable_(false), - is_fully_deoptable_(false), + is_java_debuggable_(false), zygote_max_failed_boots_(0), experimental_flags_(ExperimentalFlags::kNone), oat_file_manager_(nullptr), @@ -671,24 +672,6 @@ bool Runtime::Start() { started_ = true; - // 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_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<2> hs(soa.Self()); @@ -713,6 +696,27 @@ bool Runtime::Start() { Thread::FinishStartup(); + // Create the JIT either if we have to use JIT compilation or save profiling info. This is + // done after FinishStartup as the JIT pool needs Java thread peers, which require the main + // ThreadGroup to exist. + // + // 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_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; + } + } + } + // Send the start phase event. We have to wait till here as this is when the main thread peer // has just been generated, important root clinits have been run and JNI is completely functional. { @@ -825,14 +829,6 @@ bool Runtime::IsShuttingDown(Thread* self) { return IsShuttingDownLocked(); } -bool Runtime::IsDebuggable() const { - if (IsFullyDeoptable()) { - return true; - } - const OatFile* oat_file = GetOatFileManager().GetPrimaryOatFile(); - return oat_file != nullptr && oat_file->IsDebuggable(); -} - void Runtime::StartDaemonThreads() { ScopedTrace trace(__FUNCTION__); VLOG(startup) << "Runtime::StartDaemonThreads entering"; @@ -1038,6 +1034,12 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { compiler_executable_ = runtime_options.ReleaseOrDefault(Opt::Compiler); compiler_options_ = runtime_options.ReleaseOrDefault(Opt::CompilerOptions); + for (StringPiece option : Runtime::Current()->GetCompilerOptions()) { + if (option.starts_with("--debuggable")) { + SetJavaDebuggable(true); + break; + } + } image_compiler_options_ = runtime_options.ReleaseOrDefault(Opt::ImageCompilerOptions); image_location_ = runtime_options.GetOrDefault(Opt::Image); @@ -1052,8 +1054,6 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { verify_ = runtime_options.GetOrDefault(Opt::Verify); allow_dex_file_fallback_ = !runtime_options.Exists(Opt::NoDexFileFallback); - is_fully_deoptable_ = runtime_options.Exists(Opt::FullyDeoptable); - no_sig_chain_ = runtime_options.Exists(Opt::NoSigChain); force_native_bridge_ = runtime_options.Exists(Opt::ForceNativeBridge); @@ -1069,16 +1069,13 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { experimental_flags_ = runtime_options.GetOrDefault(Opt::Experimental); is_low_memory_mode_ = runtime_options.Exists(Opt::LowMemoryMode); - if (experimental_flags_ & ExperimentalFlags::kRuntimePlugins) { - plugins_ = runtime_options.ReleaseOrDefault(Opt::Plugins); - } - if (experimental_flags_ & ExperimentalFlags::kAgents) { - agents_ = runtime_options.ReleaseOrDefault(Opt::AgentPath); - // TODO Add back in -agentlib - // for (auto lib : runtime_options.ReleaseOrDefault(Opt::AgentLib)) { - // agents_.push_back(lib); - // } - } + plugins_ = runtime_options.ReleaseOrDefault(Opt::Plugins); + agents_ = runtime_options.ReleaseOrDefault(Opt::AgentPath); + // TODO Add back in -agentlib + // for (auto lib : runtime_options.ReleaseOrDefault(Opt::AgentLib)) { + // agents_.push_back(lib); + // } + XGcOption xgc_option = runtime_options.GetOrDefault(Opt::GcOption); heap_ = new gc::Heap(runtime_options.GetOrDefault(Opt::MemoryInitialSize), runtime_options.GetOrDefault(Opt::HeapGrowthLimit), @@ -1261,6 +1258,11 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { ScopedTrace trace2("AddImageStringsToTable"); GetInternTable()->AddImagesStringsToTable(heap_->GetBootImageSpaces()); } + if (IsJavaDebuggable()) { + // Now that we have loaded the boot image, deoptimize its methods if we are running + // debuggable, as the code may have been compiled non-debuggable. + DeoptimizeBootImage(); + } } else { std::vector<std::string> dex_filenames; Split(boot_class_path_string_, ':', &dex_filenames); @@ -1407,7 +1409,7 @@ static bool EnsureJvmtiPlugin(Runtime* runtime, } // Is the process debuggable? Otherwise, do not attempt to load the plugin. - if (!runtime->IsDebuggable()) { + if (!runtime->IsJavaDebuggable()) { *error_msg = "Process is not debuggable."; return false; } @@ -1539,6 +1541,7 @@ void Runtime::RegisterRuntimeNativeMethods(JNIEnv* env) { register_java_lang_Class(env); register_java_lang_DexCache(env); register_java_lang_Object(env); + register_java_lang_invoke_MethodHandleImpl(env); register_java_lang_ref_FinalizerReference(env); register_java_lang_reflect_Array(env); register_java_lang_reflect_Constructor(env); @@ -2207,9 +2210,15 @@ bool Runtime::IsVerificationSoftFail() const { return verify_ == verifier::VerifyMode::kSoftFail; } -bool Runtime::IsDeoptimizeable(uintptr_t code) const - REQUIRES_SHARED(Locks::mutator_lock_) { - return !heap_->IsInBootImageOatFile(reinterpret_cast<void *>(code)); +bool Runtime::IsAsyncDeoptimizeable(uintptr_t code) const { + // We only support async deopt (ie the compiled code is not explicitly asking for + // deopt, but something else like the debugger) in debuggable JIT code. + // We could look at the oat file where `code` is being defined, + // and check whether it's been compiled debuggable, but we decided to + // only rely on the JIT for debuggable apps. + return IsJavaDebuggable() && + GetJit() != nullptr && + GetJit()->GetCodeCache()->ContainsPc(reinterpret_cast<const void*>(code)); } LinearAlloc* Runtime::CreateLinearAlloc() { @@ -2293,4 +2302,43 @@ RuntimeCallbacks* Runtime::GetRuntimeCallbacks() { return callbacks_.get(); } +// Used to patch boot image method entry point to interpreter bridge. +class UpdateEntryPointsClassVisitor : public ClassVisitor { + public: + explicit UpdateEntryPointsClassVisitor(instrumentation::Instrumentation* instrumentation) + : instrumentation_(instrumentation) {} + + bool operator()(ObjPtr<mirror::Class> klass) OVERRIDE REQUIRES(Locks::mutator_lock_) { + auto pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize(); + for (auto& m : klass->GetMethods(pointer_size)) { + const void* code = m.GetEntryPointFromQuickCompiledCode(); + if (Runtime::Current()->GetHeap()->IsInBootImageOatFile(code) && + !m.IsNative() && + !m.IsProxyMethod()) { + instrumentation_->UpdateMethodsCodeForJavaDebuggable(&m, GetQuickToInterpreterBridge()); + } + } + return true; + } + + private: + instrumentation::Instrumentation* const instrumentation_; +}; + +void Runtime::SetJavaDebuggable(bool value) { + is_java_debuggable_ = value; + // Do not call DeoptimizeBootImage just yet, the runtime may still be starting up. +} + +void Runtime::DeoptimizeBootImage() { + // If we've already started and we are setting this runtime to debuggable, + // we patch entry points of methods in boot image to interpreter bridge, as + // boot image code may be AOT compiled as not debuggable. + if (!GetInstrumentation()->IsForcedInterpretOnly()) { + ScopedObjectAccess soa(Thread::Current()); + UpdateEntryPointsClassVisitor visitor(GetInstrumentation()); + GetClassLinker()->VisitClasses(&visitor); + } +} + } // namespace art diff --git a/runtime/runtime.h b/runtime/runtime.h index f7d6810ff5..30b1756d5d 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -434,7 +434,7 @@ class Runtime { kInitialize }; - jit::Jit* GetJit() { + jit::Jit* GetJit() const { return jit_.get(); } @@ -569,15 +569,14 @@ class Runtime { return jit_options_.get(); } - bool IsDebuggable() const; - - bool IsFullyDeoptable() const { - return is_fully_deoptable_; + bool IsJavaDebuggable() const { + return is_java_debuggable_; } - void SetFullyDeoptable(bool value) { - is_fully_deoptable_ = value; - } + void SetJavaDebuggable(bool value); + + // Deoptimize the boot image, called for Java debuggable apps. + void DeoptimizeBootImage(); bool IsNativeDebuggable() const { return is_native_debuggable_; @@ -639,9 +638,9 @@ class Runtime { return zygote_no_threads_; } - // Returns if the code can be deoptimized. Code may be compiled with some + // Returns if the code can be deoptimized asynchronously. Code may be compiled with some // optimization that makes it impossible to deoptimize. - bool IsDeoptimizeable(uintptr_t code) const REQUIRES_SHARED(Locks::mutator_lock_); + bool IsAsyncDeoptimizeable(uintptr_t code) const REQUIRES_SHARED(Locks::mutator_lock_); // Returns a saved copy of the environment (getenv/setenv values). // Used by Fork to protect against overwriting LD_LIBRARY_PATH, etc. @@ -863,8 +862,8 @@ class Runtime { // Whether we are running under native debugger. bool is_native_debuggable_; - // Whether we are expected to be deoptable at all points. - bool is_fully_deoptable_; + // Whether Java code needs to be debuggable. + bool is_java_debuggable_; // The maximum number of failed boots we allow before pruning the dalvik cache // and trying again. This option is only inspected when we're running as a diff --git a/runtime/runtime_android.cc b/runtime/runtime_android.cc index 0a996a9e55..495296cf7d 100644 --- a/runtime/runtime_android.cc +++ b/runtime/runtime_android.cc @@ -14,56 +14,33 @@ * limitations under the License. */ +#include "runtime.h" + #include <signal.h> -#include <string.h> -#include <sys/utsname.h> -#include <inttypes.h> -#include "base/logging.h" -#include "base/mutex.h" -#include "thread-inl.h" -#include "utils.h" +#include <cstring> -namespace art { +#include "runtime_common.h" -static constexpr bool kUseSignalHandler = false; +namespace art { struct sigaction old_action; -void HandleUnexpectedSignal(int signal_number, siginfo_t* info, void* raw_context) { - static bool handling_unexpected_signal = false; - if (handling_unexpected_signal) { - LogHelper::LogLineLowStack(__FILE__, - __LINE__, - ::android::base::FATAL_WITHOUT_ABORT, - "HandleUnexpectedSignal reentered\n"); - _exit(1); - } - handling_unexpected_signal = true; - gAborting++; // set before taking any locks - MutexLock mu(Thread::Current(), *Locks::unexpected_signal_lock_); - Runtime* runtime = Runtime::Current(); - if (runtime != nullptr) { - // Print this out first in case DumpObject faults. - LOG(FATAL_WITHOUT_ABORT) << "Fault message: " << runtime->GetFaultMessage(); - } +void HandleUnexpectedSignalAndroid(int signal_number, siginfo_t* info, void* raw_context) { + HandleUnexpectedSignalCommon(signal_number, info, raw_context, /* running_on_linux */ false); + // Run the old signal handler. old_action.sa_sigaction(signal_number, info, raw_context); } void Runtime::InitPlatformSignalHandlers() { - if (kUseSignalHandler) { - struct sigaction action; - memset(&action, 0, sizeof(action)); - sigemptyset(&action.sa_mask); - action.sa_sigaction = HandleUnexpectedSignal; - // Use the three-argument sa_sigaction handler. - action.sa_flags |= SA_SIGINFO; - // Use the alternate signal stack so we can catch stack overflows. - action.sa_flags |= SA_ONSTACK; - int rc = 0; - rc += sigaction(SIGSEGV, &action, &old_action); - CHECK_EQ(rc, 0); + // Enable the signal handler dumping crash information to the logcat + // when the Android root is not "/system". + const char* android_root = getenv("ANDROID_ROOT"); + if (android_root != nullptr && strcmp(android_root, "/system") != 0) { + InitPlatformSignalHandlersCommon(HandleUnexpectedSignalAndroid, + &old_action, + /* handle_timeout_signal */ false); } } diff --git a/runtime/runtime_callbacks_test.cc b/runtime/runtime_callbacks_test.cc index 66eb2ec3b1..f1e78b40e0 100644 --- a/runtime/runtime_callbacks_test.cc +++ b/runtime/runtime_callbacks_test.cc @@ -255,7 +255,7 @@ class ClassLoadCallbackRuntimeCallbacksTest : public RuntimeCallbacksTest { const DexFile& initial_dex_file, const DexFile::ClassDef& initial_class_def ATTRIBUTE_UNUSED, /*out*/DexFile const** final_dex_file ATTRIBUTE_UNUSED, - /*out*/DexFile::ClassDef const** final_dex_cache ATTRIBUTE_UNUSED) + /*out*/DexFile::ClassDef const** final_class_def ATTRIBUTE_UNUSED) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) { std::string location(initial_dex_file.GetLocation()); std::string event = diff --git a/runtime/runtime_common.cc b/runtime/runtime_common.cc new file mode 100644 index 0000000000..36901293bb --- /dev/null +++ b/runtime/runtime_common.cc @@ -0,0 +1,486 @@ +/* + * Copyright (C) 2017 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 "runtime_common.h" + +#include <signal.h> + +#include <cinttypes> +#include <iostream> +#include <sstream> +#include <string> + +#include "android-base/stringprintf.h" + +#include "base/logging.h" +#include "base/macros.h" +#include "base/mutex.h" +#include "native_stack_dump.h" +#include "thread-inl.h" +#include "thread_list.h" + +namespace art { + +using android::base::StringPrintf; + +static constexpr bool kUseSigRTTimeout = true; +static constexpr bool kDumpNativeStackOnTimeout = true; + +const char* GetSignalName(int signal_number) { + switch (signal_number) { + case SIGABRT: return "SIGABRT"; + case SIGBUS: return "SIGBUS"; + case SIGFPE: return "SIGFPE"; + case SIGILL: return "SIGILL"; + case SIGPIPE: return "SIGPIPE"; + case SIGSEGV: return "SIGSEGV"; +#if defined(SIGSTKFLT) + case SIGSTKFLT: return "SIGSTKFLT"; +#endif + case SIGTRAP: return "SIGTRAP"; + } + return "??"; +} + +const char* GetSignalCodeName(int signal_number, int signal_code) { + // Try the signal-specific codes... + switch (signal_number) { + case SIGILL: + switch (signal_code) { + case ILL_ILLOPC: return "ILL_ILLOPC"; + case ILL_ILLOPN: return "ILL_ILLOPN"; + case ILL_ILLADR: return "ILL_ILLADR"; + case ILL_ILLTRP: return "ILL_ILLTRP"; + case ILL_PRVOPC: return "ILL_PRVOPC"; + case ILL_PRVREG: return "ILL_PRVREG"; + case ILL_COPROC: return "ILL_COPROC"; + case ILL_BADSTK: return "ILL_BADSTK"; + } + break; + case SIGBUS: + switch (signal_code) { + case BUS_ADRALN: return "BUS_ADRALN"; + case BUS_ADRERR: return "BUS_ADRERR"; + case BUS_OBJERR: return "BUS_OBJERR"; + } + break; + case SIGFPE: + switch (signal_code) { + case FPE_INTDIV: return "FPE_INTDIV"; + case FPE_INTOVF: return "FPE_INTOVF"; + case FPE_FLTDIV: return "FPE_FLTDIV"; + case FPE_FLTOVF: return "FPE_FLTOVF"; + case FPE_FLTUND: return "FPE_FLTUND"; + case FPE_FLTRES: return "FPE_FLTRES"; + case FPE_FLTINV: return "FPE_FLTINV"; + case FPE_FLTSUB: return "FPE_FLTSUB"; + } + break; + case SIGSEGV: + switch (signal_code) { + case SEGV_MAPERR: return "SEGV_MAPERR"; + case SEGV_ACCERR: return "SEGV_ACCERR"; +#if defined(SEGV_BNDERR) + case SEGV_BNDERR: return "SEGV_BNDERR"; +#endif + } + break; + case SIGTRAP: + switch (signal_code) { + case TRAP_BRKPT: return "TRAP_BRKPT"; + case TRAP_TRACE: return "TRAP_TRACE"; + } + break; + } + // Then the other codes... + switch (signal_code) { + case SI_USER: return "SI_USER"; +#if defined(SI_KERNEL) + case SI_KERNEL: return "SI_KERNEL"; +#endif + case SI_QUEUE: return "SI_QUEUE"; + case SI_TIMER: return "SI_TIMER"; + case SI_MESGQ: return "SI_MESGQ"; + case SI_ASYNCIO: return "SI_ASYNCIO"; +#if defined(SI_SIGIO) + case SI_SIGIO: return "SI_SIGIO"; +#endif +#if defined(SI_TKILL) + case SI_TKILL: return "SI_TKILL"; +#endif + } + // Then give up... + return "?"; +} + +struct UContext { + explicit UContext(void* raw_context) + : context(reinterpret_cast<ucontext_t*>(raw_context)->uc_mcontext) {} + + void Dump(std::ostream& os) const; + + void DumpRegister32(std::ostream& os, const char* name, uint32_t value) const; + void DumpRegister64(std::ostream& os, const char* name, uint64_t value) const; + + void DumpX86Flags(std::ostream& os, uint32_t flags) const; + // Print some of the information from the status register (CPSR on ARMv7, PSTATE on ARMv8). + template <typename RegisterType> + void DumpArmStatusRegister(std::ostream& os, RegisterType status_register) const; + + mcontext_t& context; +}; + +void UContext::Dump(std::ostream& os) const { +#if defined(__APPLE__) && defined(__i386__) + DumpRegister32(os, "eax", context->__ss.__eax); + DumpRegister32(os, "ebx", context->__ss.__ebx); + DumpRegister32(os, "ecx", context->__ss.__ecx); + DumpRegister32(os, "edx", context->__ss.__edx); + os << '\n'; + + DumpRegister32(os, "edi", context->__ss.__edi); + DumpRegister32(os, "esi", context->__ss.__esi); + DumpRegister32(os, "ebp", context->__ss.__ebp); + DumpRegister32(os, "esp", context->__ss.__esp); + os << '\n'; + + DumpRegister32(os, "eip", context->__ss.__eip); + os << " "; + DumpRegister32(os, "eflags", context->__ss.__eflags); + DumpX86Flags(os, context->__ss.__eflags); + os << '\n'; + + DumpRegister32(os, "cs", context->__ss.__cs); + DumpRegister32(os, "ds", context->__ss.__ds); + DumpRegister32(os, "es", context->__ss.__es); + DumpRegister32(os, "fs", context->__ss.__fs); + os << '\n'; + DumpRegister32(os, "gs", context->__ss.__gs); + DumpRegister32(os, "ss", context->__ss.__ss); +#elif defined(__linux__) && defined(__i386__) + DumpRegister32(os, "eax", context.gregs[REG_EAX]); + DumpRegister32(os, "ebx", context.gregs[REG_EBX]); + DumpRegister32(os, "ecx", context.gregs[REG_ECX]); + DumpRegister32(os, "edx", context.gregs[REG_EDX]); + os << '\n'; + + DumpRegister32(os, "edi", context.gregs[REG_EDI]); + DumpRegister32(os, "esi", context.gregs[REG_ESI]); + DumpRegister32(os, "ebp", context.gregs[REG_EBP]); + DumpRegister32(os, "esp", context.gregs[REG_ESP]); + os << '\n'; + + DumpRegister32(os, "eip", context.gregs[REG_EIP]); + os << " "; + DumpRegister32(os, "eflags", context.gregs[REG_EFL]); + DumpX86Flags(os, context.gregs[REG_EFL]); + os << '\n'; + + DumpRegister32(os, "cs", context.gregs[REG_CS]); + DumpRegister32(os, "ds", context.gregs[REG_DS]); + DumpRegister32(os, "es", context.gregs[REG_ES]); + DumpRegister32(os, "fs", context.gregs[REG_FS]); + os << '\n'; + DumpRegister32(os, "gs", context.gregs[REG_GS]); + DumpRegister32(os, "ss", context.gregs[REG_SS]); +#elif defined(__linux__) && defined(__x86_64__) + DumpRegister64(os, "rax", context.gregs[REG_RAX]); + DumpRegister64(os, "rbx", context.gregs[REG_RBX]); + DumpRegister64(os, "rcx", context.gregs[REG_RCX]); + DumpRegister64(os, "rdx", context.gregs[REG_RDX]); + os << '\n'; + + DumpRegister64(os, "rdi", context.gregs[REG_RDI]); + DumpRegister64(os, "rsi", context.gregs[REG_RSI]); + DumpRegister64(os, "rbp", context.gregs[REG_RBP]); + DumpRegister64(os, "rsp", context.gregs[REG_RSP]); + os << '\n'; + + DumpRegister64(os, "r8 ", context.gregs[REG_R8]); + DumpRegister64(os, "r9 ", context.gregs[REG_R9]); + DumpRegister64(os, "r10", context.gregs[REG_R10]); + DumpRegister64(os, "r11", context.gregs[REG_R11]); + os << '\n'; + + DumpRegister64(os, "r12", context.gregs[REG_R12]); + DumpRegister64(os, "r13", context.gregs[REG_R13]); + DumpRegister64(os, "r14", context.gregs[REG_R14]); + DumpRegister64(os, "r15", context.gregs[REG_R15]); + os << '\n'; + + DumpRegister64(os, "rip", context.gregs[REG_RIP]); + os << " "; + DumpRegister32(os, "eflags", context.gregs[REG_EFL]); + DumpX86Flags(os, context.gregs[REG_EFL]); + os << '\n'; + + DumpRegister32(os, "cs", (context.gregs[REG_CSGSFS]) & 0x0FFFF); + DumpRegister32(os, "gs", (context.gregs[REG_CSGSFS] >> 16) & 0x0FFFF); + DumpRegister32(os, "fs", (context.gregs[REG_CSGSFS] >> 32) & 0x0FFFF); + os << '\n'; +#elif defined(__linux__) && defined(__arm__) + DumpRegister32(os, "r0", context.arm_r0); + DumpRegister32(os, "r1", context.arm_r1); + DumpRegister32(os, "r2", context.arm_r2); + DumpRegister32(os, "r3", context.arm_r3); + os << '\n'; + + DumpRegister32(os, "r4", context.arm_r4); + DumpRegister32(os, "r5", context.arm_r5); + DumpRegister32(os, "r6", context.arm_r6); + DumpRegister32(os, "r7", context.arm_r7); + os << '\n'; + + DumpRegister32(os, "r8", context.arm_r8); + DumpRegister32(os, "r9", context.arm_r9); + DumpRegister32(os, "r10", context.arm_r10); + DumpRegister32(os, "fp", context.arm_fp); + os << '\n'; + + DumpRegister32(os, "ip", context.arm_ip); + DumpRegister32(os, "sp", context.arm_sp); + DumpRegister32(os, "lr", context.arm_lr); + DumpRegister32(os, "pc", context.arm_pc); + os << '\n'; + + DumpRegister32(os, "cpsr", context.arm_cpsr); + DumpArmStatusRegister(os, context.arm_cpsr); + os << '\n'; +#elif defined(__linux__) && defined(__aarch64__) + for (size_t i = 0; i <= 30; ++i) { + std::string reg_name = "x" + std::to_string(i); + DumpRegister64(os, reg_name.c_str(), context.regs[i]); + if (i % 4 == 3) { + os << '\n'; + } + } + os << '\n'; + + DumpRegister64(os, "sp", context.sp); + DumpRegister64(os, "pc", context.pc); + os << '\n'; + + DumpRegister64(os, "pstate", context.pstate); + DumpArmStatusRegister(os, context.pstate); + os << '\n'; +#else + // TODO: Add support for MIPS32 and MIPS64. + os << "Unknown architecture/word size/OS in ucontext dump"; +#endif +} + +void UContext::DumpRegister32(std::ostream& os, const char* name, uint32_t value) const { + os << StringPrintf(" %6s: 0x%08x", name, value); +} + +void UContext::DumpRegister64(std::ostream& os, const char* name, uint64_t value) const { + os << StringPrintf(" %6s: 0x%016" PRIx64, name, value); +} + +void UContext::DumpX86Flags(std::ostream& os, uint32_t flags) const { + os << " ["; + if ((flags & (1 << 0)) != 0) { + os << " CF"; + } + if ((flags & (1 << 2)) != 0) { + os << " PF"; + } + if ((flags & (1 << 4)) != 0) { + os << " AF"; + } + if ((flags & (1 << 6)) != 0) { + os << " ZF"; + } + if ((flags & (1 << 7)) != 0) { + os << " SF"; + } + if ((flags & (1 << 8)) != 0) { + os << " TF"; + } + if ((flags & (1 << 9)) != 0) { + os << " IF"; + } + if ((flags & (1 << 10)) != 0) { + os << " DF"; + } + if ((flags & (1 << 11)) != 0) { + os << " OF"; + } + os << " ]"; +} + +template <typename RegisterType> +void UContext::DumpArmStatusRegister(std::ostream& os, RegisterType status_register) const { + // Condition flags. + constexpr RegisterType kFlagV = 1U << 28; + constexpr RegisterType kFlagC = 1U << 29; + constexpr RegisterType kFlagZ = 1U << 30; + constexpr RegisterType kFlagN = 1U << 31; + + os << " ["; + if ((status_register & kFlagN) != 0) { + os << " N"; + } + if ((status_register & kFlagZ) != 0) { + os << " Z"; + } + if ((status_register & kFlagC) != 0) { + os << " C"; + } + if ((status_register & kFlagV) != 0) { + os << " V"; + } + os << " ]"; +} + +int GetTimeoutSignal() { +#if defined(__APPLE__) + // Mac does not support realtime signals. + UNUSED(kUseSigRTTimeout); + return -1; +#else + return kUseSigRTTimeout ? (SIGRTMIN + 2) : -1; +#endif +} + +static bool IsTimeoutSignal(int signal_number) { + return signal_number == GetTimeoutSignal(); +} + +#if defined(__APPLE__) +// On macOS, clang complains about art::HandleUnexpectedSignalCommon's +// stack frame size being too large; disable that warning locally. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wframe-larger-than=" +#endif + +void HandleUnexpectedSignalCommon(int signal_number, + siginfo_t* info, + void* raw_context, + bool running_on_linux) { + bool handle_timeout_signal = running_on_linux; + bool dump_on_stderr = running_on_linux; + + static bool handling_unexpected_signal = false; + if (handling_unexpected_signal) { + LogHelper::LogLineLowStack(__FILE__, + __LINE__, + ::android::base::FATAL_WITHOUT_ABORT, + "HandleUnexpectedSignal reentered\n"); + if (handle_timeout_signal) { + if (IsTimeoutSignal(signal_number)) { + // Ignore a recursive timeout. + return; + } + } + _exit(1); + } + handling_unexpected_signal = true; + + gAborting++; // set before taking any locks + MutexLock mu(Thread::Current(), *Locks::unexpected_signal_lock_); + + bool has_address = (signal_number == SIGILL || signal_number == SIGBUS || + signal_number == SIGFPE || signal_number == SIGSEGV); + + OsInfo os_info; + const char* cmd_line = GetCmdLine(); + if (cmd_line == nullptr) { + cmd_line = "<unset>"; // Because no-one called InitLogging. + } + pid_t tid = GetTid(); + std::string thread_name(GetThreadName(tid)); + UContext thread_context(raw_context); + Backtrace thread_backtrace(raw_context); + + std::ostringstream stream; + stream << "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n" + << StringPrintf("Fatal signal %d (%s), code %d (%s)", + signal_number, + GetSignalName(signal_number), + info->si_code, + GetSignalCodeName(signal_number, info->si_code)) + << (has_address ? StringPrintf(" fault addr %p", info->si_addr) : "") << '\n' + << "OS: " << Dumpable<OsInfo>(os_info) << '\n' + << "Cmdline: " << cmd_line << '\n' + << "Thread: " << tid << " \"" << thread_name << "\"" << '\n' + << "Registers:\n" << Dumpable<UContext>(thread_context) << '\n' + << "Backtrace:\n" << Dumpable<Backtrace>(thread_backtrace) << '\n'; + if (dump_on_stderr) { + // Note: We are using cerr directly instead of LOG macros to ensure even just partial output + // makes it out. That means we lose the "dalvikvm..." prefix, but that is acceptable + // considering this is an abort situation. + std::cerr << stream.str() << std::flush; + } else { + LOG(FATAL_WITHOUT_ABORT) << stream.str() << std::flush; + } + if (kIsDebugBuild && signal_number == SIGSEGV) { + PrintFileToLog("/proc/self/maps", LogSeverity::FATAL_WITHOUT_ABORT); + } + + Runtime* runtime = Runtime::Current(); + if (runtime != nullptr) { + if (handle_timeout_signal && IsTimeoutSignal(signal_number)) { + // Special timeout signal. Try to dump all threads. + // Note: Do not use DumpForSigQuit, as that might disable native unwind, but the native parts + // are of value here. + runtime->GetThreadList()->Dump(std::cerr, kDumpNativeStackOnTimeout); + std::cerr << std::endl; + } + + if (dump_on_stderr) { + std::cerr << "Fault message: " << runtime->GetFaultMessage() << std::endl; + } else { + LOG(FATAL_WITHOUT_ABORT) << "Fault message: " << runtime->GetFaultMessage(); + } + } +} + +#if defined(__APPLE__) +#pragma GCC diagnostic pop +#endif + +void InitPlatformSignalHandlersCommon(void (*newact)(int, siginfo_t*, void*), + struct sigaction* oldact, + bool handle_timeout_signal) { + struct sigaction action; + memset(&action, 0, sizeof(action)); + sigemptyset(&action.sa_mask); + action.sa_sigaction = newact; + // Use the three-argument sa_sigaction handler. + action.sa_flags |= SA_SIGINFO; + // Use the alternate signal stack so we can catch stack overflows. + action.sa_flags |= SA_ONSTACK; + + int rc = 0; + rc += sigaction(SIGABRT, &action, oldact); + rc += sigaction(SIGBUS, &action, oldact); + rc += sigaction(SIGFPE, &action, oldact); + rc += sigaction(SIGILL, &action, oldact); + rc += sigaction(SIGPIPE, &action, oldact); + rc += sigaction(SIGSEGV, &action, oldact); +#if defined(SIGSTKFLT) + rc += sigaction(SIGSTKFLT, &action, oldact); +#endif + rc += sigaction(SIGTRAP, &action, oldact); + // Special dump-all timeout. + if (handle_timeout_signal && GetTimeoutSignal() != -1) { + rc += sigaction(GetTimeoutSignal(), &action, oldact); + } + CHECK_EQ(rc, 0); +} + +} // namespace art diff --git a/runtime/runtime_common.h b/runtime/runtime_common.h new file mode 100644 index 0000000000..832b6bbf3e --- /dev/null +++ b/runtime/runtime_common.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2017 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_RUNTIME_COMMON_H_ +#define ART_RUNTIME_RUNTIME_COMMON_H_ + +// Code shared by runtime/runtime_android.cc and runtime/runtime_linux.cc. + +#if defined(__APPLE__) +// On macOS, _XOPEN_SOURCE must be defined to access ucontext +// routines, as they are considered deprecated on that platform. +#define _XOPEN_SOURCE +#endif + +#include <sys/utsname.h> +#include <ucontext.h> + +#include <iomanip> + +#include "base/dumpable.h" +#include "native_stack_dump.h" +#include "utils.h" + +namespace art { + +struct Backtrace { + public: + explicit Backtrace(void* raw_context) : raw_context_(raw_context) {} + void Dump(std::ostream& os) const { + DumpNativeStack(os, GetTid(), nullptr, "\t", nullptr, raw_context_); + } + private: + // Stores the context of the signal that was unexpected and will terminate the runtime. The + // DumpNativeStack code will take care of casting it to the expected type. This is required + // as our signal handler runs on an alternate stack. + void* raw_context_; +}; + +struct OsInfo { + void Dump(std::ostream& os) const { + utsname info; + uname(&info); + // Linux 2.6.38.8-gg784 (x86_64) + // Darwin 11.4.0 (x86_64) + os << info.sysname << " " << info.release << " (" << info.machine << ")"; + } +}; + +const char* GetSignalName(int signal_number); +const char* GetSignalCodeName(int signal_number, int signal_code); + +// Return the signal number we recognize as timeout. -1 means not active/supported. +int GetTimeoutSignal(); + +void HandleUnexpectedSignalCommon(int signal_number, + siginfo_t* info, + void* raw_context, + bool running_on_linux); + +void InitPlatformSignalHandlersCommon(void (*newact)(int, siginfo_t*, void*), + struct sigaction* oldact, + bool handle_timeout_signal); + +} // namespace art + +#endif // ART_RUNTIME_RUNTIME_COMMON_H_ diff --git a/runtime/runtime_linux.cc b/runtime/runtime_linux.cc index b8894d2569..ad61cf373b 100644 --- a/runtime/runtime_linux.cc +++ b/runtime/runtime_linux.cc @@ -17,359 +17,19 @@ #include "runtime.h" #include <signal.h> -#include <string.h> -#include <sys/utsname.h> -#include <inttypes.h> #include <iostream> -#include <sstream> -#include "android-base/stringprintf.h" - -#include "base/dumpable.h" -#include "base/logging.h" -#include "base/macros.h" -#include "base/mutex.h" -#include "native_stack_dump.h" -#include "thread-inl.h" -#include "thread_list.h" -#include "utils.h" +#include "runtime_common.h" namespace art { -using android::base::StringPrintf; - -static constexpr bool kUseSigRTTimeout = true; -static constexpr bool kDumpNativeStackOnTimeout = true; - -struct Backtrace { - public: - explicit Backtrace(void* raw_context) : raw_context_(raw_context) {} - void Dump(std::ostream& os) const { - DumpNativeStack(os, GetTid(), nullptr, "\t", nullptr, raw_context_); - } - private: - // Stores the context of the signal that was unexpected and will terminate the runtime. The - // DumpNativeStack code will take care of casting it to the expected type. This is required - // as our signal handler runs on an alternate stack. - void* raw_context_; -}; - -struct OsInfo { - void Dump(std::ostream& os) const { - utsname info; - uname(&info); - // Linux 2.6.38.8-gg784 (x86_64) - // Darwin 11.4.0 (x86_64) - os << info.sysname << " " << info.release << " (" << info.machine << ")"; - } -}; - -static const char* GetSignalName(int signal_number) { - switch (signal_number) { - case SIGABRT: return "SIGABRT"; - case SIGBUS: return "SIGBUS"; - case SIGFPE: return "SIGFPE"; - case SIGILL: return "SIGILL"; - case SIGPIPE: return "SIGPIPE"; - case SIGSEGV: return "SIGSEGV"; -#if defined(SIGSTKFLT) - case SIGSTKFLT: return "SIGSTKFLT"; -#endif - case SIGTRAP: return "SIGTRAP"; - } - return "??"; -} - -static const char* GetSignalCodeName(int signal_number, int signal_code) { - // Try the signal-specific codes... - switch (signal_number) { - case SIGILL: - switch (signal_code) { - case ILL_ILLOPC: return "ILL_ILLOPC"; - case ILL_ILLOPN: return "ILL_ILLOPN"; - case ILL_ILLADR: return "ILL_ILLADR"; - case ILL_ILLTRP: return "ILL_ILLTRP"; - case ILL_PRVOPC: return "ILL_PRVOPC"; - case ILL_PRVREG: return "ILL_PRVREG"; - case ILL_COPROC: return "ILL_COPROC"; - case ILL_BADSTK: return "ILL_BADSTK"; - } - break; - case SIGBUS: - switch (signal_code) { - case BUS_ADRALN: return "BUS_ADRALN"; - case BUS_ADRERR: return "BUS_ADRERR"; - case BUS_OBJERR: return "BUS_OBJERR"; - } - break; - case SIGFPE: - switch (signal_code) { - case FPE_INTDIV: return "FPE_INTDIV"; - case FPE_INTOVF: return "FPE_INTOVF"; - case FPE_FLTDIV: return "FPE_FLTDIV"; - case FPE_FLTOVF: return "FPE_FLTOVF"; - case FPE_FLTUND: return "FPE_FLTUND"; - case FPE_FLTRES: return "FPE_FLTRES"; - case FPE_FLTINV: return "FPE_FLTINV"; - case FPE_FLTSUB: return "FPE_FLTSUB"; - } - break; - case SIGSEGV: - switch (signal_code) { - case SEGV_MAPERR: return "SEGV_MAPERR"; - case SEGV_ACCERR: return "SEGV_ACCERR"; -#if defined(SEGV_BNDERR) - case SEGV_BNDERR: return "SEGV_BNDERR"; -#endif - } - break; - case SIGTRAP: - switch (signal_code) { - case TRAP_BRKPT: return "TRAP_BRKPT"; - case TRAP_TRACE: return "TRAP_TRACE"; - } - break; - } - // Then the other codes... - switch (signal_code) { - case SI_USER: return "SI_USER"; -#if defined(SI_KERNEL) - case SI_KERNEL: return "SI_KERNEL"; -#endif - case SI_QUEUE: return "SI_QUEUE"; - case SI_TIMER: return "SI_TIMER"; - case SI_MESGQ: return "SI_MESGQ"; - case SI_ASYNCIO: return "SI_ASYNCIO"; -#if defined(SI_SIGIO) - case SI_SIGIO: return "SI_SIGIO"; -#endif -#if defined(SI_TKILL) - case SI_TKILL: return "SI_TKILL"; -#endif - } - // Then give up... - return "?"; -} - -struct UContext { - explicit UContext(void* raw_context) : - context(reinterpret_cast<ucontext_t*>(raw_context)->uc_mcontext) { - } - - void Dump(std::ostream& os) const { - // TODO: support non-x86 hosts (not urgent because this code doesn't run on targets). -#if defined(__APPLE__) && defined(__i386__) - DumpRegister32(os, "eax", context->__ss.__eax); - DumpRegister32(os, "ebx", context->__ss.__ebx); - DumpRegister32(os, "ecx", context->__ss.__ecx); - DumpRegister32(os, "edx", context->__ss.__edx); - os << '\n'; - - DumpRegister32(os, "edi", context->__ss.__edi); - DumpRegister32(os, "esi", context->__ss.__esi); - DumpRegister32(os, "ebp", context->__ss.__ebp); - DumpRegister32(os, "esp", context->__ss.__esp); - os << '\n'; - - DumpRegister32(os, "eip", context->__ss.__eip); - os << " "; - DumpRegister32(os, "eflags", context->__ss.__eflags); - DumpX86Flags(os, context->__ss.__eflags); - os << '\n'; - - DumpRegister32(os, "cs", context->__ss.__cs); - DumpRegister32(os, "ds", context->__ss.__ds); - DumpRegister32(os, "es", context->__ss.__es); - DumpRegister32(os, "fs", context->__ss.__fs); - os << '\n'; - DumpRegister32(os, "gs", context->__ss.__gs); - DumpRegister32(os, "ss", context->__ss.__ss); -#elif defined(__linux__) && defined(__i386__) - DumpRegister32(os, "eax", context.gregs[REG_EAX]); - DumpRegister32(os, "ebx", context.gregs[REG_EBX]); - DumpRegister32(os, "ecx", context.gregs[REG_ECX]); - DumpRegister32(os, "edx", context.gregs[REG_EDX]); - os << '\n'; - - DumpRegister32(os, "edi", context.gregs[REG_EDI]); - DumpRegister32(os, "esi", context.gregs[REG_ESI]); - DumpRegister32(os, "ebp", context.gregs[REG_EBP]); - DumpRegister32(os, "esp", context.gregs[REG_ESP]); - os << '\n'; - - DumpRegister32(os, "eip", context.gregs[REG_EIP]); - os << " "; - DumpRegister32(os, "eflags", context.gregs[REG_EFL]); - DumpX86Flags(os, context.gregs[REG_EFL]); - os << '\n'; - - DumpRegister32(os, "cs", context.gregs[REG_CS]); - DumpRegister32(os, "ds", context.gregs[REG_DS]); - DumpRegister32(os, "es", context.gregs[REG_ES]); - DumpRegister32(os, "fs", context.gregs[REG_FS]); - os << '\n'; - DumpRegister32(os, "gs", context.gregs[REG_GS]); - DumpRegister32(os, "ss", context.gregs[REG_SS]); -#elif defined(__linux__) && defined(__x86_64__) - DumpRegister64(os, "rax", context.gregs[REG_RAX]); - DumpRegister64(os, "rbx", context.gregs[REG_RBX]); - DumpRegister64(os, "rcx", context.gregs[REG_RCX]); - DumpRegister64(os, "rdx", context.gregs[REG_RDX]); - os << '\n'; - - DumpRegister64(os, "rdi", context.gregs[REG_RDI]); - DumpRegister64(os, "rsi", context.gregs[REG_RSI]); - DumpRegister64(os, "rbp", context.gregs[REG_RBP]); - DumpRegister64(os, "rsp", context.gregs[REG_RSP]); - os << '\n'; - - DumpRegister64(os, "r8 ", context.gregs[REG_R8]); - DumpRegister64(os, "r9 ", context.gregs[REG_R9]); - DumpRegister64(os, "r10", context.gregs[REG_R10]); - DumpRegister64(os, "r11", context.gregs[REG_R11]); - os << '\n'; - - DumpRegister64(os, "r12", context.gregs[REG_R12]); - DumpRegister64(os, "r13", context.gregs[REG_R13]); - DumpRegister64(os, "r14", context.gregs[REG_R14]); - DumpRegister64(os, "r15", context.gregs[REG_R15]); - os << '\n'; - - DumpRegister64(os, "rip", context.gregs[REG_RIP]); - os << " "; - DumpRegister32(os, "eflags", context.gregs[REG_EFL]); - DumpX86Flags(os, context.gregs[REG_EFL]); - os << '\n'; - - DumpRegister32(os, "cs", (context.gregs[REG_CSGSFS]) & 0x0FFFF); - DumpRegister32(os, "gs", (context.gregs[REG_CSGSFS] >> 16) & 0x0FFFF); - DumpRegister32(os, "fs", (context.gregs[REG_CSGSFS] >> 32) & 0x0FFFF); - os << '\n'; -#else - os << "Unknown architecture/word size/OS in ucontext dump"; -#endif - } - - void DumpRegister32(std::ostream& os, const char* name, uint32_t value) const { - os << StringPrintf(" %6s: 0x%08x", name, value); - } - - void DumpRegister64(std::ostream& os, const char* name, uint64_t value) const { - os << StringPrintf(" %6s: 0x%016" PRIx64, name, value); - } +void HandleUnexpectedSignalLinux(int signal_number, siginfo_t* info, void* raw_context) { + HandleUnexpectedSignalCommon(signal_number, info, raw_context, /* running_on_linux */ true); - void DumpX86Flags(std::ostream& os, uint32_t flags) const { - os << " ["; - if ((flags & (1 << 0)) != 0) { - os << " CF"; - } - if ((flags & (1 << 2)) != 0) { - os << " PF"; - } - if ((flags & (1 << 4)) != 0) { - os << " AF"; - } - if ((flags & (1 << 6)) != 0) { - os << " ZF"; - } - if ((flags & (1 << 7)) != 0) { - os << " SF"; - } - if ((flags & (1 << 8)) != 0) { - os << " TF"; - } - if ((flags & (1 << 9)) != 0) { - os << " IF"; - } - if ((flags & (1 << 10)) != 0) { - os << " DF"; - } - if ((flags & (1 << 11)) != 0) { - os << " OF"; - } - os << " ]"; - } - - mcontext_t& context; -}; - -// Return the signal number we recognize as timeout. -1 means not active/supported. -static int GetTimeoutSignal() { -#if defined(__APPLE__) - // Mac does not support realtime signals. - UNUSED(kUseSigRTTimeout); - return -1; -#else - return kUseSigRTTimeout ? (SIGRTMIN + 2) : -1; -#endif -} - -static bool IsTimeoutSignal(int signal_number) { - return signal_number == GetTimeoutSignal(); -} - -void HandleUnexpectedSignal(int signal_number, siginfo_t* info, void* raw_context) { - static bool handlingUnexpectedSignal = false; - if (handlingUnexpectedSignal) { - LogHelper::LogLineLowStack(__FILE__, - __LINE__, - ::android::base::FATAL_WITHOUT_ABORT, - "HandleUnexpectedSignal reentered\n"); - if (IsTimeoutSignal(signal_number)) { - // Ignore a recursive timeout. - return; - } - _exit(1); - } - handlingUnexpectedSignal = true; - - gAborting++; // set before taking any locks - MutexLock mu(Thread::Current(), *Locks::unexpected_signal_lock_); - - bool has_address = (signal_number == SIGILL || signal_number == SIGBUS || - signal_number == SIGFPE || signal_number == SIGSEGV); - - OsInfo os_info; - const char* cmd_line = GetCmdLine(); - if (cmd_line == nullptr) { - cmd_line = "<unset>"; // Because no-one called InitLogging. - } - pid_t tid = GetTid(); - std::string thread_name(GetThreadName(tid)); - UContext thread_context(raw_context); - Backtrace thread_backtrace(raw_context); - - // Note: We are using cerr directly instead of LOG macros to ensure even just partial output - // makes it out. That means we lose the "dalvikvm..." prefix, but that is acceptable - // considering this is an abort situation. - - std::cerr << "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n" - << StringPrintf("Fatal signal %d (%s), code %d (%s)", - signal_number, GetSignalName(signal_number), - info->si_code, - GetSignalCodeName(signal_number, info->si_code)) - << (has_address ? StringPrintf(" fault addr %p", info->si_addr) : "") << std::endl - << "OS: " << Dumpable<OsInfo>(os_info) << std::endl - << "Cmdline: " << cmd_line << std::endl - << "Thread: " << tid << " \"" << thread_name << "\"" << std::endl - << "Registers:\n" << Dumpable<UContext>(thread_context) << std::endl - << "Backtrace:\n" << Dumpable<Backtrace>(thread_backtrace) << std::endl; - if (kIsDebugBuild && signal_number == SIGSEGV) { - PrintFileToLog("/proc/self/maps", LogSeverity::FATAL_WITHOUT_ABORT); - } - Runtime* runtime = Runtime::Current(); - if (runtime != nullptr) { - if (IsTimeoutSignal(signal_number)) { - // Special timeout signal. Try to dump all threads. - // Note: Do not use DumpForSigQuit, as that might disable native unwind, but the native parts - // are of value here. - runtime->GetThreadList()->Dump(std::cerr, kDumpNativeStackOnTimeout); - std::cerr << std::endl; - } - std::cerr << "Fault message: " << runtime->GetFaultMessage() << std::endl; - } if (getenv("debug_db_uid") != nullptr || getenv("art_wait_for_gdb_on_crash") != nullptr) { + pid_t tid = GetTid(); + std::string thread_name(GetThreadName(tid)); std::cerr << "********************************************************\n" << "* Process " << getpid() << " thread " << tid << " \"" << thread_name << "\"" @@ -398,31 +58,9 @@ void HandleUnexpectedSignal(int signal_number, siginfo_t* info, void* raw_contex void Runtime::InitPlatformSignalHandlers() { // On the host, we don't have debuggerd to dump a stack for us when something unexpected happens. - struct sigaction action; - memset(&action, 0, sizeof(action)); - sigemptyset(&action.sa_mask); - action.sa_sigaction = HandleUnexpectedSignal; - // Use the three-argument sa_sigaction handler. - action.sa_flags |= SA_SIGINFO; - // Use the alternate signal stack so we can catch stack overflows. - action.sa_flags |= SA_ONSTACK; - - int rc = 0; - rc += sigaction(SIGABRT, &action, nullptr); - rc += sigaction(SIGBUS, &action, nullptr); - rc += sigaction(SIGFPE, &action, nullptr); - rc += sigaction(SIGILL, &action, nullptr); - rc += sigaction(SIGPIPE, &action, nullptr); - rc += sigaction(SIGSEGV, &action, nullptr); -#if defined(SIGSTKFLT) - rc += sigaction(SIGSTKFLT, &action, nullptr); -#endif - rc += sigaction(SIGTRAP, &action, nullptr); - // Special dump-all timeout. - if (GetTimeoutSignal() != -1) { - rc += sigaction(GetTimeoutSignal(), &action, nullptr); - } - CHECK_EQ(rc, 0); + InitPlatformSignalHandlersCommon(HandleUnexpectedSignalLinux, + nullptr, + /* handle_timeout_signal */ true); } } // namespace art diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def index 749a36e3b3..e68a1b2681 100644 --- a/runtime/runtime_options.def +++ b/runtime/runtime_options.def @@ -119,11 +119,10 @@ RUNTIME_OPTIONS_KEY (unsigned int, ZygoteMaxFailedBoots, 10) RUNTIME_OPTIONS_KEY (Unit, NoDexFileFallback) RUNTIME_OPTIONS_KEY (std::string, CpuAbiList) RUNTIME_OPTIONS_KEY (std::string, Fingerprint) -RUNTIME_OPTIONS_KEY (ExperimentalFlags, Experimental, ExperimentalFlags::kNone) // -Xexperimental:{none, agents} -RUNTIME_OPTIONS_KEY (std::vector<ti::Agent>, AgentLib) // -agentlib:<libname>=<options>, Requires -Xexperimental:agents -RUNTIME_OPTIONS_KEY (std::vector<ti::Agent>, AgentPath) // -agentpath:<libname>=<options>, Requires -Xexperimental:agents -RUNTIME_OPTIONS_KEY (std::vector<Plugin>, Plugins) // -Xplugin:<library> Requires -Xexperimental:runtime-plugins -RUNTIME_OPTIONS_KEY (Unit, FullyDeoptable) // -Xfully-deoptable +RUNTIME_OPTIONS_KEY (ExperimentalFlags, Experimental, ExperimentalFlags::kNone) // -Xexperimental:{...} +RUNTIME_OPTIONS_KEY (std::vector<ti::Agent>, AgentLib) // -agentlib:<libname>=<options> +RUNTIME_OPTIONS_KEY (std::vector<ti::Agent>, AgentPath) // -agentpath:<libname>=<options> +RUNTIME_OPTIONS_KEY (std::vector<Plugin>, Plugins) // -Xplugin:<library> // Not parse-able from command line, but can be provided explicitly. // (Do not add anything here that is defined in ParsedOptions::MakeParser) diff --git a/runtime/scoped_thread_state_change-inl.h b/runtime/scoped_thread_state_change-inl.h index d4469f4357..000da59bd2 100644 --- a/runtime/scoped_thread_state_change-inl.h +++ b/runtime/scoped_thread_state_change-inl.h @@ -110,6 +110,10 @@ inline ScopedObjectAccessUnchecked::ScopedObjectAccessUnchecked(Thread* self) Locks::mutator_lock_->AssertSharedHeld(Self()); } +inline ScopedObjectAccess::ScopedObjectAccess(JNIEnv* env) : ScopedObjectAccessUnchecked(env) {} +inline ScopedObjectAccess::ScopedObjectAccess(Thread* self) : ScopedObjectAccessUnchecked(self) {} +inline ScopedObjectAccess::~ScopedObjectAccess() {} + inline ScopedThreadSuspension::ScopedThreadSuspension(Thread* self, ThreadState suspended_state) : self_(self), suspended_state_(suspended_state) { DCHECK(self_ != nullptr); diff --git a/runtime/scoped_thread_state_change.h b/runtime/scoped_thread_state_change.h index b4992586ce..24199f76b6 100644 --- a/runtime/scoped_thread_state_change.h +++ b/runtime/scoped_thread_state_change.h @@ -159,16 +159,14 @@ class ScopedObjectAccess : public ScopedObjectAccessUnchecked { public: ALWAYS_INLINE explicit ScopedObjectAccess(JNIEnv* env) REQUIRES(!Locks::thread_suspend_count_lock_) - SHARED_LOCK_FUNCTION(Locks::mutator_lock_) - : ScopedObjectAccessUnchecked(env) {} + SHARED_LOCK_FUNCTION(Locks::mutator_lock_); ALWAYS_INLINE explicit ScopedObjectAccess(Thread* self) REQUIRES(!Locks::thread_suspend_count_lock_) - SHARED_LOCK_FUNCTION(Locks::mutator_lock_) - : ScopedObjectAccessUnchecked(self) {} + SHARED_LOCK_FUNCTION(Locks::mutator_lock_); // Base class will release share of lock. Invoked after this destructor. - ~ScopedObjectAccess() UNLOCK_FUNCTION(Locks::mutator_lock_) ALWAYS_INLINE {} + ~ScopedObjectAccess() UNLOCK_FUNCTION(Locks::mutator_lock_) ALWAYS_INLINE; private: // TODO: remove this constructor. It is used by check JNI's ScopedCheck to make it believe that diff --git a/runtime/stack.cc b/runtime/stack.cc index f9efc0b88f..c737fe49ea 100644 --- a/runtime/stack.cc +++ b/runtime/stack.cc @@ -37,7 +37,7 @@ #include "runtime.h" #include "thread.h" #include "thread_list.h" -#include "verify_object-inl.h" +#include "verify_object.h" namespace art { @@ -96,13 +96,17 @@ bool ManagedStack::ShadowFramesContain(StackReference<mirror::Object>* shadow_fr return false; } -StackVisitor::StackVisitor(Thread* thread, Context* context, StackWalkKind walk_kind) - : StackVisitor(thread, context, walk_kind, 0) {} +StackVisitor::StackVisitor(Thread* thread, + Context* context, + StackWalkKind walk_kind, + bool check_suspended) + : StackVisitor(thread, context, walk_kind, 0, check_suspended) {} StackVisitor::StackVisitor(Thread* thread, Context* context, StackWalkKind walk_kind, - size_t num_frames) + size_t num_frames, + bool check_suspended) : thread_(thread), walk_kind_(walk_kind), cur_shadow_frame_(nullptr), @@ -112,8 +116,11 @@ StackVisitor::StackVisitor(Thread* thread, num_frames_(num_frames), cur_depth_(0), current_inlining_depth_(0), - context_(context) { - DCHECK(thread == Thread::Current() || thread->IsSuspended()) << *thread; + context_(context), + check_suspended_(check_suspended) { + if (check_suspended_) { + DCHECK(thread == Thread::Current() || thread->IsSuspended()) << *thread; + } } InlineInfo StackVisitor::GetCurrentInlineInfo() const { @@ -625,7 +632,7 @@ void StackVisitor::SetMethod(ArtMethod* method) { } else { DCHECK(cur_quick_frame_ != nullptr); CHECK(!IsInInlinedFrame()) << "We do not support setting inlined method's ArtMethod!"; - *cur_quick_frame_ = method; + *cur_quick_frame_ = method; } } @@ -788,7 +795,9 @@ QuickMethodFrameInfo StackVisitor::GetCurrentQuickFrameInfo() const { template <StackVisitor::CountTransitions kCount> void StackVisitor::WalkStack(bool include_transitions) { - DCHECK(thread_ == Thread::Current() || thread_->IsSuspended()); + if (check_suspended_) { + DCHECK(thread_ == Thread::Current() || thread_->IsSuspended()); + } CHECK_EQ(cur_depth_, 0U); bool exit_stubs_installed = Runtime::Current()->GetInstrumentation()->AreExitStubsInstalled(); uint32_t instrumentation_stack_depth = 0; diff --git a/runtime/stack.h b/runtime/stack.h index 9dceb2931d..90a0aee353 100644 --- a/runtime/stack.h +++ b/runtime/stack.h @@ -590,7 +590,10 @@ class StackVisitor { }; protected: - StackVisitor(Thread* thread, Context* context, StackWalkKind walk_kind); + StackVisitor(Thread* thread, + Context* context, + StackWalkKind walk_kind, + bool check_suspended = true); bool GetRegisterIfAccessible(uint32_t reg, VRegKind kind, uint32_t* val) const REQUIRES_SHARED(Locks::mutator_lock_); @@ -797,7 +800,11 @@ class StackVisitor { private: // Private constructor known in the case that num_frames_ has already been computed. - StackVisitor(Thread* thread, Context* context, StackWalkKind walk_kind, size_t num_frames) + StackVisitor(Thread* thread, + Context* context, + StackWalkKind walk_kind, + size_t num_frames, + bool check_suspended = true) REQUIRES_SHARED(Locks::mutator_lock_); bool IsAccessibleRegister(uint32_t reg, bool is_float) const { @@ -851,6 +858,7 @@ class StackVisitor { protected: Context* const context_; + const bool check_suspended_; }; } // namespace art diff --git a/runtime/stack_map.cc b/runtime/stack_map.cc index 690b069c8e..4e7c3f4f9f 100644 --- a/runtime/stack_map.cc +++ b/runtime/stack_map.cc @@ -97,8 +97,9 @@ void StackMapEncoding::Dump(VariableIndentationOutputStream* vios) const { << ", dex_pc_bit_offset=" << static_cast<uint32_t>(dex_pc_bit_offset_) << ", dex_register_map_bit_offset=" << static_cast<uint32_t>(dex_register_map_bit_offset_) << ", inline_info_bit_offset=" << static_cast<uint32_t>(inline_info_bit_offset_) - << ", register_mask_bit_offset=" << static_cast<uint32_t>(register_mask_bit_offset_) - << ", stack_mask_bit_offset=" << static_cast<uint32_t>(stack_mask_bit_offset_) + << ", register_mask_bit_offset=" << static_cast<uint32_t>(register_mask_index_bit_offset_) + << ", stack_mask_index_bit_offset=" << static_cast<uint32_t>(stack_mask_index_bit_offset_) + << ", total_bit_size=" << static_cast<uint32_t>(total_bit_size_) << ")\n"; } @@ -198,16 +199,17 @@ void StackMap::Dump(VariableIndentationOutputStream* vios, << "StackMap" << header_suffix << std::hex << " [native_pc=0x" << code_offset + pc_offset << "]" - << " [entry_size=0x" << encoding.stack_map_size_in_bytes << "]" + << " [entry_size=0x" << encoding.stack_map_encoding.BitSize() << " bits]" << " (dex_pc=0x" << GetDexPc(stack_map_encoding) << ", native_pc_offset=0x" << pc_offset << ", dex_register_map_offset=0x" << GetDexRegisterMapOffset(stack_map_encoding) << ", inline_info_offset=0x" << GetInlineDescriptorOffset(stack_map_encoding) - << ", register_mask=0x" << GetRegisterMask(stack_map_encoding) + << ", register_mask=0x" << code_info.GetRegisterMaskOf(encoding, *this) << std::dec << ", stack_mask=0b"; - for (size_t i = 0, e = GetNumberOfStackMaskBits(stack_map_encoding); i < e; ++i) { - vios->Stream() << GetStackMaskBit(stack_map_encoding, e - i - 1); + BitMemoryRegion stack_mask = code_info.GetStackMaskOf(encoding, *this); + for (size_t i = 0, e = encoding.stack_mask_size_in_bits; i < e; ++i) { + vios->Stream() << stack_mask.LoadBit(e - i - 1); } vios->Stream() << ")\n"; if (HasDexRegisterMap(stack_map_encoding)) { diff --git a/runtime/stack_map.h b/runtime/stack_map.h index cd9a3f04cf..062404dbf2 100644 --- a/runtime/stack_map.h +++ b/runtime/stack_map.h @@ -20,6 +20,7 @@ #include "arch/code_offset.h" #include "base/bit_vector.h" #include "base/bit_utils.h" +#include "bit_memory_region.h" #include "dex_file.h" #include "memory_region.h" #include "leb128.h" @@ -665,37 +666,14 @@ struct FieldEncoding { ALWAYS_INLINE size_t BitSize() const { return end_offset_ - start_offset_; } - ALWAYS_INLINE int32_t Load(const MemoryRegion& region) const { + template <typename Region> + ALWAYS_INLINE int32_t Load(const Region& region) const { DCHECK_LE(end_offset_, region.size_in_bits()); - const size_t bit_count = BitSize(); - if (bit_count == 0) { - // Do not touch any memory if the range is empty. - return min_value_; - } - uint8_t* address = region.start() + start_offset_ / kBitsPerByte; - const uint32_t shift = start_offset_ & (kBitsPerByte - 1); - // Load the value (reading only the strictly needed bytes). - const uint32_t load_bit_count = shift + bit_count; - uint32_t value = *address++ >> shift; - if (load_bit_count > 8) { - value |= static_cast<uint32_t>(*address++) << (8 - shift); - if (load_bit_count > 16) { - value |= static_cast<uint32_t>(*address++) << (16 - shift); - if (load_bit_count > 24) { - value |= static_cast<uint32_t>(*address++) << (24 - shift); - if (load_bit_count > 32) { - value |= static_cast<uint32_t>(*address++) << (32 - shift); - } - } - } - } - // Clear unwanted most significant bits. - uint32_t clear_bit_count = 32 - bit_count; - value = (value << clear_bit_count) >> clear_bit_count; - return value + min_value_; + return static_cast<int32_t>(region.LoadBits(start_offset_, BitSize())) + min_value_; } - ALWAYS_INLINE void Store(MemoryRegion region, int32_t value) const { + template <typename Region> + ALWAYS_INLINE void Store(Region region, int32_t value) const { region.StoreBits(start_offset_, value - min_value_, BitSize()); DCHECK_EQ(Load(region), value); } @@ -711,40 +689,40 @@ class StackMapEncoding { StackMapEncoding() {} // Set stack map bit layout based on given sizes. - // Returns the size of stack map in bytes. + // Returns the size of stack map in bits. size_t SetFromSizes(size_t native_pc_max, size_t dex_pc_max, size_t dex_register_map_size, size_t inline_info_size, - size_t register_mask_max, - size_t stack_mask_bit_size) { - size_t bit_offset = 0; - DCHECK_EQ(kNativePcBitOffset, bit_offset); - bit_offset += MinimumBitsToStore(native_pc_max); + size_t number_of_register_masks, + size_t number_of_stack_masks) { + total_bit_size_ = 0; + DCHECK_EQ(kNativePcBitOffset, total_bit_size_); + total_bit_size_ += MinimumBitsToStore(native_pc_max); - dex_pc_bit_offset_ = dchecked_integral_cast<uint8_t>(bit_offset); - bit_offset += MinimumBitsToStore(1 /* kNoDexPc */ + dex_pc_max); + dex_pc_bit_offset_ = total_bit_size_; + total_bit_size_ += MinimumBitsToStore(1 /* kNoDexPc */ + dex_pc_max); // We also need +1 for kNoDexRegisterMap, but since the size is strictly // greater than any offset we might try to encode, we already implicitly have it. - dex_register_map_bit_offset_ = dchecked_integral_cast<uint8_t>(bit_offset); - bit_offset += MinimumBitsToStore(dex_register_map_size); + dex_register_map_bit_offset_ = total_bit_size_; + total_bit_size_ += MinimumBitsToStore(dex_register_map_size); // We also need +1 for kNoInlineInfo, but since the inline_info_size is strictly // greater than the offset we might try to encode, we already implicitly have it. // If inline_info_size is zero, we can encode only kNoInlineInfo (in zero bits). - inline_info_bit_offset_ = dchecked_integral_cast<uint8_t>(bit_offset); + inline_info_bit_offset_ = total_bit_size_; if (inline_info_size != 0) { - bit_offset += MinimumBitsToStore(dex_register_map_size + inline_info_size); + total_bit_size_ += MinimumBitsToStore(dex_register_map_size + inline_info_size); } - register_mask_bit_offset_ = dchecked_integral_cast<uint8_t>(bit_offset); - bit_offset += MinimumBitsToStore(register_mask_max); + register_mask_index_bit_offset_ = total_bit_size_; + total_bit_size_ += MinimumBitsToStore(number_of_register_masks); - stack_mask_bit_offset_ = dchecked_integral_cast<uint8_t>(bit_offset); - bit_offset += stack_mask_bit_size; + stack_mask_index_bit_offset_ = total_bit_size_; + total_bit_size_ += MinimumBitsToStore(number_of_stack_masks); - return RoundUp(bit_offset, kBitsPerByte) / kBitsPerByte; + return total_bit_size_; } ALWAYS_INLINE FieldEncoding GetNativePcEncoding() const { @@ -757,14 +735,18 @@ class StackMapEncoding { return FieldEncoding(dex_register_map_bit_offset_, inline_info_bit_offset_, -1 /* min_value */); } ALWAYS_INLINE FieldEncoding GetInlineInfoEncoding() const { - return FieldEncoding(inline_info_bit_offset_, register_mask_bit_offset_, -1 /* min_value */); + return FieldEncoding(inline_info_bit_offset_, + register_mask_index_bit_offset_, + -1 /* min_value */); } - ALWAYS_INLINE FieldEncoding GetRegisterMaskEncoding() const { - return FieldEncoding(register_mask_bit_offset_, stack_mask_bit_offset_); + ALWAYS_INLINE FieldEncoding GetRegisterMaskIndexEncoding() const { + return FieldEncoding(register_mask_index_bit_offset_, stack_mask_index_bit_offset_); } - ALWAYS_INLINE size_t GetStackMaskBitOffset() const { - // The end offset is not encoded. It is implicitly the end of stack map entry. - return stack_mask_bit_offset_; + ALWAYS_INLINE FieldEncoding GetStackMaskIndexEncoding() const { + return FieldEncoding(stack_mask_index_bit_offset_, total_bit_size_); + } + ALWAYS_INLINE size_t BitSize() const { + return total_bit_size_; } void Dump(VariableIndentationOutputStream* vios) const; @@ -774,8 +756,9 @@ class StackMapEncoding { uint8_t dex_pc_bit_offset_; uint8_t dex_register_map_bit_offset_; uint8_t inline_info_bit_offset_; - uint8_t register_mask_bit_offset_; - uint8_t stack_mask_bit_offset_; + uint8_t register_mask_index_bit_offset_; + uint8_t stack_mask_index_bit_offset_; + uint8_t total_bit_size_; }; /** @@ -788,13 +771,13 @@ class StackMapEncoding { * * The information is of the form: * - * [native_pc_offset, dex_pc, dex_register_map_offset, inlining_info_offset, register_mask, - * stack_mask]. + * [native_pc_offset, dex_pc, dex_register_map_offset, inlining_info_offset, register_mask_index, + * stack_mask_index]. */ class StackMap { public: StackMap() {} - explicit StackMap(MemoryRegion region) : region_(region) {} + explicit StackMap(BitMemoryRegion region) : region_(region) {} ALWAYS_INLINE bool IsValid() const { return region_.pointer() != nullptr; } @@ -834,24 +817,20 @@ class StackMap { encoding.GetInlineInfoEncoding().Store(region_, offset); } - ALWAYS_INLINE uint32_t GetRegisterMask(const StackMapEncoding& encoding) const { - return encoding.GetRegisterMaskEncoding().Load(region_); - } - - ALWAYS_INLINE void SetRegisterMask(const StackMapEncoding& encoding, uint32_t mask) { - encoding.GetRegisterMaskEncoding().Store(region_, mask); + ALWAYS_INLINE uint32_t GetRegisterMaskIndex(const StackMapEncoding& encoding) const { + return encoding.GetRegisterMaskIndexEncoding().Load(region_); } - ALWAYS_INLINE size_t GetNumberOfStackMaskBits(const StackMapEncoding& encoding) const { - return region_.size_in_bits() - encoding.GetStackMaskBitOffset(); + ALWAYS_INLINE void SetRegisterMaskIndex(const StackMapEncoding& encoding, uint32_t mask) { + encoding.GetRegisterMaskIndexEncoding().Store(region_, mask); } - ALWAYS_INLINE bool GetStackMaskBit(const StackMapEncoding& encoding, size_t index) const { - return region_.LoadBit(encoding.GetStackMaskBitOffset() + index); + ALWAYS_INLINE uint32_t GetStackMaskIndex(const StackMapEncoding& encoding) const { + return encoding.GetStackMaskIndexEncoding().Load(region_); } - ALWAYS_INLINE void SetStackMaskBit(const StackMapEncoding& encoding, size_t index, bool value) { - region_.StoreBit(encoding.GetStackMaskBitOffset() + index, value); + ALWAYS_INLINE void SetStackMaskIndex(const StackMapEncoding& encoding, uint32_t mask) { + encoding.GetStackMaskIndexEncoding().Store(region_, mask); } ALWAYS_INLINE bool HasDexRegisterMap(const StackMapEncoding& encoding) const { @@ -863,7 +842,9 @@ class StackMap { } ALWAYS_INLINE bool Equals(const StackMap& other) const { - return region_.pointer() == other.region_.pointer() && region_.size() == other.region_.size(); + return region_.pointer() == other.region_.pointer() && + region_.size() == other.region_.size() && + region_.BitOffset() == other.region_.BitOffset(); } void Dump(VariableIndentationOutputStream* vios, @@ -885,7 +866,7 @@ class StackMap { private: static constexpr int kFixedSize = 0; - MemoryRegion region_; + BitMemoryRegion region_; friend class StackMapStream; }; @@ -1051,7 +1032,10 @@ class InlineInfo { struct CodeInfoEncoding { uint32_t non_header_size; uint32_t number_of_stack_maps; - uint32_t stack_map_size_in_bytes; + uint32_t number_of_stack_masks; + uint32_t number_of_register_masks; + uint32_t stack_mask_size_in_bits; + uint32_t register_mask_size_in_bits; uint32_t number_of_location_catalog_entries; StackMapEncoding stack_map_encoding; InlineInfoEncoding inline_info_encoding; @@ -1063,7 +1047,10 @@ struct CodeInfoEncoding { const uint8_t* ptr = reinterpret_cast<const uint8_t*>(data); non_header_size = DecodeUnsignedLeb128(&ptr); number_of_stack_maps = DecodeUnsignedLeb128(&ptr); - stack_map_size_in_bytes = DecodeUnsignedLeb128(&ptr); + number_of_stack_masks = DecodeUnsignedLeb128(&ptr); + number_of_register_masks = DecodeUnsignedLeb128(&ptr); + stack_mask_size_in_bits = DecodeUnsignedLeb128(&ptr); + register_mask_size_in_bits = DecodeUnsignedLeb128(&ptr); number_of_location_catalog_entries = DecodeUnsignedLeb128(&ptr); static_assert(alignof(StackMapEncoding) == 1, "StackMapEncoding should not require alignment"); @@ -1084,7 +1071,10 @@ struct CodeInfoEncoding { void Compress(Vector* dest) const { EncodeUnsignedLeb128(dest, non_header_size); EncodeUnsignedLeb128(dest, number_of_stack_maps); - EncodeUnsignedLeb128(dest, stack_map_size_in_bytes); + EncodeUnsignedLeb128(dest, number_of_stack_masks); + EncodeUnsignedLeb128(dest, number_of_register_masks); + EncodeUnsignedLeb128(dest, stack_mask_size_in_bits); + EncodeUnsignedLeb128(dest, register_mask_size_in_bits); EncodeUnsignedLeb128(dest, number_of_location_catalog_entries); const uint8_t* stack_map_ptr = reinterpret_cast<const uint8_t*>(&stack_map_encoding); dest->insert(dest->end(), stack_map_ptr, stack_map_ptr + sizeof(StackMapEncoding)); @@ -1103,7 +1093,7 @@ struct CodeInfoEncoding { * * where CodeInfoEncoding is of the form: * - * [non_header_size, number_of_stack_maps, stack_map_size_in_bytes, + * [non_header_size, number_of_stack_maps, stack_map_size_in_bits, * number_of_location_catalog_entries, StackMapEncoding] */ class CodeInfo { @@ -1118,7 +1108,7 @@ class CodeInfo { } CodeInfoEncoding ExtractEncoding() const { - CodeInfoEncoding encoding(region_.start()); + CodeInfoEncoding encoding(region_.begin()); AssertValidStackMap(encoding); return encoding; } @@ -1133,9 +1123,41 @@ class CodeInfo { GetDexRegisterLocationCatalogSize(encoding))); } + ALWAYS_INLINE size_t GetNumberOfStackMaskBits(const CodeInfoEncoding& encoding) const { + return encoding.stack_mask_size_in_bits; + } + ALWAYS_INLINE StackMap GetStackMapAt(size_t i, const CodeInfoEncoding& encoding) const { - size_t stack_map_size = encoding.stack_map_size_in_bytes; - return StackMap(GetStackMaps(encoding).Subregion(i * stack_map_size, stack_map_size)); + const size_t map_size = encoding.stack_map_encoding.BitSize(); + return StackMap(BitMemoryRegion(GetStackMaps(encoding), i * map_size, map_size)); + } + + BitMemoryRegion GetStackMask(const CodeInfoEncoding& encoding, size_t stack_mask_index) const { + // All stack mask data is stored before register map data (which is at the very end). + const size_t entry_size = GetNumberOfStackMaskBits(encoding); + const size_t register_mask_bits = + encoding.register_mask_size_in_bits * encoding.number_of_register_masks; + return BitMemoryRegion(region_, + region_.size_in_bits() - register_mask_bits - + entry_size * (stack_mask_index + 1), + entry_size); + } + + BitMemoryRegion GetStackMaskOf(const CodeInfoEncoding& encoding, + const StackMap& stack_map) const { + return GetStackMask(encoding, stack_map.GetStackMaskIndex(encoding.stack_map_encoding)); + } + + BitMemoryRegion GetRegisterMask(const CodeInfoEncoding& encoding, size_t index) const { + const size_t entry_size = encoding.register_mask_size_in_bits; + return BitMemoryRegion(region_, + region_.size_in_bits() - entry_size * (index + 1), + entry_size); + } + + uint32_t GetRegisterMaskOf(const CodeInfoEncoding& encoding, const StackMap& stack_map) const { + size_t index = stack_map.GetRegisterMaskIndex(encoding.stack_map_encoding); + return GetRegisterMask(encoding, index).LoadBits(0u, encoding.register_mask_size_in_bits); } uint32_t GetNumberOfLocationCatalogEntries(const CodeInfoEncoding& encoding) const { @@ -1151,9 +1173,14 @@ class CodeInfo { return encoding.number_of_stack_maps; } + // Get the size of all the stack maps of this CodeInfo object, in bits. Not byte aligned. + ALWAYS_INLINE size_t GetStackMapsSizeInBits(const CodeInfoEncoding& encoding) const { + return encoding.stack_map_encoding.BitSize() * GetNumberOfStackMaps(encoding); + } + // Get the size of all the stack maps of this CodeInfo object, in bytes. size_t GetStackMapsSize(const CodeInfoEncoding& encoding) const { - return encoding.stack_map_size_in_bytes * GetNumberOfStackMaps(encoding); + return RoundUp(GetStackMapsSizeInBits(encoding), kBitsPerByte) / kBitsPerByte; } uint32_t GetDexRegisterLocationCatalogOffset(const CodeInfoEncoding& encoding) const { @@ -1303,7 +1330,7 @@ class CodeInfo { << encoding.non_header_size << "\n" << encoding.number_of_location_catalog_entries << "\n" << encoding.number_of_stack_maps << "\n" - << encoding.stack_map_size_in_bytes; + << encoding.stack_map_encoding.BitSize(); } } diff --git a/runtime/thread.cc b/runtime/thread.cc index d93eab10a0..7b6540436a 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -65,6 +65,7 @@ #include "object_lock.h" #include "quick_exception_handler.h" #include "quick/quick_method_frame_info.h" +#include "read_barrier-inl.h" #include "reflection.h" #include "runtime.h" #include "runtime_callbacks.h" @@ -77,7 +78,7 @@ #include "thread-inl.h" #include "utils.h" #include "verifier/method_verifier.h" -#include "verify_object-inl.h" +#include "verify_object.h" #include "well_known_classes.h" #include "interpreter/interpreter.h" @@ -1047,9 +1048,10 @@ void Thread::ShortDump(std::ostream& os) const { << "]"; } -void Thread::Dump(std::ostream& os, bool dump_native_stack, BacktraceMap* backtrace_map) const { +void Thread::Dump(std::ostream& os, bool dump_native_stack, BacktraceMap* backtrace_map, + bool force_dump_stack) const { DumpState(os); - DumpStack(os, dump_native_stack, backtrace_map); + DumpStack(os, dump_native_stack, backtrace_map, force_dump_stack); } mirror::String* Thread::GetThreadName() const { @@ -1582,15 +1584,24 @@ void Thread::DumpState(std::ostream& os) const { } struct StackDumpVisitor : public StackVisitor { - StackDumpVisitor(std::ostream& os_in, Thread* thread_in, Context* context, bool can_allocate_in) + StackDumpVisitor(std::ostream& os_in, + Thread* thread_in, + Context* context, + bool can_allocate_in, + bool check_suspended = true, + bool dump_locks_in = true) REQUIRES_SHARED(Locks::mutator_lock_) - : StackVisitor(thread_in, context, StackVisitor::StackWalkKind::kIncludeInlinedFrames), + : StackVisitor(thread_in, + context, + StackVisitor::StackWalkKind::kIncludeInlinedFrames, + check_suspended), os(os_in), can_allocate(can_allocate_in), last_method(nullptr), last_line_number(0), repetition_count(0), - frame_count(0) {} + frame_count(0), + dump_locks(dump_locks_in) {} virtual ~StackDumpVisitor() { if (frame_count == 0) { @@ -1635,8 +1646,10 @@ struct StackDumpVisitor : public StackVisitor { if (frame_count == 0) { Monitor::DescribeWait(os, GetThread()); } - if (can_allocate) { + if (can_allocate && dump_locks) { // Visit locks, but do not abort on errors. This would trigger a nested abort. + // Skip visiting locks if dump_locks is false as it would cause a bad_mutexes_held in + // RegTypeCache::RegTypeCache due to thread_list_lock. Monitor::VisitLocks(this, DumpLockedObject, &os, false); } } @@ -1680,6 +1693,7 @@ struct StackDumpVisitor : public StackVisitor { int last_line_number; int repetition_count; int frame_count; + const bool dump_locks; }; static bool ShouldShowNativeStack(const Thread* thread) @@ -1711,7 +1725,7 @@ static bool ShouldShowNativeStack(const Thread* thread) return current_method != nullptr && current_method->IsNative(); } -void Thread::DumpJavaStack(std::ostream& os) const { +void Thread::DumpJavaStack(std::ostream& os, bool check_suspended, bool dump_locks) const { // If flip_function is not null, it means we have run a checkpoint // before the thread wakes up to execute the flip function and the // thread roots haven't been forwarded. So the following access to @@ -1740,7 +1754,7 @@ void Thread::DumpJavaStack(std::ostream& os) const { std::unique_ptr<Context> context(Context::Create()); StackDumpVisitor dumper(os, const_cast<Thread*>(this), context.get(), - !tls32_.throwing_OutOfMemoryError); + !tls32_.throwing_OutOfMemoryError, check_suspended, dump_locks); dumper.WalkStack(); if (have_exception) { @@ -1750,7 +1764,8 @@ void Thread::DumpJavaStack(std::ostream& os) const { void Thread::DumpStack(std::ostream& os, bool dump_native_stack, - BacktraceMap* backtrace_map) const { + BacktraceMap* backtrace_map, + bool force_dump_stack) const { // TODO: we call this code when dying but may not have suspended the thread ourself. The // IsSuspended check is therefore racy with the use for dumping (normally we inhibit // the race with the thread_suspend_count_lock_). @@ -1761,14 +1776,19 @@ void Thread::DumpStack(std::ostream& os, // thread's stack in debug builds where we'll hit the not suspended check in the stack walk. safe_to_dump = (safe_to_dump || dump_for_abort); } - if (safe_to_dump) { + if (safe_to_dump || force_dump_stack) { // If we're currently in native code, dump that stack before dumping the managed stack. - if (dump_native_stack && (dump_for_abort || ShouldShowNativeStack(this))) { + if (dump_native_stack && (dump_for_abort || force_dump_stack || ShouldShowNativeStack(this))) { DumpKernelStack(os, GetTid(), " kernel: ", false); - ArtMethod* method = GetCurrentMethod(nullptr, !dump_for_abort); + ArtMethod* method = + GetCurrentMethod(nullptr, + /*check_suspended*/ !force_dump_stack, + /*abort_on_error*/ !(dump_for_abort || force_dump_stack)); DumpNativeStack(os, GetTid(), backtrace_map, " native: ", method); } - DumpJavaStack(os); + DumpJavaStack(os, + /*check_suspended*/ !force_dump_stack, + /*dump_locks*/ !force_dump_stack); } else { os << "Not able to dump stack of thread that isn't suspended"; } @@ -1843,6 +1863,7 @@ Thread::Thread(bool daemon) : tls32_(daemon), wait_monitor_(nullptr), interrupted_(false), + custom_tls_(nullptr), can_call_into_java_(true) { wait_mutex_ = new Mutex("a thread wait mutex"); wait_cond_ = new ConditionVariable("a thread wait condition variable", *wait_mutex_); @@ -2188,12 +2209,18 @@ void Thread::SetClassLoaderOverride(jobject class_loader_override) { tlsPtr_.class_loader_override = GetJniEnv()->NewGlobalRef(class_loader_override); } -class CountStackDepthVisitor : public StackVisitor { +using ArtMethodDexPcPair = std::pair<ArtMethod*, uint32_t>; + +// Counts the stack trace depth and also fetches the first max_saved_frames frames. +class FetchStackTraceVisitor : public StackVisitor { public: - explicit CountStackDepthVisitor(Thread* thread) + explicit FetchStackTraceVisitor(Thread* thread, + ArtMethodDexPcPair* saved_frames = nullptr, + size_t max_saved_frames = 0) REQUIRES_SHARED(Locks::mutator_lock_) : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames), - depth_(0), skip_depth_(0), skipping_(true) {} + saved_frames_(saved_frames), + max_saved_frames_(max_saved_frames) {} bool VisitFrame() REQUIRES_SHARED(Locks::mutator_lock_) { // We want to skip frames up to and including the exception's constructor. @@ -2206,6 +2233,10 @@ class CountStackDepthVisitor : public StackVisitor { } if (!skipping_) { if (!m->IsRuntimeMethod()) { // Ignore runtime frames (in particular callee save). + if (depth_ < max_saved_frames_) { + saved_frames_[depth_].first = m; + saved_frames_[depth_].second = m->IsProxyMethod() ? DexFile::kDexNoIndex : GetDexPc(); + } ++depth_; } } else { @@ -2214,20 +2245,22 @@ class CountStackDepthVisitor : public StackVisitor { return true; } - int GetDepth() const { + uint32_t GetDepth() const { return depth_; } - int GetSkipDepth() const { + uint32_t GetSkipDepth() const { return skip_depth_; } private: - uint32_t depth_; - uint32_t skip_depth_; - bool skipping_; + uint32_t depth_ = 0; + uint32_t skip_depth_ = 0; + bool skipping_ = true; + ArtMethodDexPcPair* saved_frames_; + const size_t max_saved_frames_; - DISALLOW_COPY_AND_ASSIGN(CountStackDepthVisitor); + DISALLOW_COPY_AND_ASSIGN(FetchStackTraceVisitor); }; template<bool kTransactionActive> @@ -2237,8 +2270,6 @@ class BuildInternalStackTraceVisitor : public StackVisitor { : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames), self_(self), skip_depth_(skip_depth), - count_(0), - trace_(nullptr), pointer_size_(Runtime::Current()->GetClassLinker()->GetImagePointerSize()) {} bool Init(int depth) REQUIRES_SHARED(Locks::mutator_lock_) ACQUIRE(Roles::uninterruptible_) { @@ -2290,17 +2321,21 @@ class BuildInternalStackTraceVisitor : public StackVisitor { if (m->IsRuntimeMethod()) { return true; // Ignore runtime frames (in particular callee save). } + AddFrame(m, m->IsProxyMethod() ? DexFile::kDexNoIndex : GetDexPc()); + return true; + } + + void AddFrame(ArtMethod* method, uint32_t dex_pc) REQUIRES_SHARED(Locks::mutator_lock_) { ObjPtr<mirror::PointerArray> trace_methods_and_pcs = GetTraceMethodsAndPCs(); - trace_methods_and_pcs->SetElementPtrSize<kTransactionActive>(count_, m, pointer_size_); + trace_methods_and_pcs->SetElementPtrSize<kTransactionActive>(count_, method, pointer_size_); trace_methods_and_pcs->SetElementPtrSize<kTransactionActive>( trace_methods_and_pcs->GetLength() / 2 + count_, - m->IsProxyMethod() ? DexFile::kDexNoIndex : GetDexPc(), + dex_pc, pointer_size_); // Save the declaring class of the method to ensure that the declaring classes of the methods // do not get unloaded while the stack trace is live. - trace_->Set(count_ + 1, m->GetDeclaringClass()); + trace_->Set(count_ + 1, method->GetDeclaringClass()); ++count_; - return true; } ObjPtr<mirror::PointerArray> GetTraceMethodsAndPCs() const REQUIRES_SHARED(Locks::mutator_lock_) { @@ -2316,12 +2351,12 @@ class BuildInternalStackTraceVisitor : public StackVisitor { // How many more frames to skip. int32_t skip_depth_; // Current position down stack trace. - uint32_t count_; + uint32_t count_ = 0; // An object array where the first element is a pointer array that contains the ArtMethod // pointers on the stack and dex PCs. The rest of the elements are the declaring // class of the ArtMethod pointers. trace_[i+1] contains the declaring class of the ArtMethod of // the i'th frame. - mirror::ObjectArray<mirror::Object>* trace_; + mirror::ObjectArray<mirror::Object>* trace_ = nullptr; // For cross compilation. const PointerSize pointer_size_; @@ -2330,11 +2365,15 @@ class BuildInternalStackTraceVisitor : public StackVisitor { template<bool kTransactionActive> jobject Thread::CreateInternalStackTrace(const ScopedObjectAccessAlreadyRunnable& soa) const { - // Compute depth of stack - CountStackDepthVisitor count_visitor(const_cast<Thread*>(this)); + // Compute depth of stack, save frames if possible to avoid needing to recompute many. + constexpr size_t kMaxSavedFrames = 256; + std::unique_ptr<ArtMethodDexPcPair[]> saved_frames(new ArtMethodDexPcPair[kMaxSavedFrames]); + FetchStackTraceVisitor count_visitor(const_cast<Thread*>(this), + &saved_frames[0], + kMaxSavedFrames); count_visitor.WalkStack(); - int32_t depth = count_visitor.GetDepth(); - int32_t skip_depth = count_visitor.GetSkipDepth(); + const uint32_t depth = count_visitor.GetDepth(); + const uint32_t skip_depth = count_visitor.GetSkipDepth(); // Build internal stack trace. BuildInternalStackTraceVisitor<kTransactionActive> build_trace_visitor(soa.Self(), @@ -2343,7 +2382,16 @@ jobject Thread::CreateInternalStackTrace(const ScopedObjectAccessAlreadyRunnable if (!build_trace_visitor.Init(depth)) { return nullptr; // Allocation failed. } - build_trace_visitor.WalkStack(); + // If we saved all of the frames we don't even need to do the actual stack walk. This is faster + // than doing the stack walk twice. + if (depth < kMaxSavedFrames) { + for (size_t i = 0; i < depth; ++i) { + build_trace_visitor.AddFrame(saved_frames[i].first, saved_frames[i].second); + } + } else { + build_trace_visitor.WalkStack(); + } + mirror::ObjectArray<mirror::Object>* trace = build_trace_visitor.GetInternalStackTrace(); if (kIsDebugBuild) { ObjPtr<mirror::PointerArray> trace_methods = build_trace_visitor.GetTraceMethodsAndPCs(); @@ -2362,9 +2410,10 @@ template jobject Thread::CreateInternalStackTrace<true>( const ScopedObjectAccessAlreadyRunnable& soa) const; bool Thread::IsExceptionThrownByCurrentMethod(ObjPtr<mirror::Throwable> exception) const { - CountStackDepthVisitor count_visitor(const_cast<Thread*>(this)); + // Only count the depth since we do not pass a stack frame array as an argument. + FetchStackTraceVisitor count_visitor(const_cast<Thread*>(this)); count_visitor.WalkStack(); - return count_visitor.GetDepth() == exception->GetStackDepth(); + return count_visitor.GetDepth() == static_cast<uint32_t>(exception->GetStackDepth()); } jobjectArray Thread::InternalStackTraceToStackTraceElementArray( @@ -2663,6 +2712,10 @@ void Thread::DumpThreadOffset(std::ostream& os, uint32_t offset) { return; \ } QUICK_ENTRY_POINT_INFO(pAllocArrayResolved) + QUICK_ENTRY_POINT_INFO(pAllocArrayResolved8) + QUICK_ENTRY_POINT_INFO(pAllocArrayResolved16) + QUICK_ENTRY_POINT_INFO(pAllocArrayResolved32) + QUICK_ENTRY_POINT_INFO(pAllocArrayResolved64) QUICK_ENTRY_POINT_INFO(pAllocObjectResolved) QUICK_ENTRY_POINT_INFO(pAllocObjectInitialized) QUICK_ENTRY_POINT_INFO(pAllocObjectWithChecks) @@ -2848,13 +2901,16 @@ void Thread::QuickDeliverException() { if (Dbg::IsForcedInterpreterNeededForException(this)) { NthCallerVisitor visitor(this, 0, false); visitor.WalkStack(); - if (Runtime::Current()->IsDeoptimizeable(visitor.caller_pc)) { + if (Runtime::Current()->IsAsyncDeoptimizeable(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(); + } else { + LOG(WARNING) << "Got a deoptimization request on un-deoptimizable method " + << visitor.caller->PrettyMethod(); } } @@ -2881,9 +2937,12 @@ Context* Thread::GetLongJumpContext() { // Note: this visitor may return with a method set, but dex_pc_ being DexFile:kDexNoIndex. This is // so we don't abort in a special situation (thinlocked monitor) when dumping the Java stack. struct CurrentMethodVisitor FINAL : public StackVisitor { - CurrentMethodVisitor(Thread* thread, Context* context, bool abort_on_error) + CurrentMethodVisitor(Thread* thread, Context* context, bool check_suspended, bool abort_on_error) REQUIRES_SHARED(Locks::mutator_lock_) - : StackVisitor(thread, context, StackVisitor::StackWalkKind::kIncludeInlinedFrames), + : StackVisitor(thread, + context, + StackVisitor::StackWalkKind::kIncludeInlinedFrames, + check_suspended), this_object_(nullptr), method_(nullptr), dex_pc_(0), @@ -2907,8 +2966,13 @@ struct CurrentMethodVisitor FINAL : public StackVisitor { const bool abort_on_error_; }; -ArtMethod* Thread::GetCurrentMethod(uint32_t* dex_pc, bool abort_on_error) const { - CurrentMethodVisitor visitor(const_cast<Thread*>(this), nullptr, abort_on_error); +ArtMethod* Thread::GetCurrentMethod(uint32_t* dex_pc, + bool check_suspended, + bool abort_on_error) const { + CurrentMethodVisitor visitor(const_cast<Thread*>(this), + nullptr, + check_suspended, + abort_on_error); visitor.WalkStack(false); if (dex_pc != nullptr) { *dex_pc = visitor.dex_pc_; @@ -3031,9 +3095,10 @@ class ReferenceMapVisitor : public StackVisitor { T vreg_info(m, code_info, encoding, map, visitor_); // Visit stack entries that hold pointers. - size_t number_of_bits = map.GetNumberOfStackMaskBits(encoding.stack_map_encoding); + const size_t number_of_bits = code_info.GetNumberOfStackMaskBits(encoding); + BitMemoryRegion stack_mask = code_info.GetStackMaskOf(encoding, map); for (size_t i = 0; i < number_of_bits; ++i) { - if (map.GetStackMaskBit(encoding.stack_map_encoding, i)) { + if (stack_mask.LoadBit(i)) { auto* ref_addr = vreg_base + i; mirror::Object* ref = ref_addr->AsMirrorPtr(); if (ref != nullptr) { @@ -3041,12 +3106,12 @@ class ReferenceMapVisitor : public StackVisitor { vreg_info.VisitStack(&new_ref, i, this); if (ref != new_ref) { ref_addr->Assign(new_ref); - } + } } } } // Visit callee-save registers that hold pointers. - uint32_t register_mask = map.GetRegisterMask(encoding.stack_map_encoding); + uint32_t register_mask = code_info.GetRegisterMaskOf(encoding, map); for (size_t i = 0; i < BitSizeOf<uint32_t>(); ++i) { if (register_mask & (1 << i)) { mirror::Object** ref_addr = reinterpret_cast<mirror::Object**>(GetGPRAddress(i)); @@ -3419,4 +3484,15 @@ bool Thread::IsAotCompiler() { return Runtime::Current()->IsAotCompiler(); } +mirror::Object* Thread::GetPeerFromOtherThread() const { + mirror::Object* peer = GetPeer(); + if (kUseReadBarrier && Current()->GetIsGcMarking()) { + // We may call Thread::Dump() in the middle of the CC thread flip and this thread's stack + // may have not been flipped yet and peer may be a from-space (stale) ref. So explicitly + // mark/forward it here. + peer = art::ReadBarrier::Mark(peer); + } + return peer; +} + } // namespace art diff --git a/runtime/thread.h b/runtime/thread.h index b609e723e9..3a1b7da406 100644 --- a/runtime/thread.h +++ b/runtime/thread.h @@ -196,11 +196,14 @@ class Thread { // Dumps the detailed thread state and the thread stack (used for SIGQUIT). void Dump(std::ostream& os, bool dump_native_stack = true, - BacktraceMap* backtrace_map = nullptr) const + BacktraceMap* backtrace_map = nullptr, + bool force_dump_stack = false) const REQUIRES(!Locks::thread_suspend_count_lock_) REQUIRES_SHARED(Locks::mutator_lock_); - void DumpJavaStack(std::ostream& os) const + void DumpJavaStack(std::ostream& os, + bool check_suspended = true, + bool dump_locks = true) const REQUIRES(!Locks::thread_suspend_count_lock_) REQUIRES_SHARED(Locks::mutator_lock_); @@ -358,6 +361,10 @@ class Thread { CHECK(tlsPtr_.jpeer == nullptr); return tlsPtr_.opeer; } + // GetPeer is not safe if called on another thread in the middle of the CC thread flip and + // the thread's stack may have not been flipped yet and peer may be a from-space (stale) ref. + // This function will explicitly mark/forward it. + mirror::Object* GetPeerFromOtherThread() const REQUIRES_SHARED(Locks::mutator_lock_); bool HasPeer() const { return tlsPtr_.jpeer != nullptr || tlsPtr_.opeer != nullptr; @@ -410,7 +417,9 @@ class Thread { // Get the current method and dex pc. If there are errors in retrieving the dex pc, this will // abort the runtime iff abort_on_error is true. - ArtMethod* GetCurrentMethod(uint32_t* dex_pc, bool abort_on_error = true) const + ArtMethod* GetCurrentMethod(uint32_t* dex_pc, + bool check_suspended = true, + bool abort_on_error = true) const REQUIRES_SHARED(Locks::mutator_lock_); // Returns whether the given exception was thrown by the current Java method being executed @@ -1204,7 +1213,8 @@ class Thread { void DumpState(std::ostream& os) const REQUIRES_SHARED(Locks::mutator_lock_); void DumpStack(std::ostream& os, bool dump_native_stack = true, - BacktraceMap* backtrace_map = nullptr) const + BacktraceMap* backtrace_map = nullptr, + bool force_dump_stack = false) const REQUIRES(!Locks::thread_suspend_count_lock_) REQUIRES_SHARED(Locks::mutator_lock_); diff --git a/runtime/thread_list.cc b/runtime/thread_list.cc index 01c940e9df..df8acc37a2 100644 --- a/runtime/thread_list.cc +++ b/runtime/thread_list.cc @@ -455,7 +455,6 @@ size_t ThreadList::FlipThreadRoots(Closure* thread_flip_visitor, Closure* flip_callback, gc::collector::GarbageCollector* collector) { TimingLogger::ScopedTiming split("ThreadListFlip", collector->GetTimings()); - const uint64_t start_time = NanoTime(); Thread* self = Thread::Current(); Locks::mutator_lock_->AssertNotHeld(self); Locks::thread_list_lock_->AssertNotHeld(self); @@ -464,13 +463,17 @@ size_t ThreadList::FlipThreadRoots(Closure* thread_flip_visitor, collector->GetHeap()->ThreadFlipBegin(self); // Sync with JNI critical calls. + // ThreadFlipBegin happens before we suspend all the threads, so it does not count towards the + // pause. + const uint64_t suspend_start_time = NanoTime(); SuspendAllInternal(self, self, nullptr); // Run the flip callback for the collector. Locks::mutator_lock_->ExclusiveLock(self); + suspend_all_historam_.AdjustAndAddValue(NanoTime() - suspend_start_time); flip_callback->Run(self); Locks::mutator_lock_->ExclusiveUnlock(self); - collector->RegisterPause(NanoTime() - start_time); + collector->RegisterPause(NanoTime() - suspend_start_time); // Resume runnable threads. size_t runnable_thread_count = 0; @@ -629,8 +632,9 @@ void ThreadList::SuspendAllInternal(Thread* self, MutexLock mu2(self, *Locks::thread_suspend_count_lock_); // Update global suspend all state for attaching threads. ++suspend_all_count_; - if (debug_suspend) + if (debug_suspend) { ++debug_suspend_all_count_; + } pending_threads.StoreRelaxed(list_.size() - num_ignored); // Increment everybody's suspend count (except those that should be ignored). for (const auto& thread : list_) { diff --git a/runtime/thread_pool.cc b/runtime/thread_pool.cc index d9179c3892..d24a5e5c4a 100644 --- a/runtime/thread_pool.cc +++ b/runtime/thread_pool.cc @@ -88,7 +88,10 @@ void ThreadPoolWorker::Run() { void* ThreadPoolWorker::Callback(void* arg) { ThreadPoolWorker* worker = reinterpret_cast<ThreadPoolWorker*>(arg); Runtime* runtime = Runtime::Current(); - CHECK(runtime->AttachCurrentThread(worker->name_.c_str(), true, nullptr, false)); + CHECK(runtime->AttachCurrentThread(worker->name_.c_str(), + true, + nullptr, + worker->thread_pool_->create_peers_)); worker->thread_ = Thread::Current(); // Thread pool workers cannot call into java. worker->thread_->SetCanCallIntoJava(false); @@ -112,7 +115,7 @@ void ThreadPool::RemoveAllTasks(Thread* self) { tasks_.clear(); } -ThreadPool::ThreadPool(const char* name, size_t num_threads) +ThreadPool::ThreadPool(const char* name, size_t num_threads, bool create_peers) : name_(name), task_queue_lock_("task queue lock"), task_queue_condition_("task queue condition", task_queue_lock_), @@ -124,7 +127,8 @@ ThreadPool::ThreadPool(const char* name, size_t num_threads) total_wait_time_(0), // Add one since the caller of constructor waits on the barrier too. creation_barier_(num_threads + 1), - max_active_workers_(num_threads) { + max_active_workers_(num_threads), + create_peers_(create_peers) { Thread* self = Thread::Current(); while (GetThreadCount() < num_threads) { const std::string worker_name = StringPrintf("%s worker thread %zu", name_.c_str(), @@ -217,6 +221,7 @@ Task* ThreadPool::TryGetTaskLocked() { void ThreadPool::Wait(Thread* self, bool do_work, bool may_hold_locks) { if (do_work) { + CHECK(!create_peers_); Task* task = nullptr; while ((task = TryGetTask(self)) != nullptr) { task->Run(self); diff --git a/runtime/thread_pool.h b/runtime/thread_pool.h index 7ecfcd1289..a465e11055 100644 --- a/runtime/thread_pool.h +++ b/runtime/thread_pool.h @@ -105,11 +105,17 @@ class ThreadPool { // Remove all tasks in the queue. void RemoveAllTasks(Thread* self) REQUIRES(!task_queue_lock_); - ThreadPool(const char* name, size_t num_threads); + // Create a named thread pool with the given number of threads. + // + // If create_peers is true, all worker threads will have a Java peer object. Note that if the + // pool is asked to do work on the current thread (see Wait), a peer may not be available. Wait + // will conservatively abort if create_peers and do_work are true. + ThreadPool(const char* name, size_t num_threads, bool create_peers = false); virtual ~ThreadPool(); // Wait for all tasks currently on queue to get completed. If the pool has been stopped, only // wait till all already running tasks are done. + // When the pool was created with peers for workers, do_work must not be true (see ThreadPool()). void Wait(Thread* self, bool do_work, bool may_hold_locks) REQUIRES(!task_queue_lock_); size_t GetTaskCount(Thread* self) REQUIRES(!task_queue_lock_); @@ -159,6 +165,7 @@ class ThreadPool { uint64_t total_wait_time_; Barrier creation_barier_; size_t max_active_workers_ GUARDED_BY(task_queue_lock_); + const bool create_peers_; private: friend class ThreadPoolWorker; diff --git a/runtime/thread_pool_test.cc b/runtime/thread_pool_test.cc index 14c2c3bac8..28aa21f7a2 100644 --- a/runtime/thread_pool_test.cc +++ b/runtime/thread_pool_test.cc @@ -20,6 +20,7 @@ #include "atomic.h" #include "common_runtime_test.h" +#include "scoped_thread_state_change-inl.h" #include "thread-inl.h" namespace art { @@ -159,4 +160,55 @@ TEST_F(ThreadPoolTest, RecursiveTest) { EXPECT_EQ((1 << depth) - 1, count.LoadSequentiallyConsistent()); } +class PeerTask : public Task { + public: + PeerTask() {} + + void Run(Thread* self) { + ScopedObjectAccess soa(self); + CHECK(self->GetPeer() != nullptr); + } + + void Finalize() { + delete this; + } +}; + +class NoPeerTask : public Task { + public: + NoPeerTask() {} + + void Run(Thread* self) { + ScopedObjectAccess soa(self); + CHECK(self->GetPeer() == nullptr); + } + + void Finalize() { + delete this; + } +}; + +// Tests for create_peer functionality. +TEST_F(ThreadPoolTest, PeerTest) { + Thread* self = Thread::Current(); + { + ThreadPool thread_pool("Thread pool test thread pool", 1); + thread_pool.AddTask(self, new NoPeerTask()); + thread_pool.StartWorkers(self); + thread_pool.Wait(self, false, false); + } + + { + // To create peers, the runtime needs to be started. + self->TransitionFromSuspendedToRunnable(); + bool started = runtime_->Start(); + ASSERT_TRUE(started); + + ThreadPool thread_pool("Thread pool test thread pool", 1, true); + thread_pool.AddTask(self, new PeerTask()); + thread_pool.StartWorkers(self); + thread_pool.Wait(self, false, false); + } +} + } // namespace art diff --git a/runtime/trace.cc b/runtime/trace.cc index 2add955f8e..3a9975a4e2 100644 --- a/runtime/trace.cc +++ b/runtime/trace.cc @@ -905,6 +905,9 @@ void Trace::FlushBuf() { void Trace::LogMethodTraceEvent(Thread* thread, ArtMethod* method, instrumentation::Instrumentation::InstrumentationEvent event, uint32_t thread_clock_diff, uint32_t wall_clock_diff) { + // Ensure we always use the non-obsolete version of the method so that entry/exit events have the + // same pointer value. + method = method->GetNonObsoleteMethod(); // Advance cur_offset_ atomically. int32_t new_offset; int32_t old_offset = 0; diff --git a/runtime/transaction.cc b/runtime/transaction.cc index 25369686fd..56ff0a13ac 100644 --- a/runtime/transaction.cc +++ b/runtime/transaction.cc @@ -41,12 +41,12 @@ Transaction::~Transaction() { MutexLock mu(Thread::Current(), log_lock_); size_t objects_count = object_logs_.size(); size_t field_values_count = 0; - for (auto it : object_logs_) { + for (const auto& it : object_logs_) { field_values_count += it.second.Size(); } size_t array_count = array_logs_.size(); size_t array_values_count = 0; - for (auto it : array_logs_) { + for (const auto& it : array_logs_) { array_values_count += it.second.Size(); } size_t intern_string_count = intern_string_logs_.size(); @@ -100,24 +100,30 @@ const std::string& Transaction::GetAbortMessage() { return abort_message_; } -void Transaction::RecordWriteFieldBoolean(mirror::Object* obj, MemberOffset field_offset, - uint8_t value, bool is_volatile) { +void Transaction::RecordWriteFieldBoolean(mirror::Object* obj, + MemberOffset field_offset, + uint8_t value, + bool is_volatile) { DCHECK(obj != nullptr); MutexLock mu(Thread::Current(), log_lock_); ObjectLog& object_log = object_logs_[obj]; object_log.LogBooleanValue(field_offset, value, is_volatile); } -void Transaction::RecordWriteFieldByte(mirror::Object* obj, MemberOffset field_offset, - int8_t value, bool is_volatile) { +void Transaction::RecordWriteFieldByte(mirror::Object* obj, + MemberOffset field_offset, + int8_t value, + bool is_volatile) { DCHECK(obj != nullptr); MutexLock mu(Thread::Current(), log_lock_); ObjectLog& object_log = object_logs_[obj]; object_log.LogByteValue(field_offset, value, is_volatile); } -void Transaction::RecordWriteFieldChar(mirror::Object* obj, MemberOffset field_offset, - uint16_t value, bool is_volatile) { +void Transaction::RecordWriteFieldChar(mirror::Object* obj, + MemberOffset field_offset, + uint16_t value, + bool is_volatile) { DCHECK(obj != nullptr); MutexLock mu(Thread::Current(), log_lock_); ObjectLog& object_log = object_logs_[obj]; @@ -125,8 +131,10 @@ void Transaction::RecordWriteFieldChar(mirror::Object* obj, MemberOffset field_o } -void Transaction::RecordWriteFieldShort(mirror::Object* obj, MemberOffset field_offset, - int16_t value, bool is_volatile) { +void Transaction::RecordWriteFieldShort(mirror::Object* obj, + MemberOffset field_offset, + int16_t value, + bool is_volatile) { DCHECK(obj != nullptr); MutexLock mu(Thread::Current(), log_lock_); ObjectLog& object_log = object_logs_[obj]; @@ -134,7 +142,9 @@ void Transaction::RecordWriteFieldShort(mirror::Object* obj, MemberOffset field_ } -void Transaction::RecordWriteField32(mirror::Object* obj, MemberOffset field_offset, uint32_t value, +void Transaction::RecordWriteField32(mirror::Object* obj, + MemberOffset field_offset, + uint32_t value, bool is_volatile) { DCHECK(obj != nullptr); MutexLock mu(Thread::Current(), log_lock_); @@ -142,7 +152,9 @@ void Transaction::RecordWriteField32(mirror::Object* obj, MemberOffset field_off object_log.Log32BitsValue(field_offset, value, is_volatile); } -void Transaction::RecordWriteField64(mirror::Object* obj, MemberOffset field_offset, uint64_t value, +void Transaction::RecordWriteField64(mirror::Object* obj, + MemberOffset field_offset, + uint64_t value, bool is_volatile) { DCHECK(obj != nullptr); MutexLock mu(Thread::Current(), log_lock_); @@ -150,8 +162,10 @@ void Transaction::RecordWriteField64(mirror::Object* obj, MemberOffset field_off object_log.Log64BitsValue(field_offset, value, is_volatile); } -void Transaction::RecordWriteFieldReference(mirror::Object* obj, MemberOffset field_offset, - mirror::Object* value, bool is_volatile) { +void Transaction::RecordWriteFieldReference(mirror::Object* obj, + MemberOffset field_offset, + mirror::Object* value, + bool is_volatile) { DCHECK(obj != nullptr); MutexLock mu(Thread::Current(), log_lock_); ObjectLog& object_log = object_logs_[obj]; @@ -163,8 +177,12 @@ void Transaction::RecordWriteArray(mirror::Array* array, size_t index, uint64_t DCHECK(array->IsArrayInstance()); DCHECK(!array->IsObjectArray()); MutexLock mu(Thread::Current(), log_lock_); - ArrayLog& array_log = array_logs_[array]; - array_log.LogValue(index, value); + auto it = array_logs_.find(array); + if (it == array_logs_.end()) { + ArrayLog log; + it = array_logs_.emplace(array, std::move(log)).first; + } + it->second.LogValue(index, value); } void Transaction::RecordResolveString(ObjPtr<mirror::DexCache> dex_cache, @@ -172,33 +190,33 @@ void Transaction::RecordResolveString(ObjPtr<mirror::DexCache> dex_cache, DCHECK(dex_cache != nullptr); DCHECK_LT(string_idx.index_, dex_cache->GetDexFile()->NumStringIds()); MutexLock mu(Thread::Current(), log_lock_); - resolve_string_logs_.push_back(ResolveStringLog(dex_cache, string_idx)); + resolve_string_logs_.emplace_back(dex_cache, string_idx); } void Transaction::RecordStrongStringInsertion(ObjPtr<mirror::String> s) { InternStringLog log(s, InternStringLog::kStrongString, InternStringLog::kInsert); - LogInternedString(log); + LogInternedString(std::move(log)); } void Transaction::RecordWeakStringInsertion(ObjPtr<mirror::String> s) { InternStringLog log(s, InternStringLog::kWeakString, InternStringLog::kInsert); - LogInternedString(log); + LogInternedString(std::move(log)); } void Transaction::RecordStrongStringRemoval(ObjPtr<mirror::String> s) { InternStringLog log(s, InternStringLog::kStrongString, InternStringLog::kRemove); - LogInternedString(log); + LogInternedString(std::move(log)); } void Transaction::RecordWeakStringRemoval(ObjPtr<mirror::String> s) { InternStringLog log(s, InternStringLog::kWeakString, InternStringLog::kRemove); - LogInternedString(log); + LogInternedString(std::move(log)); } -void Transaction::LogInternedString(const InternStringLog& log) { +void Transaction::LogInternedString(InternStringLog&& log) { Locks::intern_table_lock_->AssertExclusiveHeld(Thread::Current()); MutexLock mu(Thread::Current(), log_lock_); - intern_string_logs_.push_front(log); + intern_string_logs_.push_front(std::move(log)); } void Transaction::Rollback() { @@ -216,7 +234,7 @@ void Transaction::Rollback() { void Transaction::UndoObjectModifications() { // TODO we may not need to restore objects allocated during this transaction. Or we could directly // remove them from the heap. - for (auto it : object_logs_) { + for (const auto& it : object_logs_) { it.second.Undo(it.first); } object_logs_.clear(); @@ -225,7 +243,7 @@ void Transaction::UndoObjectModifications() { void Transaction::UndoArrayModifications() { // TODO we may not need to restore array allocated during this transaction. Or we could directly // remove them from the heap. - for (auto it : array_logs_) { + for (const auto& it : array_logs_) { it.second.Undo(it.first); } array_logs_.clear(); @@ -235,7 +253,7 @@ void Transaction::UndoInternStringTableModifications() { InternTable* const intern_table = Runtime::Current()->GetInternTable(); // We want to undo each operation from the most recent to the oldest. List has been filled so the // most recent operation is at list begin so just have to iterate over it. - for (InternStringLog& string_log : intern_string_logs_) { + for (const InternStringLog& string_log : intern_string_logs_) { string_log.Undo(intern_table); } intern_string_logs_.clear(); @@ -262,7 +280,7 @@ void Transaction::VisitObjectLogs(RootVisitor* visitor) { std::list<ObjectPair> moving_roots; // Visit roots. - for (auto it : object_logs_) { + for (auto& it : object_logs_) { it.second.VisitRoots(visitor); mirror::Object* old_root = it.first; mirror::Object* new_root = old_root; @@ -279,7 +297,7 @@ void Transaction::VisitObjectLogs(RootVisitor* visitor) { auto old_root_it = object_logs_.find(old_root); CHECK(old_root_it != object_logs_.end()); CHECK(object_logs_.find(new_root) == object_logs_.end()); - object_logs_.insert(std::make_pair(new_root, old_root_it->second)); + object_logs_.emplace(new_root, std::move(old_root_it->second)); object_logs_.erase(old_root_it); } } @@ -289,7 +307,7 @@ void Transaction::VisitArrayLogs(RootVisitor* visitor) { typedef std::pair<mirror::Array*, mirror::Array*> ArrayPair; std::list<ArrayPair> moving_roots; - for (auto it : array_logs_) { + for (auto& it : array_logs_) { mirror::Array* old_root = it.first; CHECK(!old_root->IsObjectArray()); mirror::Array* new_root = old_root; @@ -306,7 +324,7 @@ void Transaction::VisitArrayLogs(RootVisitor* visitor) { auto old_root_it = array_logs_.find(old_root); CHECK(old_root_it != array_logs_.end()); CHECK(array_logs_.find(new_root) == array_logs_.end()); - array_logs_.insert(std::make_pair(new_root, old_root_it->second)); + array_logs_.emplace(new_root, std::move(old_root_it->second)); array_logs_.erase(old_root_it); } } @@ -347,23 +365,27 @@ void Transaction::ObjectLog::Log64BitsValue(MemberOffset offset, uint64_t value, LogValue(ObjectLog::k64Bits, offset, value, is_volatile); } -void Transaction::ObjectLog::LogReferenceValue(MemberOffset offset, mirror::Object* obj, bool is_volatile) { +void Transaction::ObjectLog::LogReferenceValue(MemberOffset offset, + mirror::Object* obj, + bool is_volatile) { LogValue(ObjectLog::kReference, offset, reinterpret_cast<uintptr_t>(obj), is_volatile); } void Transaction::ObjectLog::LogValue(ObjectLog::FieldValueKind kind, - MemberOffset offset, uint64_t value, bool is_volatile) { + MemberOffset offset, + uint64_t value, + bool is_volatile) { auto it = field_values_.find(offset.Uint32Value()); if (it == field_values_.end()) { ObjectLog::FieldValue field_value; field_value.value = value; field_value.is_volatile = is_volatile; field_value.kind = kind; - field_values_.insert(std::make_pair(offset.Uint32Value(), field_value)); + field_values_.emplace(offset.Uint32Value(), std::move(field_value)); } } -void Transaction::ObjectLog::Undo(mirror::Object* obj) { +void Transaction::ObjectLog::Undo(mirror::Object* obj) const { for (auto& it : field_values_) { // Garbage collector needs to access object's class and array's length. So we don't rollback // these values. @@ -377,60 +399,71 @@ void Transaction::ObjectLog::Undo(mirror::Object* obj) { // Skip Array::length field. continue; } - FieldValue& field_value = it.second; + const FieldValue& field_value = it.second; UndoFieldWrite(obj, field_offset, field_value); } } -void Transaction::ObjectLog::UndoFieldWrite(mirror::Object* obj, MemberOffset field_offset, - const FieldValue& field_value) { +void Transaction::ObjectLog::UndoFieldWrite(mirror::Object* obj, + MemberOffset field_offset, + const FieldValue& field_value) const { // TODO We may want to abort a transaction while still being in transaction mode. In this case, // we'd need to disable the check. constexpr bool kCheckTransaction = true; switch (field_value.kind) { case kBoolean: if (UNLIKELY(field_value.is_volatile)) { - obj->SetFieldBooleanVolatile<false, kCheckTransaction>(field_offset, - static_cast<bool>(field_value.value)); + obj->SetFieldBooleanVolatile<false, kCheckTransaction>( + field_offset, + static_cast<bool>(field_value.value)); } else { - obj->SetFieldBoolean<false, kCheckTransaction>(field_offset, - static_cast<bool>(field_value.value)); + obj->SetFieldBoolean<false, kCheckTransaction>( + field_offset, + static_cast<bool>(field_value.value)); } break; case kByte: if (UNLIKELY(field_value.is_volatile)) { - obj->SetFieldByteVolatile<false, kCheckTransaction>(field_offset, - static_cast<int8_t>(field_value.value)); + obj->SetFieldByteVolatile<false, kCheckTransaction>( + field_offset, + static_cast<int8_t>(field_value.value)); } else { - obj->SetFieldByte<false, kCheckTransaction>(field_offset, - static_cast<int8_t>(field_value.value)); + obj->SetFieldByte<false, kCheckTransaction>( + field_offset, + static_cast<int8_t>(field_value.value)); } break; case kChar: if (UNLIKELY(field_value.is_volatile)) { - obj->SetFieldCharVolatile<false, kCheckTransaction>(field_offset, - static_cast<uint16_t>(field_value.value)); + obj->SetFieldCharVolatile<false, kCheckTransaction>( + field_offset, + static_cast<uint16_t>(field_value.value)); } else { - obj->SetFieldChar<false, kCheckTransaction>(field_offset, - static_cast<uint16_t>(field_value.value)); + obj->SetFieldChar<false, kCheckTransaction>( + field_offset, + static_cast<uint16_t>(field_value.value)); } break; case kShort: if (UNLIKELY(field_value.is_volatile)) { - obj->SetFieldShortVolatile<false, kCheckTransaction>(field_offset, - static_cast<int16_t>(field_value.value)); + obj->SetFieldShortVolatile<false, kCheckTransaction>( + field_offset, + static_cast<int16_t>(field_value.value)); } else { - obj->SetFieldShort<false, kCheckTransaction>(field_offset, - static_cast<int16_t>(field_value.value)); + obj->SetFieldShort<false, kCheckTransaction>( + field_offset, + static_cast<int16_t>(field_value.value)); } break; case k32Bits: if (UNLIKELY(field_value.is_volatile)) { - obj->SetField32Volatile<false, kCheckTransaction>(field_offset, - static_cast<uint32_t>(field_value.value)); + obj->SetField32Volatile<false, kCheckTransaction>( + field_offset, + static_cast<uint32_t>(field_value.value)); } else { - obj->SetField32<false, kCheckTransaction>(field_offset, - static_cast<uint32_t>(field_value.value)); + obj->SetField32<false, kCheckTransaction>( + field_offset, + static_cast<uint32_t>(field_value.value)); } break; case k64Bits: @@ -442,11 +475,13 @@ void Transaction::ObjectLog::UndoFieldWrite(mirror::Object* obj, MemberOffset fi break; case kReference: if (UNLIKELY(field_value.is_volatile)) { - obj->SetFieldObjectVolatile<false, kCheckTransaction>(field_offset, - reinterpret_cast<mirror::Object*>(field_value.value)); + obj->SetFieldObjectVolatile<false, kCheckTransaction>( + field_offset, + reinterpret_cast<mirror::Object*>(field_value.value)); } else { - obj->SetFieldObject<false, kCheckTransaction>(field_offset, - reinterpret_cast<mirror::Object*>(field_value.value)); + obj->SetFieldObject<false, kCheckTransaction>( + field_offset, + reinterpret_cast<mirror::Object*>(field_value.value)); } break; default: @@ -456,7 +491,7 @@ void Transaction::ObjectLog::UndoFieldWrite(mirror::Object* obj, MemberOffset fi } void Transaction::ObjectLog::VisitRoots(RootVisitor* visitor) { - for (auto it : field_values_) { + for (auto& it : field_values_) { FieldValue& field_value = it.second; if (field_value.kind == ObjectLog::kReference) { visitor->VisitRootIfNonNull(reinterpret_cast<mirror::Object**>(&field_value.value), @@ -465,7 +500,7 @@ void Transaction::ObjectLog::VisitRoots(RootVisitor* visitor) { } } -void Transaction::InternStringLog::Undo(InternTable* intern_table) { +void Transaction::InternStringLog::Undo(InternTable* intern_table) const { DCHECK(intern_table != nullptr); switch (string_op_) { case InternStringLog::kInsert: { @@ -506,7 +541,7 @@ void Transaction::InternStringLog::VisitRoots(RootVisitor* visitor) { str_.VisitRoot(visitor, RootInfo(kRootInternedString)); } -void Transaction::ResolveStringLog::Undo() { +void Transaction::ResolveStringLog::Undo() const { dex_cache_.Read()->ClearString(string_idx_); } @@ -538,7 +573,7 @@ void Transaction::ArrayLog::LogValue(size_t index, uint64_t value) { } } -void Transaction::ArrayLog::Undo(mirror::Array* array) { +void Transaction::ArrayLog::Undo(mirror::Array* array) const { DCHECK(array != nullptr); DCHECK(array->IsArrayInstance()); Primitive::Type type = array->GetClass()->GetComponentType()->GetPrimitiveType(); @@ -547,8 +582,10 @@ void Transaction::ArrayLog::Undo(mirror::Array* array) { } } -void Transaction::ArrayLog::UndoArrayWrite(mirror::Array* array, Primitive::Type array_type, - size_t index, uint64_t value) { +void Transaction::ArrayLog::UndoArrayWrite(mirror::Array* array, + Primitive::Type array_type, + size_t index, + uint64_t value) const { // TODO We may want to abort a transaction while still being in transaction mode. In this case, // we'd need to disable the check. switch (array_type) { diff --git a/runtime/transaction.h b/runtime/transaction.h index 1774657d40..7aa98cd33d 100644 --- a/runtime/transaction.h +++ b/runtime/transaction.h @@ -56,26 +56,40 @@ class Transaction FINAL { bool IsAborted() REQUIRES(!log_lock_); // Record object field changes. - void RecordWriteFieldBoolean(mirror::Object* obj, MemberOffset field_offset, uint8_t value, + void RecordWriteFieldBoolean(mirror::Object* obj, + MemberOffset field_offset, + uint8_t value, bool is_volatile) REQUIRES(!log_lock_); - void RecordWriteFieldByte(mirror::Object* obj, MemberOffset field_offset, int8_t value, - bool is_volatile) + void RecordWriteFieldByte(mirror::Object* obj, + MemberOffset field_offset, + int8_t value, + bool is_volatile) REQUIRES(!log_lock_); - void RecordWriteFieldChar(mirror::Object* obj, MemberOffset field_offset, uint16_t value, + void RecordWriteFieldChar(mirror::Object* obj, + MemberOffset field_offset, + uint16_t value, bool is_volatile) REQUIRES(!log_lock_); - void RecordWriteFieldShort(mirror::Object* obj, MemberOffset field_offset, int16_t value, + void RecordWriteFieldShort(mirror::Object* obj, + MemberOffset field_offset, + int16_t value, bool is_volatile) REQUIRES(!log_lock_); - void RecordWriteField32(mirror::Object* obj, MemberOffset field_offset, uint32_t value, + void RecordWriteField32(mirror::Object* obj, + MemberOffset field_offset, + uint32_t value, bool is_volatile) REQUIRES(!log_lock_); - void RecordWriteField64(mirror::Object* obj, MemberOffset field_offset, uint64_t value, + void RecordWriteField64(mirror::Object* obj, + MemberOffset field_offset, + uint64_t value, bool is_volatile) REQUIRES(!log_lock_); - void RecordWriteFieldReference(mirror::Object* obj, MemberOffset field_offset, - mirror::Object* value, bool is_volatile) + void RecordWriteFieldReference(mirror::Object* obj, + MemberOffset field_offset, + mirror::Object* value, + bool is_volatile) REQUIRES(!log_lock_); // Record array change. @@ -122,13 +136,16 @@ class Transaction FINAL { void Log64BitsValue(MemberOffset offset, uint64_t value, bool is_volatile); void LogReferenceValue(MemberOffset offset, mirror::Object* obj, bool is_volatile); - void Undo(mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_); + void Undo(mirror::Object* obj) const REQUIRES_SHARED(Locks::mutator_lock_); void VisitRoots(RootVisitor* visitor) REQUIRES_SHARED(Locks::mutator_lock_); size_t Size() const { return field_values_.size(); } + ObjectLog() = default; + ObjectLog(ObjectLog&& log) = default; + private: enum FieldValueKind { kBoolean, @@ -144,33 +161,49 @@ class Transaction FINAL { uint64_t value; FieldValueKind kind; bool is_volatile; + + FieldValue() = default; + FieldValue(FieldValue&& log) = default; + + private: + DISALLOW_COPY_AND_ASSIGN(FieldValue); }; void LogValue(FieldValueKind kind, MemberOffset offset, uint64_t value, bool is_volatile); - void UndoFieldWrite(mirror::Object* obj, MemberOffset field_offset, - const FieldValue& field_value) REQUIRES_SHARED(Locks::mutator_lock_); + void UndoFieldWrite(mirror::Object* obj, + MemberOffset field_offset, + const FieldValue& field_value) const REQUIRES_SHARED(Locks::mutator_lock_); // Maps field's offset to its value. std::map<uint32_t, FieldValue> field_values_; + + DISALLOW_COPY_AND_ASSIGN(ObjectLog); }; class ArrayLog : public ValueObject { public: void LogValue(size_t index, uint64_t value); - void Undo(mirror::Array* obj) REQUIRES_SHARED(Locks::mutator_lock_); + void Undo(mirror::Array* obj) const REQUIRES_SHARED(Locks::mutator_lock_); size_t Size() const { return array_values_.size(); } + ArrayLog() = default; + ArrayLog(ArrayLog&& log) = default; + private: - void UndoArrayWrite(mirror::Array* array, Primitive::Type array_type, size_t index, - uint64_t value) REQUIRES_SHARED(Locks::mutator_lock_); + void UndoArrayWrite(mirror::Array* array, + Primitive::Type array_type, + size_t index, + uint64_t value) const REQUIRES_SHARED(Locks::mutator_lock_); // Maps index to value. // TODO use JValue instead ? std::map<size_t, uint64_t> array_values_; + + DISALLOW_COPY_AND_ASSIGN(ArrayLog); }; class InternStringLog : public ValueObject { @@ -185,31 +218,38 @@ class Transaction FINAL { }; InternStringLog(ObjPtr<mirror::String> s, StringKind kind, StringOp op); - void Undo(InternTable* intern_table) + void Undo(InternTable* intern_table) const REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(Locks::intern_table_lock_); void VisitRoots(RootVisitor* visitor) REQUIRES_SHARED(Locks::mutator_lock_); + InternStringLog() = default; + InternStringLog(InternStringLog&& log) = default; + private: - GcRoot<mirror::String> str_; + mutable GcRoot<mirror::String> str_; const StringKind string_kind_; const StringOp string_op_; + + DISALLOW_COPY_AND_ASSIGN(InternStringLog); }; class ResolveStringLog : public ValueObject { public: ResolveStringLog(ObjPtr<mirror::DexCache> dex_cache, dex::StringIndex string_idx); - void Undo() REQUIRES_SHARED(Locks::mutator_lock_); + void Undo() const REQUIRES_SHARED(Locks::mutator_lock_); void VisitRoots(RootVisitor* visitor) REQUIRES_SHARED(Locks::mutator_lock_); private: GcRoot<mirror::DexCache> dex_cache_; const dex::StringIndex string_idx_; + + DISALLOW_COPY_AND_ASSIGN(ResolveStringLog); }; - void LogInternedString(const InternStringLog& log) + void LogInternedString(InternStringLog&& log) REQUIRES(Locks::intern_table_lock_) REQUIRES(!log_lock_); diff --git a/runtime/utils.cc b/runtime/utils.cc index 8867743ec1..6a20eaf9e0 100644 --- a/runtime/utils.cc +++ b/runtime/utils.cc @@ -415,6 +415,22 @@ std::string PrintableString(const char* utf) { return result; } +std::string GetJniShortName(const std::string& class_descriptor, const std::string& method) { + // Remove the leading 'L' and trailing ';'... + std::string class_name(class_descriptor); + CHECK_EQ(class_name[0], 'L') << class_name; + CHECK_EQ(class_name[class_name.size() - 1], ';') << class_name; + class_name.erase(0, 1); + class_name.erase(class_name.size() - 1, 1); + + std::string short_name; + short_name += "Java_"; + short_name += MangleForJni(class_name); + short_name += "_"; + short_name += MangleForJni(method); + return short_name; +} + // See http://java.sun.com/j2se/1.5.0/docs/guide/jni/spec/design.html#wp615 for the full rules. std::string MangleForJni(const std::string& s) { std::string result; @@ -788,49 +804,58 @@ void GetTaskStats(pid_t tid, char* state, int* utime, int* stime, int* task_cpu) *task_cpu = strtoull(fields[36].c_str(), nullptr, 10); } -const char* GetAndroidRoot() { - const char* android_root = getenv("ANDROID_ROOT"); - if (android_root == nullptr) { - if (OS::DirectoryExists("/system")) { - android_root = "/system"; +static const char* GetAndroidDirSafe(const char* env_var, + const char* default_dir, + std::string* error_msg) { + const char* android_dir = getenv(env_var); + if (android_dir == nullptr) { + if (OS::DirectoryExists(default_dir)) { + android_dir = default_dir; } else { - LOG(FATAL) << "ANDROID_ROOT not set and /system does not exist"; - return ""; + *error_msg = StringPrintf("%s not set and %s does not exist", env_var, default_dir); + return nullptr; } } - if (!OS::DirectoryExists(android_root)) { - LOG(FATAL) << "Failed to find ANDROID_ROOT directory " << android_root; - return ""; + if (!OS::DirectoryExists(android_dir)) { + *error_msg = StringPrintf("Failed to find %s directory %s", env_var, android_dir); + return nullptr; } - return android_root; + return android_dir; } -const char* GetAndroidData() { +const char* GetAndroidDir(const char* env_var, const char* default_dir) { std::string error_msg; - const char* dir = GetAndroidDataSafe(&error_msg); + const char* dir = GetAndroidDirSafe(env_var, default_dir, &error_msg); if (dir != nullptr) { return dir; } else { LOG(FATAL) << error_msg; - return ""; + return nullptr; } } +const char* GetAndroidRoot() { + return GetAndroidDir("ANDROID_ROOT", "/system"); +} + +const char* GetAndroidRootSafe(std::string* error_msg) { + return GetAndroidDirSafe("ANDROID_ROOT", "/system", error_msg); +} + +const char* GetAndroidData() { + return GetAndroidDir("ANDROID_DATA", "/data"); +} + const char* GetAndroidDataSafe(std::string* error_msg) { - const char* android_data = getenv("ANDROID_DATA"); - if (android_data == nullptr) { - if (OS::DirectoryExists("/data")) { - android_data = "/data"; - } else { - *error_msg = "ANDROID_DATA not set and /data does not exist"; - return nullptr; - } - } - if (!OS::DirectoryExists(android_data)) { - *error_msg = StringPrintf("Failed to find ANDROID_DATA directory %s", android_data); - return nullptr; + return GetAndroidDirSafe("ANDROID_DATA", "/data", error_msg); +} + +std::string GetDefaultBootImageLocation(std::string* error_msg) { + const char* android_root = GetAndroidRootSafe(error_msg); + if (android_root == nullptr) { + return ""; } - return android_data; + return StringPrintf("%s/framework/boot.art", android_root); } void GetDalvikCache(const char* subdir, const bool create_if_absent, std::string* dalvik_cache, @@ -904,74 +929,6 @@ std::string GetSystemImageFilename(const char* location, const InstructionSet is return filename; } -int ExecAndReturnCode(std::vector<std::string>& arg_vector, std::string* error_msg) { - const std::string command_line(android::base::Join(arg_vector, ' ')); - CHECK_GE(arg_vector.size(), 1U) << command_line; - - // Convert the args to char pointers. - const char* program = arg_vector[0].c_str(); - std::vector<char*> args; - for (size_t i = 0; i < arg_vector.size(); ++i) { - const std::string& arg = arg_vector[i]; - char* arg_str = const_cast<char*>(arg.c_str()); - CHECK(arg_str != nullptr) << i; - args.push_back(arg_str); - } - args.push_back(nullptr); - - // fork and exec - pid_t pid = fork(); - if (pid == 0) { - // no allocation allowed between fork and exec - - // change process groups, so we don't get reaped by ProcessManager - setpgid(0, 0); - - // (b/30160149): protect subprocesses from modifications to LD_LIBRARY_PATH, etc. - // Use the snapshot of the environment from the time the runtime was created. - char** envp = (Runtime::Current() == nullptr) ? nullptr : Runtime::Current()->GetEnvSnapshot(); - if (envp == nullptr) { - execv(program, &args[0]); - } else { - execve(program, &args[0], envp); - } - PLOG(ERROR) << "Failed to execve(" << command_line << ")"; - // _exit to avoid atexit handlers in child. - _exit(1); - } else { - if (pid == -1) { - *error_msg = StringPrintf("Failed to execv(%s) because fork failed: %s", - command_line.c_str(), strerror(errno)); - return -1; - } - - // wait for subprocess to finish - int status = -1; - pid_t got_pid = TEMP_FAILURE_RETRY(waitpid(pid, &status, 0)); - if (got_pid != pid) { - *error_msg = StringPrintf("Failed after fork for execv(%s) because waitpid failed: " - "wanted %d, got %d: %s", - command_line.c_str(), pid, got_pid, strerror(errno)); - return -1; - } - if (WIFEXITED(status)) { - return WEXITSTATUS(status); - } - return -1; - } -} - -bool Exec(std::vector<std::string>& arg_vector, std::string* error_msg) { - int status = ExecAndReturnCode(arg_vector, error_msg); - if (status != 0) { - const std::string command_line(android::base::Join(arg_vector, ' ')); - *error_msg = StringPrintf("Failed execv(%s) because non-0 exit status", - command_line.c_str()); - return false; - } - return true; -} - bool FileExists(const std::string& filename) { struct stat buffer; return stat(filename.c_str(), &buffer) == 0; diff --git a/runtime/utils.h b/runtime/utils.h index 16ef706159..67438b5881 100644 --- a/runtime/utils.h +++ b/runtime/utils.h @@ -101,6 +101,8 @@ std::string PrettySize(int64_t size_in_bytes); // of the JNI spec. std::string MangleForJni(const std::string& s); +std::string GetJniShortName(const std::string& class_name, const std::string& method_name); + // Turn "java.lang.String" into "Ljava/lang/String;". std::string DotToDescriptor(const char* class_name); @@ -143,12 +145,18 @@ void SetThreadName(const char* thread_name); // Find $ANDROID_ROOT, /system, or abort. const char* GetAndroidRoot(); +// Find $ANDROID_ROOT, /system, or return null. +const char* GetAndroidRootSafe(std::string* error_msg); // Find $ANDROID_DATA, /data, or abort. const char* GetAndroidData(); // Find $ANDROID_DATA, /data, or return null. const char* GetAndroidDataSafe(std::string* error_msg); +// Returns the default boot image location (ANDROID_ROOT/framework/boot.art). +// Returns an empty string if ANDROID_ROOT is not set. +std::string GetDefaultBootImageLocation(std::string* error_msg); + // Returns the dalvik-cache location, with subdir appended. Returns the empty string if the cache // could not be found. std::string GetDalvikCache(const char* subdir); @@ -167,13 +175,6 @@ bool GetDalvikCacheFilename(const char* file_location, const char* cache_locatio // Returns the system location for an image std::string GetSystemImageFilename(const char* location, InstructionSet isa); -// Wrapper on fork/execv to run a command in a subprocess. -// Both of these spawn child processes using the environment as it was set when the single instance -// of the runtime (Runtime::Current()) was started. If no instance of the runtime was started, it -// will use the current environment settings. -bool Exec(std::vector<std::string>& arg_vector, std::string* error_msg); -int ExecAndReturnCode(std::vector<std::string>& arg_vector, std::string* error_msg); - // Returns true if the file exists. bool FileExists(const std::string& filename); bool FileExistsAndNotEmpty(const std::string& filename); diff --git a/runtime/utils_test.cc b/runtime/utils_test.cc index 82d92fc2fc..02f1e1bbfe 100644 --- a/runtime/utils_test.cc +++ b/runtime/utils_test.cc @@ -21,6 +21,7 @@ #include "base/enums.h" #include "class_linker-inl.h" #include "common_runtime_test.h" +#include "exec_utils.h" #include "mirror/array.h" #include "mirror/array-inl.h" #include "mirror/object-inl.h" diff --git a/runtime/vdex_file.cc b/runtime/vdex_file.cc index dabf8c8e93..2481c8ba46 100644 --- a/runtime/vdex_file.cc +++ b/runtime/vdex_file.cc @@ -49,10 +49,10 @@ VdexFile::Header::Header(uint32_t number_of_dex_files, DCHECK(IsVersionValid()); } -VdexFile* VdexFile::Open(const std::string& vdex_filename, - bool writable, - bool low_4gb, - std::string* error_msg) { +std::unique_ptr<VdexFile> VdexFile::Open(const std::string& vdex_filename, + bool writable, + bool low_4gb, + std::string* error_msg) { if (!OS::FileExists(vdex_filename.c_str())) { *error_msg = "File " + vdex_filename + " does not exist."; return nullptr; @@ -79,12 +79,12 @@ VdexFile* VdexFile::Open(const std::string& vdex_filename, return Open(vdex_file->Fd(), vdex_length, vdex_filename, writable, low_4gb, error_msg); } -VdexFile* VdexFile::Open(int file_fd, - size_t vdex_length, - const std::string& vdex_filename, - bool writable, - bool low_4gb, - std::string* error_msg) { +std::unique_ptr<VdexFile> VdexFile::Open(int file_fd, + size_t vdex_length, + const std::string& vdex_filename, + bool writable, + bool low_4gb, + std::string* error_msg) { std::unique_ptr<MemMap> mmap(MemMap::MapFile(vdex_length, writable ? PROT_READ | PROT_WRITE : PROT_READ, MAP_SHARED, @@ -98,8 +98,14 @@ VdexFile* VdexFile::Open(int file_fd, return nullptr; } + std::unique_ptr<VdexFile> vdex(new VdexFile(mmap.release())); + if (!vdex->IsValid()) { + *error_msg = "Vdex file is not valid"; + return nullptr; + } + *error_msg = "Success"; - return new VdexFile(mmap.release()); + return vdex; } const uint8_t* VdexFile::GetNextDexFileData(const uint8_t* cursor) const { diff --git a/runtime/vdex_file.h b/runtime/vdex_file.h index bb9844af65..7daf2f8d7b 100644 --- a/runtime/vdex_file.h +++ b/runtime/vdex_file.h @@ -61,7 +61,7 @@ class VdexFile { private: static constexpr uint8_t kVdexMagic[] = { 'v', 'd', 'e', 'x' }; - static constexpr uint8_t kVdexVersion[] = { '0', '0', '2', '\0' }; // Handle verify-profile + static constexpr uint8_t kVdexVersion[] = { '0', '0', '3', '\0' }; // Remove verify-profile uint8_t magic_[4]; uint8_t version_[4]; @@ -73,17 +73,19 @@ class VdexFile { typedef uint32_t VdexChecksum; - static VdexFile* Open(const std::string& vdex_filename, - bool writable, - bool low_4gb, - std::string* error_msg); - - static VdexFile* Open(int file_fd, - size_t vdex_length, - const std::string& vdex_filename, - bool writable, - bool low_4gb, - std::string* error_msg); + // Returns nullptr if the vdex file cannot be opened or is not valid. + static std::unique_ptr<VdexFile> Open(const std::string& vdex_filename, + bool writable, + bool low_4gb, + std::string* error_msg); + + // Returns nullptr if the vdex file cannot be opened or is not valid. + static std::unique_ptr<VdexFile> Open(int file_fd, + size_t vdex_length, + const std::string& vdex_filename, + bool writable, + bool low_4gb, + std::string* error_msg); const uint8_t* Begin() const { return mmap_->Begin(); } const uint8_t* End() const { return mmap_->End(); } diff --git a/runtime/vdex_file_test.cc b/runtime/vdex_file_test.cc new file mode 100644 index 0000000000..909e117ccc --- /dev/null +++ b/runtime/vdex_file_test.cc @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017 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 "vdex_file.h" + +#include <string> + +#include <gtest/gtest.h> + +#include "common_runtime_test.h" + +namespace art { + +class VdexFileTest : public CommonRuntimeTest { +}; + +TEST_F(VdexFileTest, OpenEmptyVdex) { + // Verify we fail to open an empty vdex file. + ScratchFile tmp; + std::string error_msg; + std::unique_ptr<VdexFile> vdex = VdexFile::Open(tmp.GetFd(), + 0, + tmp.GetFilename(), + /*writable*/false, + /*low_4gb*/false, + &error_msg); + EXPECT_TRUE(vdex == nullptr); + + vdex = VdexFile::Open(tmp.GetFilename(), /*writable*/false, /*low_4gb*/false, &error_msg); + EXPECT_TRUE(vdex == nullptr); +} + +} // namespace art diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc index b915457557..5f55f3fd29 100644 --- a/runtime/verifier/method_verifier.cc +++ b/runtime/verifier/method_verifier.cc @@ -415,12 +415,12 @@ MethodVerifier::FailureData MethodVerifier::VerifyMethod(Thread* self, result.kind = kSoftFailure; if (method != nullptr && !CanCompilerHandleVerificationFailure(verifier.encountered_failure_types_)) { - method->AddAccessFlags(kAccCompileDontBother); + method->SetDontCompile(); } } if (method != nullptr) { if (verifier.HasInstructionThatWillThrow()) { - method->AddAccessFlags(kAccCompileDontBother); + method->SetDontCompile(); if (Runtime::Current()->IsAotCompiler() && (callbacks != nullptr) && !callbacks->IsBootImage()) { // When compiling apps, make HasInstructionThatWillThrow a soft error to trigger diff --git a/runtime/verify_object-inl.h b/runtime/verify_object-inl.h index 43151dd425..363fde220d 100644 --- a/runtime/verify_object-inl.h +++ b/runtime/verify_object-inl.h @@ -19,33 +19,11 @@ #include "verify_object.h" -#include "gc/heap.h" #include "mirror/object-inl.h" #include "obj_ptr-inl.h" namespace art { -inline void VerifyObject(ObjPtr<mirror::Object> obj) { - if (kVerifyObjectSupport > kVerifyObjectModeDisabled && obj != nullptr) { - if (kVerifyObjectSupport > kVerifyObjectModeFast) { - // Slow object verification, try the heap right away. - Runtime::Current()->GetHeap()->VerifyObjectBody(obj); - } else { - // Fast object verification, only call the heap if our quick sanity tests fail. The heap will - // print the diagnostic message. - bool failed = !IsAligned<kObjectAlignment>(obj.Ptr()); - if (!failed) { - mirror::Class* c = obj->GetClass<kVerifyNone>(); - failed = failed || !IsAligned<kObjectAlignment>(c); - failed = failed || !VerifyClassClass(c); - } - if (UNLIKELY(failed)) { - Runtime::Current()->GetHeap()->VerifyObjectBody(obj); - } - } - } -} - inline bool VerifyClassClass(ObjPtr<mirror::Class> c) { if (UNLIKELY(c == nullptr)) { return false; diff --git a/runtime/verify_object.cc b/runtime/verify_object.cc new file mode 100644 index 0000000000..a031a07a94 --- /dev/null +++ b/runtime/verify_object.cc @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2017 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 "verify_object-inl.h" + +#include "base/bit_utils.h" +#include "gc/heap.h" +#include "globals.h" +#include "mirror/object-inl.h" +#include "obj_ptr-inl.h" +#include "runtime.h" + +namespace art { + +void VerifyObjectImpl(ObjPtr<mirror::Object> obj) { + if (kVerifyObjectSupport > kVerifyObjectModeFast) { + // Slow object verification, try the heap right away. + Runtime::Current()->GetHeap()->VerifyObjectBody(obj); + } else { + // Fast object verification, only call the heap if our quick sanity tests fail. The heap will + // print the diagnostic message. + bool failed = !IsAligned<kObjectAlignment>(obj.Ptr()); + if (!failed) { + mirror::Class* c = obj->GetClass<kVerifyNone>(); + failed = failed || !IsAligned<kObjectAlignment>(c); + failed = failed || !VerifyClassClass(c); + } + if (UNLIKELY(failed)) { + Runtime::Current()->GetHeap()->VerifyObjectBody(obj); + } + } +} + +} // namespace art diff --git a/runtime/verify_object.h b/runtime/verify_object.h index 384e56f7f4..519f7f5f5a 100644 --- a/runtime/verify_object.h +++ b/runtime/verify_object.h @@ -53,7 +53,16 @@ static constexpr VerifyObjectFlags kDefaultVerifyFlags = kVerifyNone; static constexpr VerifyObjectMode kVerifyObjectSupport = kDefaultVerifyFlags != 0 ? kVerifyObjectModeFast : kVerifyObjectModeDisabled; -ALWAYS_INLINE void VerifyObject(ObjPtr<mirror::Object> obj) NO_THREAD_SAFETY_ANALYSIS; +// Implements the actual object checks. +void VerifyObjectImpl(ObjPtr<mirror::Object> obj) NO_THREAD_SAFETY_ANALYSIS; + +// Is a front to optimize out any calls if no verification is enabled. +ALWAYS_INLINE +static inline void VerifyObject(ObjPtr<mirror::Object> obj) NO_THREAD_SAFETY_ANALYSIS { + if (kVerifyObjectSupport > kVerifyObjectModeDisabled && obj != nullptr) { + VerifyObjectImpl(obj); + } +} // Check that c.getClass() == c.getClass().getClass(). ALWAYS_INLINE bool VerifyClassClass(ObjPtr<mirror::Class> c) NO_THREAD_SAFETY_ANALYSIS; diff --git a/runtime/well_known_classes.cc b/runtime/well_known_classes.cc index 507ea165e5..2610252aa7 100644 --- a/runtime/well_known_classes.cc +++ b/runtime/well_known_classes.cc @@ -51,7 +51,6 @@ jclass WellKnownClasses::java_lang_ClassLoader; jclass WellKnownClasses::java_lang_ClassNotFoundException; jclass WellKnownClasses::java_lang_Daemons; jclass WellKnownClasses::java_lang_Error; -jclass WellKnownClasses::java_lang_ExceptionInInitializerError; jclass WellKnownClasses::java_lang_invoke_MethodHandle; jclass WellKnownClasses::java_lang_IllegalAccessError; jclass WellKnownClasses::java_lang_NoClassDefFoundError; @@ -290,7 +289,6 @@ void WellKnownClasses::Init(JNIEnv* env) { java_lang_Object = CacheClass(env, "java/lang/Object"); java_lang_OutOfMemoryError = CacheClass(env, "java/lang/OutOfMemoryError"); java_lang_Error = CacheClass(env, "java/lang/Error"); - java_lang_ExceptionInInitializerError = CacheClass(env, "java/lang/ExceptionInInitializerError"); java_lang_IllegalAccessError = CacheClass(env, "java/lang/IllegalAccessError"); java_lang_invoke_MethodHandle = CacheClass(env, "java/lang/invoke/MethodHandle"); java_lang_NoClassDefFoundError = CacheClass(env, "java/lang/NoClassDefFoundError"); diff --git a/runtime/well_known_classes.h b/runtime/well_known_classes.h index b3ce3d1597..db8a53c44c 100644 --- a/runtime/well_known_classes.h +++ b/runtime/well_known_classes.h @@ -61,7 +61,6 @@ struct WellKnownClasses { static jclass java_lang_ClassNotFoundException; static jclass java_lang_Daemons; static jclass java_lang_Error; - static jclass java_lang_ExceptionInInitializerError; static jclass java_lang_IllegalAccessError; static jclass java_lang_invoke_MethodHandle; static jclass java_lang_NoClassDefFoundError; diff --git a/test/004-NativeAllocations/src/Main.java b/test/004-NativeAllocations/src/Main.java index 92f4e21f40..8712755125 100644 --- a/test/004-NativeAllocations/src/Main.java +++ b/test/004-NativeAllocations/src/Main.java @@ -16,6 +16,7 @@ import java.lang.reflect.*; import java.lang.Runtime; +import dalvik.system.VMRuntime; public class Main { static Object nativeLock = new Object(); @@ -33,10 +34,19 @@ public class Main { NativeAllocation(int bytes, boolean testingDeadlock) throws Exception { this.bytes = bytes; register_native_allocation.invoke(runtime, bytes); + + // Register native allocation can only provide guarantees bounding + // the maximum outstanding allocations if finalizers don't time + // out. In case finalizers have timed out, wait longer for them + // now to complete so we can test the guarantees. + if (!testingDeadlock) { + VMRuntime.runFinalization(0); + } + synchronized (nativeLock) { if (!testingDeadlock) { nativeBytes += bytes; - if (nativeBytes > maxMem) { + if (nativeBytes > 2 * maxMem) { throw new OutOfMemoryError(); } } diff --git a/test/008-exceptions/expected.txt b/test/008-exceptions/expected.txt index 083ecf7ced..fcf2ef49f3 100644 --- a/test/008-exceptions/expected.txt +++ b/test/008-exceptions/expected.txt @@ -1,11 +1,11 @@ Got an NPE: second throw java.lang.NullPointerException: second throw - at Main.catchAndRethrow(Main.java:77) - at Main.exceptions_007(Main.java:59) - at Main.main(Main.java:67) + at Main.catchAndRethrow(Main.java:94) + at Main.exceptions_007(Main.java:74) + at Main.main(Main.java:82) Caused by: java.lang.NullPointerException: first throw - at Main.throwNullPointerException(Main.java:84) - at Main.catchAndRethrow(Main.java:74) + at Main.throwNullPointerException(Main.java:101) + at Main.catchAndRethrow(Main.java:91) ... 2 more Static Init BadError: This is bad by convention: BadInit @@ -15,3 +15,11 @@ Static BadInitNoStringInit BadErrorNoStringInit: This is bad by convention java.lang.NoClassDefFoundError: BadInitNoStringInit BadErrorNoStringInit: This is bad by convention +BadSuperClass Static Init +BadError: This is bad by convention: BadInit +MultiDexBadInit Static Init +java.lang.Error: MultiDexBadInit +java.lang.NoClassDefFoundError: MultiDexBadInit + cause: java.lang.Error: MultiDexBadInit +java.lang.NoClassDefFoundError: MultiDexBadInit + cause: java.lang.Error: MultiDexBadInit diff --git a/test/008-exceptions/multidex.jpp b/test/008-exceptions/multidex.jpp new file mode 100644 index 0000000000..a3746f5149 --- /dev/null +++ b/test/008-exceptions/multidex.jpp @@ -0,0 +1,27 @@ +BadError: + @@com.android.jack.annotations.ForceInMainDex + class BadError +BadInit: + @@com.android.jack.annotations.ForceInMainDex + class BadInit +BadErrorNoStringInit: + @@com.android.jack.annotations.ForceInMainDex + class BadErrorNoStringInit +BadInitNoStringInit: + @@com.android.jack.annotations.ForceInMainDex + class BadInitNoStringInit +BadSuperClass: + @@com.android.jack.annotations.ForceInMainDex + class BadSuperClass +DerivedFromBadSuperClass: + @@com.android.jack.annotations.ForceInMainDex + class DerivedFromBadSuperClass +Main: + @@com.android.jack.annotations.ForceInMainDex + class Main +MultiDexBadInit: + @@com.android.jack.annotations.ForceInMainDex + class MultiDexBadInit +MultiDexBadInitWrapper1: + @@com.android.jack.annotations.ForceInMainDex + class MultiDexBadInitWrapper1 diff --git a/test/008-exceptions/src-multidex/MultiDexBadInitWrapper2.java b/test/008-exceptions/src-multidex/MultiDexBadInitWrapper2.java new file mode 100644 index 0000000000..f3953bd4c7 --- /dev/null +++ b/test/008-exceptions/src-multidex/MultiDexBadInitWrapper2.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. + */ + +class MultiDexBadInitWrapper2 { + public static void setDummy(int value) { + if (doThrow) { throw new Error(); } + MultiDexBadInit.dummy = value; + } + + public static boolean doThrow = false; +} diff --git a/test/008-exceptions/src/Main.java b/test/008-exceptions/src/Main.java index b8231f12bd..74af00ccf7 100644 --- a/test/008-exceptions/src/Main.java +++ b/test/008-exceptions/src/Main.java @@ -50,6 +50,21 @@ class BadInitNoStringInit { } } +// A class that throws BadError during static initialization, serving as a super class. +class BadSuperClass { + static int dummy; + static { + System.out.println("BadSuperClass Static Init"); + if (true) { + throw new BadError("BadInit"); + } + } +} + +// A class that derives from BadSuperClass. +class DerivedFromBadSuperClass extends BadSuperClass { +} + /** * Exceptions across method calls */ @@ -63,10 +78,12 @@ public class Main { npe.printStackTrace(System.out); } } - public static void main (String args[]) { + public static void main(String args[]) { exceptions_007(); exceptionsRethrowClassInitFailure(); exceptionsRethrowClassInitFailureNoStringInit(); + exceptionsForSuperClassInitFailure(); + exceptionsInMultiDex(); } private static void catchAndRethrow() { @@ -129,4 +146,70 @@ public class Main { error.printStackTrace(System.out); } } + + private static void exceptionsForSuperClassInitFailure() { + try { + // Resolve DerivedFromBadSuperClass. + BadSuperClass.dummy = 1; + throw new IllegalStateException("Should not reach here."); + } catch (BadError e) { + System.out.println(e); + } catch (Throwable t) { + t.printStackTrace(); + } + try { + // Before splitting mirror::Class::kStatusError into + // kStatusErrorUnresolved and kStatusErrorResolved, + // this would trigger a + // CHECK(super_class->IsResolved()) + // failure in + // ClassLinker::LoadSuperAndInterfaces(). + // After the change we're getting either VerifyError + // (for Optimizing) or NoClassDefFoundError wrapping + // BadError (for interpreter or JIT). + new DerivedFromBadSuperClass(); + throw new IllegalStateException("Should not reach here."); + } catch (NoClassDefFoundError ncdfe) { + if (!(ncdfe.getCause() instanceof BadError)) { + ncdfe.getCause().printStackTrace(); + } + } catch (VerifyError e) { + } catch (Throwable t) { + t.printStackTrace(); + } + } + + private static void exceptionsInMultiDex() { + try { + MultiDexBadInit.dummy = 1; + throw new IllegalStateException("Should not reach here."); + } catch (Error e) { + System.out.println(e); + } catch (Throwable t) { + t.printStackTrace(); + } + // Before splitting mirror::Class::kStatusError into + // kStatusErrorUnresolved and kStatusErrorResolved, + // the exception from wrapper 1 would have been + // wrapped in NoClassDefFoundError but the exception + // from wrapper 2 would have been unwrapped. + try { + MultiDexBadInitWrapper1.setDummy(1); + throw new IllegalStateException("Should not reach here."); + } catch (NoClassDefFoundError ncdfe) { + System.out.println(ncdfe); + System.out.println(" cause: " + ncdfe.getCause()); + } catch (Throwable t) { + t.printStackTrace(); + } + try { + MultiDexBadInitWrapper2.setDummy(1); + throw new IllegalStateException("Should not reach here."); + } catch (NoClassDefFoundError ncdfe) { + System.out.println(ncdfe); + System.out.println(" cause: " + ncdfe.getCause()); + } catch (Throwable t) { + t.printStackTrace(); + } + } } diff --git a/test/008-exceptions/src/MultiDexBadInit.java b/test/008-exceptions/src/MultiDexBadInit.java new file mode 100644 index 0000000000..e3ebb9ca45 --- /dev/null +++ b/test/008-exceptions/src/MultiDexBadInit.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. + */ + +class MultiDexBadInit { + static int dummy; + static { + System.out.println("MultiDexBadInit Static Init"); + if (true) { + throw new Error("MultiDexBadInit"); + } + } +} diff --git a/test/008-exceptions/src/MultiDexBadInitWrapper1.java b/test/008-exceptions/src/MultiDexBadInitWrapper1.java new file mode 100644 index 0000000000..059e6a3d7d --- /dev/null +++ b/test/008-exceptions/src/MultiDexBadInitWrapper1.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. + */ + +class MultiDexBadInitWrapper1 { + public static void setDummy(int value) { + if (doThrow) { throw new Error(); } + MultiDexBadInit.dummy = value; + } + + public static boolean doThrow = false; +} diff --git a/test/082-inline-execute/src/Main.java b/test/082-inline-execute/src/Main.java index 06f193af32..072f0e68ee 100644 --- a/test/082-inline-execute/src/Main.java +++ b/test/082-inline-execute/src/Main.java @@ -535,6 +535,8 @@ public class Main { Assert.assertEquals(Math.min(0.0f, Float.MAX_VALUE), 0.0f); Assert.assertEquals(Math.min(Float.MIN_VALUE, 0.0f), 0.0f); Assert.assertEquals(Math.min(Float.MIN_VALUE, Float.MAX_VALUE), Float.MIN_VALUE); + // Should not have flush-to-zero behavior. + Assert.assertEquals(Math.min(Float.MIN_VALUE, Float.MIN_VALUE), Float.MIN_VALUE); } public static void test_Math_max_F() { @@ -548,8 +550,10 @@ public class Main { Assert.assertEquals(Math.max(1.0f, 0.0f), 1.0f); Assert.assertEquals(Math.max(0.0f, 1.0f), 1.0f); Assert.assertEquals(Math.max(0.0f, Float.MAX_VALUE), Float.MAX_VALUE); - Assert.assertEquals(Math.max(Float.MIN_VALUE, 0.0f), Float.MIN_VALUE); Assert.assertEquals(Math.max(Float.MIN_VALUE, Float.MAX_VALUE), Float.MAX_VALUE); + // Should not have flush-to-zero behavior. + Assert.assertEquals(Math.max(Float.MIN_VALUE, 0.0f), Float.MIN_VALUE); + Assert.assertEquals(Math.max(Float.MIN_VALUE, Float.MIN_VALUE), Float.MIN_VALUE); } public static void test_Math_min_D() { @@ -565,6 +569,8 @@ public class Main { Assert.assertEquals(Math.min(0.0d, Double.MAX_VALUE), 0.0d); Assert.assertEquals(Math.min(Double.MIN_VALUE, 0.0d), 0.0d); Assert.assertEquals(Math.min(Double.MIN_VALUE, Double.MAX_VALUE), Double.MIN_VALUE); + // Should not have flush-to-zero behavior. + Assert.assertEquals(Math.min(Double.MIN_VALUE, Double.MIN_VALUE), Double.MIN_VALUE); } public static void test_Math_max_D() { @@ -580,6 +586,9 @@ public class Main { Assert.assertEquals(Math.max(0.0d, Double.MAX_VALUE), Double.MAX_VALUE); Assert.assertEquals(Math.max(Double.MIN_VALUE, 0.0d), Double.MIN_VALUE); Assert.assertEquals(Math.max(Double.MIN_VALUE, Double.MAX_VALUE), Double.MAX_VALUE); + // Should not have flush-to-zero behavior. + Assert.assertEquals(Math.max(Double.MIN_VALUE, 0.0d), Double.MIN_VALUE); + Assert.assertEquals(Math.max(Double.MIN_VALUE, Double.MIN_VALUE), Double.MIN_VALUE); } public static void test_Math_sqrt() { @@ -730,16 +739,19 @@ public class Main { Math.rint(+2.1); Assert.assertEquals(Math.rint(+0.0), +0.0d, 0.0); Assert.assertEquals(Math.rint(-0.0), -0.0d, 0.0); + Assert.assertEquals(Math.rint(+0.5), +0.0d, 0.0); // expects tie-to-even Assert.assertEquals(Math.rint(+2.0), +2.0d, 0.0); Assert.assertEquals(Math.rint(+2.1), +2.0d, 0.0); - Assert.assertEquals(Math.rint(+2.5), +2.0d, 0.0); + Assert.assertEquals(Math.rint(+2.5), +2.0d, 0.0); // expects tie-to-even Assert.assertEquals(Math.rint(+2.9), +3.0d, 0.0); Assert.assertEquals(Math.rint(+3.0), +3.0d, 0.0); + Assert.assertEquals(Math.rint(+3.5), +4.0d, 0.0); // expects tie-to-even Assert.assertEquals(Math.rint(-2.0), -2.0d, 0.0); Assert.assertEquals(Math.rint(-2.1), -2.0d, 0.0); - Assert.assertEquals(Math.rint(-2.5), -2.0d, 0.0); + Assert.assertEquals(Math.rint(-2.5), -2.0d, 0.0); // expects tie-to-even Assert.assertEquals(Math.rint(-2.9), -3.0d, 0.0); Assert.assertEquals(Math.rint(-3.0), -3.0d, 0.0); + Assert.assertEquals(Math.rint(-3.5), -4.0d, 0.0); // expects tie-to-even // 2^52 - 1.5 Assert.assertEquals(Math.rint(Double.longBitsToDouble(0x432FFFFFFFFFFFFDl)), Double.longBitsToDouble(0x432FFFFFFFFFFFFCl), 0.0); diff --git a/test/142-classloader2/expected.txt b/test/142-classloader2/expected.txt index 86f5e220e2..056d9785de 100644 --- a/test/142-classloader2/expected.txt +++ b/test/142-classloader2/expected.txt @@ -1 +1,5 @@ +Loaded class B. +Caught VerifyError. +Loaded class B. +Caught wrapped VerifyError. Everything OK. diff --git a/test/142-classloader2/src/Main.java b/test/142-classloader2/src/Main.java index 80b00e7dd2..a0c77645a3 100644 --- a/test/142-classloader2/src/Main.java +++ b/test/142-classloader2/src/Main.java @@ -74,16 +74,25 @@ public class Main { // Try to load a dex file with bad dex code. Use new instance to force verification. try { Class<?> badClass = Main.class.getClassLoader().loadClass("B"); + System.out.println("Loaded class B."); badClass.newInstance(); - System.out.println("Should not be able to load class from bad dex file."); + System.out.println("Should not be able to instantiate B with bad dex bytecode."); } catch (VerifyError e) { + System.out.println("Caught VerifyError."); } // Make sure the same error is rethrown when reloading the bad class. try { Class<?> badClass = Main.class.getClassLoader().loadClass("B"); - System.out.println("Should not be able to load class from bad dex file."); - } catch (VerifyError e) { + System.out.println("Loaded class B."); + badClass.newInstance(); + System.out.println("Should not be able to instantiate B with bad dex bytecode."); + } catch (NoClassDefFoundError e) { + if (e.getCause() instanceof VerifyError) { + System.out.println("Caught wrapped VerifyError."); + } else { + e.printStackTrace(); + } } System.out.println("Everything OK."); diff --git a/test/154-gc-loop/expected.txt b/test/154-gc-loop/expected.txt new file mode 100644 index 0000000000..6106818c79 --- /dev/null +++ b/test/154-gc-loop/expected.txt @@ -0,0 +1,2 @@ +JNI_OnLoad called +Finalize count too large: false diff --git a/test/154-gc-loop/heap_interface.cc b/test/154-gc-loop/heap_interface.cc new file mode 100644 index 0000000000..8d610a87fa --- /dev/null +++ b/test/154-gc-loop/heap_interface.cc @@ -0,0 +1,28 @@ +/* + * 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 "runtime.h" + +namespace art { +namespace { + +extern "C" JNIEXPORT void JNICALL Java_Main_backgroundProcessState(JNIEnv*, jclass) { + Runtime::Current()->UpdateProcessState(kProcessStateJankImperceptible); +} + +} // namespace +} // namespace art diff --git a/test/154-gc-loop/info.txt b/test/154-gc-loop/info.txt new file mode 100644 index 0000000000..f599db1d01 --- /dev/null +++ b/test/154-gc-loop/info.txt @@ -0,0 +1 @@ +Test that GC doesn't happen too often for a few small allocations. diff --git a/test/154-gc-loop/src/Main.java b/test/154-gc-loop/src/Main.java new file mode 100644 index 0000000000..3a256c109e --- /dev/null +++ b/test/154-gc-loop/src/Main.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017 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.ref.WeakReference; + +public class Main { + static final class GcWatcher { + protected void finalize() throws Throwable { + watcher = new WeakReference<GcWatcher>(new GcWatcher()); + ++finalizeCounter; + } + } + static WeakReference<GcWatcher> watcher = new WeakReference<GcWatcher>(new GcWatcher()); + static Object o = new Object(); + static int finalizeCounter = 0; + + public static void main(String[] args) { + System.loadLibrary(args[0]); + backgroundProcessState(); + try { + Runtime.getRuntime().gc(); + for (int i = 0; i < 10; ++i) { + o = new Object(); + Thread.sleep(1000); + } + } catch (Exception e) {} + System.out.println("Finalize count too large: " + + ((finalizeCounter >= 10) ? Integer.toString(finalizeCounter) : "false")); + } + + private static native void backgroundProcessState(); +} diff --git a/test/466-get-live-vreg/get_live_vreg_jni.cc b/test/466-get-live-vreg/get_live_vreg_jni.cc index d3a033b12a..6cea673b41 100644 --- a/test/466-get-live-vreg/get_live_vreg_jni.cc +++ b/test/466-get-live-vreg/get_live_vreg_jni.cc @@ -47,7 +47,7 @@ class TestVisitor : public StackVisitor { uint32_t value = 0; if (GetCurrentQuickFrame() != nullptr && GetCurrentOatQuickMethodHeader()->IsOptimized() && - !Runtime::Current()->IsDebuggable()) { + !Runtime::Current()->IsJavaDebuggable()) { CHECK_EQ(GetVReg(m, dex_register_of_first_parameter, kIntVReg, &value), false); } else { CHECK(GetVReg(m, dex_register_of_first_parameter, kIntVReg, &value)); diff --git a/test/482-checker-loop-back-edge-use/src/Main.java b/test/482-checker-loop-back-edge-use/src/Main.java index 65dfd411fd..86977d1b8e 100644 --- a/test/482-checker-loop-back-edge-use/src/Main.java +++ b/test/482-checker-loop-back-edge-use/src/Main.java @@ -164,6 +164,12 @@ public class Main { } } + + static boolean $opt$noinline$ensureSideEffects() { + if (doThrow) throw new Error(""); + return true; + } + /// CHECK-START: void Main.loop9() liveness (after) /// CHECK: <<Arg:z\d+>> StaticFieldGet liveness:<<ArgLiv:\d+>> ranges:{[<<ArgLiv>>,<<ArgLoopUse:\d+>>)} uses:[<<ArgUse:\d+>>,<<ArgLoopUse>>] /// CHECK: If [<<Arg>>] liveness:<<IfLiv:\d+>> @@ -178,7 +184,7 @@ public class Main { // Add some code at entry to avoid having the entry block be a pre header. // This avoids having to create a synthesized block. System.out.println("Enter"); - while (Runtime.getRuntime() != null) { + while ($opt$noinline$ensureSideEffects()) { // 'incoming' must only have a use in the inner loop. boolean incoming = field; while (incoming) {} @@ -189,4 +195,5 @@ public class Main { } static boolean field; + static boolean doThrow = false; } diff --git a/test/552-checker-sharpening/src/Main.java b/test/552-checker-sharpening/src/Main.java index db437686f0..dd77423870 100644 --- a/test/552-checker-sharpening/src/Main.java +++ b/test/552-checker-sharpening/src/Main.java @@ -52,7 +52,6 @@ public class Main { /// CHECK: InvokeStaticOrDirect method_load_kind:dex_cache_pc_relative /// CHECK-START-MIPS: int Main.testSimple(int) sharpening (after) - /// CHECK-NOT: MipsDexCacheArraysBase /// CHECK: InvokeStaticOrDirect method_load_kind:dex_cache_pc_relative /// CHECK-START-MIPS64: int Main.testSimple(int) sharpening (after) @@ -69,10 +68,6 @@ public class Main { /// CHECK: ArmDexCacheArraysBase /// CHECK-NOT: ArmDexCacheArraysBase - /// CHECK-START-MIPS: int Main.testSimple(int) dex_cache_array_fixups_mips (after) - /// CHECK: MipsDexCacheArraysBase - /// CHECK-NOT: MipsDexCacheArraysBase - /// CHECK-START-X86: int Main.testSimple(int) pc_relative_fixups_x86 (after) /// CHECK: X86ComputeBaseMethodAddress /// CHECK-NOT: X86ComputeBaseMethodAddress @@ -95,7 +90,6 @@ public class Main { /// CHECK: InvokeStaticOrDirect method_load_kind:dex_cache_pc_relative /// CHECK-START-MIPS: int Main.testDiamond(boolean, int) sharpening (after) - /// CHECK-NOT: MipsDexCacheArraysBase /// CHECK: InvokeStaticOrDirect method_load_kind:dex_cache_pc_relative /// CHECK: InvokeStaticOrDirect method_load_kind:dex_cache_pc_relative @@ -120,14 +114,6 @@ public class Main { /// CHECK: ArmDexCacheArraysBase /// CHECK-NEXT: If - /// CHECK-START-MIPS: int Main.testDiamond(boolean, int) dex_cache_array_fixups_mips (after) - /// CHECK: MipsDexCacheArraysBase - /// CHECK-NOT: MipsDexCacheArraysBase - - /// CHECK-START-MIPS: int Main.testDiamond(boolean, int) dex_cache_array_fixups_mips (after) - /// CHECK: MipsDexCacheArraysBase - /// CHECK-NEXT: If - /// CHECK-START-X86: int Main.testDiamond(boolean, int) pc_relative_fixups_x86 (after) /// CHECK: X86ComputeBaseMethodAddress /// CHECK-NOT: X86ComputeBaseMethodAddress @@ -182,24 +168,6 @@ public class Main { /// CHECK: begin_block /// CHECK: InvokeStaticOrDirect method_load_kind:dex_cache_pc_relative - /// CHECK-START-MIPS: int Main.testLoop(int[], int) dex_cache_array_fixups_mips (before) - /// CHECK-NOT: MipsDexCacheArraysBase - - /// CHECK-START-MIPS: int Main.testLoop(int[], int) dex_cache_array_fixups_mips (after) - /// CHECK: MipsDexCacheArraysBase - /// CHECK-NOT: MipsDexCacheArraysBase - - /// CHECK-START-MIPS: int Main.testLoop(int[], int) dex_cache_array_fixups_mips (after) - /// CHECK: InvokeStaticOrDirect - /// CHECK-NOT: InvokeStaticOrDirect - - /// CHECK-START-MIPS: int Main.testLoop(int[], int) dex_cache_array_fixups_mips (after) - /// CHECK: ArrayLength - /// CHECK-NEXT: MipsDexCacheArraysBase - /// CHECK-NEXT: Goto - /// CHECK: begin_block - /// CHECK: InvokeStaticOrDirect method_load_kind:dex_cache_pc_relative - public static int testLoop(int[] array, int x) { // PC-relative bases used by ARM, MIPS and X86 should be pulled before the loop. for (int i : array) { @@ -228,16 +196,6 @@ public class Main { /// CHECK-NEXT: ArmDexCacheArraysBase /// CHECK-NEXT: Goto - /// CHECK-START-MIPS: int Main.testLoopWithDiamond(int[], boolean, int) dex_cache_array_fixups_mips (before) - /// CHECK-NOT: MipsDexCacheArraysBase - - /// CHECK-START-MIPS: int Main.testLoopWithDiamond(int[], boolean, int) dex_cache_array_fixups_mips (after) - /// CHECK: If - /// CHECK: begin_block - /// CHECK: ArrayLength - /// CHECK-NEXT: MipsDexCacheArraysBase - /// CHECK-NEXT: Goto - public static int testLoopWithDiamond(int[] array, boolean negate, int x) { // PC-relative bases used by ARM, MIPS and X86 should be pulled before the loop // but not outside the if. @@ -325,9 +283,6 @@ public class Main { return "non-boot-image-string"; } - /// CHECK-START: java.lang.Class Main.$noinline$getStringClass() sharpening (before) - /// CHECK: LoadClass load_kind:DexCacheViaMethod class_name:java.lang.String - /// CHECK-START-X86: java.lang.Class Main.$noinline$getStringClass() sharpening (after) // Note: load kind depends on PIC/non-PIC // TODO: Remove DexCacheViaMethod when read barrier config supports BootImageAddress. @@ -365,9 +320,6 @@ public class Main { return String.class; } - /// CHECK-START: java.lang.Class Main.$noinline$getOtherClass() sharpening (before) - /// CHECK: LoadClass load_kind:DexCacheViaMethod class_name:Other - /// CHECK-START-X86: java.lang.Class Main.$noinline$getOtherClass() sharpening (after) /// CHECK: LoadClass load_kind:BssEntry class_name:Other @@ -381,20 +333,12 @@ public class Main { /// CHECK-START-ARM: java.lang.Class Main.$noinline$getOtherClass() sharpening (after) /// CHECK: LoadClass load_kind:BssEntry class_name:Other - /// CHECK-START-ARM: java.lang.Class Main.$noinline$getOtherClass() dex_cache_array_fixups_arm (after) - /// CHECK-DAG: ArmDexCacheArraysBase - /// CHECK-DAG: LoadClass load_kind:BssEntry class_name:Other - /// CHECK-START-ARM64: java.lang.Class Main.$noinline$getOtherClass() sharpening (after) /// CHECK: LoadClass load_kind:BssEntry class_name:Other /// CHECK-START-MIPS: java.lang.Class Main.$noinline$getOtherClass() sharpening (after) /// CHECK: LoadClass load_kind:BssEntry class_name:Other - /// CHECK-START-MIPS: java.lang.Class Main.$noinline$getOtherClass() dex_cache_array_fixups_mips (after) - /// CHECK-DAG: MipsDexCacheArraysBase - /// CHECK-DAG: LoadClass load_kind:BssEntry class_name:Other - /// CHECK-START-MIPS64: java.lang.Class Main.$noinline$getOtherClass() sharpening (after) /// CHECK: LoadClass load_kind:BssEntry class_name:Other diff --git a/test/616-cha-abstract/expected.txt b/test/616-cha-abstract/expected.txt new file mode 100644 index 0000000000..6a5618ebc6 --- /dev/null +++ b/test/616-cha-abstract/expected.txt @@ -0,0 +1 @@ +JNI_OnLoad called diff --git a/test/616-cha-abstract/info.txt b/test/616-cha-abstract/info.txt new file mode 100644 index 0000000000..4f7e01391d --- /dev/null +++ b/test/616-cha-abstract/info.txt @@ -0,0 +1 @@ +Test for Class Hierarchy Analysis (CHA) on abstract method. diff --git a/test/616-cha-abstract/run b/test/616-cha-abstract/run new file mode 100644 index 0000000000..d8b4f0d26c --- /dev/null +++ b/test/616-cha-abstract/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright (C) 2017 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. + +# Run without an app image to prevent the classes to be loaded at startup. +exec ${RUN} "${@}" --no-app-image diff --git a/test/616-cha-abstract/src/Main.java b/test/616-cha-abstract/src/Main.java new file mode 100644 index 0000000000..e1d7db170d --- /dev/null +++ b/test/616-cha-abstract/src/Main.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2017 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. + */ + +abstract class Base { + abstract void foo(int i); + + void printError(String msg) { + System.out.println(msg); + } +} + +class Main1 extends Base { + void foo(int i) { + if (i != 1) { + printError("error1"); + } + } +} + +class Main2 extends Main1 { + void foo(int i) { + if (i != 2) { + printError("error2"); + } + } +} + +public class Main { + static Main1 sMain1; + static Main1 sMain2; + + static boolean sIsOptimizing = true; + static boolean sHasJIT = true; + static volatile boolean sOtherThreadStarted; + + private static void assertSingleImplementation(Class<?> clazz, String method_name, boolean b) { + if (hasSingleImplementation(clazz, method_name) != b) { + System.out.println(clazz + "." + method_name + + " doesn't have single implementation value of " + b); + } + } + + // sMain1.foo() will be always be Main1.foo() before Main2 is loaded/linked. + // So sMain1.foo() can be devirtualized to Main1.foo() and be inlined. + // After Dummy.createMain2() which links in Main2, live testOverride() on stack + // should be deoptimized. + static void testOverride(boolean createMain2, boolean wait, boolean setHasJIT) { + if (setHasJIT) { + if (isInterpreted()) { + sHasJIT = false; + } + return; + } + + if (createMain2 && (sIsOptimizing || sHasJIT)) { + assertIsManaged(); + } + + sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2); + + if (createMain2) { + // Wait for the other thread to start. + while (!sOtherThreadStarted); + // Create an Main2 instance and assign it to sMain2. + // sMain1 is kept the same. + sMain2 = Dummy.createMain2(); + // Wake up the other thread. + synchronized(Main.class) { + Main.class.notify(); + } + } else if (wait) { + // This is the other thread. + synchronized(Main.class) { + sOtherThreadStarted = true; + // Wait for Main2 to be linked and deoptimization is triggered. + try { + Main.class.wait(); + } catch (Exception e) { + } + } + } + + // There should be a deoptimization here right after Main2 is linked by + // calling Dummy.createMain2(), even though sMain1 didn't change. + // The behavior here would be different if inline-cache is used, which + // doesn't deoptimize since sMain1 still hits the type cache. + sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2); + if ((createMain2 || wait) && sHasJIT && !sIsOptimizing) { + // This method should be deoptimized right after Main2 is created. + assertIsInterpreted(); + } + + if (sMain2 != null) { + sMain2.foo(sMain2.getClass() == Main1.class ? 1 : 2); + } + } + + // Test scenarios under which CHA-based devirtualization happens, + // and class loading that overrides a method can invalidate compiled code. + public static void main(String[] args) { + System.loadLibrary(args[0]); + + if (isInterpreted()) { + sIsOptimizing = false; + } + + // sMain1 is an instance of Main1. Main2 hasn't bee loaded yet. + sMain1 = new Main1(); + + ensureJitCompiled(Main.class, "testOverride"); + testOverride(false, false, true); + + if (sHasJIT && !sIsOptimizing) { + assertSingleImplementation(Base.class, "foo", true); + assertSingleImplementation(Main1.class, "foo", true); + } else { + // Main2 is verified ahead-of-time so it's linked in already. + } + + // Create another thread that also calls sMain1.foo(). + // Try to test suspend and deopt another thread. + new Thread() { + public void run() { + testOverride(false, true, false); + } + }.start(); + + // This will create Main2 instance in the middle of testOverride(). + testOverride(true, false, false); + assertSingleImplementation(Base.class, "foo", false); + assertSingleImplementation(Main1.class, "foo", false); + } + + private static native void ensureJitCompiled(Class<?> itf, String method_name); + private static native void assertIsInterpreted(); + private static native void assertIsManaged(); + private static native boolean isInterpreted(); + private static native boolean hasSingleImplementation(Class<?> clazz, String method_name); +} + +// Put createMain2() in another class to avoid class loading due to verifier. +class Dummy { + static Main1 createMain2() { + return new Main2(); + } +} diff --git a/test/623-checker-loop-regressions/src/Main.java b/test/623-checker-loop-regressions/src/Main.java index 7cc0b8b652..7509d9b4f3 100644 --- a/test/623-checker-loop-regressions/src/Main.java +++ b/test/623-checker-loop-regressions/src/Main.java @@ -154,8 +154,8 @@ public class Main { /// CHECK-NOT: Phi // /// CHECK-START: int Main.polynomialInt() instruction_simplifier$after_bce (after) - /// CHECK-DAG: <<Int:i\d+>> IntConstant -45 loop:none - /// CHECK-DAG: Return [<<Int>>] loop:none + /// CHECK-DAG: <<Int:i\d+>> IntConstant -45 loop:none + /// CHECK-DAG: Return [<<Int>>] loop:none static int polynomialInt() { int x = 0; for (int i = 0; i < 10; i++) { @@ -164,6 +164,81 @@ public class Main { return x; } + // Regression test for b/34779592 (found with fuzz testing): overflow for last value + // of division truncates to zero, for multiplication it simply truncates. + // + /// CHECK-START: int Main.geoIntDivLastValue(int) loop_optimization (before) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: Phi loop:<<Loop>> outer_loop:none + // + /// CHECK-START: int Main.geoIntDivLastValue(int) loop_optimization (after) + /// CHECK-NOT: Phi + // + /// CHECK-START: int Main.geoIntDivLastValue(int) instruction_simplifier$after_bce (after) + /// CHECK-DAG: <<Int:i\d+>> IntConstant 0 loop:none + /// CHECK-DAG: Return [<<Int>>] loop:none + static int geoIntDivLastValue(int x) { + for (int i = 0; i < 2; i++) { + x /= 1081788608; + } + return x; + } + + /// CHECK-START: int Main.geoIntMulLastValue(int) loop_optimization (before) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: Phi loop:<<Loop>> outer_loop:none + // + /// CHECK-START: int Main.geoIntMulLastValue(int) loop_optimization (after) + /// CHECK-NOT: Phi + // + /// CHECK-START: int Main.geoIntMulLastValue(int) instruction_simplifier$after_bce (after) + /// CHECK-DAG: <<Par:i\d+>> ParameterValue loop:none + /// CHECK-DAG: <<Int:i\d+>> IntConstant -194211840 loop:none + /// CHECK-DAG: <<Mul:i\d+>> Mul [<<Par>>,<<Int>>] loop:none + /// CHECK-DAG: Return [<<Mul>>] loop:none + static int geoIntMulLastValue(int x) { + for (int i = 0; i < 2; i++) { + x *= 1081788608; + } + return x; + } + + /// CHECK-START: long Main.geoLongDivLastValue(long) loop_optimization (before) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: Phi loop:<<Loop>> outer_loop:none + // + /// CHECK-START: long Main.geoLongDivLastValue(long) loop_optimization (after) + /// CHECK-NOT: Phi + // + /// CHECK-START: long Main.geoLongDivLastValue(long) instruction_simplifier$after_bce (after) + /// CHECK-DAG: <<Long:j\d+>> LongConstant 0 loop:none + /// CHECK-DAG: Return [<<Long>>] loop:none + static long geoLongDivLastValue(long x) { + for (int i = 0; i < 10; i++) { + x /= 1081788608; + } + return x; + } + + /// CHECK-START: long Main.geoLongMulLastValue(long) loop_optimization (before) + /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: Phi loop:<<Loop>> outer_loop:none + // + /// CHECK-START: long Main.geoLongMulLastValue(long) loop_optimization (after) + /// CHECK-NOT: Phi + // + /// CHECK-START: long Main.geoLongMulLastValue(long) instruction_simplifier$after_bce (after) + /// CHECK-DAG: <<Par:j\d+>> ParameterValue loop:none + /// CHECK-DAG: <<Long:j\d+>> LongConstant -8070450532247928832 loop:none + /// CHECK-DAG: <<Mul:j\d+>> Mul [<<Par>>,<<Long>>] loop:none + /// CHECK-DAG: Return [<<Mul>>] loop:none + static long geoLongMulLastValue(long x) { + for (int i = 0; i < 10; i++) { + x *= 1081788608; + } + return x; + } + public static void main(String[] args) { expectEquals(10, earlyExitFirst(-1)); for (int i = 0; i <= 10; i++) { @@ -185,6 +260,42 @@ public class Main { expectEquals(-45, polynomialIntFromLong()); expectEquals(-45, polynomialInt()); + expectEquals(0, geoIntDivLastValue(0)); + expectEquals(0, geoIntDivLastValue(1)); + expectEquals(0, geoIntDivLastValue(2)); + expectEquals(0, geoIntDivLastValue(1081788608)); + expectEquals(0, geoIntDivLastValue(-1081788608)); + expectEquals(0, geoIntDivLastValue(2147483647)); + expectEquals(0, geoIntDivLastValue(-2147483648)); + + expectEquals( 0, geoIntMulLastValue(0)); + expectEquals( -194211840, geoIntMulLastValue(1)); + expectEquals( -388423680, geoIntMulLastValue(2)); + expectEquals(-1041498112, geoIntMulLastValue(1081788608)); + expectEquals( 1041498112, geoIntMulLastValue(-1081788608)); + expectEquals( 194211840, geoIntMulLastValue(2147483647)); + expectEquals( 0, geoIntMulLastValue(-2147483648)); + + expectEquals(0L, geoLongDivLastValue(0L)); + expectEquals(0L, geoLongDivLastValue(1L)); + expectEquals(0L, geoLongDivLastValue(2L)); + expectEquals(0L, geoLongDivLastValue(1081788608L)); + expectEquals(0L, geoLongDivLastValue(-1081788608L)); + expectEquals(0L, geoLongDivLastValue(2147483647L)); + expectEquals(0L, geoLongDivLastValue(-2147483648L)); + expectEquals(0L, geoLongDivLastValue(9223372036854775807L)); + expectEquals(0L, geoLongDivLastValue(-9223372036854775808L)); + + expectEquals( 0L, geoLongMulLastValue(0L)); + expectEquals(-8070450532247928832L, geoLongMulLastValue(1L)); + expectEquals( 2305843009213693952L, geoLongMulLastValue(2L)); + expectEquals( 0L, geoLongMulLastValue(1081788608L)); + expectEquals( 0L, geoLongMulLastValue(-1081788608L)); + expectEquals( 8070450532247928832L, geoLongMulLastValue(2147483647L)); + expectEquals( 0L, geoLongMulLastValue(-2147483648L)); + expectEquals( 8070450532247928832L, geoLongMulLastValue(9223372036854775807L)); + expectEquals( 0L, geoLongMulLastValue(-9223372036854775808L)); + System.out.println("passed"); } @@ -193,4 +304,10 @@ public class Main { 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); + } + } } diff --git a/test/626-checker-arm64-scratch-register/src/Main.java b/test/626-checker-arm64-scratch-register/src/Main.java index aa211be33c..6dd4374116 100644 --- a/test/626-checker-arm64-scratch-register/src/Main.java +++ b/test/626-checker-arm64-scratch-register/src/Main.java @@ -95,8 +95,8 @@ public class Main { /// CHECK: str s1, [sp, #28] /// CHECK: ldr s1, [sp, #32] /// CHECK: str s31, [sp, #32] - /// CHECK: ldr w16, [sp, #20] - /// CHECK: str w16, [sp, #40] + /// CHECK: ldr s31, [sp, #20] + /// CHECK: str s31, [sp, #40] /// CHECK: str s12, [sp, #20] /// CHECK: fmov d12, d11 /// CHECK: fmov d11, d10 diff --git a/test/626-const-class-linking/clear_dex_cache_types.cc b/test/626-const-class-linking/clear_dex_cache_types.cc index b035896166..c0aedc199f 100644 --- a/test/626-const-class-linking/clear_dex_cache_types.cc +++ b/test/626-const-class-linking/clear_dex_cache_types.cc @@ -15,6 +15,9 @@ */ #include "jni.h" +#include "mirror/class-inl.h" +#include "mirror/class_loader.h" +#include "mirror/dex_cache-inl.h" #include "object_lock.h" #include "scoped_thread_state_change-inl.h" diff --git a/test/635-checker-arm64-volatile-load-cc/expected.txt b/test/635-checker-arm64-volatile-load-cc/expected.txt new file mode 100644 index 0000000000..b0aad4deb5 --- /dev/null +++ b/test/635-checker-arm64-volatile-load-cc/expected.txt @@ -0,0 +1 @@ +passed diff --git a/test/635-checker-arm64-volatile-load-cc/info.txt b/test/635-checker-arm64-volatile-load-cc/info.txt new file mode 100644 index 0000000000..5d67df4a19 --- /dev/null +++ b/test/635-checker-arm64-volatile-load-cc/info.txt @@ -0,0 +1,3 @@ +Regression test checking that the VIXL ARM64 scratch register pool is +not exhausted when generating a volatile field load with a large +offset with (Baker) read barriers (b/34726333). diff --git a/test/635-checker-arm64-volatile-load-cc/src/Main.java b/test/635-checker-arm64-volatile-load-cc/src/Main.java new file mode 100644 index 0000000000..6a26e940ac --- /dev/null +++ b/test/635-checker-arm64-volatile-load-cc/src/Main.java @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2017 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 volatile Object s000, s001, s002, s003, s004, s005, s006, s007, s008, s009; + static volatile Object s010, s011, s012, s013, s014, s015, s016, s017, s018, s019; + static volatile Object s020, s021, s022, s023, s024, s025, s026, s027, s028, s029; + static volatile Object s030, s031, s032, s033, s034, s035, s036, s037, s038, s039; + static volatile Object s040, s041, s042, s043, s044, s045, s046, s047, s048, s049; + static volatile Object s050, s051, s052, s053, s054, s055, s056, s057, s058, s059; + static volatile Object s060, s061, s062, s063, s064, s065, s066, s067, s068, s069; + static volatile Object s070, s071, s072, s073, s074, s075, s076, s077, s078, s079; + static volatile Object s080, s081, s082, s083, s084, s085, s086, s087, s088, s089; + static volatile Object s090, s091, s092, s093, s094, s095, s096, s097, s098, s099; + + static volatile Object s100, s101, s102, s103, s104, s105, s106, s107, s108, s109; + static volatile Object s110, s111, s112, s113, s114, s115, s116, s117, s118, s119; + static volatile Object s120, s121, s122, s123, s124, s125, s126, s127, s128, s129; + static volatile Object s130, s131, s132, s133, s134, s135, s136, s137, s138, s139; + static volatile Object s140, s141, s142, s143, s144, s145, s146, s147, s148, s149; + static volatile Object s150, s151, s152, s153, s154, s155, s156, s157, s158, s159; + static volatile Object s160, s161, s162, s163, s164, s165, s166, s167, s168, s169; + static volatile Object s170, s171, s172, s173, s174, s175, s176, s177, s178, s179; + static volatile Object s180, s181, s182, s183, s184, s185, s186, s187, s188, s189; + static volatile Object s190, s191, s192, s193, s194, s195, s196, s197, s198, s199; + + static volatile Object s200, s201, s202, s203, s204, s205, s206, s207, s208, s209; + static volatile Object s210, s211, s212, s213, s214, s215, s216, s217, s218, s219; + static volatile Object s220, s221, s222, s223, s224, s225, s226, s227, s228, s229; + static volatile Object s230, s231, s232, s233, s234, s235, s236, s237, s238, s239; + static volatile Object s240, s241, s242, s243, s244, s245, s246, s247, s248, s249; + static volatile Object s250, s251, s252, s253, s254, s255, s256, s257, s258, s259; + static volatile Object s260, s261, s262, s263, s264, s265, s266, s267, s268, s269; + static volatile Object s270, s271, s272, s273, s274, s275, s276, s277, s278, s279; + static volatile Object s280, s281, s282, s283, s284, s285, s286, s287, s288, s289; + static volatile Object s290, s291, s292, s293, s294, s295, s296, s297, s298, s299; + + static volatile Object s300, s301, s302, s303, s304, s305, s306, s307, s308, s309; + static volatile Object s310, s311, s312, s313, s314, s315, s316, s317, s318, s319; + static volatile Object s320, s321, s322, s323, s324, s325, s326, s327, s328, s329; + static volatile Object s330, s331, s332, s333, s334, s335, s336, s337, s338, s339; + static volatile Object s340, s341, s342, s343, s344, s345, s346, s347, s348, s349; + static volatile Object s350, s351, s352, s353, s354, s355, s356, s357, s358, s359; + static volatile Object s360, s361, s362, s363, s364, s365, s366, s367, s368, s369; + static volatile Object s370, s371, s372, s373, s374, s375, s376, s377, s378, s379; + static volatile Object s380, s381, s382, s383, s384, s385, s386, s387, s388, s389; + static volatile Object s390, s391, s392, s393, s394, s395, s396, s397, s398, s399; + + static volatile Object s400, s401, s402, s403, s404, s405, s406, s407, s408, s409; + static volatile Object s410, s411, s412, s413, s414, s415, s416, s417, s418, s419; + static volatile Object s420, s421, s422, s423, s424, s425, s426, s427, s428, s429; + static volatile Object s430, s431, s432, s433, s434, s435, s436, s437, s438, s439; + static volatile Object s440, s441, s442, s443, s444, s445, s446, s447, s448, s449; + static volatile Object s450, s451, s452, s453, s454, s455, s456, s457, s458, s459; + static volatile Object s460, s461, s462, s463, s464, s465, s466, s467, s468, s469; + static volatile Object s470, s471, s472, s473, s474, s475, s476, s477, s478, s479; + static volatile Object s480, s481, s482, s483, s484, s485, s486, s487, s488, s489; + static volatile Object s490, s491, s492, s493, s494, s495, s496, s497, s498, s499; + + static volatile Object s500, s501, s502, s503, s504, s505, s506, s507, s508, s509; + static volatile Object s510, s511, s512, s513, s514, s515, s516, s517, s518, s519; + static volatile Object s520, s521, s522, s523, s524, s525, s526, s527, s528, s529; + static volatile Object s530, s531, s532, s533, s534, s535, s536, s537, s538, s539; + static volatile Object s540, s541, s542, s543, s544, s545, s546, s547, s548, s549; + static volatile Object s550, s551, s552, s553, s554, s555, s556, s557, s558, s559; + static volatile Object s560, s561, s562, s563, s564, s565, s566, s567, s568, s569; + static volatile Object s570, s571, s572, s573, s574, s575, s576, s577, s578, s579; + static volatile Object s580, s581, s582, s583, s584, s585, s586, s587, s588, s589; + static volatile Object s590, s591, s592, s593, s594, s595, s596, s597, s598, s599; + + static volatile Object s600, s601, s602, s603, s604, s605, s606, s607, s608, s609; + static volatile Object s610, s611, s612, s613, s614, s615, s616, s617, s618, s619; + static volatile Object s620, s621, s622, s623, s624, s625, s626, s627, s628, s629; + static volatile Object s630, s631, s632, s633, s634, s635, s636, s637, s638, s639; + static volatile Object s640, s641, s642, s643, s644, s645, s646, s647, s648, s649; + static volatile Object s650, s651, s652, s653, s654, s655, s656, s657, s658, s659; + static volatile Object s660, s661, s662, s663, s664, s665, s666, s667, s668, s669; + static volatile Object s670, s671, s672, s673, s674, s675, s676, s677, s678, s679; + static volatile Object s680, s681, s682, s683, s684, s685, s686, s687, s688, s689; + static volatile Object s690, s691, s692, s693, s694, s695, s696, s697, s698, s699; + + static volatile Object s700, s701, s702, s703, s704, s705, s706, s707, s708, s709; + static volatile Object s710, s711, s712, s713, s714, s715, s716, s717, s718, s719; + static volatile Object s720, s721, s722, s723, s724, s725, s726, s727, s728, s729; + static volatile Object s730, s731, s732, s733, s734, s735, s736, s737, s738, s739; + static volatile Object s740, s741, s742, s743, s744, s745, s746, s747, s748, s749; + static volatile Object s750, s751, s752, s753, s754, s755, s756, s757, s758, s759; + static volatile Object s760, s761, s762, s763, s764, s765, s766, s767, s768, s769; + static volatile Object s770, s771, s772, s773, s774, s775, s776, s777, s778, s779; + static volatile Object s780, s781, s782, s783, s784, s785, s786, s787, s788, s789; + static volatile Object s790, s791, s792, s793, s794, s795, s796, s797, s798, s799; + + static volatile Object s800, s801, s802, s803, s804, s805, s806, s807, s808, s809; + static volatile Object s810, s811, s812, s813, s814, s815, s816, s817, s818, s819; + static volatile Object s820, s821, s822, s823, s824, s825, s826, s827, s828, s829; + static volatile Object s830, s831, s832, s833, s834, s835, s836, s837, s838, s839; + static volatile Object s840, s841, s842, s843, s844, s845, s846, s847, s848, s849; + static volatile Object s850, s851, s852, s853, s854, s855, s856, s857, s858, s859; + static volatile Object s860, s861, s862, s863, s864, s865, s866, s867, s868, s869; + static volatile Object s870, s871, s872, s873, s874, s875, s876, s877, s878, s879; + static volatile Object s880, s881, s882, s883, s884, s885, s886, s887, s888, s889; + static volatile Object s890, s891, s892, s893, s894, s895, s896, s897, s898, s899; + + static volatile Object s900, s901, s902, s903, s904, s905, s906, s907, s908, s909; + static volatile Object s910, s911, s912, s913, s914, s915, s916, s917, s918, s919; + static volatile Object s920, s921, s922, s923, s924, s925, s926, s927, s928, s929; + static volatile Object s930, s931, s932, s933, s934, s935, s936, s937, s938, s939; + static volatile Object s940, s941, s942, s943, s944, s945, s946, s947, s948, s949; + static volatile Object s950, s951, s952, s953, s954, s955, s956, s957, s958, s959; + static volatile Object s960, s961, s962, s963, s964, s965, s966, s967, s968, s969; + static volatile Object s970, s971, s972, s973, s974, s975, s976, s977, s978, s979; + static volatile Object s980, s981, s982, s983, s984, s985, s986, s987, s988, s989; + static volatile Object s990, s991, s992, s993, s994, s995, s996, s997, s998, s999; + + + volatile Object i0000, i0001, i0002, i0003, i0004, i0005, i0006, i0007, i0008, i0009; + volatile Object i0010, i0011, i0012, i0013, i0014, i0015, i0016, i0017, i0018, i0019; + volatile Object i0020, i0021, i0022, i0023, i0024, i0025, i0026, i0027, i0028, i0029; + volatile Object i0030, i0031, i0032, i0033, i0034, i0035, i0036, i0037, i0038, i0039; + volatile Object i0040, i0041, i0042, i0043, i0044, i0045, i0046, i0047, i0048, i0049; + volatile Object i0050, i0051, i0052, i0053, i0054, i0055, i0056, i0057, i0058, i0059; + volatile Object i0060, i0061, i0062, i0063, i0064, i0065, i0066, i0067, i0068, i0069; + volatile Object i0070, i0071, i0072, i0073, i0074, i0075, i0076, i0077, i0078, i0079; + volatile Object i0080, i0081, i0082, i0083, i0084, i0085, i0086, i0087, i0088, i0089; + volatile Object i0090, i0091, i0092, i0093, i0094, i0095, i0096, i0097, i0098, i0099; + + volatile Object i0100, i0101, i0102, i0103, i0104, i0105, i0106, i0107, i0108, i0109; + volatile Object i0110, i0111, i0112, i0113, i0114, i0115, i0116, i0117, i0118, i0119; + volatile Object i0120, i0121, i0122, i0123, i0124, i0125, i0126, i0127, i0128, i0129; + volatile Object i0130, i0131, i0132, i0133, i0134, i0135, i0136, i0137, i0138, i0139; + volatile Object i0140, i0141, i0142, i0143, i0144, i0145, i0146, i0147, i0148, i0149; + volatile Object i0150, i0151, i0152, i0153, i0154, i0155, i0156, i0157, i0158, i0159; + volatile Object i0160, i0161, i0162, i0163, i0164, i0165, i0166, i0167, i0168, i0169; + volatile Object i0170, i0171, i0172, i0173, i0174, i0175, i0176, i0177, i0178, i0179; + volatile Object i0180, i0181, i0182, i0183, i0184, i0185, i0186, i0187, i0188, i0189; + volatile Object i0190, i0191, i0192, i0193, i0194, i0195, i0196, i0197, i0198, i0199; + + volatile Object i0200, i0201, i0202, i0203, i0204, i0205, i0206, i0207, i0208, i0209; + volatile Object i0210, i0211, i0212, i0213, i0214, i0215, i0216, i0217, i0218, i0219; + volatile Object i0220, i0221, i0222, i0223, i0224, i0225, i0226, i0227, i0228, i0229; + volatile Object i0230, i0231, i0232, i0233, i0234, i0235, i0236, i0237, i0238, i0239; + volatile Object i0240, i0241, i0242, i0243, i0244, i0245, i0246, i0247, i0248, i0249; + volatile Object i0250, i0251, i0252, i0253, i0254, i0255, i0256, i0257, i0258, i0259; + volatile Object i0260, i0261, i0262, i0263, i0264, i0265, i0266, i0267, i0268, i0269; + volatile Object i0270, i0271, i0272, i0273, i0274, i0275, i0276, i0277, i0278, i0279; + volatile Object i0280, i0281, i0282, i0283, i0284, i0285, i0286, i0287, i0288, i0289; + volatile Object i0290, i0291, i0292, i0293, i0294, i0295, i0296, i0297, i0298, i0299; + + volatile Object i0300, i0301, i0302, i0303, i0304, i0305, i0306, i0307, i0308, i0309; + volatile Object i0310, i0311, i0312, i0313, i0314, i0315, i0316, i0317, i0318, i0319; + volatile Object i0320, i0321, i0322, i0323, i0324, i0325, i0326, i0327, i0328, i0329; + volatile Object i0330, i0331, i0332, i0333, i0334, i0335, i0336, i0337, i0338, i0339; + volatile Object i0340, i0341, i0342, i0343, i0344, i0345, i0346, i0347, i0348, i0349; + volatile Object i0350, i0351, i0352, i0353, i0354, i0355, i0356, i0357, i0358, i0359; + volatile Object i0360, i0361, i0362, i0363, i0364, i0365, i0366, i0367, i0368, i0369; + volatile Object i0370, i0371, i0372, i0373, i0374, i0375, i0376, i0377, i0378, i0379; + volatile Object i0380, i0381, i0382, i0383, i0384, i0385, i0386, i0387, i0388, i0389; + volatile Object i0390, i0391, i0392, i0393, i0394, i0395, i0396, i0397, i0398, i0399; + + volatile Object i0400, i0401, i0402, i0403, i0404, i0405, i0406, i0407, i0408, i0409; + volatile Object i0410, i0411, i0412, i0413, i0414, i0415, i0416, i0417, i0418, i0419; + volatile Object i0420, i0421, i0422, i0423, i0424, i0425, i0426, i0427, i0428, i0429; + volatile Object i0430, i0431, i0432, i0433, i0434, i0435, i0436, i0437, i0438, i0439; + volatile Object i0440, i0441, i0442, i0443, i0444, i0445, i0446, i0447, i0448, i0449; + volatile Object i0450, i0451, i0452, i0453, i0454, i0455, i0456, i0457, i0458, i0459; + volatile Object i0460, i0461, i0462, i0463, i0464, i0465, i0466, i0467, i0468, i0469; + volatile Object i0470, i0471, i0472, i0473, i0474, i0475, i0476, i0477, i0478, i0479; + volatile Object i0480, i0481, i0482, i0483, i0484, i0485, i0486, i0487, i0488, i0489; + volatile Object i0490, i0491, i0492, i0493, i0494, i0495, i0496, i0497, i0498, i0499; + + volatile Object i0500, i0501, i0502, i0503, i0504, i0505, i0506, i0507, i0508, i0509; + volatile Object i0510, i0511, i0512, i0513, i0514, i0515, i0516, i0517, i0518, i0519; + volatile Object i0520, i0521, i0522, i0523, i0524, i0525, i0526, i0527, i0528, i0529; + volatile Object i0530, i0531, i0532, i0533, i0534, i0535, i0536, i0537, i0538, i0539; + volatile Object i0540, i0541, i0542, i0543, i0544, i0545, i0546, i0547, i0548, i0549; + volatile Object i0550, i0551, i0552, i0553, i0554, i0555, i0556, i0557, i0558, i0559; + volatile Object i0560, i0561, i0562, i0563, i0564, i0565, i0566, i0567, i0568, i0569; + volatile Object i0570, i0571, i0572, i0573, i0574, i0575, i0576, i0577, i0578, i0579; + volatile Object i0580, i0581, i0582, i0583, i0584, i0585, i0586, i0587, i0588, i0589; + volatile Object i0590, i0591, i0592, i0593, i0594, i0595, i0596, i0597, i0598, i0599; + + volatile Object i0600, i0601, i0602, i0603, i0604, i0605, i0606, i0607, i0608, i0609; + volatile Object i0610, i0611, i0612, i0613, i0614, i0615, i0616, i0617, i0618, i0619; + volatile Object i0620, i0621, i0622, i0623, i0624, i0625, i0626, i0627, i0628, i0629; + volatile Object i0630, i0631, i0632, i0633, i0634, i0635, i0636, i0637, i0638, i0639; + volatile Object i0640, i0641, i0642, i0643, i0644, i0645, i0646, i0647, i0648, i0649; + volatile Object i0650, i0651, i0652, i0653, i0654, i0655, i0656, i0657, i0658, i0659; + volatile Object i0660, i0661, i0662, i0663, i0664, i0665, i0666, i0667, i0668, i0669; + volatile Object i0670, i0671, i0672, i0673, i0674, i0675, i0676, i0677, i0678, i0679; + volatile Object i0680, i0681, i0682, i0683, i0684, i0685, i0686, i0687, i0688, i0689; + volatile Object i0690, i0691, i0692, i0693, i0694, i0695, i0696, i0697, i0698, i0699; + + volatile Object i0700, i0701, i0702, i0703, i0704, i0705, i0706, i0707, i0708, i0709; + volatile Object i0710, i0711, i0712, i0713, i0714, i0715, i0716, i0717, i0718, i0719; + volatile Object i0720, i0721, i0722, i0723, i0724, i0725, i0726, i0727, i0728, i0729; + volatile Object i0730, i0731, i0732, i0733, i0734, i0735, i0736, i0737, i0738, i0739; + volatile Object i0740, i0741, i0742, i0743, i0744, i0745, i0746, i0747, i0748, i0749; + volatile Object i0750, i0751, i0752, i0753, i0754, i0755, i0756, i0757, i0758, i0759; + volatile Object i0760, i0761, i0762, i0763, i0764, i0765, i0766, i0767, i0768, i0769; + volatile Object i0770, i0771, i0772, i0773, i0774, i0775, i0776, i0777, i0778, i0779; + volatile Object i0780, i0781, i0782, i0783, i0784, i0785, i0786, i0787, i0788, i0789; + volatile Object i0790, i0791, i0792, i0793, i0794, i0795, i0796, i0797, i0798, i0799; + + volatile Object i0800, i0801, i0802, i0803, i0804, i0805, i0806, i0807, i0808, i0809; + volatile Object i0810, i0811, i0812, i0813, i0814, i0815, i0816, i0817, i0818, i0819; + volatile Object i0820, i0821, i0822, i0823, i0824, i0825, i0826, i0827, i0828, i0829; + volatile Object i0830, i0831, i0832, i0833, i0834, i0835, i0836, i0837, i0838, i0839; + volatile Object i0840, i0841, i0842, i0843, i0844, i0845, i0846, i0847, i0848, i0849; + volatile Object i0850, i0851, i0852, i0853, i0854, i0855, i0856, i0857, i0858, i0859; + volatile Object i0860, i0861, i0862, i0863, i0864, i0865, i0866, i0867, i0868, i0869; + volatile Object i0870, i0871, i0872, i0873, i0874, i0875, i0876, i0877, i0878, i0879; + volatile Object i0880, i0881, i0882, i0883, i0884, i0885, i0886, i0887, i0888, i0889; + volatile Object i0890, i0891, i0892, i0893, i0894, i0895, i0896, i0897, i0898, i0899; + + volatile Object i0900, i0901, i0902, i0903, i0904, i0905, i0906, i0907, i0908, i0909; + volatile Object i0910, i0911, i0912, i0913, i0914, i0915, i0916, i0917, i0918, i0919; + volatile Object i0920, i0921, i0922, i0923, i0924, i0925, i0926, i0927, i0928, i0929; + volatile Object i0930, i0931, i0932, i0933, i0934, i0935, i0936, i0937, i0938, i0939; + volatile Object i0940, i0941, i0942, i0943, i0944, i0945, i0946, i0947, i0948, i0949; + volatile Object i0950, i0951, i0952, i0953, i0954, i0955, i0956, i0957, i0958, i0959; + volatile Object i0960, i0961, i0962, i0963, i0964, i0965, i0966, i0967, i0968, i0969; + volatile Object i0970, i0971, i0972, i0973, i0974, i0975, i0976, i0977, i0978, i0979; + volatile Object i0980, i0981, i0982, i0983, i0984, i0985, i0986, i0987, i0988, i0989; + volatile Object i0990, i0991, i0992, i0993, i0994, i0995, i0996, i0997, i0998, i0999; + + volatile Object i1000, i1001, i1002, i1003, i1004, i1005, i1006, i1007, i1008, i1009; + volatile Object i1010, i1011, i1012, i1013, i1014, i1015, i1016, i1017, i1018, i1019; + volatile Object i1020, i1021, i1022, i1023, i1024, i1025, i1026, i1027, i1028, i1029; + volatile Object i1030, i1031, i1032, i1033, i1034, i1035, i1036, i1037, i1038, i1039; + volatile Object i1040, i1041, i1042, i1043, i1044, i1045, i1046, i1047, i1048, i1049; + volatile Object i1050, i1051, i1052, i1053, i1054, i1055, i1056, i1057, i1058, i1059; + volatile Object i1060, i1061, i1062, i1063, i1064, i1065, i1066, i1067, i1068, i1069; + volatile Object i1070, i1071, i1072, i1073, i1074, i1075, i1076, i1077, i1078, i1079; + volatile Object i1080, i1081, i1082, i1083, i1084, i1085, i1086, i1087, i1088, i1089; + volatile Object i1090, i1091, i1092, i1093, i1094, i1095, i1096, i1097, i1098, i1099; + + + // Note: ARM64, registers X16 and X17 are respectively IP0 and IP1, + // the scratch registers used by the VIXL AArch64 assembler (and to + // some extent, by ART's ARM64 code generator). + + /// CHECK-START-ARM64: void Main.testStaticVolatileFieldGetWithLargeOffset() disassembly (after) + /// CHECK: StaticFieldGet + /// CHECK: mov x17, #<<Offset:0x[0-9a-f]{4}>> + /// CHECK: add x16, {{x\d+}}, x17 + /// CHECK: ldar {{w\d+}}, [x16] + static void testStaticVolatileFieldGetWithLargeOffset() { + // The offset of this static field cannot be encoded as an immediate on ARM64. + Object s = s999; + } + + /// CHECK-START-ARM64: void Main.testInstanceVolatileFieldGetWithLargeOffset() disassembly (after) + /// CHECK: InstanceFieldGet + /// CHECK: mov x17, #<<Offset:0x[0-9a-f]{4}>> + /// CHECK: add x16, {{x\d+}}, x17 + /// CHECK: ldar {{w\d+}}, [x16] + void testInstanceVolatileFieldGetWithLargeOffset() { + // The offset of this instance field cannot be encoded as an immediate on ARM64. + Object i = i1029; + } + + + public static void main(String[] args) { + testStaticVolatileFieldGetWithLargeOffset(); + Main m = new Main(); + m.testInstanceVolatileFieldGetWithLargeOffset(); + System.out.println("passed"); + } + +} diff --git a/test/636-wrong-static-access/expected.txt b/test/636-wrong-static-access/expected.txt new file mode 100644 index 0000000000..6a5618ebc6 --- /dev/null +++ b/test/636-wrong-static-access/expected.txt @@ -0,0 +1 @@ +JNI_OnLoad called diff --git a/test/636-wrong-static-access/info.txt b/test/636-wrong-static-access/info.txt new file mode 100644 index 0000000000..184d858cb9 --- /dev/null +++ b/test/636-wrong-static-access/info.txt @@ -0,0 +1,2 @@ +Test that the compiler checks if a resolved field is +of the expected static/instance kind. diff --git a/test/636-wrong-static-access/run b/test/636-wrong-static-access/run new file mode 100755 index 0000000000..5e999209b8 --- /dev/null +++ b/test/636-wrong-static-access/run @@ -0,0 +1,20 @@ +#!/bin/bash +# +# Copyright (C) 2017 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 verification soft fail, to ensure the verifier does not flag +# the method we want to compile as "non-compilable" because it sees +# the method will throw IncompatibleClassChangeError. +exec ${RUN} $@ --verify-soft-fail diff --git a/test/636-wrong-static-access/src-ex/Foo.java b/test/636-wrong-static-access/src-ex/Foo.java new file mode 100644 index 0000000000..9e3b7a74c8 --- /dev/null +++ b/test/636-wrong-static-access/src-ex/Foo.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2017 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 Foo { + public static void doTest() { + // Execute foo once to make sure the dex cache will be updated. + try { + foo(); + throw new Error("Expected IncompatibleClassChangeError"); + } catch (IncompatibleClassChangeError e) { + // Expected. + } + Main.ensureJitCompiled(Foo.class, "foo"); + try { + foo(); + throw new Error("Expected IncompatibleClassChangeError"); + } catch (IncompatibleClassChangeError e) { + // Expected. + } + } + + public static void foo() { + System.out.println(Holder.field); + } +} diff --git a/test/636-wrong-static-access/src/Holder.java b/test/636-wrong-static-access/src/Holder.java new file mode 100644 index 0000000000..f3b1c5717c --- /dev/null +++ b/test/636-wrong-static-access/src/Holder.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2017 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 Holder { + public static int field = 42; +} diff --git a/test/636-wrong-static-access/src/Main.java b/test/636-wrong-static-access/src/Main.java new file mode 100644 index 0000000000..bd8548e372 --- /dev/null +++ b/test/636-wrong-static-access/src/Main.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017 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.Method; + +public class Main { + static final String DEX_FILE = System.getenv("DEX_LOCATION") + "/636-wrong-static-access-ex.jar"; + + public static void main(String[] args) throws Exception { + System.loadLibrary(args[0]); + Class<?> pathClassLoader = Class.forName("dalvik.system.PathClassLoader"); + if (pathClassLoader == null) { + throw new AssertionError("Couldn't find path class loader class"); + } + Constructor<?> constructor = + pathClassLoader.getDeclaredConstructor(String.class, ClassLoader.class); + ClassLoader loader = (ClassLoader) constructor.newInstance( + DEX_FILE, ClassLoader.getSystemClassLoader()); + Class<?> foo = loader.loadClass("Foo"); + Method doTest = foo.getDeclaredMethod("doTest"); + doTest.invoke(null); + } + + public static native void ensureJitCompiled(Class<?> cls, String methodName); +} diff --git a/test/636-wrong-static-access/src2/Holder.java b/test/636-wrong-static-access/src2/Holder.java new file mode 100644 index 0000000000..a26da24319 --- /dev/null +++ b/test/636-wrong-static-access/src2/Holder.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2017 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 Holder { + public int field = 42; +} diff --git a/test/706-checker-scheduler/expected.txt b/test/706-checker-scheduler/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/706-checker-scheduler/expected.txt diff --git a/test/706-checker-scheduler/info.txt b/test/706-checker-scheduler/info.txt new file mode 100644 index 0000000000..b4ad9b4378 --- /dev/null +++ b/test/706-checker-scheduler/info.txt @@ -0,0 +1 @@ +Tests for HInstruction scheduler. diff --git a/test/706-checker-scheduler/src/Main.java b/test/706-checker-scheduler/src/Main.java new file mode 100644 index 0000000000..1721e4294e --- /dev/null +++ b/test/706-checker-scheduler/src/Main.java @@ -0,0 +1,82 @@ +/* + * 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 int static_variable = 0; + + /// CHECK-START-ARM64: int Main.arrayAccess() scheduler (before) + /// CHECK: <<Const1:i\d+>> IntConstant 1 + /// CHECK: <<i0:i\d+>> Phi + /// CHECK: <<res0:i\d+>> Phi + /// CHECK: <<Array:i\d+>> IntermediateAddress + /// CHECK: <<ArrayGet1:i\d+>> ArrayGet [<<Array>>,<<i0>>] + /// CHECK: <<res1:i\d+>> Add [<<res0>>,<<ArrayGet1>>] + /// CHECK: <<i1:i\d+>> Add [<<i0>>,<<Const1>>] + /// CHECK: <<ArrayGet2:i\d+>> ArrayGet [<<Array>>,<<i1>>] + /// CHECK: Add [<<res1>>,<<ArrayGet2>>] + + /// CHECK-START-ARM64: int Main.arrayAccess() scheduler (after) + /// CHECK: <<Const1:i\d+>> IntConstant 1 + /// CHECK: <<i0:i\d+>> Phi + /// CHECK: <<res0:i\d+>> Phi + /// CHECK: <<Array:i\d+>> IntermediateAddress + /// CHECK: <<ArrayGet1:i\d+>> ArrayGet [<<Array>>,<<i0>>] + /// CHECK: <<i1:i\d+>> Add [<<i0>>,<<Const1>>] + /// CHECK: <<ArrayGet2:i\d+>> ArrayGet [<<Array>>,<<i1>>] + /// CHECK: <<res1:i\d+>> Add [<<res0>>,<<ArrayGet1>>] + /// CHECK: Add [<<res1>>,<<ArrayGet2>>] + + public static int arrayAccess() { + int res = 0; + int [] array = new int[10]; + for (int i = 0; i < 9; i++) { + res += array[i]; + res += array[i + 1]; + } + return res; + } + + /// CHECK-START-ARM64: int Main.intDiv(int) scheduler (before) + /// CHECK: Sub + /// CHECK: DivZeroCheck + /// CHECK: Div + /// CHECK: StaticFieldSet + + /// CHECK-START-ARM64: int Main.intDiv(int) scheduler (after) + /// CHECK: Sub + /// CHECK-NOT: StaticFieldSet + /// CHECK: DivZeroCheck + /// CHECK-NOT: Sub + /// CHECK: Div + public static int intDiv(int arg) { + int res = 0; + int tmp = arg; + for (int i = 1; i < arg; i++) { + tmp -= i; + res = res / i; // div-zero check barrier. + static_variable++; + } + res += tmp; + return res; + } + + public static void main(String[] args) { + if ((arrayAccess() + intDiv(10)) != -35) { + System.out.println("FAIL"); + } + } +} diff --git a/test/900-hello-plugin/run b/test/900-hello-plugin/run index 35b08715a1..50835f89af 100755 --- a/test/900-hello-plugin/run +++ b/test/900-hello-plugin/run @@ -18,7 +18,5 @@ plugin=libartagentd.so if [[ "$@" == *"-O"* ]]; then plugin=libartagent.so fi -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --runtime-option -agentpath:${plugin}=test_900 \ +./default-run "$@" --runtime-option -agentpath:${plugin}=test_900 \ --android-runtime-option -Xplugin:${plugin} diff --git a/test/901-hello-ti-agent/run b/test/901-hello-ti-agent/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/901-hello-ti-agent/run +++ b/test/901-hello-ti-agent/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/902-hello-transformation/run b/test/902-hello-transformation/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/902-hello-transformation/run +++ b/test/902-hello-transformation/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/903-hello-tagging/run b/test/903-hello-tagging/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/903-hello-tagging/run +++ b/test/903-hello-tagging/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/904-object-allocation/run b/test/904-object-allocation/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/904-object-allocation/run +++ b/test/904-object-allocation/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/905-object-free/run b/test/905-object-free/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/905-object-free/run +++ b/test/905-object-free/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/906-iterate-heap/run b/test/906-iterate-heap/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/906-iterate-heap/run +++ b/test/906-iterate-heap/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/907-get-loaded-classes/run b/test/907-get-loaded-classes/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/907-get-loaded-classes/run +++ b/test/907-get-loaded-classes/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/908-gc-start-finish/gc_callbacks.cc b/test/908-gc-start-finish/gc_callbacks.cc index 59801ff648..8f96ee63ef 100644 --- a/test/908-gc-start-finish/gc_callbacks.cc +++ b/test/908-gc-start-finish/gc_callbacks.cc @@ -38,43 +38,32 @@ static void JNICALL GarbageCollectionStart(jvmtiEnv* ti_env ATTRIBUTE_UNUSED) { } extern "C" JNIEXPORT void JNICALL Java_Main_setupGcCallback( - JNIEnv* env ATTRIBUTE_UNUSED, jclass klass ATTRIBUTE_UNUSED) { + JNIEnv* env, jclass klass ATTRIBUTE_UNUSED) { jvmtiEventCallbacks callbacks; memset(&callbacks, 0, sizeof(jvmtiEventCallbacks)); callbacks.GarbageCollectionFinish = GarbageCollectionFinish; callbacks.GarbageCollectionStart = GarbageCollectionStart; jvmtiError ret = jvmti_env->SetEventCallbacks(&callbacks, sizeof(callbacks)); - if (ret != JVMTI_ERROR_NONE) { - char* err; - jvmti_env->GetErrorName(ret, &err); - printf("Error setting callbacks: %s\n", err); - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err)); - } + JvmtiErrorToException(env, ret); } -extern "C" JNIEXPORT void JNICALL Java_Main_enableGcTracking(JNIEnv* env ATTRIBUTE_UNUSED, +extern "C" JNIEXPORT void JNICALL Java_Main_enableGcTracking(JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jboolean enable) { jvmtiError ret = jvmti_env->SetEventNotificationMode( enable ? JVMTI_ENABLE : JVMTI_DISABLE, JVMTI_EVENT_GARBAGE_COLLECTION_START, nullptr); - if (ret != JVMTI_ERROR_NONE) { - char* err; - jvmti_env->GetErrorName(ret, &err); - printf("Error enabling/disabling gc callbacks: %s\n", err); - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err)); + if (JvmtiErrorToException(env, ret)) { + return; } ret = jvmti_env->SetEventNotificationMode( enable ? JVMTI_ENABLE : JVMTI_DISABLE, JVMTI_EVENT_GARBAGE_COLLECTION_FINISH, nullptr); - if (ret != JVMTI_ERROR_NONE) { - char* err; - jvmti_env->GetErrorName(ret, &err); - printf("Error enabling/disabling gc callbacks: %s\n", err); - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err)); + if (JvmtiErrorToException(env, ret)) { + return; } } diff --git a/test/908-gc-start-finish/run b/test/908-gc-start-finish/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/908-gc-start-finish/run +++ b/test/908-gc-start-finish/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/909-attach-agent/run b/test/909-attach-agent/run index 985341bd4f..4a2eb34a1d 100755 --- a/test/909-attach-agent/run +++ b/test/909-attach-agent/run @@ -21,17 +21,13 @@ if [[ "$@" == *"-O"* ]]; then plugin=libopenjdkjvmti.so fi -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --android-runtime-option -Xplugin:${plugin} \ - --android-runtime-option -Xfully-deoptable \ +./default-run "$@" --android-runtime-option -Xplugin:${plugin} \ + --android-runtime-option -Xcompiler-option \ + --android-runtime-option --debuggable \ --args agent:${agent}=909-attach-agent -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --android-runtime-option -Xfully-deoptable \ +./default-run "$@" --android-runtime-option -Xcompiler-option \ + --android-runtime-option --debuggable \ --args agent:${agent}=909-attach-agent -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --args agent:${agent}=909-attach-agent +./default-run "$@" --args agent:${agent}=909-attach-agent diff --git a/test/909-attach-agent/src/Main.java b/test/909-attach-agent/src/Main.java index 8a8a087458..569b89ad7d 100644 --- a/test/909-attach-agent/src/Main.java +++ b/test/909-attach-agent/src/Main.java @@ -19,7 +19,7 @@ import java.io.IOException; public class Main { public static void main(String[] args) { - System.out.println("Hello, world!"); + System.err.println("Hello, world!"); for(String a : args) { if(a.startsWith("agent:")) { String agent = a.substring(6); @@ -30,6 +30,6 @@ public class Main { } } } - System.out.println("Goodbye!"); + System.err.println("Goodbye!"); } } diff --git a/test/910-methods/run b/test/910-methods/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/910-methods/run +++ b/test/910-methods/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/911-get-stack-trace/run b/test/911-get-stack-trace/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/911-get-stack-trace/run +++ b/test/911-get-stack-trace/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/911-get-stack-trace/src/PrintThread.java b/test/911-get-stack-trace/src/PrintThread.java index 97815ccad9..136fd80d40 100644 --- a/test/911-get-stack-trace/src/PrintThread.java +++ b/test/911-get-stack-trace/src/PrintThread.java @@ -44,6 +44,9 @@ public class PrintThread { if (name.contains("Daemon")) { // Do not print daemon stacks, as they're non-deterministic. stackSerialization = "<not printed>"; + } else if (name.startsWith("Jit thread pool worker")) { + // Skip JIT thread pool. It may or may not be there depending on configuration. + continue; } else { StringBuilder sb = new StringBuilder(); for (String[] stackElement : (String[][])stackInfo[1]) { diff --git a/test/912-classes/expected.txt b/test/912-classes/expected.txt index d0b77a4bae..328216b324 100644 --- a/test/912-classes/expected.txt +++ b/test/912-classes/expected.txt @@ -29,7 +29,7 @@ int 100000 class [Ljava.lang.String; 10000 class java.lang.Object 111 class Main$TestForNonInit 11 -class Main$TestForInitFail 1001 +class Main$TestForInitFail 1011 int [] class [Ljava.lang.String; [] class java.lang.Object [] diff --git a/test/912-classes/run b/test/912-classes/run index 20dfc4b767..f24db40cb0 100755 --- a/test/912-classes/run +++ b/test/912-classes/run @@ -18,7 +18,5 @@ # In certain configurations, the app images may be valid even in a new classloader. Turn off # app images to avoid the issue. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti \ +./default-run "$@" --jvmti \ --no-app-image diff --git a/test/913-heaps/run b/test/913-heaps/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/913-heaps/run +++ b/test/913-heaps/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/914-hello-obsolescence/run b/test/914-hello-obsolescence/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/914-hello-obsolescence/run +++ b/test/914-hello-obsolescence/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/915-obsolete-2/run b/test/915-obsolete-2/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/915-obsolete-2/run +++ b/test/915-obsolete-2/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/916-obsolete-jit/run b/test/916-obsolete-jit/run index 9056211284..b6d406fd99 100755 --- a/test/916-obsolete-jit/run +++ b/test/916-obsolete-jit/run @@ -21,7 +21,5 @@ if [[ "$@" == *"--jit"* ]]; then else other_args="--jit" fi -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - ${other_args} \ +./default-run "$@" ${other_args} \ --jvmti diff --git a/test/916-obsolete-jit/src/Main.java b/test/916-obsolete-jit/src/Main.java index 1b03200ba5..2b3296f1f2 100644 --- a/test/916-obsolete-jit/src/Main.java +++ b/test/916-obsolete-jit/src/Main.java @@ -116,37 +116,27 @@ public class Main { doTest(new Transform(), new TestWatcher()); } - // TODO Workaround to (1) inability to ensure that current_method is not put into a register by - // the JIT and/or (2) inability to deoptimize frames near runtime functions. - // TODO Fix one/both of these issues. - public static void doCall(Runnable r) { - r.run(); - } - private static boolean interpreting = true; private static boolean retry = false; public static void doTest(Transform t, TestWatcher w) { // Get the methods that need to be optimized. Method say_hi_method; - Method do_call_method; // Figure out if we can even JIT at all. final boolean has_jit = hasJit(); try { say_hi_method = Transform.class.getDeclaredMethod( "sayHi", Runnable.class, Consumer.class); - do_call_method = Main.class.getDeclaredMethod("doCall", Runnable.class); } catch (Exception e) { System.out.println("Unable to find methods!"); e.printStackTrace(); return; } // Makes sure the stack is the way we want it for the test and does the redefinition. It will - // set the retry boolean to true if we need to go around again due to a bad stack. + // set the retry boolean to true if the stack does not have a JIT-compiled sayHi entry. This can + // only happen if the method gets GC'd. Runnable do_redefinition = () -> { - if (has_jit && - (Main.isInterpretedFunction(say_hi_method, true) || - Main.isInterpretedFunction(do_call_method, false))) { + if (has_jit && Main.isInterpretedFunction(say_hi_method, true)) { // Try again. We are not running the right jitted methods/cannot redefine them now. retry = true; } else { @@ -161,7 +151,6 @@ public class Main { do { // Run ensureJitCompiled here since it might get GCd ensureJitCompiled(Transform.class, "sayHi"); - ensureJitCompiled(Main.class, "doCall"); // Clear output. w.clear(); // Try and redefine. diff --git a/test/916-obsolete-jit/src/Transform.java b/test/916-obsolete-jit/src/Transform.java index f4dcf09dc6..9c9adbc22d 100644 --- a/test/916-obsolete-jit/src/Transform.java +++ b/test/916-obsolete-jit/src/Transform.java @@ -29,13 +29,7 @@ class Transform { reporter.accept("Pre Start private method call"); Start(reporter); reporter.accept("Post Start private method call"); - // TODO Revisit with b/33616143 - // TODO Uncomment this once either b/33630159 or b/33616143 are resolved. - // r.run(); - // TODO This doCall function is a very temporary fix until we get either deoptimization near - // runtime frames working, forcing current method to be always read from the stack or both - // working. - Main.doCall(r); + r.run(); reporter.accept("Pre Finish private method call"); Finish(reporter); reporter.accept("Post Finish private method call"); diff --git a/test/917-fields-transformation/run b/test/917-fields-transformation/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/917-fields-transformation/run +++ b/test/917-fields-transformation/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/918-fields/run b/test/918-fields/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/918-fields/run +++ b/test/918-fields/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/919-obsolete-fields/run b/test/919-obsolete-fields/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/919-obsolete-fields/run +++ b/test/919-obsolete-fields/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/919-obsolete-fields/src/Main.java b/test/919-obsolete-fields/src/Main.java index 1d893f125a..ffb9897236 100644 --- a/test/919-obsolete-fields/src/Main.java +++ b/test/919-obsolete-fields/src/Main.java @@ -120,13 +120,6 @@ public class Main { doTest(new Transform(w), w); } - // TODO Workaround to (1) inability to ensure that current_method is not put into a register by - // the JIT and/or (2) inability to deoptimize frames near runtime functions. - // TODO Fix one/both of these issues. - public static void doCall(Runnable r) { - r.run(); - } - private static boolean interpreting = true; private static boolean retry = false; diff --git a/test/919-obsolete-fields/src/Transform.java b/test/919-obsolete-fields/src/Transform.java index abd1d19b66..c8e3cbd934 100644 --- a/test/919-obsolete-fields/src/Transform.java +++ b/test/919-obsolete-fields/src/Transform.java @@ -34,12 +34,7 @@ class Transform { reporter.accept("Pre Start private method call"); Start(); reporter.accept("Post Start private method call"); - // TODO Revist with b/33616143 - // TODO Uncomment this - // r.run(); - // TODO This is a very temporary fix until we get either deoptimization near runtime frames - // working, forcing current method to be always read from the stack or both working. - Main.doCall(r); + r.run(); reporter.accept("Pre Finish private method call"); Finish(); reporter.accept("Post Finish private method call"); diff --git a/test/920-objects/run b/test/920-objects/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/920-objects/run +++ b/test/920-objects/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/921-hello-failure/expected.txt b/test/921-hello-failure/expected.txt index 9615e6b33d..e9b6a20cd6 100644 --- a/test/921-hello-failure/expected.txt +++ b/test/921-hello-failure/expected.txt @@ -29,3 +29,21 @@ hello2 - MultiRetrans Transformation error : java.lang.Exception(Failed to retransform classes <LTransform;, LTransform2;> due to JVMTI_ERROR_NAMES_DONT_MATCH) hello - MultiRetrans hello2 - MultiRetrans +hello - NewMethod +Transformation error : java.lang.Exception(Failed to redefine class <LTransform;> due to JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_ADDED) +hello - NewMethod +hello2 - MissingMethod +Transformation error : java.lang.Exception(Failed to redefine class <LTransform3;> due to JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_DELETED) +hello2 - MissingMethod +hello - MethodChange +Transformation error : java.lang.Exception(Failed to redefine class <LTransform;> due to JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_MODIFIERS_CHANGED) +hello - MethodChange +hello - NewField +Transformation error : java.lang.Exception(Failed to redefine class <LTransform;> due to JVMTI_ERROR_UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED) +hello - NewField +hello there - MissingField +Transformation error : java.lang.Exception(Failed to redefine class <LTransform4;> due to JVMTI_ERROR_UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED) +hello there - MissingField +hello there again - FieldChange +Transformation error : java.lang.Exception(Failed to redefine class <LTransform4;> due to JVMTI_ERROR_UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED) +hello there again - FieldChange diff --git a/test/921-hello-failure/run b/test/921-hello-failure/run index 3ef4832da2..8be0ed4aed 100755 --- a/test/921-hello-failure/run +++ b/test/921-hello-failure/run @@ -15,6 +15,4 @@ # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/921-hello-failure/src/FieldChange.java b/test/921-hello-failure/src/FieldChange.java new file mode 100644 index 0000000000..cc2ea284d9 --- /dev/null +++ b/test/921-hello-failure/src/FieldChange.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2017 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.Base64; + +class FieldChange { + // The following is a base64 encoding of the following class. + // class Transform4 { + // private Object greeting; + // public Transform4(String hi) { } + // public void sayHi(String name) { + // throw new Error("Should not be called!"); + // } + // } + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAFwoABgAQBwARCAASCgACABMHABQHABUBAAhncmVldGluZwEAEkxqYXZhL2xhbmcv" + + "T2JqZWN0OwEABjxpbml0PgEAFShMamF2YS9sYW5nL1N0cmluZzspVgEABENvZGUBAA9MaW5lTnVt" + + "YmVyVGFibGUBAAVzYXlIaQEAClNvdXJjZUZpbGUBAA9UcmFuc2Zvcm00LmphdmEMAAkAFgEAD2ph" + + "dmEvbGFuZy9FcnJvcgEAFVNob3VsZCBub3QgYmUgY2FsbGVkIQwACQAKAQAKVHJhbnNmb3JtNAEA" + + "EGphdmEvbGFuZy9PYmplY3QBAAMoKVYAIAAFAAYAAAABAAIABwAIAAAAAgABAAkACgABAAsAAAAd" + + "AAEAAgAAAAUqtwABsQAAAAEADAAAAAYAAQAAAAMAAQANAAoAAQALAAAAIgADAAIAAAAKuwACWRID" + + "twAEvwAAAAEADAAAAAYAAQAAAAUAAQAOAAAAAgAP"); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQASXs5yszuhud+/w4q07495k9eO7Yb+l8u4AgAAcAAAAHhWNBIAAAAAAAAAABgCAAAM" + + "AAAAcAAAAAUAAACgAAAAAgAAALQAAAABAAAAzAAAAAQAAADUAAAAAQAAAPQAAACkAQAAFAEAAFYB" + + "AABeAQAAbAEAAH8BAACTAQAApwEAAL4BAADPAQAA0gEAANYBAADqAQAA9AEAAAEAAAACAAAAAwAA" + + "AAQAAAAHAAAABwAAAAQAAAAAAAAACAAAAAQAAABQAQAAAAACAAoAAAAAAAEAAAAAAAAAAQALAAAA" + + "AQABAAAAAAACAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAGAAAAAAAAAAcCAAAAAAAAAgACAAEAAAD7" + + "AQAABAAAAHAQAwAAAA4ABAACAAIAAAABAgAACQAAACIAAQAbAQUAAABwIAIAEAAnAAAAAQAAAAMA" + + "Bjxpbml0PgAMTFRyYW5zZm9ybTQ7ABFMamF2YS9sYW5nL0Vycm9yOwASTGphdmEvbGFuZy9PYmpl" + + "Y3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAFVNob3VsZCBub3QgYmUgY2FsbGVkIQAPVHJhbnNmb3Jt" + + "NC5qYXZhAAFWAAJWTAASZW1pdHRlcjogamFjay00LjIyAAhncmVldGluZwAFc2F5SGkAAwEABw4A" + + "BQEABw4AAAEBAQACAIGABJQCAQGsAgANAAAAAAAAAAEAAAAAAAAAAQAAAAwAAABwAAAAAgAAAAUA" + + "AACgAAAAAwAAAAIAAAC0AAAABAAAAAEAAADMAAAABQAAAAQAAADUAAAABgAAAAEAAAD0AAAAASAA" + + "AAIAAAAUAQAAARAAAAEAAABQAQAAAiAAAAwAAABWAQAAAyAAAAIAAAD7AQAAACAAAAEAAAAHAgAA" + + "ABAAAAEAAAAYAgAA"); + + public static void doTest(Transform4 t) { + t.sayHi("FieldChange"); + try { + Main.doCommonClassRedefinition(Transform4.class, CLASS_BYTES, DEX_BYTES); + } catch (Exception e) { + System.out.println( + "Transformation error : " + e.getClass().getName() + "(" + e.getMessage() + ")"); + } + t.sayHi("FieldChange"); + } +} diff --git a/test/921-hello-failure/src/Main.java b/test/921-hello-failure/src/Main.java index 67ca1e15d6..61d69e7396 100644 --- a/test/921-hello-failure/src/Main.java +++ b/test/921-hello-failure/src/Main.java @@ -25,6 +25,12 @@ public class Main { ReorderInterface.doTest(new Transform2()); MultiRedef.doTest(new Transform(), new Transform2()); MultiRetrans.doTest(new Transform(), new Transform2()); + NewMethod.doTest(new Transform()); + MissingMethod.doTest(new Transform3()); + MethodChange.doTest(new Transform()); + NewField.doTest(new Transform()); + MissingField.doTest(new Transform4("there")); + FieldChange.doTest(new Transform4("there again")); } // Transforms the class. This throws an exception if something goes wrong. diff --git a/test/921-hello-failure/src/MethodChange.java b/test/921-hello-failure/src/MethodChange.java new file mode 100644 index 0000000000..16f57788c8 --- /dev/null +++ b/test/921-hello-failure/src/MethodChange.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2017 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.Base64; + +class MethodChange { + // The following is a base64 encoding of the following class. + // class Transform { + // void sayHi(String name) { + // throw new Error("Should not be called!"); + // } + // } + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAFQoABgAPBwAQCAARCgACABIHABMHABQBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAP" + + "TGluZU51bWJlclRhYmxlAQAFc2F5SGkBABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAApTb3VyY2VG" + + "aWxlAQAOVHJhbnNmb3JtLmphdmEMAAcACAEAD2phdmEvbGFuZy9FcnJvcgEAFVNob3VsZCBub3Qg" + + "YmUgY2FsbGVkIQwABwAMAQAJVHJhbnNmb3JtAQAQamF2YS9sYW5nL09iamVjdAAgAAUABgAAAAAA" + + "AgAAAAcACAABAAkAAAAdAAEAAQAAAAUqtwABsQAAAAEACgAAAAYAAQAAAAIAAAALAAwAAQAJAAAA" + + "IgADAAIAAAAKuwACWRIDtwAEvwAAAAEACgAAAAYAAQAAAAQAAQANAAAAAgAO"); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQCrV81cy4Q+YKMMMqc0bZEO5Y1X5u7irPeQAgAAcAAAAHhWNBIAAAAAAAAAAPwBAAAL" + + "AAAAcAAAAAUAAACcAAAAAgAAALAAAAAAAAAAAAAAAAQAAADIAAAAAQAAAOgAAACIAQAACAEAAEoB" + + "AABSAQAAXwEAAHIBAACGAQAAmgEAALEBAADBAQAAxAEAAMgBAADcAQAAAQAAAAIAAAADAAAABAAA" + + "AAcAAAAHAAAABAAAAAAAAAAIAAAABAAAAEQBAAAAAAAAAAAAAAAAAQAKAAAAAQABAAAAAAACAAAA" + + "AAAAAAAAAAAAAAAAAgAAAAAAAAAGAAAAAAAAAO4BAAAAAAAAAQABAAEAAADjAQAABAAAAHAQAwAA" + + "AA4ABAACAAIAAADoAQAACQAAACIAAQAbAQUAAABwIAIAEAAnAAAAAQAAAAMABjxpbml0PgALTFRy" + + "YW5zZm9ybTsAEUxqYXZhL2xhbmcvRXJyb3I7ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZhL2xh" + + "bmcvU3RyaW5nOwAVU2hvdWxkIG5vdCBiZSBjYWxsZWQhAA5UcmFuc2Zvcm0uamF2YQABVgACVkwA" + + "EmVtaXR0ZXI6IGphY2stNC4yNAAFc2F5SGkAAgAHDgAEAQAHDgAAAAEBAICABIgCAQCgAgwAAAAA" + + "AAAAAQAAAAAAAAABAAAACwAAAHAAAAACAAAABQAAAJwAAAADAAAAAgAAALAAAAAFAAAABAAAAMgA" + + "AAAGAAAAAQAAAOgAAAABIAAAAgAAAAgBAAABEAAAAQAAAEQBAAACIAAACwAAAEoBAAADIAAAAgAA" + + "AOMBAAAAIAAAAQAAAO4BAAAAEAAAAQAAAPwBAAA="); + + public static void doTest(Transform t) { + t.sayHi("MethodChange"); + try { + Main.doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES); + } catch (Exception e) { + System.out.println( + "Transformation error : " + e.getClass().getName() + "(" + e.getMessage() + ")"); + } + t.sayHi("MethodChange"); + } +} diff --git a/test/921-hello-failure/src/MissingField.java b/test/921-hello-failure/src/MissingField.java new file mode 100644 index 0000000000..2f643cc871 --- /dev/null +++ b/test/921-hello-failure/src/MissingField.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2017 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.Base64; + +class MissingField { + // The following is a base64 encoding of the following class. + // class Transform4 { + // public Transform4(String s) { } + // public void sayHi(String name) { + // throw new Error("Should not be called!"); + // } + // } + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAFQoABgAOBwAPCAAQCgACABEHABIHABMBAAY8aW5pdD4BABUoTGphdmEvbGFuZy9T" + + "dHJpbmc7KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAFc2F5SGkBAApTb3VyY2VGaWxlAQAP" + + "VHJhbnNmb3JtNC5qYXZhDAAHABQBAA9qYXZhL2xhbmcvRXJyb3IBABVTaG91bGQgbm90IGJlIGNh" + + "bGxlZCEMAAcACAEAClRyYW5zZm9ybTQBABBqYXZhL2xhbmcvT2JqZWN0AQADKClWACAABQAGAAAA" + + "AAACAAEABwAIAAEACQAAAB0AAQACAAAABSq3AAGxAAAAAQAKAAAABgABAAAAAgABAAsACAABAAkA" + + "AAAiAAMAAgAAAAq7AAJZEgO3AAS/AAAAAQAKAAAABgABAAAABAABAAwAAAACAA0="); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQDBVUVrMUEFx3lYkgJF54evq9vHvOUDZveUAgAAcAAAAHhWNBIAAAAAAAAAAAACAAAL" + + "AAAAcAAAAAUAAACcAAAAAgAAALAAAAAAAAAAAAAAAAQAAADIAAAAAQAAAOgAAACMAQAACAEAAEoB" + + "AABSAQAAYAEAAHMBAACHAQAAmwEAALIBAADDAQAAxgEAAMoBAADeAQAAAQAAAAIAAAADAAAABAAA" + + "AAcAAAAHAAAABAAAAAAAAAAIAAAABAAAAEQBAAAAAAEAAAAAAAAAAQAKAAAAAQABAAAAAAACAAAA" + + "AAAAAAAAAAAAAAAAAgAAAAAAAAAGAAAAAAAAAPEBAAAAAAAAAgACAAEAAADlAQAABAAAAHAQAwAA" + + "AA4ABAACAAIAAADrAQAACQAAACIAAQAbAQUAAABwIAIAEAAnAAAAAQAAAAMABjxpbml0PgAMTFRy" + + "YW5zZm9ybTQ7ABFMamF2YS9sYW5nL0Vycm9yOwASTGphdmEvbGFuZy9PYmplY3Q7ABJMamF2YS9s" + + "YW5nL1N0cmluZzsAFVNob3VsZCBub3QgYmUgY2FsbGVkIQAPVHJhbnNmb3JtNC5qYXZhAAFWAAJW" + + "TAASZW1pdHRlcjogamFjay00LjIyAAVzYXlIaQACAQAHDgAEAQAHDgAAAAEBAIGABIgCAQGgAgAM" + + "AAAAAAAAAAEAAAAAAAAAAQAAAAsAAABwAAAAAgAAAAUAAACcAAAAAwAAAAIAAACwAAAABQAAAAQA" + + "AADIAAAABgAAAAEAAADoAAAAASAAAAIAAAAIAQAAARAAAAEAAABEAQAAAiAAAAsAAABKAQAAAyAA" + + "AAIAAADlAQAAACAAAAEAAADxAQAAABAAAAEAAAAAAgAA"); + + public static void doTest(Transform4 t) { + t.sayHi("MissingField"); + try { + Main.doCommonClassRedefinition(Transform4.class, CLASS_BYTES, DEX_BYTES); + } catch (Exception e) { + System.out.println( + "Transformation error : " + e.getClass().getName() + "(" + e.getMessage() + ")"); + } + t.sayHi("MissingField"); + } +} diff --git a/test/921-hello-failure/src/MissingMethod.java b/test/921-hello-failure/src/MissingMethod.java new file mode 100644 index 0000000000..3f1925c9ad --- /dev/null +++ b/test/921-hello-failure/src/MissingMethod.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2017 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.Base64; + +class MissingMethod { + // The following is a base64 encoding of the following class. + // class Transform3 { + // public void sayHi(String name) { + // throw new Error("Should not be called!"); + // } + // } + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAFQoABgAPBwAQCAARCgACABIHABMHABQBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAP" + + "TGluZU51bWJlclRhYmxlAQAFc2F5SGkBABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAApTb3VyY2VG" + + "aWxlAQAPVHJhbnNmb3JtMy5qYXZhDAAHAAgBAA9qYXZhL2xhbmcvRXJyb3IBABVTaG91bGQgbm90" + + "IGJlIGNhbGxlZCEMAAcADAEAClRyYW5zZm9ybTMBABBqYXZhL2xhbmcvT2JqZWN0ACAABQAGAAAA" + + "AAACAAAABwAIAAEACQAAAB0AAQABAAAABSq3AAGxAAAAAQAKAAAABgABAAAAAgABAAsADAABAAkA" + + "AAAiAAMAAgAAAAq7AAJZEgO3AAS/AAAAAQAKAAAABgABAAAABAABAA0AAAACAA4="); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQDnVQvyn7XrwDiCC/SE55zBCtEqk4pzA2mUAgAAcAAAAHhWNBIAAAAAAAAAAAACAAAL" + + "AAAAcAAAAAUAAACcAAAAAgAAALAAAAAAAAAAAAAAAAQAAADIAAAAAQAAAOgAAACMAQAACAEAAEoB" + + "AABSAQAAYAEAAHMBAACHAQAAmwEAALIBAADDAQAAxgEAAMoBAADeAQAAAQAAAAIAAAADAAAABAAA" + + "AAcAAAAHAAAABAAAAAAAAAAIAAAABAAAAEQBAAAAAAAAAAAAAAAAAQAKAAAAAQABAAAAAAACAAAA" + + "AAAAAAAAAAAAAAAAAgAAAAAAAAAGAAAAAAAAAPABAAAAAAAAAQABAAEAAADlAQAABAAAAHAQAwAA" + + "AA4ABAACAAIAAADqAQAACQAAACIAAQAbAQUAAABwIAIAEAAnAAAAAQAAAAMABjxpbml0PgAMTFRy" + + "YW5zZm9ybTM7ABFMamF2YS9sYW5nL0Vycm9yOwASTGphdmEvbGFuZy9PYmplY3Q7ABJMamF2YS9s" + + "YW5nL1N0cmluZzsAFVNob3VsZCBub3QgYmUgY2FsbGVkIQAPVHJhbnNmb3JtMy5qYXZhAAFWAAJW" + + "TAASZW1pdHRlcjogamFjay00LjI0AAVzYXlIaQACAAcOAAQBAAcOAAAAAQEAgIAEiAIBAaACAAAM" + + "AAAAAAAAAAEAAAAAAAAAAQAAAAsAAABwAAAAAgAAAAUAAACcAAAAAwAAAAIAAACwAAAABQAAAAQA" + + "AADIAAAABgAAAAEAAADoAAAAASAAAAIAAAAIAQAAARAAAAEAAABEAQAAAiAAAAsAAABKAQAAAyAA" + + "AAIAAADlAQAAACAAAAEAAADwAQAAABAAAAEAAAAAAgAA"); + + public static void doTest(Transform3 t) { + t.sayHi("MissingMethod"); + try { + Main.doCommonClassRedefinition(Transform3.class, CLASS_BYTES, DEX_BYTES); + } catch (Exception e) { + System.out.println( + "Transformation error : " + e.getClass().getName() + "(" + e.getMessage() + ")"); + } + t.sayHi("MissingMethod"); + } +} diff --git a/test/921-hello-failure/src/NewField.java b/test/921-hello-failure/src/NewField.java new file mode 100644 index 0000000000..c85b79e824 --- /dev/null +++ b/test/921-hello-failure/src/NewField.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017 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.Base64; + +class NewField { + // The following is a base64 encoding of the following class. + // class Transform { + // private Object field; + // public void sayHi(String name) { + // throw new Error("Should not be called!"); + // } + // } + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAFwoABgARBwASCAATCgACABQHABUHABYBAAVmaWVsZAEAEkxqYXZhL2xhbmcvT2Jq" + + "ZWN0OwEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEAFShM" + + "amF2YS9sYW5nL1N0cmluZzspVgEAClNvdXJjZUZpbGUBAA5UcmFuc2Zvcm0uamF2YQwACQAKAQAP" + + "amF2YS9sYW5nL0Vycm9yAQAVU2hvdWxkIG5vdCBiZSBjYWxsZWQhDAAJAA4BAAlUcmFuc2Zvcm0B" + + "ABBqYXZhL2xhbmcvT2JqZWN0ACAABQAGAAAAAQACAAcACAAAAAIAAAAJAAoAAQALAAAAHQABAAEA" + + "AAAFKrcAAbEAAAABAAwAAAAGAAEAAAABAAEADQAOAAEACwAAACIAAwACAAAACrsAAlkSA7cABL8A" + + "AAABAAwAAAAGAAEAAAAEAAEADwAAAAIAEA=="); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQBNWknL2iyjim487p0EIH/8V5OjOeLgw5e0AgAAcAAAAHhWNBIAAAAAAAAAABQCAAAM" + + "AAAAcAAAAAUAAACgAAAAAgAAALQAAAABAAAAzAAAAAQAAADUAAAAAQAAAPQAAACgAQAAFAEAAFYB" + + "AABeAQAAawEAAH4BAACSAQAApgEAAL0BAADNAQAA0AEAANQBAADoAQAA7wEAAAEAAAACAAAAAwAA" + + "AAQAAAAHAAAABwAAAAQAAAAAAAAACAAAAAQAAABQAQAAAAACAAoAAAAAAAAAAAAAAAAAAQALAAAA" + + "AQABAAAAAAACAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAGAAAAAAAAAAECAAAAAAAAAQABAAEAAAD2" + + "AQAABAAAAHAQAwAAAA4ABAACAAIAAAD7AQAACQAAACIAAQAbAQUAAABwIAIAEAAnAAAAAQAAAAMA" + + "Bjxpbml0PgALTFRyYW5zZm9ybTsAEUxqYXZhL2xhbmcvRXJyb3I7ABJMamF2YS9sYW5nL09iamVj" + + "dDsAEkxqYXZhL2xhbmcvU3RyaW5nOwAVU2hvdWxkIG5vdCBiZSBjYWxsZWQhAA5UcmFuc2Zvcm0u" + + "amF2YQABVgACVkwAEmVtaXR0ZXI6IGphY2stNC4yMgAFZmllbGQABXNheUhpAAEABw4ABAEABw4A" + + "AAEBAQACAICABJQCAQGsAgAAAA0AAAAAAAAAAQAAAAAAAAABAAAADAAAAHAAAAACAAAABQAAAKAA" + + "AAADAAAAAgAAALQAAAAEAAAAAQAAAMwAAAAFAAAABAAAANQAAAAGAAAAAQAAAPQAAAABIAAAAgAA" + + "ABQBAAABEAAAAQAAAFABAAACIAAADAAAAFYBAAADIAAAAgAAAPYBAAAAIAAAAQAAAAECAAAAEAAA" + + "AQAAABQCAAA="); + + public static void doTest(Transform t) { + t.sayHi("NewField"); + try { + Main.doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES); + } catch (Exception e) { + System.out.println( + "Transformation error : " + e.getClass().getName() + "(" + e.getMessage() + ")"); + } + t.sayHi("NewField"); + } +} diff --git a/test/921-hello-failure/src/NewMethod.java b/test/921-hello-failure/src/NewMethod.java new file mode 100644 index 0000000000..5eac670c68 --- /dev/null +++ b/test/921-hello-failure/src/NewMethod.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017 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.Base64; + +class NewMethod { + // The following is a base64 encoding of the following class. + // class Transform { + // public void extraMethod() {} + // public void sayHi(String name) { + // throw new Error("Should not be called!"); + // } + // } + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAFgoABgAQBwARCAASCgACABMHABQHABUBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAP" + + "TGluZU51bWJlclRhYmxlAQALZXh0cmFNZXRob2QBAAVzYXlIaQEAFShMamF2YS9sYW5nL1N0cmlu" + + "ZzspVgEAClNvdXJjZUZpbGUBAA5UcmFuc2Zvcm0uamF2YQwABwAIAQAPamF2YS9sYW5nL0Vycm9y" + + "AQAVU2hvdWxkIG5vdCBiZSBjYWxsZWQhDAAHAA0BAAlUcmFuc2Zvcm0BABBqYXZhL2xhbmcvT2Jq" + + "ZWN0ACAABQAGAAAAAAADAAAABwAIAAEACQAAAB0AAQABAAAABSq3AAGxAAAAAQAKAAAABgABAAAA" + + "AQABAAsACAABAAkAAAAZAAAAAQAAAAGxAAAAAQAKAAAABgABAAAAAgABAAwADQABAAkAAAAiAAMA" + + "AgAAAAq7AAJZEgO3AAS/AAAAAQAKAAAABgABAAAABAABAA4AAAACAA8="); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQBeV7dLAwN1GBTa/yRlkuiIQatNHghVdrnIAgAAcAAAAHhWNBIAAAAAAAAAADQCAAAM" + + "AAAAcAAAAAUAAACgAAAAAgAAALQAAAAAAAAAAAAAAAUAAADMAAAAAQAAAPQAAAC0AQAAFAEAAGoB" + + "AAByAQAAfwEAAJIBAACmAQAAugEAANEBAADhAQAA5AEAAOgBAAD8AQAACQIAAAEAAAACAAAAAwAA" + + "AAQAAAAHAAAABwAAAAQAAAAAAAAACAAAAAQAAABkAQAAAAAAAAAAAAAAAAAACgAAAAAAAQALAAAA" + + "AQABAAAAAAACAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAGAAAAAAAAACACAAAAAAAAAQABAAEAAAAQ" + + "AgAABAAAAHAQBAAAAA4AAQABAAAAAAAVAgAAAQAAAA4AAAAEAAIAAgAAABoCAAAJAAAAIgABABsB" + + "BQAAAHAgAwAQACcAAAABAAAAAwAGPGluaXQ+AAtMVHJhbnNmb3JtOwARTGphdmEvbGFuZy9FcnJv" + + "cjsAEkxqYXZhL2xhbmcvT2JqZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7ABVTaG91bGQgbm90IGJl" + + "IGNhbGxlZCEADlRyYW5zZm9ybS5qYXZhAAFWAAJWTAASZW1pdHRlcjogamFjay00LjIyAAtleHRy" + + "YU1ldGhvZAAFc2F5SGkAAQAHDgACAAcOAAQBAAcOAAAAAQIAgIAElAIBAawCAQHAAgAADAAAAAAA" + + "AAABAAAAAAAAAAEAAAAMAAAAcAAAAAIAAAAFAAAAoAAAAAMAAAACAAAAtAAAAAUAAAAFAAAAzAAA" + + "AAYAAAABAAAA9AAAAAEgAAADAAAAFAEAAAEQAAABAAAAZAEAAAIgAAAMAAAAagEAAAMgAAADAAAA" + + "EAIAAAAgAAABAAAAIAIAAAAQAAABAAAANAIAAA=="); + + public static void doTest(Transform t) { + t.sayHi("NewMethod"); + try { + Main.doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES); + } catch (Exception e) { + System.out.println( + "Transformation error : " + e.getClass().getName() + "(" + e.getMessage() + ")"); + } + t.sayHi("NewMethod"); + } +} diff --git a/test/921-hello-failure/src/Transform3.java b/test/921-hello-failure/src/Transform3.java new file mode 100644 index 0000000000..d2cb064956 --- /dev/null +++ b/test/921-hello-failure/src/Transform3.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2017 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 Transform3 { + public void extraMethod(String name) { + System.out.println("extraMethod - " + name); + } + public void sayHi(String name) { + System.out.println("hello2 - " + name); + } +} diff --git a/test/921-hello-failure/src/Transform4.java b/test/921-hello-failure/src/Transform4.java new file mode 100644 index 0000000000..fd763386ba --- /dev/null +++ b/test/921-hello-failure/src/Transform4.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2017 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 Transform4 { + private String greeting; + public Transform4(String hi) { + greeting = hi; + } + public void sayHi(String name) { + System.out.println("hello " + greeting + " - " + name); + } +} diff --git a/test/922-properties/run b/test/922-properties/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/922-properties/run +++ b/test/922-properties/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/923-monitors/run b/test/923-monitors/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/923-monitors/run +++ b/test/923-monitors/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/924-threads/run b/test/924-threads/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/924-threads/run +++ b/test/924-threads/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/924-threads/src/Main.java b/test/924-threads/src/Main.java index 29c4aa330c..f18d70e8e1 100644 --- a/test/924-threads/src/Main.java +++ b/test/924-threads/src/Main.java @@ -20,6 +20,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.concurrent.CountDownLatch; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; @@ -162,8 +163,20 @@ public class Main { private static void doAllThreadsTests() { Thread[] threads = getAllThreads(); - Arrays.sort(threads, THREAD_COMP); - System.out.println(Arrays.toString(threads)); + List<Thread> threadList = new ArrayList<>(Arrays.asList(threads)); + + // Filter out JIT thread. It may or may not be there depending on configuration. + Iterator<Thread> it = threadList.iterator(); + while (it.hasNext()) { + Thread t = it.next(); + if (t.getName().startsWith("Jit thread pool worker")) { + it.remove(); + break; + } + } + + Collections.sort(threadList, THREAD_COMP); + System.out.println(threadList); } private static void doTLSTests() throws Exception { diff --git a/test/925-threadgroups/run b/test/925-threadgroups/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/925-threadgroups/run +++ b/test/925-threadgroups/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/925-threadgroups/src/Main.java b/test/925-threadgroups/src/Main.java index 3d7a4ca740..bf7441f9bf 100644 --- a/test/925-threadgroups/src/Main.java +++ b/test/925-threadgroups/src/Main.java @@ -14,8 +14,12 @@ * limitations under the License. */ +import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; public class Main { public static void main(String[] args) throws Exception { @@ -64,10 +68,23 @@ public class Main { Thread[] threads = (Thread[])data[0]; ThreadGroup[] groups = (ThreadGroup[])data[1]; - Arrays.sort(threads, THREAD_COMP); + List<Thread> threadList = new ArrayList<>(Arrays.asList(threads)); + + // Filter out JIT thread. It may or may not be there depending on configuration. + Iterator<Thread> it = threadList.iterator(); + while (it.hasNext()) { + Thread t = it.next(); + if (t.getName().startsWith("Jit thread pool worker")) { + it.remove(); + break; + } + } + + Collections.sort(threadList, THREAD_COMP); + Arrays.sort(groups, THREADGROUP_COMP); System.out.println(tg.getName() + ":"); - System.out.println(" " + Arrays.toString(threads)); + System.out.println(" " + threadList); System.out.println(" " + Arrays.toString(groups)); if (tg.getParent() != null) { diff --git a/test/926-multi-obsolescence/run b/test/926-multi-obsolescence/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/926-multi-obsolescence/run +++ b/test/926-multi-obsolescence/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/927-timers/run b/test/927-timers/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/927-timers/run +++ b/test/927-timers/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/928-jni-table/run b/test/928-jni-table/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/928-jni-table/run +++ b/test/928-jni-table/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/929-search/run b/test/929-search/run index 0a8d0672f6..67923a7984 100755 --- a/test/929-search/run +++ b/test/929-search/run @@ -17,7 +17,5 @@ # This test checks whether dex files can be injected into parent classloaders. App images preload # classes, which will make the injection moot. Turn off app images to avoid the issue. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti \ +./default-run "$@" --jvmti \ --no-app-image diff --git a/test/930-hello-retransform/run b/test/930-hello-retransform/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/930-hello-retransform/run +++ b/test/930-hello-retransform/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/931-agent-thread/run b/test/931-agent-thread/run index 0a8d0672f6..67923a7984 100755 --- a/test/931-agent-thread/run +++ b/test/931-agent-thread/run @@ -17,7 +17,5 @@ # This test checks whether dex files can be injected into parent classloaders. App images preload # classes, which will make the injection moot. Turn off app images to avoid the issue. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti \ +./default-run "$@" --jvmti \ --no-app-image diff --git a/test/932-transform-saves/run b/test/932-transform-saves/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/932-transform-saves/run +++ b/test/932-transform-saves/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/933-misc-events/run b/test/933-misc-events/run index 0a8d0672f6..67923a7984 100755 --- a/test/933-misc-events/run +++ b/test/933-misc-events/run @@ -17,7 +17,5 @@ # This test checks whether dex files can be injected into parent classloaders. App images preload # classes, which will make the injection moot. Turn off app images to avoid the issue. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti \ +./default-run "$@" --jvmti \ --no-app-image diff --git a/test/934-load-transform/build b/test/934-load-transform/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/934-load-transform/build @@ -0,0 +1,17 @@ +#!/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. + +./default-build "$@" --experimental agents diff --git a/test/934-load-transform/expected.txt b/test/934-load-transform/expected.txt new file mode 100644 index 0000000000..2b60207f03 --- /dev/null +++ b/test/934-load-transform/expected.txt @@ -0,0 +1 @@ +Goodbye diff --git a/test/934-load-transform/info.txt b/test/934-load-transform/info.txt new file mode 100644 index 0000000000..875a5f6ec1 --- /dev/null +++ b/test/934-load-transform/info.txt @@ -0,0 +1 @@ +Tests basic functions in the jvmti plugin. diff --git a/test/934-load-transform/run b/test/934-load-transform/run new file mode 100755 index 0000000000..adb1a1c507 --- /dev/null +++ b/test/934-load-transform/run @@ -0,0 +1,17 @@ +#!/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. + +./default-run "$@" --jvmti --no-app-image diff --git a/test/934-load-transform/src-ex/TestMain.java b/test/934-load-transform/src-ex/TestMain.java new file mode 100644 index 0000000000..33be9cd09d --- /dev/null +++ b/test/934-load-transform/src-ex/TestMain.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. + */ + +public class TestMain { + public static void runTest() { + new Transform().sayHi(); + } +} diff --git a/test/934-load-transform/src-ex/Transform.java b/test/934-load-transform/src-ex/Transform.java new file mode 100644 index 0000000000..f624c3ac99 --- /dev/null +++ b/test/934-load-transform/src-ex/Transform.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. + */ + +class Transform { + public void sayHi() { + throw new Error("Should not be called!"); + } +} diff --git a/test/934-load-transform/src/Main.java b/test/934-load-transform/src/Main.java new file mode 100644 index 0000000000..de312b03da --- /dev/null +++ b/test/934-load-transform/src/Main.java @@ -0,0 +1,95 @@ +/* + * 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.*; +import java.util.Base64; + +class Main { + public static String TEST_NAME = "934-load-transform"; + + /** + * base64 encoded class/dex file for + * class Transform { + * public void sayHi() { + * System.out.println("Goodbye"); + * } + * } + */ + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAHAoABgAOCQAPABAIABEKABIAEwcAFAcAFQEABjxpbml0PgEAAygpVgEABENvZGUB" + + "AA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEAClNvdXJjZUZpbGUBAA5UcmFuc2Zvcm0uamF2YQwA" + + "BwAIBwAWDAAXABgBAAdHb29kYnllBwAZDAAaABsBAAlUcmFuc2Zvcm0BABBqYXZhL2xhbmcvT2Jq" + + "ZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2ph" + + "dmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACAABQAG" + + "AAAAAAACAAAABwAIAAEACQAAAB0AAQABAAAABSq3AAGxAAAAAQAKAAAABgABAAAAEQABAAsACAAB" + + "AAkAAAAlAAIAAQAAAAmyAAISA7YABLEAAAABAAoAAAAKAAIAAAATAAgAFAABAAwAAAACAA0="); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQCLXSBQ5FiS3f16krSYZFF8xYZtFVp0GRXMAgAAcAAAAHhWNBIAAAAAAAAAACwCAAAO" + + "AAAAcAAAAAYAAACoAAAAAgAAAMAAAAABAAAA2AAAAAQAAADgAAAAAQAAAAABAACsAQAAIAEAAGIB" + + "AABqAQAAcwEAAIABAACXAQAAqwEAAL8BAADTAQAA4wEAAOYBAADqAQAA/gEAAAMCAAAMAgAAAgAA" + + "AAMAAAAEAAAABQAAAAYAAAAIAAAACAAAAAUAAAAAAAAACQAAAAUAAABcAQAABAABAAsAAAAAAAAA" + + "AAAAAAAAAAANAAAAAQABAAwAAAACAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAHAAAAAAAAAB4CAAAA" + + "AAAAAQABAAEAAAATAgAABAAAAHAQAwAAAA4AAwABAAIAAAAYAgAACQAAAGIAAAAbAQEAAABuIAIA" + + "EAAOAAAAAQAAAAMABjxpbml0PgAHR29vZGJ5ZQALTFRyYW5zZm9ybTsAFUxqYXZhL2lvL1ByaW50" + + "U3RyZWFtOwASTGphdmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xh" + + "bmcvU3lzdGVtOwAOVHJhbnNmb3JtLmphdmEAAVYAAlZMABJlbWl0dGVyOiBqYWNrLTMuMzYAA291" + + "dAAHcHJpbnRsbgAFc2F5SGkAEQAHDgATAAcOhQAAAAEBAICABKACAQG4Ag0AAAAAAAAAAQAAAAAA" + + "AAABAAAADgAAAHAAAAACAAAABgAAAKgAAAADAAAAAgAAAMAAAAAEAAAAAQAAANgAAAAFAAAABAAA" + + "AOAAAAAGAAAAAQAAAAABAAABIAAAAgAAACABAAABEAAAAQAAAFwBAAACIAAADgAAAGIBAAADIAAA" + + "AgAAABMCAAAAIAAAAQAAAB4CAAAAEAAAAQAAACwCAAA="); + + public static ClassLoader getClassLoaderFor(String location) throws Exception { + try { + Class<?> class_loader_class = Class.forName("dalvik.system.PathClassLoader"); + Constructor<?> ctor = class_loader_class.getConstructor(String.class, ClassLoader.class); + /* on Dalvik, this is a DexFile; otherwise, it's null */ + return (ClassLoader)ctor.newInstance(location + "/" + TEST_NAME + "-ex.jar", + Main.class.getClassLoader()); + } catch (ClassNotFoundException e) { + // Running on RI. Use URLClassLoader. + return new java.net.URLClassLoader( + new java.net.URL[] { new java.net.URL("file://" + location + "/classes-ex/") }); + } + } + + public static void main(String[] args) { + // Don't pop transformations. Make sure that even if 2 threads race to define the class both + // will get the same result. + setPopRetransformations(false); + addCommonTransformationResult("Transform", CLASS_BYTES, DEX_BYTES); + enableCommonRetransformation(true); + try { + /* this is the "alternate" DEX/Jar file */ + ClassLoader new_loader = getClassLoaderFor(System.getenv("DEX_LOCATION")); + Class<?> klass = (Class<?>)new_loader.loadClass("TestMain"); + if (klass == null) { + throw new AssertionError("loadClass failed"); + } + Method run_test = klass.getMethod("runTest"); + run_test.invoke(null); + } catch (Exception e) { + System.out.println(e.toString()); + e.printStackTrace(); + } + } + + private static native void setPopRetransformations(boolean should_pop); + // Transforms the class + private static native void enableCommonRetransformation(boolean enable); + private static native void addCommonTransformationResult(String target_name, + byte[] class_bytes, + byte[] dex_bytes); +} diff --git a/test/935-non-retransformable/build b/test/935-non-retransformable/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/935-non-retransformable/build @@ -0,0 +1,17 @@ +#!/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. + +./default-build "$@" --experimental agents diff --git a/test/935-non-retransformable/expected.txt b/test/935-non-retransformable/expected.txt new file mode 100644 index 0000000000..ccd50a66a0 --- /dev/null +++ b/test/935-non-retransformable/expected.txt @@ -0,0 +1,6 @@ +Hello +Hello +Goodbye +Hello +Hello +Goodbye diff --git a/test/935-non-retransformable/info.txt b/test/935-non-retransformable/info.txt new file mode 100644 index 0000000000..875a5f6ec1 --- /dev/null +++ b/test/935-non-retransformable/info.txt @@ -0,0 +1 @@ +Tests basic functions in the jvmti plugin. diff --git a/test/935-non-retransformable/run b/test/935-non-retransformable/run new file mode 100755 index 0000000000..adb1a1c507 --- /dev/null +++ b/test/935-non-retransformable/run @@ -0,0 +1,17 @@ +#!/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. + +./default-run "$@" --jvmti --no-app-image diff --git a/test/935-non-retransformable/src-ex/TestMain.java b/test/935-non-retransformable/src-ex/TestMain.java new file mode 100644 index 0000000000..d412fba37a --- /dev/null +++ b/test/935-non-retransformable/src-ex/TestMain.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 TestMain { + public static void runTest() throws Exception { + Transform t = new Transform(); + // Call functions with reflection. Since the sayGoodbye function does not exist in the + // LTransform; when we compile this for the first time we need to use reflection. + Method hi = Transform.class.getMethod("sayHi"); + Method bye = Transform.class.getMethod("sayGoodbye"); + hi.invoke(t); + t.sayHi(); + bye.invoke(t); + } +} diff --git a/test/935-non-retransformable/src-ex/Transform.java b/test/935-non-retransformable/src-ex/Transform.java new file mode 100644 index 0000000000..f624c3ac99 --- /dev/null +++ b/test/935-non-retransformable/src-ex/Transform.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. + */ + +class Transform { + public void sayHi() { + throw new Error("Should not be called!"); + } +} diff --git a/test/935-non-retransformable/src/Main.java b/test/935-non-retransformable/src/Main.java new file mode 100644 index 0000000000..82ba197b7e --- /dev/null +++ b/test/935-non-retransformable/src/Main.java @@ -0,0 +1,110 @@ +/* + * 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.*; +import java.util.Base64; + +class Main { + public static String TEST_NAME = "935-non-retransformable"; + + /** + * base64 encoded class/dex file for + * class Transform { + * public void sayHi() { + * System.out.println("Hello"); + * } + * public void sayGoodbye() { + * System.out.println("Goodbye"); + * } + * } + */ + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAHwoABwAQCQARABIIABMKABQAFQgAFgcAFwcAGAEABjxpbml0PgEAAygpVgEABENv" + + "ZGUBAA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEACnNheUdvb2RieWUBAApTb3VyY2VGaWxlAQAO" + + "VHJhbnNmb3JtLmphdmEMAAgACQcAGQwAGgAbAQAFSGVsbG8HABwMAB0AHgEAB0dvb2RieWUBAAlU" + + "cmFuc2Zvcm0BABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxq" + + "YXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExq" + + "YXZhL2xhbmcvU3RyaW5nOylWACAABgAHAAAAAAADAAAACAAJAAEACgAAAB0AAQABAAAABSq3AAGx" + + "AAAAAQALAAAABgABAAAAAQABAAwACQABAAoAAAAlAAIAAQAAAAmyAAISA7YABLEAAAABAAsAAAAK" + + "AAIAAAADAAgABAABAA0ACQABAAoAAAAlAAIAAQAAAAmyAAISBbYABLEAAAABAAsAAAAKAAIAAAAG" + + "AAgABwABAA4AAAACAA8="); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQDpaN+7jX/ZLl9Jr0HAEV7nqL1YDuakKakgAwAAcAAAAHhWNBIAAAAAAAAAAIACAAAQ" + + "AAAAcAAAAAYAAACwAAAAAgAAAMgAAAABAAAA4AAAAAUAAADoAAAAAQAAABABAADwAQAAMAEAAJYB" + + "AACeAQAApwEAAK4BAAC7AQAA0gEAAOYBAAD6AQAADgIAAB4CAAAhAgAAJQIAADkCAAA+AgAARwIA" + + "AFMCAAADAAAABAAAAAUAAAAGAAAABwAAAAkAAAAJAAAABQAAAAAAAAAKAAAABQAAAJABAAAEAAEA" + + "DAAAAAAAAAAAAAAAAAAAAA4AAAAAAAAADwAAAAEAAQANAAAAAgAAAAAAAAAAAAAAAAAAAAIAAAAA" + + "AAAACAAAAAAAAABrAgAAAAAAAAEAAQABAAAAWgIAAAQAAABwEAQAAAAOAAMAAQACAAAAXwIAAAkA" + + "AABiAAAAGwEBAAAAbiADABAADgAAAAMAAQACAAAAZQIAAAkAAABiAAAAGwECAAAAbiADABAADgAA" + + "AAEAAAADAAY8aW5pdD4AB0dvb2RieWUABUhlbGxvAAtMVHJhbnNmb3JtOwAVTGphdmEvaW8vUHJp" + + "bnRTdHJlYW07ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZhL2xhbmcvU3RyaW5nOwASTGphdmEv" + + "bGFuZy9TeXN0ZW07AA5UcmFuc2Zvcm0uamF2YQABVgACVkwAEmVtaXR0ZXI6IGphY2stNC4yMgAD" + + "b3V0AAdwcmludGxuAApzYXlHb29kYnllAAVzYXlIaQABAAcOAAYABw6HAAMABw6HAAAAAQIAgIAE" + + "sAIBAcgCAQHsAgAAAA0AAAAAAAAAAQAAAAAAAAABAAAAEAAAAHAAAAACAAAABgAAALAAAAADAAAA" + + "AgAAAMgAAAAEAAAAAQAAAOAAAAAFAAAABQAAAOgAAAAGAAAAAQAAABABAAABIAAAAwAAADABAAAB" + + "EAAAAQAAAJABAAACIAAAEAAAAJYBAAADIAAAAwAAAFoCAAAAIAAAAQAAAGsCAAAAEAAAAQAAAIAC" + + "AAA="); + + + public static ClassLoader getClassLoaderFor(String location) throws Exception { + try { + Class<?> class_loader_class = Class.forName("dalvik.system.PathClassLoader"); + Constructor<?> ctor = class_loader_class.getConstructor(String.class, ClassLoader.class); + /* on Dalvik, this is a DexFile; otherwise, it's null */ + return (ClassLoader)ctor.newInstance(location + "/" + TEST_NAME + "-ex.jar", + Main.class.getClassLoader()); + } catch (ClassNotFoundException e) { + // Running on RI. Use URLClassLoader. + return new java.net.URLClassLoader( + new java.net.URL[] { new java.net.URL("file://" + location + "/classes-ex/") }); + } + } + + public static void main(String[] args) { + setPopRetransformations(false); + addCommonTransformationResult("Transform", CLASS_BYTES, DEX_BYTES); + enableCommonRetransformation(true); + try { + /* this is the "alternate" DEX/Jar file */ + ClassLoader new_loader = getClassLoaderFor(System.getenv("DEX_LOCATION")); + Class<?> klass = (Class<?>)new_loader.loadClass("TestMain"); + if (klass == null) { + throw new AssertionError("loadClass failed"); + } + Method run_test = klass.getMethod("runTest"); + run_test.invoke(null); + + // Remove the original transformation. It has been used by now. + popTransformationFor("Transform"); + // Make sure we don't get called for transformation again. + addCommonTransformationResult("Transform", new byte[0], new byte[0]); + doCommonClassRetransformation(new_loader.loadClass("Transform")); + run_test.invoke(null); + } catch (Exception e) { + System.out.println(e.toString()); + e.printStackTrace(); + } + } + + // Transforms the class + private static native void doCommonClassRetransformation(Class<?>... classes); + private static native void enableCommonRetransformation(boolean enable); + private static native void addCommonTransformationResult(String target_name, + byte[] class_bytes, + byte[] dex_bytes); + private static native void setPopRetransformations(boolean should_pop); + private static native void popTransformationFor(String target_name); +} diff --git a/test/936-search-onload/build b/test/936-search-onload/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/936-search-onload/build @@ -0,0 +1,17 @@ +#!/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. + +./default-build "$@" --experimental agents diff --git a/test/936-search-onload/expected.txt b/test/936-search-onload/expected.txt new file mode 100644 index 0000000000..2eec8e1aac --- /dev/null +++ b/test/936-search-onload/expected.txt @@ -0,0 +1,3 @@ +B was loaded with boot classloader +A was loaded with system classloader +Done diff --git a/test/936-search-onload/info.txt b/test/936-search-onload/info.txt new file mode 100644 index 0000000000..875a5f6ec1 --- /dev/null +++ b/test/936-search-onload/info.txt @@ -0,0 +1 @@ +Tests basic functions in the jvmti plugin. diff --git a/test/936-search-onload/run b/test/936-search-onload/run new file mode 100755 index 0000000000..67923a7984 --- /dev/null +++ b/test/936-search-onload/run @@ -0,0 +1,21 @@ +#!/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. + +# This test checks whether dex files can be injected into parent classloaders. App images preload +# classes, which will make the injection moot. Turn off app images to avoid the issue. + +./default-run "$@" --jvmti \ + --no-app-image diff --git a/test/936-search-onload/search_onload.cc b/test/936-search-onload/search_onload.cc new file mode 100644 index 0000000000..2286a467d3 --- /dev/null +++ b/test/936-search-onload/search_onload.cc @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2017 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 "search_onload.h" + +#include <inttypes.h> + +#include "android-base/stringprintf.h" +#include "base/logging.h" +#include "base/macros.h" +#include "jni.h" +#include "openjdkjvmti/jvmti.h" +#include "ScopedUtfChars.h" + +#include "ti-agent/common_helper.h" +#include "ti-agent/common_load.h" + +namespace art { +namespace Test936SearchOnload { + +jint OnLoad(JavaVM* vm, + char* options ATTRIBUTE_UNUSED, + void* reserved ATTRIBUTE_UNUSED) { + if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { + printf("Unable to get jvmti env!\n"); + return 1; + } + SetAllCapabilities(jvmti_env); + + char* dex_loc = getenv("DEX_LOCATION"); + std::string dex1 = android::base::StringPrintf("%s/936-search-onload.jar", dex_loc); + std::string dex2 = android::base::StringPrintf("%s/936-search-onload-ex.jar", dex_loc); + + jvmtiError result = jvmti_env->AddToBootstrapClassLoaderSearch(dex1.c_str()); + if (result != JVMTI_ERROR_NONE) { + printf("Could not add to bootstrap classloader.\n"); + return 1; + } + + result = jvmti_env->AddToSystemClassLoaderSearch(dex2.c_str()); + if (result != JVMTI_ERROR_NONE) { + printf("Could not add to system classloader.\n"); + return 1; + } + + return JNI_OK; +} + +} // namespace Test936SearchOnload +} // namespace art diff --git a/test/936-search-onload/search_onload.h b/test/936-search-onload/search_onload.h new file mode 100644 index 0000000000..e55689252d --- /dev/null +++ b/test/936-search-onload/search_onload.h @@ -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. + */ + +#ifndef ART_TEST_936_SEARCH_ONLOAD_SEARCH_ONLOAD_H_ +#define ART_TEST_936_SEARCH_ONLOAD_SEARCH_ONLOAD_H_ + +#include <jni.h> + +namespace art { +namespace Test936SearchOnload { + +jint OnLoad(JavaVM* vm, char* options, void* reserved); + +} // namespace Test936SearchOnload +} // namespace art + +#endif // ART_TEST_936_SEARCH_ONLOAD_SEARCH_ONLOAD_H_ diff --git a/test/936-search-onload/src-ex/A.java b/test/936-search-onload/src-ex/A.java new file mode 100644 index 0000000000..64acb2fcfe --- /dev/null +++ b/test/936-search-onload/src-ex/A.java @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2017 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 A { +}
\ No newline at end of file diff --git a/test/936-search-onload/src/B.java b/test/936-search-onload/src/B.java new file mode 100644 index 0000000000..f1458c3bca --- /dev/null +++ b/test/936-search-onload/src/B.java @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2017 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 B { +}
\ No newline at end of file diff --git a/test/936-search-onload/src/Main.java b/test/936-search-onload/src/Main.java new file mode 100644 index 0000000000..2e7a87193b --- /dev/null +++ b/test/936-search-onload/src/Main.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2017 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; + +public class Main { + public static void main(String[] args) throws Exception { + doTest(); + } + + private static void doTest() throws Exception { + doTest(true, "B"); + doTest(false, "A"); + System.out.println("Done"); + } + + private static void doTest(boolean boot, String className) throws Exception { + ClassLoader expectedClassLoader; + if (boot) { + expectedClassLoader = Object.class.getClassLoader(); + } else { + expectedClassLoader = ClassLoader.getSystemClassLoader(); + } + + Class<?> c = Class.forName(className, false, ClassLoader.getSystemClassLoader()); + if (c.getClassLoader() != expectedClassLoader) { + throw new RuntimeException(className + "(" + boot + "): " + + c.getClassLoader() + " vs " + expectedClassLoader); + } else { + System.out.println(className + " was loaded with " + (boot ? "boot" : "system") + + " classloader"); + } + } +} diff --git a/test/937-hello-retransform-package/build b/test/937-hello-retransform-package/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/937-hello-retransform-package/build @@ -0,0 +1,17 @@ +#!/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. + +./default-build "$@" --experimental agents diff --git a/test/937-hello-retransform-package/expected.txt b/test/937-hello-retransform-package/expected.txt new file mode 100644 index 0000000000..4774b81b49 --- /dev/null +++ b/test/937-hello-retransform-package/expected.txt @@ -0,0 +1,2 @@ +hello +Goodbye diff --git a/test/937-hello-retransform-package/info.txt b/test/937-hello-retransform-package/info.txt new file mode 100644 index 0000000000..875a5f6ec1 --- /dev/null +++ b/test/937-hello-retransform-package/info.txt @@ -0,0 +1 @@ +Tests basic functions in the jvmti plugin. diff --git a/test/937-hello-retransform-package/run b/test/937-hello-retransform-package/run new file mode 100755 index 0000000000..c6e62ae6cd --- /dev/null +++ b/test/937-hello-retransform-package/run @@ -0,0 +1,17 @@ +#!/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. + +./default-run "$@" --jvmti diff --git a/test/937-hello-retransform-package/src/Main.java b/test/937-hello-retransform-package/src/Main.java new file mode 100644 index 0000000000..4b9271b4f4 --- /dev/null +++ b/test/937-hello-retransform-package/src/Main.java @@ -0,0 +1,73 @@ +/* + * 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.Base64; + +import testing.*; +public class Main { + + /** + * base64 encoded class/dex file for + * package testing; + * class Transform { + * public void sayHi() { + * System.out.println("Goodbye"); + * } + * } + */ + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAHAoABgAOCQAPABAIABEKABIAEwcAFAcAFQEABjxpbml0PgEAAygpVgEABENvZGUB" + + "AA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEAClNvdXJjZUZpbGUBAA5UcmFuc2Zvcm0uamF2YQwA" + + "BwAIBwAWDAAXABgBAAdHb29kYnllBwAZDAAaABsBABF0ZXN0aW5nL1RyYW5zZm9ybQEAEGphdmEv" + + "bGFuZy9PYmplY3QBABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJl" + + "YW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7" + + "KVYAIQAFAAYAAAAAAAIAAQAHAAgAAQAJAAAAHQABAAEAAAAFKrcAAbEAAAABAAoAAAAGAAEAAAAC" + + "AAEACwAIAAEACQAAACUAAgABAAAACbIAAhIDtgAEsQAAAAEACgAAAAoAAgAAAAQACAAFAAEADAAA" + + "AAIADQ=="); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQBhYIi3Gs9Nn/GN1fCzF+aFQ0AbhA1h1WHUAgAAcAAAAHhWNBIAAAAAAAAAADQCAAAO" + + "AAAAcAAAAAYAAACoAAAAAgAAAMAAAAABAAAA2AAAAAQAAADgAAAAAQAAAAABAAC0AQAAIAEAAGIB" + + "AABqAQAAcwEAAIoBAACeAQAAsgEAAMYBAADbAQAA6wEAAO4BAADyAQAABgIAAAsCAAAUAgAAAgAA" + + "AAMAAAAEAAAABQAAAAYAAAAIAAAACAAAAAUAAAAAAAAACQAAAAUAAABcAQAAAwAAAAsAAAAAAAEA" + + "DAAAAAEAAAAAAAAABAAAAAAAAAAEAAAADQAAAAQAAAABAAAAAQAAAAAAAAAHAAAAAAAAACYCAAAA" + + "AAAAAQABAAEAAAAbAgAABAAAAHAQAQAAAA4AAwABAAIAAAAgAgAACQAAAGIAAAAbAQEAAABuIAAA" + + "EAAOAAAAAQAAAAIABjxpbml0PgAHR29vZGJ5ZQAVTGphdmEvaW8vUHJpbnRTdHJlYW07ABJMamF2" + + "YS9sYW5nL09iamVjdDsAEkxqYXZhL2xhbmcvU3RyaW5nOwASTGphdmEvbGFuZy9TeXN0ZW07ABNM" + + "dGVzdGluZy9UcmFuc2Zvcm07AA5UcmFuc2Zvcm0uamF2YQABVgACVkwAEmVtaXR0ZXI6IGphY2st" + + "NC4yMgADb3V0AAdwcmludGxuAAVzYXlIaQACAAcOAAQABw6HAAAAAQECgYAEoAIDAbgCDQAAAAAA" + + "AAABAAAAAAAAAAEAAAAOAAAAcAAAAAIAAAAGAAAAqAAAAAMAAAACAAAAwAAAAAQAAAABAAAA2AAA" + + "AAUAAAAEAAAA4AAAAAYAAAABAAAAAAEAAAEgAAACAAAAIAEAAAEQAAABAAAAXAEAAAIgAAAOAAAA" + + "YgEAAAMgAAACAAAAGwIAAAAgAAABAAAAJgIAAAAQAAABAAAANAIAAA=="); + + public static void main(String[] args) { + doTest(new Transform()); + } + + public static void doTest(Transform t) { + t.sayHi(); + addCommonTransformationResult("testing/Transform", CLASS_BYTES, DEX_BYTES); + enableCommonRetransformation(true); + doCommonClassRetransformation(Transform.class); + t.sayHi(); + } + + // Transforms the class + private static native void doCommonClassRetransformation(Class<?>... target); + private static native void enableCommonRetransformation(boolean enable); + private static native void addCommonTransformationResult(String target_name, + byte[] class_bytes, + byte[] dex_bytes); +} diff --git a/test/937-hello-retransform-package/src/Transform.java b/test/937-hello-retransform-package/src/Transform.java new file mode 100644 index 0000000000..db92612299 --- /dev/null +++ b/test/937-hello-retransform-package/src/Transform.java @@ -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. + */ + +package testing; +public class Transform { + public void sayHi() { + System.out.println("hello"); + } +} diff --git a/test/938-load-transform-bcp/build b/test/938-load-transform-bcp/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/938-load-transform-bcp/build @@ -0,0 +1,17 @@ +#!/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. + +./default-build "$@" --experimental agents diff --git a/test/938-load-transform-bcp/expected.txt b/test/938-load-transform-bcp/expected.txt new file mode 100644 index 0000000000..16c3f8f726 --- /dev/null +++ b/test/938-load-transform-bcp/expected.txt @@ -0,0 +1,2 @@ +ol.foo() -> 'This is foo for val=123' +ol.toString() -> 'This is toString() for val=123' diff --git a/test/938-load-transform-bcp/info.txt b/test/938-load-transform-bcp/info.txt new file mode 100644 index 0000000000..875a5f6ec1 --- /dev/null +++ b/test/938-load-transform-bcp/info.txt @@ -0,0 +1 @@ +Tests basic functions in the jvmti plugin. diff --git a/test/938-load-transform-bcp/run b/test/938-load-transform-bcp/run new file mode 100755 index 0000000000..adb1a1c507 --- /dev/null +++ b/test/938-load-transform-bcp/run @@ -0,0 +1,17 @@ +#!/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. + +./default-run "$@" --jvmti --no-app-image diff --git a/test/938-load-transform-bcp/src-ex/TestMain.java b/test/938-load-transform-bcp/src-ex/TestMain.java new file mode 100644 index 0000000000..3757a0f778 --- /dev/null +++ b/test/938-load-transform-bcp/src-ex/TestMain.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2017 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.OptionalLong; +public class TestMain { + public static void runTest() { + // This should be our redefined OptionalLong. + OptionalLong ol = OptionalLong.of(123); + try { + // OptionalLong is a class that is unlikely to be used by the time this test starts. + Method foo = OptionalLong.class.getMethod("foo"); + System.out.println("ol.foo() -> '" + (String)foo.invoke(ol) + "'"); + System.out.println("ol.toString() -> '" + ol.toString() + "'"); + } catch (Exception e) { + System.out.println( + "Exception occured (did something load OptionalLong before this test method!: " + + e.toString()); + e.printStackTrace(); + } + } +} diff --git a/test/938-load-transform-bcp/src/Main.java b/test/938-load-transform-bcp/src/Main.java new file mode 100644 index 0000000000..548489939e --- /dev/null +++ b/test/938-load-transform-bcp/src/Main.java @@ -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. + */ + +import java.lang.reflect.*; +import java.util.Base64; + +class Main { + public static String TEST_NAME = "938-load-transform-bcp"; + + /** + * base64 encoded class/dex file for + * + * // Yes this version of OptionalLong is not compatible with the real one but since it isn't used + * // for anything in the runtime initialization it should be fine. + * + * package java.util; + * public final class OptionalLong { + * private long val; + * + * private OptionalLong(long abc) { + * this.val = abc; + * } + * + * public static OptionalLong of(long abc) { + * return new OptionalLong(abc); + * } + * + * public String foo() { + * return "This is foo for val=" + val; + * } + * + * public String toString() { + * return "This is toString() for val=" + val; + * } + * } + */ + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAKQoADAAaCQADABsHABwKAAMAHQcAHgoABQAaCAAfCgAFACAKAAUAIQoABQAiCAAj" + + "BwAkAQADdmFsAQABSgEABjxpbml0PgEABChKKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAC" + + "b2YBABsoSilMamF2YS91dGlsL09wdGlvbmFsTG9uZzsBAANmb28BABQoKUxqYXZhL2xhbmcvU3Ry" + + "aW5nOwEACHRvU3RyaW5nAQAKU291cmNlRmlsZQEAEU9wdGlvbmFsTG9uZy5qYXZhDAAPACUMAA0A" + + "DgEAFmphdmEvdXRpbC9PcHRpb25hbExvbmcMAA8AEAEAF2phdmEvbGFuZy9TdHJpbmdCdWlsZGVy" + + "AQAUVGhpcyBpcyBmb28gZm9yIHZhbD0MACYAJwwAJgAoDAAXABYBABtUaGlzIGlzIHRvU3RyaW5n" + + "KCkgZm9yIHZhbD0BABBqYXZhL2xhbmcvT2JqZWN0AQADKClWAQAGYXBwZW5kAQAtKExqYXZhL2xh" + + "bmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7AQAcKEopTGphdmEvbGFuZy9TdHJp" + + "bmdCdWlsZGVyOwAxAAMADAAAAAEAAgANAA4AAAAEAAIADwAQAAEAEQAAACoAAwADAAAACiq3AAEq" + + "H7UAArEAAAABABIAAAAOAAMAAAAFAAQABgAJAAcACQATABQAAQARAAAAIQAEAAIAAAAJuwADWR63" + + "AASwAAAAAQASAAAABgABAAAACgABABUAFgABABEAAAAvAAMAAQAAABe7AAVZtwAGEge2AAgqtAAC" + + "tgAJtgAKsAAAAAEAEgAAAAYAAQAAAA4AAQAXABYAAQARAAAALwADAAEAAAAXuwAFWbcABhILtgAI" + + "KrQAArYACbYACrAAAAABABIAAAAGAAEAAAASAAEAGAAAAAIAGQ=="); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQAOe/TYJCvVthTToFA3tveMDhwTo7uDf0IcBAAAcAAAAHhWNBIAAAAAAAAAAHwDAAAU" + + "AAAAcAAAAAYAAADAAAAABgAAANgAAAABAAAAIAEAAAkAAAAoAQAAAQAAAHABAACMAgAAkAEAAFYC" + + "AABeAgAAYQIAAGQCAABoAgAAbAIAAIACAACUAgAArwIAAMkCAADcAgAA8gIAAA8DAAASAwAAFgMA" + + "AB4DAAAyAwAANwMAADsDAABFAwAAAQAAAAUAAAAGAAAABwAAAAgAAAAMAAAAAgAAAAIAAAAAAAAA" + + "AwAAAAMAAABIAgAABAAAAAMAAABQAgAAAwAAAAQAAABIAgAADAAAAAUAAAAAAAAADQAAAAUAAABI" + + "AgAABAAAABMAAAABAAQAAAAAAAMABAAAAAAAAwABAA4AAAADAAIADgAAAAMAAAASAAAABAAFAAAA" + + "AAAEAAAAEAAAAAQAAwARAAAABAAAABIAAAAEAAAAEQAAAAEAAAAAAAAACQAAAAAAAABiAwAAAAAA" + + "AAQAAwABAAAASgMAAAYAAABwEAAAAQBaEgAADgAEAAIAAwAAAFIDAAAGAAAAIgAEAHAwBQAgAxEA" + + "BQABAAMAAABYAwAAFwAAACIAAwBwEAEAAAAbAQoAAABuIAMAEAAMAFNCAABuMAIAIAMMAG4QBAAA" + + "AAwAEQAAAAUAAQADAAAAXQMAABcAAAAiAAMAcBABAAAAGwELAAAAbiADABAADABTQgAAbjACACAD" + + "DABuEAQAAAAMABEAAAABAAAAAAAAAAEAAAACAAY8aW5pdD4AAUoAAUwAAkxKAAJMTAASTGphdmEv" + + "bGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAGUxqYXZhL2xhbmcvU3RyaW5nQnVpbGRl" + + "cjsAGExqYXZhL3V0aWwvT3B0aW9uYWxMb25nOwART3B0aW9uYWxMb25nLmphdmEAFFRoaXMgaXMg" + + "Zm9vIGZvciB2YWw9ABtUaGlzIGlzIHRvU3RyaW5nKCkgZm9yIHZhbD0AAVYAAlZKAAZhcHBlbmQA" + + "EmVtaXR0ZXI6IGphY2stNC4yMgADZm9vAAJvZgAIdG9TdHJpbmcAA3ZhbAAFAQAHDjwtAAoBAAcO" + + "AA4ABw4AEgAHDgAAAQICAAIFgoAEkAMCCawDBgHIAwIBiAQAAA0AAAAAAAAAAQAAAAAAAAABAAAA" + + "FAAAAHAAAAACAAAABgAAAMAAAAADAAAABgAAANgAAAAEAAAAAQAAACABAAAFAAAACQAAACgBAAAG" + + "AAAAAQAAAHABAAABIAAABAAAAJABAAABEAAAAgAAAEgCAAACIAAAFAAAAFYCAAADIAAABAAAAEoD" + + "AAAAIAAAAQAAAGIDAAAAEAAAAQAAAHwDAAA="); + + public static ClassLoader getClassLoaderFor(String location) throws Exception { + try { + Class<?> class_loader_class = Class.forName("dalvik.system.PathClassLoader"); + Constructor<?> ctor = class_loader_class.getConstructor(String.class, ClassLoader.class); + return (ClassLoader)ctor.newInstance(location + "/" + TEST_NAME + "-ex.jar", + Main.class.getClassLoader()); + } catch (ClassNotFoundException e) { + // Running on RI. Use URLClassLoader. + return new java.net.URLClassLoader( + new java.net.URL[] { new java.net.URL("file://" + location + "/classes-ex/") }); + } + } + + public static void main(String[] args) { + setPopRetransformations(false); + addCommonTransformationResult("java/util/OptionalLong", CLASS_BYTES, DEX_BYTES); + enableCommonRetransformation(true); + try { + /* this is the "alternate" DEX/Jar file */ + ClassLoader new_loader = getClassLoaderFor(System.getenv("DEX_LOCATION")); + Class<?> klass = (Class<?>)new_loader.loadClass("TestMain"); + if (klass == null) { + throw new AssertionError("loadClass failed"); + } + Method run_test = klass.getMethod("runTest"); + run_test.invoke(null); + } catch (Exception e) { + System.out.println(e.toString()); + e.printStackTrace(); + } + } + + private static native void setPopRetransformations(boolean should_pop); + // Transforms the class + private static native void enableCommonRetransformation(boolean enable); + private static native void addCommonTransformationResult(String target_name, + byte[] class_bytes, + byte[] dex_bytes); +} diff --git a/test/939-hello-transformation-bcp/build b/test/939-hello-transformation-bcp/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/939-hello-transformation-bcp/build @@ -0,0 +1,17 @@ +#!/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. + +./default-build "$@" --experimental agents diff --git a/test/939-hello-transformation-bcp/expected.txt b/test/939-hello-transformation-bcp/expected.txt new file mode 100644 index 0000000000..90fd25828d --- /dev/null +++ b/test/939-hello-transformation-bcp/expected.txt @@ -0,0 +1,3 @@ +ol.toString() -> 'OptionalLong[-559038737]' +Redefining OptionalLong! +ol.toString() -> 'Redefined OptionalLong!' diff --git a/test/939-hello-transformation-bcp/info.txt b/test/939-hello-transformation-bcp/info.txt new file mode 100644 index 0000000000..d230a382bd --- /dev/null +++ b/test/939-hello-transformation-bcp/info.txt @@ -0,0 +1,6 @@ +Tests basic functions in the jvmti plugin. + +Note this function is reliant on the definition of java.util.OptionalLong not +changing. If this classes definition changes we will need to update this class +so that the CLASS_BYTES and DEX_BYTES fields contain dex/class bytes for an +OptionalLong with all the same methods and fields. diff --git a/test/939-hello-transformation-bcp/run b/test/939-hello-transformation-bcp/run new file mode 100755 index 0000000000..c6e62ae6cd --- /dev/null +++ b/test/939-hello-transformation-bcp/run @@ -0,0 +1,17 @@ +#!/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. + +./default-run "$@" --jvmti diff --git a/test/939-hello-transformation-bcp/src/Main.java b/test/939-hello-transformation-bcp/src/Main.java new file mode 100644 index 0000000000..bdf7f592ef --- /dev/null +++ b/test/939-hello-transformation-bcp/src/Main.java @@ -0,0 +1,126 @@ +/* + * 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.Base64; +import java.util.OptionalLong; +public class Main { + + /** + * This is the base64 encoded class/dex. + * + * package java.util; + * import java.util.function.LongConsumer; + * import java.util.function.LongSupplier; + * import java.util.function.Supplier; + * public final class OptionalLong { + * // Make sure we have a <clinit> function since the real implementation of OptionalLong does. + * static { EMPTY = null; } + * private static final OptionalLong EMPTY; + * private final boolean isPresent; + * private final long value; + * private OptionalLong() { isPresent = false; value = 0; } + * private OptionalLong(long l) { this(); } + * public static OptionalLong empty() { return null; } + * public static OptionalLong of(long value) { return null; } + * public long getAsLong() { return 0; } + * public boolean isPresent() { return false; } + * public void ifPresent(LongConsumer c) { } + * public long orElse(long l) { return 0; } + * public long orElseGet(LongSupplier s) { return 0; } + * public<X extends Throwable> long orElseThrow(Supplier<X> s) throws X { return 0; } + * public boolean equals(Object o) { return false; } + * public int hashCode() { return 0; } + * public String toString() { return "Redefined OptionalLong!"; } + * } + */ + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAOAoACAAwCQAHADEJAAcAMgoABwAwCAAzCQAHADQHADUHADYBAAVFTVBUWQEAGExq" + + "YXZhL3V0aWwvT3B0aW9uYWxMb25nOwEACWlzUHJlc2VudAEAAVoBAAV2YWx1ZQEAAUoBAAY8aW5p" + + "dD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAEKEopVgEABWVtcHR5AQAaKClMamF2" + + "YS91dGlsL09wdGlvbmFsTG9uZzsBAAJvZgEAGyhKKUxqYXZhL3V0aWwvT3B0aW9uYWxMb25nOwEA" + + "CWdldEFzTG9uZwEAAygpSgEAAygpWgEACWlmUHJlc2VudAEAJChMamF2YS91dGlsL2Z1bmN0aW9u" + + "L0xvbmdDb25zdW1lcjspVgEABm9yRWxzZQEABChKKUoBAAlvckVsc2VHZXQBACQoTGphdmEvdXRp" + + "bC9mdW5jdGlvbi9Mb25nU3VwcGxpZXI7KUoBAAtvckVsc2VUaHJvdwEAIChMamF2YS91dGlsL2Z1" + + "bmN0aW9uL1N1cHBsaWVyOylKAQAKRXhjZXB0aW9ucwcANwEACVNpZ25hdHVyZQEAQjxYOkxqYXZh" + + "L2xhbmcvVGhyb3dhYmxlOz4oTGphdmEvdXRpbC9mdW5jdGlvbi9TdXBwbGllcjxUWDs+OylKXlRY" + + "OwEABmVxdWFscwEAFShMamF2YS9sYW5nL09iamVjdDspWgEACGhhc2hDb2RlAQADKClJAQAIdG9T" + + "dHJpbmcBABQoKUxqYXZhL2xhbmcvU3RyaW5nOwEACDxjbGluaXQ+AQAKU291cmNlRmlsZQEAEU9w" + + "dGlvbmFsTG9uZy5qYXZhDAAPABAMAAsADAwADQAOAQAXUmVkZWZpbmVkIE9wdGlvbmFsTG9uZyEM" + + "AAkACgEAFmphdmEvdXRpbC9PcHRpb25hbExvbmcBABBqYXZhL2xhbmcvT2JqZWN0AQATamF2YS9s" + + "YW5nL1Rocm93YWJsZQAxAAcACAAAAAMAGgAJAAoAAAASAAsADAAAABIADQAOAAAADgACAA8AEAAB" + + "ABEAAAAnAAMAAQAAAA8qtwABKgO1AAIqCbUAA7EAAAABABIAAAAGAAEAAAALAAIADwATAAEAEQAA" + + "AB0AAQADAAAABSq3AASxAAAAAQASAAAABgABAAAADAAJABQAFQABABEAAAAaAAEAAAAAAAIBsAAA" + + "AAEAEgAAAAYAAQAAAA0ACQAWABcAAQARAAAAGgABAAIAAAACAbAAAAABABIAAAAGAAEAAAAOAAEA" + + "GAAZAAEAEQAAABoAAgABAAAAAgmtAAAAAQASAAAABgABAAAADwABAAsAGgABABEAAAAaAAEAAQAA" + + "AAIDrAAAAAEAEgAAAAYAAQAAABAAAQAbABwAAQARAAAAGQAAAAIAAAABsQAAAAEAEgAAAAYAAQAA" + + "ABEAAQAdAB4AAQARAAAAGgACAAMAAAACCa0AAAABABIAAAAGAAEAAAASAAEAHwAgAAEAEQAAABoA" + + "AgACAAAAAgmtAAAAAQASAAAABgABAAAAEwABACEAIgADABEAAAAaAAIAAgAAAAIJrQAAAAEAEgAA" + + "AAYAAQAAABQAIwAAAAQAAQAkACUAAAACACYAAQAnACgAAQARAAAAGgABAAIAAAACA6wAAAABABIA" + + "AAAGAAEAAAAVAAEAKQAqAAEAEQAAABoAAQABAAAAAgOsAAAAAQASAAAABgABAAAAFgABACsALAAB" + + "ABEAAAAbAAEAAQAAAAMSBbAAAAABABIAAAAGAAEAAAAXAAgALQAQAAEAEQAAAB0AAQAAAAAABQGz" + + "AAaxAAAAAQASAAAABgABAAAABwABAC4AAAACAC8="); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQCvAoivSJqk6GdYOgJmvrM/b2/flxhw99q8BwAAcAAAAHhWNBIAAAAAAAAAAPgGAAAq" + + "AAAAcAAAAA0AAAAYAQAADQAAAEwBAAADAAAA6AEAAA8AAAAAAgAAAQAAAHgCAAAkBQAAmAIAACoE" + + "AAA4BAAAPQQAAEcEAABPBAAAUwQAAFoEAABdBAAAYAQAAGQEAABoBAAAawQAAG8EAACOBAAAqgQA" + + "AL4EAADSBAAA6QQAAAMFAAAmBQAASQUAAGcFAACGBQAAmQUAALIFAAC1BQAAuQUAAL0FAADABQAA" + + "xAUAANgFAADfBQAA5wUAAPIFAAD8BQAABwYAABIGAAAWBgAAHgYAACkGAAA2BgAAQAYAAAYAAAAH" + + "AAAADAAAAA0AAAAOAAAADwAAABAAAAARAAAAEgAAABMAAAAVAAAAGAAAABsAAAAGAAAAAAAAAAAA" + + "AAAHAAAAAQAAAAAAAAAIAAAAAQAAAAQEAAAJAAAAAQAAAAwEAAAJAAAAAQAAABQEAAAKAAAABQAA" + + "AAAAAAAKAAAABwAAAAAAAAALAAAABwAAAAQEAAAYAAAACwAAAAAAAAAZAAAACwAAAAQEAAAaAAAA" + + "CwAAABwEAAAbAAAADAAAAAAAAAAcAAAADAAAACQEAAAHAAcABQAAAAcADAAjAAAABwABACkAAAAE" + + "AAgAAwAAAAcACAACAAAABwAIAAMAAAAHAAkAAwAAAAcABgAeAAAABwAMAB8AAAAHAAEAIAAAAAcA" + + "AAAhAAAABwAKACIAAAAHAAsAIwAAAAcABwAkAAAABwACACUAAAAHAAMAJgAAAAcABAAnAAAABwAF" + + "ACgAAAAHAAAAEQAAAAQAAAAAAAAAFgAAAOwDAACtBgAAAAAAAAIAAACVBgAApQYAAAEAAAAAAAAA" + + "RwYAAAQAAAASAGkAAAAOAAMAAQABAAAATQYAAAsAAABwEAAAAgASAFwgAQAWAAAAWiACAA4AAAAD" + + "AAMAAQAAAFIGAAAEAAAAcBACAAAADgABAAAAAAAAAFgGAAACAAAAEgARAAMAAgAAAAAAXQYAAAIA" + + "AAASABEAAwACAAAAAABjBgAAAgAAABIADwADAAEAAAAAAGkGAAADAAAAFgAAABAAAAACAAEAAAAA" + + "AG4GAAACAAAAEgAPAAIAAgAAAAAAcwYAAAEAAAAOAAAAAgABAAAAAAB5BgAAAgAAABIADwAFAAMA" + + "AAAAAH4GAAADAAAAFgAAABAAAAAEAAIAAAAAAIQGAAADAAAAFgAAABAAAAAEAAIAAAAAAIoGAAAD" + + "AAAAFgAAABAAAAACAAEAAAAAAJAGAAAEAAAAGwAXAAAAEQAAAAAAAAAAAAEAAAAAAAAADQAAAJgC" + + "AAABAAAAAQAAAAEAAAAJAAAAAQAAAAoAAAABAAAACAAAAAEAAAAEAAw8VFg7PjspSl5UWDsAAzxY" + + "OgAIPGNsaW5pdD4ABjxpbml0PgACPigABUVNUFRZAAFJAAFKAAJKSgACSkwAAUwAAkxKAB1MZGFs" + + "dmlrL2Fubm90YXRpb24vU2lnbmF0dXJlOwAaTGRhbHZpay9hbm5vdGF0aW9uL1Rocm93czsAEkxq" + + "YXZhL2xhbmcvT2JqZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7ABVMamF2YS9sYW5nL1Rocm93YWJs" + + "ZTsAGExqYXZhL3V0aWwvT3B0aW9uYWxMb25nOwAhTGphdmEvdXRpbC9mdW5jdGlvbi9Mb25nQ29u" + + "c3VtZXI7ACFMamF2YS91dGlsL2Z1bmN0aW9uL0xvbmdTdXBwbGllcjsAHExqYXZhL3V0aWwvZnVu" + + "Y3Rpb24vU3VwcGxpZXIAHUxqYXZhL3V0aWwvZnVuY3Rpb24vU3VwcGxpZXI7ABFPcHRpb25hbExv" + + "bmcuamF2YQAXUmVkZWZpbmVkIE9wdGlvbmFsTG9uZyEAAVYAAlZKAAJWTAABWgACWkwAEmVtaXR0" + + "ZXI6IGphY2stNC4yMgAFZW1wdHkABmVxdWFscwAJZ2V0QXNMb25nAAhoYXNoQ29kZQAJaWZQcmVz" + + "ZW50AAlpc1ByZXNlbnQAAm9mAAZvckVsc2UACW9yRWxzZUdldAALb3JFbHNlVGhyb3cACHRvU3Ry" + + "aW5nAAV2YWx1ZQAHAAcOOQALAAcOAAwBAAcOAA0ABw4ADgEABw4AFQEABw4ADwAHDgAWAAcOABEB" + + "AAcOABAABw4AEgEABw4AEwEABw4AFAEABw4AFwAHDgACAgEpHAUXARcQFwQXFBcAAgMBKRwBGAYB" + + "AgUJABoBEgESAYiABKQFAYKABLwFAYKABOQFAQn8BQYJkAYFAaQGAQG4BgEB0AYBAeQGAQH4BgIB" + + "jAcBAaQHAQG8BwEB1AcAAAAQAAAAAAAAAAEAAAAAAAAAAQAAACoAAABwAAAAAgAAAA0AAAAYAQAA" + + "AwAAAA0AAABMAQAABAAAAAMAAADoAQAABQAAAA8AAAAAAgAABgAAAAEAAAB4AgAAAxAAAAEAAACY" + + "AgAAASAAAA4AAACkAgAABiAAAAEAAADsAwAAARAAAAUAAAAEBAAAAiAAACoAAAAqBAAAAyAAAA4A" + + "AABHBgAABCAAAAIAAACVBgAAACAAAAEAAACtBgAAABAAAAEAAAD4BgAA"); + + public static void main(String[] args) { + // OptionalLong is a class that is unlikely to be used by the time this test starts and is not + // likely to be changed in any meaningful way in the future. + OptionalLong ol = OptionalLong.of(0xDEADBEEF); + System.out.println("ol.toString() -> '" + ol.toString() + "'"); + System.out.println("Redefining OptionalLong!"); + doCommonClassRedefinition(OptionalLong.class, CLASS_BYTES, DEX_BYTES); + System.out.println("ol.toString() -> '" + ol.toString() + "'"); + } + + // Transforms the class + private static native void doCommonClassRedefinition(Class<?> target, + byte[] class_file, + byte[] dex_file); +} diff --git a/test/940-recursive-obsolete/build b/test/940-recursive-obsolete/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/940-recursive-obsolete/build @@ -0,0 +1,17 @@ +#!/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. + +./default-build "$@" --experimental agents diff --git a/test/940-recursive-obsolete/expected.txt b/test/940-recursive-obsolete/expected.txt new file mode 100644 index 0000000000..18ffc25d8a --- /dev/null +++ b/test/940-recursive-obsolete/expected.txt @@ -0,0 +1,21 @@ +hello2 +hello1 +Not doing anything here +hello0 +goodbye0 +goodbye1 +goodbye2 +hello2 +hello1 +transforming calling function +Hello0 - transformed +Goodbye0 - transformed +goodbye1 +goodbye2 +Hello2 - transformed +Hello1 - transformed +Not doing anything here +Hello0 - transformed +Goodbye0 - transformed +Goodbye1 - transformed +Goodbye2 - transformed diff --git a/test/940-recursive-obsolete/info.txt b/test/940-recursive-obsolete/info.txt new file mode 100644 index 0000000000..c8b892cedd --- /dev/null +++ b/test/940-recursive-obsolete/info.txt @@ -0,0 +1 @@ +Tests basic obsolete method support diff --git a/test/940-recursive-obsolete/run b/test/940-recursive-obsolete/run new file mode 100755 index 0000000000..c6e62ae6cd --- /dev/null +++ b/test/940-recursive-obsolete/run @@ -0,0 +1,17 @@ +#!/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. + +./default-run "$@" --jvmti diff --git a/test/940-recursive-obsolete/src/Main.java b/test/940-recursive-obsolete/src/Main.java new file mode 100644 index 0000000000..3766906a89 --- /dev/null +++ b/test/940-recursive-obsolete/src/Main.java @@ -0,0 +1,89 @@ +/* + * 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.Base64; + +public class Main { + + // class Transform { + // public void sayHi(int recur, Runnable r) { + // System.out.println("Hello" + recur + " - transformed"); + // if (recur == 1) { + // r.run(); + // sayHi(recur - 1, r); + // } else if (recur != 0) { + // sayHi(recur - 1, r); + // } + // System.out.println("Goodbye" + recur + " - transformed"); + // } + // } + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQANwoADwAZCQAaABsHABwKAAMAGQgAHQoAAwAeCgADAB8IACAKAAMAIQoAIgAjCwAk" + + "ACUKAA4AJggAJwcAKAcAKQEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUB" + + "AAVzYXlIaQEAGChJTGphdmEvbGFuZy9SdW5uYWJsZTspVgEADVN0YWNrTWFwVGFibGUBAApTb3Vy" + + "Y2VGaWxlAQAOVHJhbnNmb3JtLmphdmEMABAAEQcAKgwAKwAsAQAXamF2YS9sYW5nL1N0cmluZ0J1" + + "aWxkZXIBAAVIZWxsbwwALQAuDAAtAC8BAA4gLSB0cmFuc2Zvcm1lZAwAMAAxBwAyDAAzADQHADUM" + + "ADYAEQwAFAAVAQAHR29vZGJ5ZQEACVRyYW5zZm9ybQEAEGphdmEvbGFuZy9PYmplY3QBABBqYXZh" + + "L2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQAGYXBwZW5kAQAtKExq" + + "YXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7AQAcKEkpTGphdmEvbGFu" + + "Zy9TdHJpbmdCdWlsZGVyOwEACHRvU3RyaW5nAQAUKClMamF2YS9sYW5nL1N0cmluZzsBABNqYXZh" + + "L2lvL1ByaW50U3RyZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgEAEmphdmEv" + + "bGFuZy9SdW5uYWJsZQEAA3J1bgAgAA4ADwAAAAAAAgAAABAAEQABABIAAAAdAAEAAQAAAAUqtwAB" + + "sQAAAAEAEwAAAAYAAQAAAAEAAQAUABUAAQASAAAAnQADAAMAAABfsgACuwADWbcABBIFtgAGG7YA" + + "BxIItgAGtgAJtgAKGwSgABQsuQALAQAqGwRkLLYADKcADxuZAAsqGwRkLLYADLIAArsAA1m3AAQS" + + "DbYABhu2AAcSCLYABrYACbYACrEAAAACABMAAAAiAAgAAAADAB4ABAAjAAUAKQAGADQABwA4AAgA" + + "QAAKAF4ACwAWAAAABAACNAsAAQAXAAAAAgAY"); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQA3pkIgnymz2/eri+mp2dyZo3jolQmaRPKEBAAAcAAAAHhWNBIAAAAAAAAAAOQDAAAa" + + "AAAAcAAAAAkAAADYAAAABgAAAPwAAAABAAAARAEAAAkAAABMAQAAAQAAAJQBAADQAgAAtAEAAJwC" + + "AACsAgAAtAIAAL0CAADEAgAAxwIAAMoCAADOAgAA0gIAAN8CAAD2AgAACgMAACADAAA0AwAATwMA" + + "AGMDAABzAwAAdgMAAHsDAAB/AwAAhwMAAJsDAACgAwAAqQMAAK4DAAC1AwAABAAAAAgAAAAJAAAA" + + "CgAAAAsAAAAMAAAADQAAAA4AAAAQAAAABQAAAAUAAAAAAAAABgAAAAYAAACEAgAABwAAAAYAAACM" + + "AgAAEAAAAAgAAAAAAAAAEQAAAAgAAACUAgAAEgAAAAgAAACMAgAABwACABUAAAABAAMAAQAAAAEA" + + "BAAYAAAAAgAFABYAAAADAAMAAQAAAAQAAwAXAAAABgADAAEAAAAGAAEAEwAAAAYAAgATAAAABgAA" + + "ABkAAAABAAAAAAAAAAMAAAAAAAAADwAAAAAAAADWAwAAAAAAAAEAAQABAAAAvwMAAAQAAABwEAMA" + + "AAAOAAYAAwADAAAAxAMAAFQAAABiAAAAIgEGAHAQBQABABsCAwAAAG4gBwAhAAwBbiAGAEEADAEb" + + "AgAAAABuIAcAIQAMAW4QCAABAAwBbiACABAAEhAzBCsAchAEAAUA2AAE/24wAQADBWIAAAAiAQYA" + + "cBAFAAEAGwICAAAAbiAHACEADAFuIAYAQQAMARsCAAAAAG4gBwAhAAwBbhAIAAEADAFuIAIAEAAO" + + "ADgE3//YAAT/bjABAAMFKNgBAAAAAAAAAAEAAAAFAAAAAgAAAAAABAAOIC0gdHJhbnNmb3JtZWQA" + + "Bjxpbml0PgAHR29vZGJ5ZQAFSGVsbG8AAUkAAUwAAkxJAAJMTAALTFRyYW5zZm9ybTsAFUxqYXZh" + + "L2lvL1ByaW50U3RyZWFtOwASTGphdmEvbGFuZy9PYmplY3Q7ABRMamF2YS9sYW5nL1J1bm5hYmxl" + + "OwASTGphdmEvbGFuZy9TdHJpbmc7ABlMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7ABJMamF2YS9s" + + "YW5nL1N5c3RlbTsADlRyYW5zZm9ybS5qYXZhAAFWAANWSUwAAlZMAAZhcHBlbmQAEmVtaXR0ZXI6" + + "IGphY2stNC4yNAADb3V0AAdwcmludGxuAANydW4ABXNheUhpAAh0b1N0cmluZwABAAcOAAMCAAAH" + + "DgEgDzw8XQEgDxktAAAAAQEAgIAEtAMBAcwDDQAAAAAAAAABAAAAAAAAAAEAAAAaAAAAcAAAAAIA" + + "AAAJAAAA2AAAAAMAAAAGAAAA/AAAAAQAAAABAAAARAEAAAUAAAAJAAAATAEAAAYAAAABAAAAlAEA" + + "AAEgAAACAAAAtAEAAAEQAAADAAAAhAIAAAIgAAAaAAAAnAIAAAMgAAACAAAAvwMAAAAgAAABAAAA" + + "1gMAAAAQAAABAAAA5AMAAA=="); + + public static void main(String[] args) { + doTest(new Transform()); + } + + public static void doTest(Transform t) { + t.sayHi(2, () -> { System.out.println("Not doing anything here"); }); + t.sayHi(2, () -> { + System.out.println("transforming calling function"); + doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES); + }); + t.sayHi(2, () -> { System.out.println("Not doing anything here"); }); + } + + // Transforms the class + private static native void doCommonClassRedefinition(Class<?> target, + byte[] classfile, + byte[] dexfile); +} diff --git a/test/940-recursive-obsolete/src/Transform.java b/test/940-recursive-obsolete/src/Transform.java new file mode 100644 index 0000000000..97522cddf6 --- /dev/null +++ b/test/940-recursive-obsolete/src/Transform.java @@ -0,0 +1,28 @@ +/* + * 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 Transform { + public void sayHi(int recur, Runnable r) { + System.out.println("hello" + recur); + if (recur == 1) { + r.run(); + sayHi(recur - 1, r); + } else if (recur != 0) { + sayHi(recur - 1, r); + } + System.out.println("goodbye" + recur); + } +} diff --git a/test/941-recurive-obsolete-jit/build b/test/941-recurive-obsolete-jit/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/941-recurive-obsolete-jit/build @@ -0,0 +1,17 @@ +#!/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. + +./default-build "$@" --experimental agents diff --git a/test/941-recurive-obsolete-jit/expected.txt b/test/941-recurive-obsolete-jit/expected.txt new file mode 100644 index 0000000000..086f7b03dc --- /dev/null +++ b/test/941-recurive-obsolete-jit/expected.txt @@ -0,0 +1,22 @@ +hello2 +hello1 +Not doing anything here +hello0 +goodbye0 +goodbye1 +goodbye2 +hello2 +hello1 +transforming calling function +Hello0 - transformed +Goodbye0 - transformed +goodbye1 +goodbye2 +Hello2 - transformed +Hello1 - transformed +Not doing anything here +Hello0 - transformed +Goodbye0 - transformed +Goodbye1 - transformed +Goodbye2 - transformed + diff --git a/test/941-recurive-obsolete-jit/info.txt b/test/941-recurive-obsolete-jit/info.txt new file mode 100644 index 0000000000..c8b892cedd --- /dev/null +++ b/test/941-recurive-obsolete-jit/info.txt @@ -0,0 +1 @@ +Tests basic obsolete method support diff --git a/test/941-recurive-obsolete-jit/run b/test/941-recurive-obsolete-jit/run new file mode 100755 index 0000000000..c6e62ae6cd --- /dev/null +++ b/test/941-recurive-obsolete-jit/run @@ -0,0 +1,17 @@ +#!/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. + +./default-run "$@" --jvmti diff --git a/test/941-recurive-obsolete-jit/src/Main.java b/test/941-recurive-obsolete-jit/src/Main.java new file mode 100644 index 0000000000..f6d6416b55 --- /dev/null +++ b/test/941-recurive-obsolete-jit/src/Main.java @@ -0,0 +1,155 @@ +/* + * 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.Base64; +import java.util.function.Consumer; +import java.lang.reflect.Method; + +public class Main { + + // import java.util.function.Consumer; + // class Transform { + // public void sayHi(int recur, Consumer<String> reporter, Runnable r) { + // reporter.accept("Hello" + recur + " - transformed"); + // if (recur == 1) { + // r.run(); + // sayHi(recur - 1, reporter, r); + // } else if (recur != 0) { + // sayHi(recur - 1, reporter, r); + // } + // reporter.accept("Goodbye" + recur + " - transformed"); + // } + // } + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAMwoADgAaBwAbCgACABoIABwKAAIAHQoAAgAeCAAfCgACACALACEAIgsAIwAkCgAN" + + "ACUIACYHACcHACgBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAFc2F5" + + "SGkBADUoSUxqYXZhL3V0aWwvZnVuY3Rpb24vQ29uc3VtZXI7TGphdmEvbGFuZy9SdW5uYWJsZTsp" + + "VgEADVN0YWNrTWFwVGFibGUBAAlTaWduYXR1cmUBAEkoSUxqYXZhL3V0aWwvZnVuY3Rpb24vQ29u" + + "c3VtZXI8TGphdmEvbGFuZy9TdHJpbmc7PjtMamF2YS9sYW5nL1J1bm5hYmxlOylWAQAKU291cmNl" + + "RmlsZQEADlRyYW5zZm9ybS5qYXZhDAAPABABABdqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcgEABUhl" + + "bGxvDAApACoMACkAKwEADiAtIHRyYW5zZm9ybWVkDAAsAC0HAC4MAC8AMAcAMQwAMgAQDAATABQB" + + "AAdHb29kYnllAQAJVHJhbnNmb3JtAQAQamF2YS9sYW5nL09iamVjdAEABmFwcGVuZAEALShMamF2" + + "YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwEAHChJKUxqYXZhL2xhbmcv" + + "U3RyaW5nQnVpbGRlcjsBAAh0b1N0cmluZwEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAbamF2YS91" + + "dGlsL2Z1bmN0aW9uL0NvbnN1bWVyAQAGYWNjZXB0AQAVKExqYXZhL2xhbmcvT2JqZWN0OylWAQAS" + + "amF2YS9sYW5nL1J1bm5hYmxlAQADcnVuACAADQAOAAAAAAACAAAADwAQAAEAEQAAAB0AAQABAAAA" + + "BSq3AAGxAAAAAQASAAAABgABAAAAAgABABMAFAACABEAAACfAAQABAAAAGEsuwACWbcAAxIEtgAF" + + "G7YABhIHtgAFtgAIuQAJAgAbBKAAFS25AAoBACobBGQsLbYAC6cAEBuZAAwqGwRkLC22AAssuwAC" + + "WbcAAxIMtgAFG7YABhIHtgAFtgAIuQAJAgCxAAAAAgASAAAAIgAIAAAABAAeAAUAIwAGACkABwA1" + + "AAgAOQAJAEIACwBgAAwAFQAAAAQAAjUMABYAAAACABcAAQAYAAAAAgAZ"); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQA7uevryhDgvad3G3EACTdspZGfNKv2i3kkBQAAcAAAAHhWNBIAAAAAAAAAAGwEAAAf" + + "AAAAcAAAAAkAAADsAAAABgAAABABAAAAAAAAAAAAAAkAAABYAQAAAQAAAKABAABkAwAAwAEAAMoC" + + "AADaAgAA3gIAAOICAADlAgAA7QIAAPECAAD6AgAAAQMAAAQDAAAHAwAACwMAAA8DAAAcAwAAOwMA" + + "AE8DAABlAwAAeQMAAJQDAACyAwAA0QMAAOEDAADkAwAA6gMAAO4DAAD2AwAA/gMAABIEAAAXBAAA" + + "HgQAACgEAAAIAAAADAAAAA0AAAAOAAAADwAAABAAAAARAAAAEwAAABUAAAAJAAAABQAAAAAAAAAK" + + "AAAABgAAAKgCAAALAAAABgAAALACAAAVAAAACAAAAAAAAAAWAAAACAAAALgCAAAXAAAACAAAAMQC" + + "AAABAAMABAAAAAEABAAcAAAAAwADAAQAAAAEAAMAGwAAAAYAAwAEAAAABgABABkAAAAGAAIAGQAA" + + "AAYAAAAdAAAABwAFABgAAAABAAAAAAAAAAMAAAAAAAAAFAAAAJACAABbBAAAAAAAAAEAAABHBAAA" + + "AQABAAEAAAAvBAAABAAAAHAQAgAAAA4ABgAEAAQAAAA0BAAAUAAAACIABgBwEAQAAAAbAQcAAABu" + + "IAYAEAAMAG4gBQAwAAwAGwEAAAAAbiAGABAADABuEAcAAAAMAHIgCAAEABIQMwMpAHIQAwAFANgA" + + "A/9uQAEAAlQiAAYAcBAEAAAAGwEGAAAAbiAGABAADABuIAUAMAAMABsBAAAAAG4gBgAQAAwAbhAH" + + "AAAADAByIAgABAAOADgD4f/YAAP/bkABAAJUKNoAAAAAAAAAAAEAAAAAAAAAAQAAAMABAAABAAAA" + + "AAAAAAEAAAAFAAAAAwAAAAAABwAEAAAAAQAAAAMADiAtIHRyYW5zZm9ybWVkAAIoSQACKVYAATwA" + + "Bjxpbml0PgACPjsAB0dvb2RieWUABUhlbGxvAAFJAAFMAAJMSQACTEwAC0xUcmFuc2Zvcm07AB1M" + + "ZGFsdmlrL2Fubm90YXRpb24vU2lnbmF0dXJlOwASTGphdmEvbGFuZy9PYmplY3Q7ABRMamF2YS9s" + + "YW5nL1J1bm5hYmxlOwASTGphdmEvbGFuZy9TdHJpbmc7ABlMamF2YS9sYW5nL1N0cmluZ0J1aWxk" + + "ZXI7ABxMamF2YS91dGlsL2Z1bmN0aW9uL0NvbnN1bWVyAB1MamF2YS91dGlsL2Z1bmN0aW9uL0Nv" + + "bnN1bWVyOwAOVHJhbnNmb3JtLmphdmEAAVYABFZJTEwAAlZMAAZhY2NlcHQABmFwcGVuZAASZW1p" + + "dHRlcjogamFjay00LjI0AANydW4ABXNheUhpAAh0b1N0cmluZwAFdmFsdWUAAgAHDgAEAwAAAAcO" + + "AR4PPDxdAR4PGS0AAgIBHhwHFwEXEhcDFxAXBRcPFwIAAAEBAICABMgDAQHgAwAAAA8AAAAAAAAA" + + "AQAAAAAAAAABAAAAHwAAAHAAAAACAAAACQAAAOwAAAADAAAABgAAABABAAAFAAAACQAAAFgBAAAG" + + "AAAAAQAAAKABAAADEAAAAQAAAMABAAABIAAAAgAAAMgBAAAGIAAAAQAAAJACAAABEAAABAAAAKgC" + + "AAACIAAAHwAAAMoCAAADIAAAAgAAAC8EAAAEIAAAAQAAAEcEAAAAIAAAAQAAAFsEAAAAEAAAAQAA" + + "AGwEAAA="); + + // A class that we can use to keep track of the output of this test. + private static class TestWatcher implements Consumer<String> { + private StringBuilder sb; + public TestWatcher() { + sb = new StringBuilder(); + } + + @Override + public void accept(String s) { + sb.append(s); + sb.append('\n'); + } + + public String getOutput() { + return sb.toString(); + } + + public void clear() { + sb = new StringBuilder(); + } + } + + public static void main(String[] args) { + doTest(new Transform()); + } + + private static boolean retry = false; + + public static void doTest(Transform t) { + final TestWatcher reporter = new TestWatcher(); + Method say_hi_method; + // Figure out if we can even JIT at all. + final boolean has_jit = hasJit(); + try { + say_hi_method = Transform.class.getDeclaredMethod( + "sayHi", int.class, Consumer.class, Runnable.class); + } catch (Exception e) { + System.out.println("Unable to find methods!"); + e.printStackTrace(); + return; + } + // Makes sure the stack is the way we want it for the test and does the redefinition. It will + // set the retry boolean to true if we need to go around again due to jit code being GCd. + Runnable do_redefinition = () -> { + if (has_jit && Main.isInterpretedFunction(say_hi_method, true)) { + // Try again. We are not running the right jitted methods/cannot redefine them now. + retry = true; + } else { + // Actually do the redefinition. The stack looks good. + retry = false; + reporter.accept("transforming calling function"); + doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES); + } + }; + do { + // Run ensureJitCompiled here since it might get GCd + ensureJitCompiled(Transform.class, "sayHi"); + // Clear output. + reporter.clear(); + t.sayHi(2, reporter, () -> { reporter.accept("Not doing anything here"); }); + t.sayHi(2, reporter, do_redefinition); + t.sayHi(2, reporter, () -> { reporter.accept("Not doing anything here"); }); + } while(retry); + System.out.println(reporter.getOutput()); + } + + private static native boolean hasJit(); + + private static native boolean isInterpretedFunction(Method m, boolean require_deoptimizable); + + private static native void ensureJitCompiled(Class c, String name); + + // Transforms the class + private static native void doCommonClassRedefinition(Class<?> target, + byte[] classfile, + byte[] dexfile); +} diff --git a/test/941-recurive-obsolete-jit/src/Transform.java b/test/941-recurive-obsolete-jit/src/Transform.java new file mode 100644 index 0000000000..e6a913a391 --- /dev/null +++ b/test/941-recurive-obsolete-jit/src/Transform.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. + */ + +import java.util.function.Consumer; +class Transform { + public void sayHi(int recur, Consumer<String> c, Runnable r) { + c.accept("hello" + recur); + if (recur == 1) { + r.run(); + sayHi(recur - 1, c, r); + } else if (recur != 0) { + sayHi(recur - 1, c, r); + } + c.accept("goodbye" + recur); + } +} diff --git a/test/942-private-recursive/build b/test/942-private-recursive/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/942-private-recursive/build @@ -0,0 +1,17 @@ +#!/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. + +./default-build "$@" --experimental agents diff --git a/test/942-private-recursive/expected.txt b/test/942-private-recursive/expected.txt new file mode 100644 index 0000000000..18ffc25d8a --- /dev/null +++ b/test/942-private-recursive/expected.txt @@ -0,0 +1,21 @@ +hello2 +hello1 +Not doing anything here +hello0 +goodbye0 +goodbye1 +goodbye2 +hello2 +hello1 +transforming calling function +Hello0 - transformed +Goodbye0 - transformed +goodbye1 +goodbye2 +Hello2 - transformed +Hello1 - transformed +Not doing anything here +Hello0 - transformed +Goodbye0 - transformed +Goodbye1 - transformed +Goodbye2 - transformed diff --git a/test/942-private-recursive/info.txt b/test/942-private-recursive/info.txt new file mode 100644 index 0000000000..c8b892cedd --- /dev/null +++ b/test/942-private-recursive/info.txt @@ -0,0 +1 @@ +Tests basic obsolete method support diff --git a/test/942-private-recursive/run b/test/942-private-recursive/run new file mode 100755 index 0000000000..c6e62ae6cd --- /dev/null +++ b/test/942-private-recursive/run @@ -0,0 +1,17 @@ +#!/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. + +./default-run "$@" --jvmti diff --git a/test/942-private-recursive/src/Main.java b/test/942-private-recursive/src/Main.java new file mode 100644 index 0000000000..8cbab7bac3 --- /dev/null +++ b/test/942-private-recursive/src/Main.java @@ -0,0 +1,94 @@ +/* + * 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.Base64; + +public class Main { + + // class Transform { + // public void sayHi(int recur, Runnable r) { + // privateSayHi(recur, r); + // } + // private void privateSayHi(int recur, Runnable r) { + // System.out.println("Hello" + recur + " - transformed"); + // if (recur == 1) { + // r.run(); + // privateSayHi(recur - 1, r); + // } else if (recur != 0) { + // privateSayHi(recur - 1, r); + // } + // System.out.println("Goodbye" + recur + " - transformed"); + // } + // } + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAOAoADwAaCgAOABsJABwAHQcAHgoABAAaCAAfCgAEACAKAAQAIQgAIgoABAAjCgAk" + + "ACULACYAJwgAKAcAKQcAKgEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUB" + + "AAVzYXlIaQEAGChJTGphdmEvbGFuZy9SdW5uYWJsZTspVgEADHByaXZhdGVTYXlIaQEADVN0YWNr" + + "TWFwVGFibGUBAApTb3VyY2VGaWxlAQAOVHJhbnNmb3JtLmphdmEMABAAEQwAFgAVBwArDAAsAC0B" + + "ABdqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcgEABUhlbGxvDAAuAC8MAC4AMAEADiAtIHRyYW5zZm9y" + + "bWVkDAAxADIHADMMADQANQcANgwANwARAQAHR29vZGJ5ZQEACVRyYW5zZm9ybQEAEGphdmEvbGFu" + + "Zy9PYmplY3QBABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07" + + "AQAGYXBwZW5kAQAtKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7" + + "AQAcKEkpTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwEACHRvU3RyaW5nAQAUKClMamF2YS9sYW5n" + + "L1N0cmluZzsBABNqYXZhL2lvL1ByaW50U3RyZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0" + + "cmluZzspVgEAEmphdmEvbGFuZy9SdW5uYWJsZQEAA3J1bgAgAA4ADwAAAAAAAwAAABAAEQABABIA" + + "AAAdAAEAAQAAAAUqtwABsQAAAAEAEwAAAAYAAQAAAAEAAQAUABUAAQASAAAAIwADAAMAAAAHKhss" + + "twACsQAAAAEAEwAAAAoAAgAAAAMABgAEAAIAFgAVAAEAEgAAAJ0AAwADAAAAX7IAA7sABFm3AAUS" + + "BrYABxu2AAgSCbYAB7YACrYACxsEoAAULLkADAEAKhsEZCy3AAKnAA8bmQALKhsEZCy3AAKyAAO7" + + "AARZtwAFEg22AAcbtgAIEgm2AAe2AAq2AAuxAAAAAgATAAAAIgAIAAAABgAeAAcAIwAIACkACQA0" + + "AAoAOAALAEAADQBeAA4AFwAAAAQAAjQLAAEAGAAAAAIAGQ=="); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQBQqwVIiZvIuS8j1HDurKbXZEV62Mnug5PEBAAAcAAAAHhWNBIAAAAAAAAAACQEAAAb" + + "AAAAcAAAAAkAAADcAAAABgAAAAABAAABAAAASAEAAAoAAABQAQAAAQAAAKABAAAEAwAAwAEAAMAC" + + "AADQAgAA2AIAAOECAADoAgAA6wIAAO4CAADyAgAA9gIAAAMDAAAaAwAALgMAAEQDAABYAwAAcwMA" + + "AIcDAACXAwAAmgMAAJ8DAACjAwAAqwMAAL8DAADEAwAAzQMAANsDAADgAwAA5wMAAAQAAAAIAAAA" + + "CQAAAAoAAAALAAAADAAAAA0AAAAOAAAAEAAAAAUAAAAFAAAAAAAAAAYAAAAGAAAAqAIAAAcAAAAG" + + "AAAAsAIAABAAAAAIAAAAAAAAABEAAAAIAAAAuAIAABIAAAAIAAAAsAIAAAcAAgAVAAAAAQADAAEA" + + "AAABAAQAFwAAAAEABAAZAAAAAgAFABYAAAADAAMAAQAAAAQAAwAYAAAABgADAAEAAAAGAAEAEwAA" + + "AAYAAgATAAAABgAAABoAAAABAAAAAAAAAAMAAAAAAAAADwAAAAAAAAAQBAAAAAAAAAEAAQABAAAA" + + "8QMAAAQAAABwEAQAAAAOAAYAAwADAAAA9gMAAFQAAABiAAAAIgEGAHAQBgABABsCAwAAAG4gCAAh" + + "AAwBbiAHAEEADAEbAgAAAABuIAgAIQAMAW4QCQABAAwBbiADABAAEhAzBCsAchAFAAUA2AAE/3Aw" + + "AQADBWIAAAAiAQYAcBAGAAEAGwICAAAAbiAIACEADAFuIAcAQQAMARsCAAAAAG4gCAAhAAwBbhAJ" + + "AAEADAFuIAMAEAAOADgE3//YAAT/cDABAAMFKNgDAAMAAwAAAAgEAAAEAAAAcDABABACDgABAAAA" + + "AAAAAAEAAAAFAAAAAgAAAAAABAAOIC0gdHJhbnNmb3JtZWQABjxpbml0PgAHR29vZGJ5ZQAFSGVs" + + "bG8AAUkAAUwAAkxJAAJMTAALTFRyYW5zZm9ybTsAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwASTGph" + + "dmEvbGFuZy9PYmplY3Q7ABRMamF2YS9sYW5nL1J1bm5hYmxlOwASTGphdmEvbGFuZy9TdHJpbmc7" + + "ABlMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7ABJMamF2YS9sYW5nL1N5c3RlbTsADlRyYW5zZm9y" + + "bS5qYXZhAAFWAANWSUwAAlZMAAZhcHBlbmQAEmVtaXR0ZXI6IGphY2stNC4yNAADb3V0AAdwcmlu" + + "dGxuAAxwcml2YXRlU2F5SGkAA3J1bgAFc2F5SGkACHRvU3RyaW5nAAEABw4ABgIAAAcOASAPPDxd" + + "ASAPGS0AAwIAAAcOPAAAAAIBAICABMADAQLYAwIBkAUAAA0AAAAAAAAAAQAAAAAAAAABAAAAGwAA" + + "AHAAAAACAAAACQAAANwAAAADAAAABgAAAAABAAAEAAAAAQAAAEgBAAAFAAAACgAAAFABAAAGAAAA" + + "AQAAAKABAAABIAAAAwAAAMABAAABEAAAAwAAAKgCAAACIAAAGwAAAMACAAADIAAAAwAAAPEDAAAA" + + "IAAAAQAAABAEAAAAEAAAAQAAACQEAAA="); + + public static void main(String[] args) { + doTest(new Transform()); + } + + public static void doTest(Transform t) { + t.sayHi(2, () -> { System.out.println("Not doing anything here"); }); + t.sayHi(2, () -> { + System.out.println("transforming calling function"); + doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES); + }); + t.sayHi(2, () -> { System.out.println("Not doing anything here"); }); + } + + // Transforms the class + private static native void doCommonClassRedefinition(Class<?> target, + byte[] classfile, + byte[] dexfile); +} diff --git a/test/942-private-recursive/src/Transform.java b/test/942-private-recursive/src/Transform.java new file mode 100644 index 0000000000..7714326066 --- /dev/null +++ b/test/942-private-recursive/src/Transform.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. + */ + +class Transform { + private void privateSayHi(int recur, Runnable r) { + System.out.println("hello" + recur); + if (recur == 1) { + r.run(); + privateSayHi(recur - 1, r); + } else if (recur != 0) { + privateSayHi(recur - 1, r); + } + System.out.println("goodbye" + recur); + } + + public void sayHi(int recur, Runnable r) { + privateSayHi(recur, r); + } +} diff --git a/test/943-private-recursive-jit/build b/test/943-private-recursive-jit/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/943-private-recursive-jit/build @@ -0,0 +1,17 @@ +#!/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. + +./default-build "$@" --experimental agents diff --git a/test/943-private-recursive-jit/expected.txt b/test/943-private-recursive-jit/expected.txt new file mode 100644 index 0000000000..447f4a2245 --- /dev/null +++ b/test/943-private-recursive-jit/expected.txt @@ -0,0 +1,22 @@ +hello2 +hello1 +Not doing anything here +hello0 +goodbye0 +goodbye1 +goodbye2 +hello2 +hello1 +transforming calling function +hello0 - transformed +goodbye0 - transformed +goodbye1 +goodbye2 +hello2 - transformed +hello1 - transformed +Not doing anything here +hello0 - transformed +goodbye0 - transformed +goodbye1 - transformed +goodbye2 - transformed + diff --git a/test/943-private-recursive-jit/info.txt b/test/943-private-recursive-jit/info.txt new file mode 100644 index 0000000000..c8b892cedd --- /dev/null +++ b/test/943-private-recursive-jit/info.txt @@ -0,0 +1 @@ +Tests basic obsolete method support diff --git a/test/943-private-recursive-jit/run b/test/943-private-recursive-jit/run new file mode 100755 index 0000000000..c6e62ae6cd --- /dev/null +++ b/test/943-private-recursive-jit/run @@ -0,0 +1,17 @@ +#!/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. + +./default-run "$@" --jvmti diff --git a/test/943-private-recursive-jit/src/Main.java b/test/943-private-recursive-jit/src/Main.java new file mode 100644 index 0000000000..8fa534d997 --- /dev/null +++ b/test/943-private-recursive-jit/src/Main.java @@ -0,0 +1,171 @@ +/* + * 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.Base64; +import java.util.function.Consumer; +import java.lang.reflect.Method; + +public class Main { + static final boolean ALWAYS_PRINT = false; + + // import java.util.function.Consumer; + // class Transform { + // public void sayHi(int recur, Consumer<String> reporter, Runnable r) { + // privateSayHi(recur, reporter, r); + // } + // private void privateSayHi(int recur, Consumer<String> reporter, Runnable r) { + // reporter.accpet("hello" + recur + " - transformed"); + // if (recur == 1) { + // r.run(); + // privateSayHi(recur - 1, reporter, r); + // } else if (recur != 0) { + // privateSayHi(recur - 1, reporter, r); + // } + // reporter.accept("goodbye" + recur + " - transformed"); + // } + // } + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQANAoADgAbCgANABwHAB0KAAMAGwgAHgoAAwAfCgADACAIACEKAAMAIgsAIwAkCwAl" + + "ACYIACcHACgHACkBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAFc2F5" + + "SGkBADUoSUxqYXZhL3V0aWwvZnVuY3Rpb24vQ29uc3VtZXI7TGphdmEvbGFuZy9SdW5uYWJsZTsp" + + "VgEACVNpZ25hdHVyZQEASShJTGphdmEvdXRpbC9mdW5jdGlvbi9Db25zdW1lcjxMamF2YS9sYW5n" + + "L1N0cmluZzs+O0xqYXZhL2xhbmcvUnVubmFibGU7KVYBAAxwcml2YXRlU2F5SGkBAA1TdGFja01h" + + "cFRhYmxlAQAKU291cmNlRmlsZQEADlRyYW5zZm9ybS5qYXZhDAAPABAMABcAFAEAF2phdmEvbGFu" + + "Zy9TdHJpbmdCdWlsZGVyAQAFaGVsbG8MACoAKwwAKgAsAQAOIC0gdHJhbnNmb3JtZWQMAC0ALgcA" + + "LwwAMAAxBwAyDAAzABABAAdnb29kYnllAQAJVHJhbnNmb3JtAQAQamF2YS9sYW5nL09iamVjdAEA" + + "BmFwcGVuZAEALShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwEA" + + "HChJKUxqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcjsBAAh0b1N0cmluZwEAFCgpTGphdmEvbGFuZy9T" + + "dHJpbmc7AQAbamF2YS91dGlsL2Z1bmN0aW9uL0NvbnN1bWVyAQAGYWNjZXB0AQAVKExqYXZhL2xh" + + "bmcvT2JqZWN0OylWAQASamF2YS9sYW5nL1J1bm5hYmxlAQADcnVuACAADQAOAAAAAAADAAAADwAQ" + + "AAEAEQAAAB0AAQABAAAABSq3AAGxAAAAAQASAAAABgABAAAAAgABABMAFAACABEAAAAkAAQABAAA" + + "AAgqGywttwACsQAAAAEAEgAAAAoAAgAAAAQABwAFABUAAAACABYAAgAXABQAAgARAAAAnwAEAAQA" + + "AABhLLsAA1m3AAQSBbYABhu2AAcSCLYABrYACbkACgIAGwSgABUtuQALAQAqGwRkLC23AAKnABAb" + + "mQAMKhsEZCwttwACLLsAA1m3AAQSDLYABhu2AAcSCLYABrYACbkACgIAsQAAAAIAEgAAACIACAAA" + + "AAcAHgAIACMACQApAAoANQALADkADABCAA4AYAAPABgAAAAEAAI1DAAVAAAAAgAWAAEAGQAAAAIA" + + "Gg=="); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQCevtlr8B0kh/duuDYqXkGz/w9lMmtCCuRoBQAAcAAAAHhWNBIAAAAAAAAAALAEAAAg" + + "AAAAcAAAAAkAAADwAAAABgAAABQBAAAAAAAAAAAAAAoAAABcAQAAAQAAAKwBAACcAwAAzAEAAPYC" + + "AAAGAwAACgMAAA4DAAARAwAAGQMAAB0DAAAgAwAAIwMAACcDAAArAwAAOAMAAFcDAABrAwAAgQMA" + + "AJUDAACwAwAAzgMAAO0DAAD9AwAAAAQAAAYEAAAKBAAAEgQAABoEAAAuBAAANwQAAD4EAABMBAAA" + + "UQQAAFgEAABiBAAABgAAAAoAAAALAAAADAAAAA0AAAAOAAAADwAAABEAAAATAAAABwAAAAUAAAAA" + + "AAAACAAAAAYAAADUAgAACQAAAAYAAADcAgAAEwAAAAgAAAAAAAAAFAAAAAgAAADkAgAAFQAAAAgA" + + "AADwAgAAAQADAAQAAAABAAQAGwAAAAEABAAdAAAAAwADAAQAAAAEAAMAHAAAAAYAAwAEAAAABgAB" + + "ABcAAAAGAAIAFwAAAAYAAAAeAAAABwAFABYAAAABAAAAAAAAAAMAAAAAAAAAEgAAALQCAACeBAAA" + + "AAAAAAEAAACKBAAAAQABAAEAAABpBAAABAAAAHAQAwAAAA4ABgAEAAQAAABuBAAAUAAAACIABgBw" + + "EAUAAAAbARoAAABuIAcAEAAMAG4gBgAwAAwAGwEAAAAAbiAHABAADABuEAgAAAAMAHIgCQAEABIQ" + + "MwMpAHIQBAAFANgAA/9wQAEAAlQiAAYAcBAFAAAAGwEZAAAAbiAHABAADABuIAYAMAAMABsBAAAA" + + "AG4gBwAQAAwAbhAIAAAADAByIAkABAAOADgD4f/YAAP/cEABAAJUKNoEAAQABAAAAIEEAAAEAAAA" + + "cEABABAyDgAAAAAAAAAAAAIAAAAAAAAAAQAAAMwBAAACAAAAzAEAAAEAAAAAAAAAAQAAAAUAAAAD" + + "AAAAAAAHAAQAAAABAAAAAwAOIC0gdHJhbnNmb3JtZWQAAihJAAIpVgABPAAGPGluaXQ+AAI+OwAB" + + "SQABTAACTEkAAkxMAAtMVHJhbnNmb3JtOwAdTGRhbHZpay9hbm5vdGF0aW9uL1NpZ25hdHVyZTsA" + + "EkxqYXZhL2xhbmcvT2JqZWN0OwAUTGphdmEvbGFuZy9SdW5uYWJsZTsAEkxqYXZhL2xhbmcvU3Ry" + + "aW5nOwAZTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwAcTGphdmEvdXRpbC9mdW5jdGlvbi9Db25z" + + "dW1lcgAdTGphdmEvdXRpbC9mdW5jdGlvbi9Db25zdW1lcjsADlRyYW5zZm9ybS5qYXZhAAFWAARW" + + "SUxMAAJWTAAGYWNjZXB0AAZhcHBlbmQAEmVtaXR0ZXI6IGphY2stNC4yNAAHZ29vZGJ5ZQAFaGVs" + + "bG8ADHByaXZhdGVTYXlIaQADcnVuAAVzYXlIaQAIdG9TdHJpbmcABXZhbHVlAAIABw4ABwMAAAAH" + + "DgEeDzw8XQEeDxktAAQDAAAABw48AAICAR8cBxcBFxAXAxcOFwUXDRcCAAACAQCAgATUAwEC7AMC" + + "AZwFDwAAAAAAAAABAAAAAAAAAAEAAAAgAAAAcAAAAAIAAAAJAAAA8AAAAAMAAAAGAAAAFAEAAAUA" + + "AAAKAAAAXAEAAAYAAAABAAAArAEAAAMQAAABAAAAzAEAAAEgAAADAAAA1AEAAAYgAAABAAAAtAIA" + + "AAEQAAAEAAAA1AIAAAIgAAAgAAAA9gIAAAMgAAADAAAAaQQAAAQgAAABAAAAigQAAAAgAAABAAAA" + + "ngQAAAAQAAABAAAAsAQAAA=="); + + // A class that we can use to keep track of the output of this test. + private static class TestWatcher implements Consumer<String> { + private StringBuilder sb; + public TestWatcher() { + sb = new StringBuilder(); + } + + @Override + public void accept(String s) { + if (Main.ALWAYS_PRINT) { + System.out.println(s); + } + sb.append(s); + sb.append('\n'); + } + + public String getOutput() { + return sb.toString(); + } + + public void clear() { + sb = new StringBuilder(); + } + } + + public static void main(String[] args) { + doTest(new Transform()); + } + + private static boolean retry = false; + + public static void doTest(Transform t) { + final TestWatcher reporter = new TestWatcher(); + Method say_hi_method; + Method private_say_hi_method; + // Figure out if we can even JIT at all. + final boolean has_jit = hasJit(); + try { + say_hi_method = Transform.class.getDeclaredMethod( + "sayHi", int.class, Consumer.class, Runnable.class); + private_say_hi_method = Transform.class.getDeclaredMethod( + "privateSayHi", int.class, Consumer.class, Runnable.class); + } catch (Exception e) { + System.out.println("Unable to find methods!"); + e.printStackTrace(); + return; + } + // Makes sure the stack is the way we want it for the test and does the redefinition. It will + // set the retry boolean to true if we need to go around again due to jit code being GCd. + Runnable do_redefinition = () -> { + if (has_jit && + (Main.isInterpretedFunction(say_hi_method, true) || + Main.isInterpretedFunction(private_say_hi_method, true))) { + // Try again. We are not running the right jitted methods/cannot redefine them now. + retry = true; + } else { + // Actually do the redefinition. The stack looks good. + retry = false; + reporter.accept("transforming calling function"); + doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES); + } + }; + do { + // Run ensureJitCompiled here since it might get GCd + ensureJitCompiled(Transform.class, "sayHi"); + ensureJitCompiled(Transform.class, "privateSayHi"); + // Clear output. + reporter.clear(); + t.sayHi(2, reporter, () -> { reporter.accept("Not doing anything here"); }); + t.sayHi(2, reporter, do_redefinition); + t.sayHi(2, reporter, () -> { reporter.accept("Not doing anything here"); }); + } while(retry); + System.out.println(reporter.getOutput()); + } + + private static native boolean hasJit(); + + private static native boolean isInterpretedFunction(Method m, boolean require_deoptimizable); + + private static native void ensureJitCompiled(Class c, String name); + + // Transforms the class + private static native void doCommonClassRedefinition(Class<?> target, + byte[] classfile, + byte[] dexfile); +} diff --git a/test/943-private-recursive-jit/src/Transform.java b/test/943-private-recursive-jit/src/Transform.java new file mode 100644 index 0000000000..9ec3e42544 --- /dev/null +++ b/test/943-private-recursive-jit/src/Transform.java @@ -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. + */ + +import java.util.function.Consumer; +class Transform { + public void sayHi(int recur, Consumer<String> reporter, Runnable r) { + privateSayHi(recur, reporter, r); + } + + private void privateSayHi(int recur, Consumer<String> reporter, Runnable r) { + reporter.accept("hello" + recur); + if (recur == 1) { + r.run(); + privateSayHi(recur - 1, reporter, r); + } else if (recur != 0) { + privateSayHi(recur - 1, reporter, r); + } + reporter.accept("goodbye" + recur); + } +} diff --git a/test/944-transform-classloaders/build b/test/944-transform-classloaders/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/944-transform-classloaders/build @@ -0,0 +1,17 @@ +#!/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. + +./default-build "$@" --experimental agents diff --git a/test/944-transform-classloaders/classloader.cc b/test/944-transform-classloaders/classloader.cc new file mode 100644 index 0000000000..5fbd8e11c9 --- /dev/null +++ b/test/944-transform-classloaders/classloader.cc @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017 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/macros.h" +#include "jni.h" +#include "mirror/class-inl.h" +#include "openjdkjvmti/jvmti.h" +#include "ScopedLocalRef.h" + +#include "ti-agent/common_helper.h" +#include "ti-agent/common_load.h" + +namespace art { +namespace Test944TransformClassloaders { + + +extern "C" JNIEXPORT jlong JNICALL Java_Main_getDexFilePointer(JNIEnv* env, jclass, jclass klass) { + if (Runtime::Current() == nullptr) { + env->ThrowNew(env->FindClass("java/lang/Exception"), + "We do not seem to be running in ART! Unable to get dex file."); + return 0; + } + ScopedObjectAccess soa(env); + // This sequence of casts must be the same as those done in + // runtime/native/dalvik_system_DexFile.cc in order to ensure that we get the same results. + return static_cast<jlong>(reinterpret_cast<uintptr_t>( + &soa.Decode<mirror::Class>(klass)->GetDexFile())); +} + +} // namespace Test944TransformClassloaders +} // namespace art diff --git a/test/944-transform-classloaders/expected.txt b/test/944-transform-classloaders/expected.txt new file mode 100644 index 0000000000..79522479dd --- /dev/null +++ b/test/944-transform-classloaders/expected.txt @@ -0,0 +1,5 @@ +hello +hello2 +Goodbye +Goodbye2 +Passed diff --git a/test/944-transform-classloaders/info.txt b/test/944-transform-classloaders/info.txt new file mode 100644 index 0000000000..9155564d62 --- /dev/null +++ b/test/944-transform-classloaders/info.txt @@ -0,0 +1,7 @@ +Tests that redefined dex files are stored in the appropriate classloader. + +This test cannot run on the RI. + +We use reflection with setAccessible(true) to examine the private internals of +classloaders. Changes to the internal operation or definition of +dalvik.system.BaseDexClassLoader might cause this test to fail. diff --git a/test/944-transform-classloaders/run b/test/944-transform-classloaders/run new file mode 100755 index 0000000000..c6e62ae6cd --- /dev/null +++ b/test/944-transform-classloaders/run @@ -0,0 +1,17 @@ +#!/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. + +./default-run "$@" --jvmti diff --git a/test/944-transform-classloaders/src/CommonClassDefinition.java b/test/944-transform-classloaders/src/CommonClassDefinition.java new file mode 100644 index 0000000000..62602a02e9 --- /dev/null +++ b/test/944-transform-classloaders/src/CommonClassDefinition.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017 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 CommonClassDefinition { + public final Class<?> target; + public final byte[] class_file_bytes; + public final byte[] dex_file_bytes; + + CommonClassDefinition(Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) { + this.target = target; + this.class_file_bytes = class_file_bytes; + this.dex_file_bytes = dex_file_bytes; + } +} diff --git a/test/944-transform-classloaders/src/Main.java b/test/944-transform-classloaders/src/Main.java new file mode 100644 index 0000000000..4911e00a70 --- /dev/null +++ b/test/944-transform-classloaders/src/Main.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2017 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; +import java.util.ArrayList; +import java.util.Base64; +import java.lang.reflect.*; +public class Main { + + /** + * base64 encoded class/dex file for + * class Transform { + * public void sayHi() { + * System.out.println("Goodbye"); + * } + * } + */ + private static CommonClassDefinition TRANSFORM_DEFINITION = new CommonClassDefinition( + Transform.class, + Base64.getDecoder().decode( + "yv66vgAAADQAHAoABgAOCQAPABAIABEKABIAEwcAFAcAFQEABjxpbml0PgEAAygpVgEABENvZGUB" + + "AA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEAClNvdXJjZUZpbGUBAA5UcmFuc2Zvcm0uamF2YQwA" + + "BwAIBwAWDAAXABgBAAdHb29kYnllBwAZDAAaABsBAAlUcmFuc2Zvcm0BABBqYXZhL2xhbmcvT2Jq" + + "ZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2ph" + + "dmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACAABQAG" + + "AAAAAAACAAAABwAIAAEACQAAAB0AAQABAAAABSq3AAGxAAAAAQAKAAAABgABAAAAEQABAAsACAAB" + + "AAkAAAAlAAIAAQAAAAmyAAISA7YABLEAAAABAAoAAAAKAAIAAAATAAgAFAABAAwAAAACAA0="), + Base64.getDecoder().decode( + "ZGV4CjAzNQCLXSBQ5FiS3f16krSYZFF8xYZtFVp0GRXMAgAAcAAAAHhWNBIAAAAAAAAAACwCAAAO" + + "AAAAcAAAAAYAAACoAAAAAgAAAMAAAAABAAAA2AAAAAQAAADgAAAAAQAAAAABAACsAQAAIAEAAGIB" + + "AABqAQAAcwEAAIABAACXAQAAqwEAAL8BAADTAQAA4wEAAOYBAADqAQAA/gEAAAMCAAAMAgAAAgAA" + + "AAMAAAAEAAAABQAAAAYAAAAIAAAACAAAAAUAAAAAAAAACQAAAAUAAABcAQAABAABAAsAAAAAAAAA" + + "AAAAAAAAAAANAAAAAQABAAwAAAACAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAHAAAAAAAAAB4CAAAA" + + "AAAAAQABAAEAAAATAgAABAAAAHAQAwAAAA4AAwABAAIAAAAYAgAACQAAAGIAAAAbAQEAAABuIAIA" + + "EAAOAAAAAQAAAAMABjxpbml0PgAHR29vZGJ5ZQALTFRyYW5zZm9ybTsAFUxqYXZhL2lvL1ByaW50" + + "U3RyZWFtOwASTGphdmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xh" + + "bmcvU3lzdGVtOwAOVHJhbnNmb3JtLmphdmEAAVYAAlZMABJlbWl0dGVyOiBqYWNrLTMuMzYAA291" + + "dAAHcHJpbnRsbgAFc2F5SGkAEQAHDgATAAcOhQAAAAEBAICABKACAQG4Ag0AAAAAAAAAAQAAAAAA" + + "AAABAAAADgAAAHAAAAACAAAABgAAAKgAAAADAAAAAgAAAMAAAAAEAAAAAQAAANgAAAAFAAAABAAA" + + "AOAAAAAGAAAAAQAAAAABAAABIAAAAgAAACABAAABEAAAAQAAAFwBAAACIAAADgAAAGIBAAADIAAA" + + "AgAAABMCAAAAIAAAAQAAAB4CAAAAEAAAAQAAACwCAAA=")); + + /** + * base64 encoded class/dex file for + * class Transform2 { + * public void sayHi() { + * System.out.println("Goodbye2"); + * } + * } + */ + private static CommonClassDefinition TRANSFORM2_DEFINITION = new CommonClassDefinition( + Transform2.class, + Base64.getDecoder().decode( + "yv66vgAAADQAHAoABgAOCQAPABAIABEKABIAEwcAFAcAFQEABjxpbml0PgEAAygpVgEABENvZGUB" + + "AA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEAClNvdXJjZUZpbGUBAA9UcmFuc2Zvcm0yLmphdmEM" + + "AAcACAcAFgwAFwAYAQAIR29vZGJ5ZTIHABkMABoAGwEAClRyYW5zZm9ybTIBABBqYXZhL2xhbmcv" + + "T2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEA" + + "E2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACAA" + + "BQAGAAAAAAACAAAABwAIAAEACQAAAB0AAQABAAAABSq3AAGxAAAAAQAKAAAABgABAAAAAQABAAsA" + + "CAABAAkAAAAlAAIAAQAAAAmyAAISA7YABLEAAAABAAoAAAAKAAIAAAADAAgABAABAAwAAAACAA0="), + Base64.getDecoder().decode( + "ZGV4CjAzNQABX6vL8OT7aGLjbzFBEfCM9Aaz+zzGzVnQAgAAcAAAAHhWNBIAAAAAAAAAADACAAAO" + + "AAAAcAAAAAYAAACoAAAAAgAAAMAAAAABAAAA2AAAAAQAAADgAAAAAQAAAAABAACwAQAAIAEAAGIB" + + "AABqAQAAdAEAAIIBAACZAQAArQEAAMEBAADVAQAA5gEAAOkBAADtAQAAAQIAAAYCAAAPAgAAAgAA" + + "AAMAAAAEAAAABQAAAAYAAAAIAAAACAAAAAUAAAAAAAAACQAAAAUAAABcAQAABAABAAsAAAAAAAAA" + + "AAAAAAAAAAANAAAAAQABAAwAAAACAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAHAAAAAAAAACECAAAA" + + "AAAAAQABAAEAAAAWAgAABAAAAHAQAwAAAA4AAwABAAIAAAAbAgAACQAAAGIAAAAbAQEAAABuIAIA" + + "EAAOAAAAAQAAAAMABjxpbml0PgAIR29vZGJ5ZTIADExUcmFuc2Zvcm0yOwAVTGphdmEvaW8vUHJp" + + "bnRTdHJlYW07ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZhL2xhbmcvU3RyaW5nOwASTGphdmEv" + + "bGFuZy9TeXN0ZW07AA9UcmFuc2Zvcm0yLmphdmEAAVYAAlZMABJlbWl0dGVyOiBqYWNrLTQuMjQA" + + "A291dAAHcHJpbnRsbgAFc2F5SGkAAQAHDgADAAcOhwAAAAEBAICABKACAQG4AgANAAAAAAAAAAEA" + + "AAAAAAAAAQAAAA4AAABwAAAAAgAAAAYAAACoAAAAAwAAAAIAAADAAAAABAAAAAEAAADYAAAABQAA" + + "AAQAAADgAAAABgAAAAEAAAAAAQAAASAAAAIAAAAgAQAAARAAAAEAAABcAQAAAiAAAA4AAABiAQAA" + + "AyAAAAIAAAAWAgAAACAAAAEAAAAhAgAAABAAAAEAAAAwAgAA")); + + public static void main(String[] args) throws Exception { + doTest(); + System.out.println("Passed"); + } + + private static void checkIsInstance(Class<?> klass, Object o) throws Exception { + if (!klass.isInstance(o)) { + throw new Exception(klass + " is not the class of " + o); + } + } + + private static boolean arrayContains(long[] arr, long value) { + if (arr == null) { + return false; + } + for (int i = 0; i < arr.length; i++) { + if (arr[i] == value) { + return true; + } + } + return false; + } + + /** + * Checks that we can find the dex-file for the given class in its classloader. + * + * Throws if it fails. + */ + private static void checkDexFileInClassLoader(Class<?> klass) throws Exception { + // If all the android BCP classes were availible when compiling this test and access checks + // weren't a thing this function would be written as follows: + // + // long dexFilePtr = getDexFilePointer(klass); + // dalvik.system.BaseDexClassLoader loader = + // (dalvik.system.BaseDexClassLoader)klass.getClassLoader(); + // dalvik.system.DexPathList pathListValue = loader.pathList; + // dalvik.system.DexPathList.Element[] elementArrayValue = pathListValue.dexElements; + // int array_length = elementArrayValue.length; + // for (int i = 0; i < array_length; i++) { + // dalvik.system.DexPathList.Element curElement = elementArrayValue[i]; + // dalvik.system.DexFile curDexFile = curElement.dexFile; + // if (curDexFile == null) { + // continue; + // } + // long[] curCookie = (long[])curDexFile.mCookie; + // long[] curInternalCookie = (long[])curDexFile.mInternalCookie; + // if (arrayContains(curCookie, dexFilePtr) || arrayContains(curInternalCookie, dexFilePtr)) { + // return; + // } + // } + // throw new Exception( + // "Unable to find dex file pointer " + dexFilePtr + " in class loader for " + klass); + + // Get all the fields and classes we need by reflection. + Class<?> baseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader"); + Field pathListField = baseDexClassLoaderClass.getDeclaredField("pathList"); + + Class<?> dexPathListClass = Class.forName("dalvik.system.DexPathList"); + Field elementArrayField = dexPathListClass.getDeclaredField("dexElements"); + + Class<?> dexPathListElementClass = Class.forName("dalvik.system.DexPathList$Element"); + Field dexFileField = dexPathListElementClass.getDeclaredField("dexFile"); + + Class<?> dexFileClass = Class.forName("dalvik.system.DexFile"); + Field dexFileCookieField = dexFileClass.getDeclaredField("mCookie"); + Field dexFileInternalCookieField = dexFileClass.getDeclaredField("mInternalCookie"); + + // Make all the fields accessible + AccessibleObject.setAccessible(new AccessibleObject[] { pathListField, + elementArrayField, + dexFileField, + dexFileCookieField, + dexFileInternalCookieField }, true); + + long dexFilePtr = getDexFilePointer(klass); + + ClassLoader loader = klass.getClassLoader(); + checkIsInstance(baseDexClassLoaderClass, loader); + // DexPathList pathListValue = ((BaseDexClassLoader) loader).pathList; + Object pathListValue = pathListField.get(loader); + + checkIsInstance(dexPathListClass, pathListValue); + + // DexPathList.Element[] elementArrayValue = pathListValue.dexElements; + Object elementArrayValue = elementArrayField.get(pathListValue); + if (!elementArrayValue.getClass().isArray() || + elementArrayValue.getClass().getComponentType() != dexPathListElementClass) { + throw new Exception("elementArrayValue is not an " + dexPathListElementClass + " array!"); + } + // int array_length = elementArrayValue.length; + int array_length = Array.getLength(elementArrayValue); + for (int i = 0; i < array_length; i++) { + // DexPathList.Element curElement = elementArrayValue[i]; + Object curElement = Array.get(elementArrayValue, i); + checkIsInstance(dexPathListElementClass, curElement); + + // DexFile curDexFile = curElement.dexFile; + Object curDexFile = dexFileField.get(curElement); + if (curDexFile == null) { + continue; + } + checkIsInstance(dexFileClass, curDexFile); + + // long[] curCookie = (long[])curDexFile.mCookie; + long[] curCookie = (long[])dexFileCookieField.get(curDexFile); + // long[] curInternalCookie = (long[])curDexFile.mInternalCookie; + long[] curInternalCookie = (long[])dexFileInternalCookieField.get(curDexFile); + + if (arrayContains(curCookie, dexFilePtr) || arrayContains(curInternalCookie, dexFilePtr)) { + return; + } + } + throw new Exception( + "Unable to find dex file pointer " + dexFilePtr + " in class loader for " + klass); + } + + private static void doTest() throws Exception { + Transform t = new Transform(); + Transform2 t2 = new Transform2(); + + long initial_t1_dex = getDexFilePointer(Transform.class); + long initial_t2_dex = getDexFilePointer(Transform2.class); + if (initial_t2_dex != initial_t1_dex) { + throw new Exception("The classes " + Transform.class + " and " + Transform2.class + " " + + "have different initial dex files!"); + } + checkDexFileInClassLoader(Transform.class); + checkDexFileInClassLoader(Transform2.class); + + // Make sure they are loaded + t.sayHi(); + t2.sayHi(); + // Redefine both of the classes. + doMultiClassRedefinition(TRANSFORM_DEFINITION, TRANSFORM2_DEFINITION); + // Make sure we actually transformed them! + t.sayHi(); + t2.sayHi(); + + long final_t1_dex = getDexFilePointer(Transform.class); + long final_t2_dex = getDexFilePointer(Transform2.class); + if (final_t2_dex == final_t1_dex) { + throw new Exception("The classes " + Transform.class + " and " + Transform2.class + " " + + "have the same initial dex files!"); + } else if (final_t1_dex == initial_t1_dex) { + throw new Exception("The class " + Transform.class + " did not get a new dex file!"); + } else if (final_t2_dex == initial_t2_dex) { + throw new Exception("The class " + Transform2.class + " did not get a new dex file!"); + } + // Check to make sure the new dex files are in the class loader. + checkDexFileInClassLoader(Transform.class); + checkDexFileInClassLoader(Transform2.class); + } + + private static void doMultiClassRedefinition(CommonClassDefinition... defs) { + ArrayList<Class<?>> classes = new ArrayList<>(); + ArrayList<byte[]> class_files = new ArrayList<>(); + ArrayList<byte[]> dex_files = new ArrayList<>(); + + for (CommonClassDefinition d : defs) { + classes.add(d.target); + class_files.add(d.class_file_bytes); + dex_files.add(d.dex_file_bytes); + } + doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]), + class_files.toArray(new byte[0][]), + dex_files.toArray(new byte[0][])); + } + + // Gets the 'long' (really a native pointer) that is stored in the ClassLoader representing the + // DexFile a class is loaded from. This is converted from the DexFile* in the same way it is done + // in runtime/native/dalvik_system_DexFile.cc + private static native long getDexFilePointer(Class<?> target); + // Transforms the classes + private static native void doCommonMultiClassRedefinition(Class<?>[] targets, + byte[][] classfiles, + byte[][] dexfiles); +} diff --git a/test/944-transform-classloaders/src/Transform.java b/test/944-transform-classloaders/src/Transform.java new file mode 100644 index 0000000000..8e8af355da --- /dev/null +++ b/test/944-transform-classloaders/src/Transform.java @@ -0,0 +1,28 @@ +/* + * 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 Transform { + public void sayHi() { + // Use lower 'h' to make sure the string will have a different string id + // than the transformation (the transformation code is the same except + // the actual printed String, which was making the test inacurately passing + // in JIT mode when loading the string from the dex cache, as the string ids + // of the two different strings were the same). + // We know the string ids will be different because lexicographically: + // "Goodbye" < "LTransform;" < "hello". + System.out.println("hello"); + } +} diff --git a/test/944-transform-classloaders/src/Transform2.java b/test/944-transform-classloaders/src/Transform2.java new file mode 100644 index 0000000000..eb22842184 --- /dev/null +++ b/test/944-transform-classloaders/src/Transform2.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. + */ + +class Transform2 { + public void sayHi() { + System.out.println("hello2"); + } +} diff --git a/test/956-methodhandles/src/Main.java b/test/956-methodhandles/src/Main.java index f8daba6239..fc9f030559 100644 --- a/test/956-methodhandles/src/Main.java +++ b/test/956-methodhandles/src/Main.java @@ -15,6 +15,7 @@ */ import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandleInfo; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; import java.lang.invoke.MethodType; @@ -77,6 +78,7 @@ public class Main { testReturnValueConversions(); testVariableArity(); testVariableArity_MethodHandles_bind(); + testRevealDirect(); } public static void testfindSpecial_invokeSuperBehaviour() throws Throwable { @@ -384,6 +386,10 @@ public class Main { public String publicMethod() { return "publicMethod"; } + + public String publicVarArgsMethod(String... args) { + return "publicVarArgsMethod"; + } } public static void testUnreflects() throws Throwable { @@ -670,6 +676,13 @@ public class Main { Integer.class, MethodType.methodType(Integer.class, Integer.class)); fail("Unexpected success for non-void type for findConstructor"); } catch (NoSuchMethodException e) {} + + // Array class constructor. + try { + MethodHandle foo = MethodHandles.lookup().findConstructor( + Object[].class, MethodType.methodType(void.class)); + fail("Unexpected success for array class type for findConstructor"); + } catch (NoSuchMethodException e) {} } public static void testStringConstructors() throws Throwable { @@ -1486,4 +1499,117 @@ public class Main { fail(); } catch (WrongMethodTypeException e) {} } + + public static void testRevealDirect() throws Throwable { + // Test with a virtual method : + MethodType type = MethodType.methodType(String.class); + MethodHandle handle = MethodHandles.lookup().findVirtual( + UnreflectTester.class, "publicMethod", type); + + // Comparisons with an equivalent member obtained via reflection : + MethodHandleInfo info = MethodHandles.lookup().revealDirect(handle); + Method meth = UnreflectTester.class.getMethod("publicMethod"); + + assertEquals(MethodHandleInfo.REF_invokeVirtual, info.getReferenceKind()); + assertEquals("publicMethod", info.getName()); + assertTrue(UnreflectTester.class == info.getDeclaringClass()); + assertFalse(info.isVarArgs()); + assertEquals(meth, info.reflectAs(Method.class, MethodHandles.lookup())); + assertEquals(type, info.getMethodType()); + + // Resolution via a public lookup should fail because the method in question + // isn't public. + try { + info.reflectAs(Method.class, MethodHandles.publicLookup()); + fail(); + } catch (IllegalArgumentException expected) { + } + + // Test with a static method : + handle = MethodHandles.lookup().findStatic(UnreflectTester.class, + "publicStaticMethod", + MethodType.methodType(String.class)); + + info = MethodHandles.lookup().revealDirect(handle); + meth = UnreflectTester.class.getMethod("publicStaticMethod"); + assertEquals(MethodHandleInfo.REF_invokeStatic, info.getReferenceKind()); + assertEquals("publicStaticMethod", info.getName()); + assertTrue(UnreflectTester.class == info.getDeclaringClass()); + assertFalse(info.isVarArgs()); + assertEquals(meth, info.reflectAs(Method.class, MethodHandles.lookup())); + assertEquals(type, info.getMethodType()); + + // Test with a var-args method : + type = MethodType.methodType(String.class, String[].class); + handle = MethodHandles.lookup().findVirtual(UnreflectTester.class, + "publicVarArgsMethod", type); + + info = MethodHandles.lookup().revealDirect(handle); + meth = UnreflectTester.class.getMethod("publicVarArgsMethod", String[].class); + assertEquals(MethodHandleInfo.REF_invokeVirtual, info.getReferenceKind()); + assertEquals("publicVarArgsMethod", info.getName()); + assertTrue(UnreflectTester.class == info.getDeclaringClass()); + assertTrue(info.isVarArgs()); + assertEquals(meth, info.reflectAs(Method.class, MethodHandles.lookup())); + assertEquals(type, info.getMethodType()); + + // Test with a constructor : + Constructor cons = UnreflectTester.class.getConstructor(String.class, boolean.class); + type = MethodType.methodType(void.class, String.class, boolean.class); + handle = MethodHandles.lookup().findConstructor(UnreflectTester.class, type); + + info = MethodHandles.lookup().revealDirect(handle); + assertEquals(MethodHandleInfo.REF_newInvokeSpecial, info.getReferenceKind()); + assertEquals("<init>", info.getName()); + assertTrue(UnreflectTester.class == info.getDeclaringClass()); + assertFalse(info.isVarArgs()); + assertEquals(cons, info.reflectAs(Constructor.class, MethodHandles.lookup())); + assertEquals(type, info.getMethodType()); + + // Test with a static field : + Field field = UnreflectTester.class.getField("publicStaticField"); + + handle = MethodHandles.lookup().findStaticSetter( + UnreflectTester.class, "publicStaticField", String.class); + + info = MethodHandles.lookup().revealDirect(handle); + assertEquals(MethodHandleInfo.REF_putStatic, info.getReferenceKind()); + assertEquals("publicStaticField", info.getName()); + assertTrue(UnreflectTester.class == info.getDeclaringClass()); + assertFalse(info.isVarArgs()); + assertEquals(field, info.reflectAs(Field.class, MethodHandles.lookup())); + assertEquals(MethodType.methodType(void.class, String.class), info.getMethodType()); + + // Test with a setter on the same field, the type of the handle should change + // but everything else must remain the same. + handle = MethodHandles.lookup().findStaticGetter( + UnreflectTester.class, "publicStaticField", String.class); + info = MethodHandles.lookup().revealDirect(handle); + assertEquals(MethodHandleInfo.REF_getStatic, info.getReferenceKind()); + assertEquals(field, info.reflectAs(Field.class, MethodHandles.lookup())); + assertEquals(MethodType.methodType(String.class), info.getMethodType()); + + // Test with an instance field : + field = UnreflectTester.class.getField("publicField"); + + handle = MethodHandles.lookup().findSetter( + UnreflectTester.class, "publicField", String.class); + + info = MethodHandles.lookup().revealDirect(handle); + assertEquals(MethodHandleInfo.REF_putField, info.getReferenceKind()); + assertEquals("publicField", info.getName()); + assertTrue(UnreflectTester.class == info.getDeclaringClass()); + assertFalse(info.isVarArgs()); + assertEquals(field, info.reflectAs(Field.class, MethodHandles.lookup())); + assertEquals(MethodType.methodType(void.class, String.class), info.getMethodType()); + + // Test with a setter on the same field, the type of the handle should change + // but everything else must remain the same. + handle = MethodHandles.lookup().findGetter( + UnreflectTester.class, "publicField", String.class); + info = MethodHandles.lookup().revealDirect(handle); + assertEquals(MethodHandleInfo.REF_getField, info.getReferenceKind()); + assertEquals(field, info.reflectAs(Field.class, MethodHandles.lookup())); + assertEquals(MethodType.methodType(String.class), info.getMethodType()); + } } diff --git a/test/957-methodhandle-transforms/expected.txt b/test/957-methodhandle-transforms/expected.txt index 154051f847..cf6b5a14b5 100644 --- a/test/957-methodhandle-transforms/expected.txt +++ b/test/957-methodhandle-transforms/expected.txt @@ -47,3 +47,32 @@ a: a, b:1.0, c: 2.0, d: 3.0 a: a, b:1.0, c: 2.0, d: 3.0 a: a, b:1.0, c: 2.0 a: a, b:1.0, c: 2.0 +a: a, b:b, c: c +a: a, b:b, c: c +a: a, b:43 +a: a, b:b, c: c +a: a, b:true, c: false +a: a, b:1, c: 2 +a: a, b:a, c: b +a: a, b:3, c: 4 +a: a, b:42, c: 43 +a: a, b:100, c: 99 +a: a, b:8.9, c: 9.1 +a: a, b:6.7, c: 7.8 +a: a, b: b, c:c, d:d +a: a, b: b, c:c, d:d +a: a, b: b, c:c, d:d +a: a+b, b: c, c: d +a: a, b: b+c, c: d +a: a, b: b, c: c+d +voidFilter +a: a, b: b, c: c +voidFilter +a: a, b: b, c: c +a: foo, b:45, c:56, d:bar +a: foo, b:56, c:57, d:bar +a: foo, b:56, c:57, d:bar +a: foo, b:45, c:46, d:bar +a: c+d ,b:c ,c:d ,d:e +c+d +a: a ,b:c ,c:d ,d:e diff --git a/test/957-methodhandle-transforms/src/Main.java b/test/957-methodhandle-transforms/src/Main.java index 3271108995..b6bbe74b9c 100644 --- a/test/957-methodhandle-transforms/src/Main.java +++ b/test/957-methodhandle-transforms/src/Main.java @@ -36,6 +36,12 @@ public class Main { testInvokers(); testSpreaders_reference(); testSpreaders_primitive(); + testInvokeWithArguments(); + testAsCollector(); + testFilterArguments(); + testCollectArguments(); + testInsertArguments(); + testFoldArguments(); } public static void testThrowException() throws Throwable { @@ -1223,6 +1229,418 @@ public class Main { assertEquals(51, ret); } + public static void testInvokeWithArguments() throws Throwable { + MethodType methodType = MethodType.methodType(int.class, + new Class<?>[] { String.class, String.class, String.class }); + MethodHandle handle = MethodHandles.lookup().findStatic( + Main.class, "spreadReferences", methodType); + + Object ret = handle.invokeWithArguments(new Object[] { "a", "b", "c"}); + assertEquals(42, (int) ret); + handle.invokeWithArguments(new String[] { "a", "b", "c" }); + assertEquals(42, (int) ret); + + // Pass in an array that's too small. Should throw an IAE. + try { + handle.invokeWithArguments(new Object[] { "a", "b" }); + fail(); + } catch (IllegalArgumentException expected) { + } catch (WrongMethodTypeException expected) { + } + + // Test implicit unboxing. + MethodType methodType2 = MethodType.methodType(int.class, + new Class<?>[] { String.class, int.class }); + MethodHandle handle2 = MethodHandles.lookup().findStatic( + Main.class, "spreadReferences_Unbox", methodType2); + + ret = (int) handle2.invokeWithArguments(new Object[] { "a", 43 }); + assertEquals(43, (int) ret); + } + + public static int collectBoolean(String a, boolean[] b) { + System.out.println("a: " + a + ", b:" + b[0] + ", c: " + b[1]); + return 44; + } + + public static int collectByte(String a, byte[] b) { + System.out.println("a: " + a + ", b:" + b[0] + ", c: " + b[1]); + return 45; + } + + public static int collectChar(String a, char[] b) { + System.out.println("a: " + a + ", b:" + b[0] + ", c: " + b[1]); + return 46; + } + + public static int collectShort(String a, short[] b) { + System.out.println("a: " + a + ", b:" + b[0] + ", c: " + b[1]); + return 47; + } + + public static int collectInt(String a, int[] b) { + System.out.println("a: " + a + ", b:" + b[0] + ", c: " + b[1]); + return 48; + } + + public static int collectLong(String a, long[] b) { + System.out.println("a: " + a + ", b:" + b[0] + ", c: " + b[1]); + return 49; + } + + public static int collectFloat(String a, float[] b) { + System.out.println("a: " + a + ", b:" + b[0] + ", c: " + b[1]); + return 50; + } + + public static int collectDouble(String a, double[] b) { + System.out.println("a: " + a + ", b:" + b[0] + ", c: " + b[1]); + return 51; + } + + public static int collectCharSequence(String a, CharSequence[] b) { + System.out.println("a: " + a + ", b:" + b[0] + ", c: " + b[1]); + return 99; + } + + public static void testAsCollector() throws Throwable { + // Reference arrays. + // ------------------- + MethodHandle trailingRef = MethodHandles.lookup().findStatic( + Main.class, "collectCharSequence", + MethodType.methodType(int.class, String.class, CharSequence[].class)); + + // int[] is not convertible to CharSequence[].class. + try { + trailingRef.asCollector(int[].class, 1); + fail(); + } catch (IllegalArgumentException expected) { + } + + // Object[] is not convertible to CharSequence[].class. + try { + trailingRef.asCollector(Object[].class, 1); + fail(); + } catch (IllegalArgumentException expected) { + } + + // String[].class is convertible to CharSequence.class + MethodHandle collector = trailingRef.asCollector(String[].class, 2); + assertEquals(99, (int) collector.invoke("a", "b", "c")); + + // Too few arguments should fail with a WMTE. + try { + collector.invoke("a", "b"); + fail(); + } catch (WrongMethodTypeException expected) { + } + + // Too many arguments should fail with a WMTE. + try { + collector.invoke("a", "b", "c", "d"); + fail(); + } catch (WrongMethodTypeException expected) { + } + + // Sanity checks on other array types. + + MethodHandle target = MethodHandles.lookup().findStatic( + Main.class, "collectBoolean", + MethodType.methodType(int.class, String.class, boolean[].class)); + assertEquals(44, (int) target.asCollector(boolean[].class, 2).invoke("a", true, false)); + + target = MethodHandles.lookup().findStatic(Main.class, "collectByte", + MethodType.methodType(int.class, String.class, byte[].class)); + assertEquals(45, (int) target.asCollector(byte[].class, 2).invoke("a", (byte) 1, (byte) 2)); + + target = MethodHandles.lookup().findStatic(Main.class, "collectChar", + MethodType.methodType(int.class, String.class, char[].class)); + assertEquals(46, (int) target.asCollector(char[].class, 2).invoke("a", 'a', 'b')); + + target = MethodHandles.lookup().findStatic(Main.class, "collectShort", + MethodType.methodType(int.class, String.class, short[].class)); + assertEquals(47, (int) target.asCollector(short[].class, 2).invoke("a", (short) 3, (short) 4)); + + target = MethodHandles.lookup().findStatic(Main.class, "collectInt", + MethodType.methodType(int.class, String.class, int[].class)); + assertEquals(48, (int) target.asCollector(int[].class, 2).invoke("a", 42, 43)); + + target = MethodHandles.lookup().findStatic(Main.class, "collectLong", + MethodType.methodType(int.class, String.class, long[].class)); + assertEquals(49, (int) target.asCollector(long[].class, 2).invoke("a", 100, 99)); + + target = MethodHandles.lookup().findStatic(Main.class, "collectFloat", + MethodType.methodType(int.class, String.class, float[].class)); + assertEquals(50, (int) target.asCollector(float[].class, 2).invoke("a", 8.9f, 9.1f)); + + target = MethodHandles.lookup().findStatic(Main.class, "collectDouble", + MethodType.methodType(int.class, String.class, double[].class)); + assertEquals(51, (int) target.asCollector(double[].class, 2).invoke("a", 6.7, 7.8)); + } + + public static String filter1(char a) { + return String.valueOf(a); + } + + public static char filter2(String b) { + return b.charAt(0); + } + + public static String badFilter1(char a, char b) { + return "bad"; + } + + public static int filterTarget(String a, char b, String c, char d) { + System.out.println("a: " + a + ", b: " + b + ", c:" + c + ", d:" + d); + return 56; + } + + public static void testFilterArguments() throws Throwable { + MethodHandle filter1 = MethodHandles.lookup().findStatic( + Main.class, "filter1", MethodType.methodType(String.class, char.class)); + MethodHandle filter2 = MethodHandles.lookup().findStatic( + Main.class, "filter2", MethodType.methodType(char.class, String.class)); + + MethodHandle target = MethodHandles.lookup().findStatic( + Main.class, "filterTarget", MethodType.methodType(int.class, + String.class, char.class, String.class, char.class)); + + // In all the cases below, the values printed will be 'a', 'b', 'c', 'd'. + + // Filter arguments [0, 1] - all other arguments are passed through + // as is. + MethodHandle adapter = MethodHandles.filterArguments( + target, 0, filter1, filter2); + assertEquals(56, (int) adapter.invokeExact('a', "bXXXX", "c", 'd')); + + // Filter arguments [1, 2]. + adapter = MethodHandles.filterArguments(target, 1, filter2, filter1); + assertEquals(56, (int) adapter.invokeExact("a", "bXXXX", 'c', 'd')); + + // Filter arguments [2, 3]. + adapter = MethodHandles.filterArguments(target, 2, filter1, filter2); + assertEquals(56, (int) adapter.invokeExact("a", 'b', 'c', "dXXXXX")); + + // Try out a few error cases : + + // The return types of the filter doesn't align with the expected argument + // type of the target. + try { + adapter = MethodHandles.filterArguments(target, 2, filter2, filter1); + fail(); + } catch (IllegalArgumentException expected) { + } + + // There are more filters than arguments. + try { + adapter = MethodHandles.filterArguments(target, 3, filter2, filter1); + fail(); + } catch (IllegalArgumentException expected) { + } + + // We pass in an obviously bogus position. + try { + adapter = MethodHandles.filterArguments(target, -1, filter2, filter1); + fail(); + } catch (ArrayIndexOutOfBoundsException expected) { + } + + // We pass in a function that has more than one argument. + MethodHandle badFilter1 = MethodHandles.lookup().findStatic( + Main.class, "badFilter1", + MethodType.methodType(String.class, char.class, char.class)); + + try { + adapter = MethodHandles.filterArguments(target, 0, badFilter1, filter2); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + static void voidFilter(char a, char b) { + System.out.println("voidFilter"); + } + + static String filter(char a, char b) { + return String.valueOf(a) + "+" + b; + } + + static char badFilter(char a, char b) { + return 0; + } + + static int target(String a, String b, String c) { + System.out.println("a: " + a + ", b: " + b + ", c: " + c); + return 57; + } + + public static void testCollectArguments() throws Throwable { + // Test non-void filters. + MethodHandle filter = MethodHandles.lookup().findStatic( + Main.class, "filter", + MethodType.methodType(String.class, char.class, char.class)); + + MethodHandle target = MethodHandles.lookup().findStatic( + Main.class, "target", + MethodType.methodType(int.class, String.class, String.class, String.class)); + + // Filter at position 0. + MethodHandle adapter = MethodHandles.collectArguments(target, 0, filter); + assertEquals(57, (int) adapter.invokeExact('a', 'b', "c", "d")); + + // Filter at position 1. + adapter = MethodHandles.collectArguments(target, 1, filter); + assertEquals(57, (int) adapter.invokeExact("a", 'b', 'c', "d")); + + // Filter at position 2. + adapter = MethodHandles.collectArguments(target, 2, filter); + assertEquals(57, (int) adapter.invokeExact("a", "b", 'c', 'd')); + + // Test void filters. Note that we're passing in one more argument + // than usual because the filter returns nothing - we have to invoke with + // the full set of filter args and the full set of target args. + filter = MethodHandles.lookup().findStatic(Main.class, "voidFilter", + MethodType.methodType(void.class, char.class, char.class)); + adapter = MethodHandles.collectArguments(target, 0, filter); + assertEquals(57, (int) adapter.invokeExact('a', 'b', "a", "b", "c")); + + adapter = MethodHandles.collectArguments(target, 1, filter); + assertEquals(57, (int) adapter.invokeExact("a", 'a', 'b', "b", "c")); + + // Test out a few failure cases. + filter = MethodHandles.lookup().findStatic( + Main.class, "filter", + MethodType.methodType(String.class, char.class, char.class)); + + // Bogus filter position. + try { + adapter = MethodHandles.collectArguments(target, 3, filter); + fail(); + } catch (IndexOutOfBoundsException expected) { + } + + // Mismatch in filter return type. + filter = MethodHandles.lookup().findStatic( + Main.class, "badFilter", + MethodType.methodType(char.class, char.class, char.class)); + try { + adapter = MethodHandles.collectArguments(target, 0, filter); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + static int insertReceiver(String a, int b, Integer c, String d) { + System.out.println("a: " + a + ", b:" + b + ", c:" + c + ", d:" + d); + return 73; + } + + public static void testInsertArguments() throws Throwable { + MethodHandle target = MethodHandles.lookup().findStatic( + Main.class, "insertReceiver", + MethodType.methodType(int.class, + String.class, int.class, Integer.class, String.class)); + + // Basic single element array inserted at position 0. + MethodHandle adapter = MethodHandles.insertArguments( + target, 0, new Object[] { "foo" }); + assertEquals(73, (int) adapter.invokeExact(45, Integer.valueOf(56), "bar")); + + // Exercise unboxing. + adapter = MethodHandles.insertArguments( + target, 1, new Object[] { Integer.valueOf(56), 57 }); + assertEquals(73, (int) adapter.invokeExact("foo", "bar")); + + // Exercise a widening conversion. + adapter = MethodHandles.insertArguments( + target, 1, new Object[] { (short) 56, Integer.valueOf(57) }); + assertEquals(73, (int) adapter.invokeExact("foo", "bar")); + + // Insert an argument at the last position. + adapter = MethodHandles.insertArguments( + target, 3, new Object[] { "bar" }); + assertEquals(73, (int) adapter.invokeExact("foo", 45, Integer.valueOf(46))); + + // Exercise a few error cases. + + // A reference type that can't be cast to another reference type. + try { + MethodHandles.insertArguments(target, 3, new Object[] { new Object() }); + fail(); + } catch (ClassCastException expected) { + } + + // A boxed type that can't be unboxed correctly. + try { + MethodHandles.insertArguments(target, 1, new Object[] { Long.valueOf(56) }); + fail(); + } catch (ClassCastException expected) { + } + } + + public static String foldFilter(char a, char b) { + return String.valueOf(a) + "+" + b; + } + + public static void voidFoldFilter(String e, char a, char b) { + System.out.println(String.valueOf(a) + "+" + b); + } + + public static int foldTarget(String a, char b, char c, String d) { + System.out.println("a: " + a + " ,b:" + b + " ,c:" + c + " ,d:" + d); + return 89; + } + + public static void mismatchedVoidFilter(Integer a) { + } + + public static Integer mismatchedNonVoidFilter(char a, char b) { + return null; + } + + public static void testFoldArguments() throws Throwable { + // Test non-void filters. + MethodHandle filter = MethodHandles.lookup().findStatic( + Main.class, "foldFilter", + MethodType.methodType(String.class, char.class, char.class)); + + MethodHandle target = MethodHandles.lookup().findStatic( + Main.class, "foldTarget", + MethodType.methodType(int.class, String.class, + char.class, char.class, String.class)); + + // Folder with a non-void type. + MethodHandle adapter = MethodHandles.foldArguments(target, filter); + assertEquals(89, (int) adapter.invokeExact('c', 'd', "e")); + + // Folder with a void type. + filter = MethodHandles.lookup().findStatic( + Main.class, "voidFoldFilter", + MethodType.methodType(void.class, String.class, char.class, char.class)); + adapter = MethodHandles.foldArguments(target, filter); + assertEquals(89, (int) adapter.invokeExact("a", 'c', 'd', "e")); + + // Test a few erroneous cases. + + filter = MethodHandles.lookup().findStatic( + Main.class, "mismatchedVoidFilter", + MethodType.methodType(void.class, Integer.class)); + try { + adapter = MethodHandles.foldArguments(target, filter); + fail(); + } catch (IllegalArgumentException expected) { + } + + filter = MethodHandles.lookup().findStatic( + Main.class, "mismatchedNonVoidFilter", + MethodType.methodType(Integer.class, char.class, char.class)); + try { + adapter = MethodHandles.foldArguments(target, filter); + fail(); + } catch (IllegalArgumentException expected) { + } + } + public static void fail() { System.out.println("FAIL"); Thread.dumpStack(); diff --git a/test/Android.bp b/test/Android.bp index 89e409223e..d3244a683a 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -173,12 +173,13 @@ art_cc_library { whole_static_libs: [ "libart-compiler-gtest", "libart-runtime-gtest", - "libgtest", + "libgtest" ], shared_libs: [ "libartd", "libartd-compiler", "libbase", + "libbacktrace" ], target: { android: { @@ -271,6 +272,8 @@ art_cc_defaults { "929-search/search.cc", "931-agent-thread/agent_thread.cc", "933-misc-events/misc_events.cc", + "936-search-onload/search_onload.cc", + "944-transform-classloaders/classloader.cc", ], shared_libs: [ "libbase", @@ -317,6 +320,7 @@ cc_defaults { "141-class-unload/jni_unload.cc", "148-multithread-gc-annotations/gc_coverage.cc", "149-suspend-all-stress/suspend_all.cc", + "154-gc-loop/heap_interface.cc", "454-get-vreg/get_vreg_jni.cc", "457-regs/regs_jni.cc", "461-get-reference-vreg/get_reference_vreg_jni.cc", diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk index 5bb39fe098..742353da46 100644 --- a/test/Android.run-test.mk +++ b/test/Android.run-test.mk @@ -136,9 +136,9 @@ ifeq ($(ART_TEST_OPTIMIZING_GRAPH_COLOR),true) COMPILER_TYPES += regalloc_gc OPTIMIZING_COMPILER_TYPES += regalloc_gc endif -RELOCATE_TYPES := relocate -ifeq ($(ART_TEST_RUN_TEST_NO_RELOCATE),true) - RELOCATE_TYPES += no-relocate +RELOCATE_TYPES := no-relocate +ifeq ($(ART_TEST_RUN_TEST_RELOCATE),true) + RELOCATE_TYPES += relocate endif ifeq ($(ART_TEST_RUN_TEST_RELOCATE_NO_PATCHOAT),true) RELOCATE_TYPES += relocate-npatchoat @@ -161,7 +161,9 @@ JNI_TYPES := checkjni ifeq ($(ART_TEST_JNI_FORCECOPY),true) JNI_TYPES += forcecopy endif +ifeq ($(ART_TEST_RUN_TEST_IMAGE),true) IMAGE_TYPES := picimage +endif ifeq ($(ART_TEST_RUN_TEST_NO_IMAGE),true) IMAGE_TYPES += no-image endif @@ -379,6 +381,7 @@ TEST_ART_BROKEN_INTERPRETER_ACCESS_CHECK_TESTS := # slows down allocations significantly which these tests do a lot. TEST_ART_BROKEN_GCSTRESS_RUN_TESTS := \ 137-cfi \ + 154-gc-loop \ 908-gc-start-finish \ 913-heaps \ 961-default-iface-resolution-gen \ @@ -436,13 +439,14 @@ TEST_ART_BROKEN_FALLBACK_RUN_TESTS := \ 629-vdex-speed # This test fails without an image. -# 018, 961, 964 often time out. b/34369284 +# 018, 961, 964, 968 often time out. b/34369284 TEST_ART_BROKEN_NO_IMAGE_RUN_TESTS := \ 137-cfi \ 138-duplicate-classes-check \ 018-stack-overflow \ 961-default-iface-resolution-gen \ - 964-default-iface-init + 964-default-iface-init \ + 968-default-partial-compile-gen \ ifneq (,$(filter no-dex2oat,$(PREBUILD_TYPES))) ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),no-dex2oat, \ @@ -604,6 +608,7 @@ TEST_ART_BROKEN_OPTIMIZING_MIPS64_RUN_TESTS := TEST_ART_BROKEN_OPTIMIZING_NONDEBUGGABLE_RUN_TESTS := \ 454-get-vreg \ 457-regs \ + 602-deoptimizeable ifneq (,$(filter $(OPTIMIZING_COMPILER_TYPES),$(COMPILER_TYPES))) ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(PREBUILD_TYPES), \ @@ -706,8 +711,10 @@ endif TEST_ART_BROKEN_OPTIMIZING_HEAP_POISONING_RUN_TESTS := -# Tests that check semantics for a non-debuggable app. +# 909: Tests that check semantics for a non-debuggable app. +# 137: relies on AOT code and debuggable makes us JIT always. TEST_ART_BROKEN_DEBUGGABLE_RUN_TESTS := \ + 137-cfi \ 909-attach-agent \ ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(PREBUILD_TYPES), \ diff --git a/test/ErroneousInit/ErroneousInit.java b/test/ErroneousInit/ErroneousInit.java new file mode 100644 index 0000000000..67b7b204dc --- /dev/null +++ b/test/ErroneousInit/ErroneousInit.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. + */ + +class ErroneousInit { + static { + if (true) { + throw new Error(); + } + } +} diff --git a/test/Nested/Nested.java b/test/Nested/Nested.java index 78b273bec0..f493989268 100644 --- a/test/Nested/Nested.java +++ b/test/Nested/Nested.java @@ -17,4 +17,6 @@ class Nested { class Inner { } + Object x = new Object() { + }; } diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar index 28fa130443..186a1513ee 100755 --- a/test/etc/run-test-jar +++ b/test/etc/run-test-jar @@ -39,7 +39,7 @@ OPTIMIZE="y" PATCHOAT="" PREBUILD="y" QUIET="n" -RELOCATE="y" +RELOCATE="n" STRIP_DEX="n" SECONDARY_DEX="" TIME_OUT="gdb" # "n" (disabled), "timeout" (use timeout), "gdb" (use gdb) @@ -344,7 +344,7 @@ if [ "$IS_JVMTI_TEST" = "y" ]; then else FLAGS="${FLAGS} -agentpath:${agent}=${TEST_NAME},art" FLAGS="${FLAGS} -Xplugin:${plugin}" - FLAGS="${FLAGS} -Xfully-deoptable" + FLAGS="${FLAGS} -Xcompiler-option --debuggable" # Always make the compilation be debuggable. COMPILE_FLAGS="${COMPILE_FLAGS} --debuggable" fi @@ -364,6 +364,8 @@ fi if [ "$HAVE_IMAGE" = "n" ]; then + # Add 5 minutes to give some time to generate the boot image. + TIME_OUT_VALUE=$((${TIME_OUT_VALUE} + 300)) DALVIKVM_BOOT_OPT="-Ximage:/system/non-existant/core.art" else DALVIKVM_BOOT_OPT="-Ximage:${BOOT_IMAGE}" diff --git a/test/run-test b/test/run-test index c78fa35e24..27c700e89e 100755 --- a/test/run-test +++ b/test/run-test @@ -111,7 +111,7 @@ target_mode="yes" dev_mode="no" update_mode="no" debug_mode="no" -relocate="yes" +relocate="no" runtime="art" usage="no" build_only="no" @@ -156,6 +156,7 @@ while true; do shift elif [ "x$1" = "x--jvm" ]; then target_mode="no" + DEX_LOCATION="$tmp_dir" runtime="jvm" image_args="" prebuild_mode="no" @@ -408,7 +409,9 @@ tmp_dir="`cd $oldwd ; python -c "import os; print os.path.realpath('$tmp_dir')"` mkdir -p $tmp_dir # Add thread suspend timeout flag -run_args="${run_args} --runtime-option -XX:ThreadSuspendTimeout=$suspend_timeout" +if [ ! "$runtime" = "jvm" ]; then + run_args="${run_args} --runtime-option -XX:ThreadSuspendTimeout=$suspend_timeout" +fi if [ "$basic_verify" = "true" ]; then # Set HspaceCompactForOOMMinIntervalMs to zero to run hspace compaction for OOM more frequently in tests. @@ -624,8 +627,8 @@ if [ "$usage" = "yes" ]; then 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)" - echo " --no-relocate Force the use of no relocating in the test" + echo " address before running." + echo " --no-relocate Force the use of no relocating in the test. (default)" echo " --image Run the test using a precompiled boot image. (default)" echo " --no-image Run the test without a precompiled boot image." echo " --host Use the host-mode virtual machine." diff --git a/test/ti-agent/common_helper.cc b/test/ti-agent/common_helper.cc index 80e1797369..ea6359e5e0 100644 --- a/test/ti-agent/common_helper.cc +++ b/test/ti-agent/common_helper.cc @@ -210,6 +210,7 @@ struct CommonTransformationResult { // Map from class name to transformation result. std::map<std::string, std::deque<CommonTransformationResult>> gTransformations; +bool gPopTransformations = true; extern "C" JNIEXPORT void JNICALL Java_Main_addCommonTransformationResult(JNIEnv* env, jclass, @@ -266,7 +267,32 @@ void JNICALL CommonClassFileLoadHookRetransformable(jvmtiEnv* jvmti_env, memcpy(new_data, desired_array.data(), desired_array.size()); *new_class_data = new_data; *new_class_data_len = desired_array.size(); + if (gPopTransformations) { + gTransformations[name_str].pop_front(); + } + } +} + +extern "C" JNIEXPORT void Java_Main_setPopRetransformations(JNIEnv*, + jclass, + jboolean enable) { + gPopTransformations = enable; +} + +extern "C" JNIEXPORT void Java_Main_popTransformationFor(JNIEnv* env, + jclass, + jstring class_name) { + const char* name_chrs = env->GetStringUTFChars(class_name, nullptr); + std::string name_str(name_chrs); + env->ReleaseStringUTFChars(class_name, name_chrs); + if (gTransformations.find(name_str) != gTransformations.end() && + gTransformations[name_str].size() > 0) { gTransformations[name_str].pop_front(); + } else { + std::stringstream err; + err << "No transformations found for class " << name_str; + std::string message = err.str(); + env->ThrowNew(env->FindClass("java/lang/Exception"), message.c_str()); } } @@ -301,11 +327,36 @@ static void DoClassRetransformation(jvmtiEnv* jvmti_env, JNIEnv* env, jobjectArr } } -// TODO Write something useful. extern "C" JNIEXPORT void JNICALL Java_Main_doCommonClassRetransformation(JNIEnv* env, jclass, jobjectArray targets) { - DoClassRetransformation(jvmti_env, env, targets); + jvmtiCapabilities caps; + jvmtiError caps_err = jvmti_env->GetCapabilities(&caps); + if (caps_err != JVMTI_ERROR_NONE) { + env->ThrowNew(env->FindClass("java/lang/Exception"), + "Unable to get current jvmtiEnv capabilities"); + return; + } + + // Allocate a new environment if we don't have the can_retransform_classes capability needed to + // call the RetransformClasses function. + jvmtiEnv* real_env = nullptr; + if (caps.can_retransform_classes != 1) { + JavaVM* vm = nullptr; + if (env->GetJavaVM(&vm) != 0 || + vm->GetEnv(reinterpret_cast<void**>(&real_env), JVMTI_VERSION_1_0) != 0) { + env->ThrowNew(env->FindClass("java/lang/Exception"), + "Unable to create temporary jvmtiEnv for RetransformClasses call."); + return; + } + SetAllCapabilities(real_env); + } else { + real_env = jvmti_env; + } + DoClassRetransformation(real_env, env, targets); + if (caps.can_retransform_classes != 1) { + real_env->DisposeEnvironment(); + } } // Get all capabilities except those related to retransformation. @@ -329,6 +380,38 @@ jint OnLoad(JavaVM* vm, } // namespace common_retransform +namespace common_transform { + +using art::common_retransform::CommonClassFileLoadHookRetransformable; + +// Get all capabilities except those related to retransformation. +jint OnLoad(JavaVM* vm, + char* options ATTRIBUTE_UNUSED, + void* reserved ATTRIBUTE_UNUSED) { + if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { + printf("Unable to get jvmti env!\n"); + return 1; + } + // Don't set the retransform caps + jvmtiCapabilities caps; + jvmti_env->GetPotentialCapabilities(&caps); + caps.can_retransform_classes = 0; + caps.can_retransform_any_class = 0; + jvmti_env->AddCapabilities(&caps); + + // Use the same callback as the retransform test. + jvmtiEventCallbacks cb; + memset(&cb, 0, sizeof(cb)); + cb.ClassFileLoadHook = CommonClassFileLoadHookRetransformable; + if (jvmti_env->SetEventCallbacks(&cb, sizeof(cb)) != JVMTI_ERROR_NONE) { + printf("Unable to set class file load hook cb!\n"); + return 1; + } + return 0; +} + +} // namespace common_transform + static void BindMethod(jvmtiEnv* jenv, JNIEnv* env, jclass klass, @@ -340,15 +423,29 @@ static void BindMethod(jvmtiEnv* jenv, LOG(FATAL) << "Could not get methods"; } - ArtMethod* m = jni::DecodeArtMethod(method); - std::string names[2]; - { + if (IsJVM()) { + // TODO Get the JNI long name + char* klass_name; + jvmtiError klass_result = jenv->GetClassSignature(klass, &klass_name, nullptr); + if (klass_result == JVMTI_ERROR_NONE) { + std::string name_str(name); + std::string klass_str(klass_name); + names[0] = GetJniShortName(klass_str, name_str); + jenv->Deallocate(reinterpret_cast<unsigned char*>(klass_name)); + } else { + LOG(FATAL) << "Could not get class name!"; + } + } else { ScopedObjectAccess soa(Thread::Current()); + ArtMethod* m = jni::DecodeArtMethod(method); names[0] = m->JniShortName(); names[1] = m->JniLongName(); } for (const std::string& mangled_name : names) { + if (mangled_name == "") { + continue; + } void* sym = dlsym(RTLD_DEFAULT, mangled_name.c_str()); if (sym == nullptr) { continue; diff --git a/test/ti-agent/common_helper.h b/test/ti-agent/common_helper.h index c60553dc5a..031850147e 100644 --- a/test/ti-agent/common_helper.h +++ b/test/ti-agent/common_helper.h @@ -27,10 +27,15 @@ namespace common_redefine { jint OnLoad(JavaVM* vm, char* options, void* reserved); } // namespace common_redefine + namespace common_retransform { jint OnLoad(JavaVM* vm, char* options, void* reserved); } // namespace common_retransform +namespace common_transform { +jint OnLoad(JavaVM* vm, char* options, void* reserved); +} // namespace common_transform + extern bool RuntimeIsJVM; diff --git a/test/ti-agent/common_load.cc b/test/ti-agent/common_load.cc index 8ed8e67e42..c5a93568c6 100644 --- a/test/ti-agent/common_load.cc +++ b/test/ti-agent/common_load.cc @@ -28,6 +28,7 @@ #include "901-hello-ti-agent/basics.h" #include "909-attach-agent/attach.h" +#include "936-search-onload/search_onload.h" namespace art { @@ -110,6 +111,17 @@ static AgentLib agents[] = { { "926-multi-obsolescence", common_redefine::OnLoad, nullptr }, { "930-hello-retransform", common_retransform::OnLoad, nullptr }, { "932-transform-saves", common_retransform::OnLoad, nullptr }, + { "934-load-transform", common_retransform::OnLoad, nullptr }, + { "935-non-retransformable", common_transform::OnLoad, nullptr }, + { "936-search-onload", Test936SearchOnload::OnLoad, nullptr }, + { "937-hello-retransform-package", common_retransform::OnLoad, nullptr }, + { "938-load-transform-bcp", common_retransform::OnLoad, nullptr }, + { "939-hello-transformation-bcp", common_redefine::OnLoad, nullptr }, + { "940-recursive-obsolete", common_redefine::OnLoad, nullptr }, + { "941-recursive-obsolete-jit", common_redefine::OnLoad, nullptr }, + { "942-private-recursive", common_redefine::OnLoad, nullptr }, + { "943-private-recursive-jit", common_redefine::OnLoad, nullptr }, + { "944-transform-classloaders", common_redefine::OnLoad, nullptr }, }; static AgentLib* FindAgent(char* name) { diff --git a/tools/buildbot-build.sh b/tools/buildbot-build.sh index 2d26b4858d..963efa49a5 100755 --- a/tools/buildbot-build.sh +++ b/tools/buildbot-build.sh @@ -52,6 +52,9 @@ while true; do shift elif [[ "$1" == "" ]]; then break + else + echo "Unknown options $@" + exit 1 fi done diff --git a/tools/cpp-define-generator/Android.bp b/tools/cpp-define-generator/Android.bp index d792e906ef..59c52117eb 100644 --- a/tools/cpp-define-generator/Android.bp +++ b/tools/cpp-define-generator/Android.bp @@ -20,7 +20,7 @@ // // In the future we may wish to parameterize this on (32,64)x(read_barrier,no_read_barrier). -art_cc_binary { +cc_binary { // Do not use art_cc_binary because HOST_PREFER_32_BIT is incompatible with genrule. name: "cpp-define-generator-data", host_supported: true, device_supported: false, @@ -34,3 +34,14 @@ art_cc_binary { "libbase", ], } + +// Note: See $OUT_DIR/soong/build.ninja +// For the exact filename that this generates to run make command on just +// this rule later. +genrule { + name: "cpp-define-generator-asm-support", + out: ["asm_support_gen.h"], + tools: ["cpp-define-generator-data"], + tool_files: ["verify-asm-support"], + cmd: "$(location verify-asm-support) --quiet \"$(location cpp-define-generator-data)\" \"$(out)\"" +} diff --git a/tools/cpp-define-generator/presubmit-check-files-up-to-date b/tools/cpp-define-generator/presubmit-check-files-up-to-date new file mode 100755 index 0000000000..67a702adc7 --- /dev/null +++ b/tools/cpp-define-generator/presubmit-check-files-up-to-date @@ -0,0 +1,67 @@ +#!/bin/bash +# +# Copyright (C) 2017 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. + +# --------------------------------------------------------------------------- + +# Generates asm_support_gen.h into a temporary location. +# Then verifies it is the same as our local stored copy. + +GEN_TOOL=cpp-define-generator-data + +if ! which "$GEN_TOOL"; then + echo "ERROR: Please build cpp-define-generator-data or source build/envsetup.sh" >&2 + exit 1 +fi + +####################### +####################### + +PREUPLOAD_COMMIT_COPY="$(mktemp ${TMPDIR:-/tmp}/tmp.XXXXXX)" +BUILD_COPY="$(mktemp ${TMPDIR:-/tmp}/tmp.XXXXXX)" + +function finish() { + # Delete temp files. + [[ -f "$PREUPLOAD_COMMIT_COPY" ]] && rm "$PREUPLOAD_COMMIT_COPY" + [[ -f "$BUILD_COPY" ]] && rm "$BUILD_COPY" +} +trap finish EXIT + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +ART_DIR="$( cd "$DIR/../.." && pwd )" +ASM_SUPPORT_GEN_CHECKED_IN_COPY="runtime/generated/asm_support_gen.h" + +# Repo upload hook runs inside of the top-level git directory. +# If we run this script manually, be in the right place for git. +cd "$ART_DIR" + +if [[ -z $PREUPLOAD_COMMIT ]]; then + echo "WARNING: Not running as a pre-upload hook. Assuming commit to check = 'HEAD'" + PREUPLOAD_COMMIT=HEAD +fi + +# Get version we are about to push into git. +git show "$PREUPLOAD_COMMIT:$ASM_SUPPORT_GEN_CHECKED_IN_COPY" > "$PREUPLOAD_COMMIT_COPY" || exit 1 +# Get version that our build would have made. +"$GEN_TOOL" > "$BUILD_COPY" || exit 1 + +if ! diff "$PREUPLOAD_COMMIT_COPY" "$BUILD_COPY"; then + echo "asm-support: ERROR: Checked-in copy of '$ASM_SUPPORT_GEN_CHECKED_IN_COPY' " >&2 + echo " has diverged from the build copy." >&2 + echo " Please re-run the 'generate-asm-support' command to resync the header." >&2 + exit 1 +fi + +# Success. Print nothing to avoid spamming users. diff --git a/tools/cpp-define-generator/verify-asm-support b/tools/cpp-define-generator/verify-asm-support new file mode 100755 index 0000000000..745b1153c9 --- /dev/null +++ b/tools/cpp-define-generator/verify-asm-support @@ -0,0 +1,101 @@ +#!/bin/bash +# +# Copyright (C) 2017 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. + +# --------------------------------------------------------------------------- + +# Generates asm_support_gen.h into the $OUT directory in the build. +# Then verifies that it is the same as in runtime/generated/asm_support_gen.h + +# Validates that art/runtime/generated/asm_support_gen.h +# - This must be run after a build since it uses cpp-define-generator-data + +# Path to asm_support_gen.h that we check into our git repository. +ASM_SUPPORT_GEN_CHECKED_IN_COPY="runtime/generated/asm_support_gen.h" +# Instead of producing an error if checked-in copy differs from the generated version, +# overwrite the local checked-in copy instead. +OVERWRITE_CHECKED_IN_COPY_IF_CHANGED="n" + +####################### +####################### + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +ART_DIR="$( cd "$DIR/../.." && pwd )" +ABS_ASM_SUPPORT_GEN_CHECKED_IN_COPY="$ART_DIR/runtime/generated/asm_support_gen.h" + +# Sanity check that we haven't moved the file around. +# If we did, perhaps the above constant should be updated. +if ! [[ -f "$ABS_ASM_SUPPORT_GEN_CHECKED_IN_COPY" ]]; then + echo "ERROR: Missing asm_support_gen.h, expected to be in '$ABS_ASM_SUPPORT_GEN_CHECKED_IN_COPY'" >&2 + exit 1 +fi + +# The absolute path to cpp-define-generator is in $1 +# Generate the file as part of the build into the out location specified by $2. + +# Compare that the generated file matches our golden copy that's checked into git. +# If not, it is a fatal error and the user needs to run 'generate-asm-support' to rebuild. + +if [[ $# -lt 2 ]]; then + echo "Usage: $0 [--quiet] [--presubmit] <path-to-cpp-define-generator-data-binary> <output-file>'" >&2 + exit 1 +fi + +# Supress 'chatty' messages during the build. +# If anything is printed in a success case then +# the main Android build can't reuse the same line for +# showing multiple commands being executed. +QUIET=false +if [[ "$1" == "--quiet" ]]; then + QUIET=true + shift +fi + +CPP_DEFINE_GENERATOR_TOOL="$1" +OUTPUT_FILE="$2" + +function pecho() { + if ! $QUIET; then + echo "$@" + fi +} + +# Generate the header. Print the command we're running to console for readability. +pecho "cpp-define-generator-data > \"$OUTPUT_FILE\"" +"$CPP_DEFINE_GENERATOR_TOOL" > "$OUTPUT_FILE" +retval="$?" + +if [[ $retval -ne 0 ]]; then + echo "verify-asm-support: FATAL: Error while running cpp-define-generator-data" >&2 + exit $retval +fi + +if ! diff "$ABS_ASM_SUPPORT_GEN_CHECKED_IN_COPY" "$OUTPUT_FILE"; then + + if [[ $OVERWRITE_CHECKED_IN_COPY_IF_CHANGED == "y" ]]; then + cp "$OUTPUT_FILE" "$ABS_ASM_SUPPORT_GEN_CHECKED_IN_COPY" + echo "verify-asm-support: OK: Overwrote '$ASM_SUPPORT_GEN_CHECKED_IN_COPY' with build copy." + echo " Please 'git add $ASM_SUPPORT_GEN_CHECKED_IN_COPY'." + else + echo "---------------------------------------------------------------------------------------------" >&2 + echo "verify-asm-support: ERROR: Checked-in copy of '$ASM_SUPPORT_GEN_CHECKED_IN_COPY' " >&2 + echo " has diverged from the build copy." >&2 + echo " Please re-run the 'generate-asm-support' command to resync the header." >&2 + [[ -f "$OUTPUT_FILE" ]] && rm "$OUTPUT_FILE" + exit 1 + fi +fi + +pecho "verify-asm-support: SUCCESS. Built '$OUTPUT_FILE' which matches our checked in copy." diff --git a/tools/dexfuzz/README b/tools/dexfuzz/README index c1cdf1e3f4..78f73f5c11 100644 --- a/tools/dexfuzz/README +++ b/tools/dexfuzz/README @@ -98,7 +98,7 @@ MutateFail - because mutation is a random process, and has attempt thresholds to Timed Out - mutated files that timed out for one or more backends. Current timeouts are: Optimizing - 5 seconds - Intepreter - 30 seconds + Interpreter - 30 seconds (use --short-timeouts to set all backends to 2 seconds.) Successful - mutated files that executed and all backends agreed on the resulting output. NB: if all backends crashed with the same output, this would diff --git a/tools/jfuzz/run_dex_fuzz_test.py b/tools/jfuzz/run_dex_fuzz_test.py index 50c4f20aa7..34a92f6ea8 100755 --- a/tools/jfuzz/run_dex_fuzz_test.py +++ b/tools/jfuzz/run_dex_fuzz_test.py @@ -19,7 +19,7 @@ import os import shutil import sys -from subprocess import check_call +from subprocess import call from tempfile import mkdtemp sys.path.append(os.path.dirname(os.path.dirname( @@ -75,6 +75,9 @@ class DexFuzzTester(object): top = GetEnvVariableOrError('ANDROID_BUILD_TOP') self._dexfuzz_env['PATH'] = (top + '/art/tools/bisection_search:' + self._dexfuzz_env['PATH']) + android_root = GetEnvVariableOrError('ANDROID_HOST_OUT') + self._dexfuzz_env['ANDROID_ROOT'] = android_root + self._dexfuzz_env['LD_LIBRARY_PATH'] = android_root + '/lib' os.chdir(self._dexfuzz_dir) os.mkdir('divergent_programs') os.mkdir('bisection_outputs') @@ -119,24 +122,30 @@ class DexFuzzTester(object): def RunDexFuzz(self): """Starts the DexFuzz testing.""" os.chdir(self._dexfuzz_dir) - dexfuzz_args = ['--inputs=' + self._inputs_dir, '--execute', - '--execute-class=Test', '--repeat=' + str(self._num_tests), - '--dump-output', '--interpreter', '--optimizing', + dexfuzz_args = ['--inputs=' + self._inputs_dir, + '--execute', + '--execute-class=Test', + '--repeat=' + str(self._num_tests), + '--dump-output', '--dump-verify', + '--interpreter', '--optimizing', '--bisection-search'] if self._device is not None: dexfuzz_args += ['--device=' + self._device, '--allarm'] else: dexfuzz_args += ['--host'] # Assume host otherwise. - check_call(['dexfuzz'] + dexfuzz_args, env=self._dexfuzz_env) - # TODO: summarize findings. + cmd = ['dexfuzz'] + dexfuzz_args + print('**** Running ****\n\n', cmd, '\n') + call(cmd, env=self._dexfuzz_env) + print('\n**** Results (report.log) ****\n') + call(['tail', '-n 24', 'report.log']) def main(): # Handle arguments. parser = argparse.ArgumentParser() - parser.add_argument('--num_tests', default=10000, + parser.add_argument('--num_tests', default=1000, type=int, help='number of tests to run') - parser.add_argument('--num_inputs', default=50, + parser.add_argument('--num_inputs', default=10, type=int, help='number of JFuzz program to generate') parser.add_argument('--device', help='target device serial number') args = parser.parse_args() diff --git a/tools/jfuzz/run_jfuzz_test_nightly.py b/tools/jfuzz/run_jfuzz_test_nightly.py index 29595f2886..a9f8365c8f 100755 --- a/tools/jfuzz/run_jfuzz_test_nightly.py +++ b/tools/jfuzz/run_jfuzz_test_nightly.py @@ -26,9 +26,6 @@ from glob import glob from tempfile import mkdtemp from tempfile import TemporaryFile -# Default arguments for run_jfuzz_test.py. -DEFAULT_ARGS = ['--num_tests=20000'] - # run_jfuzz_test.py success string. SUCCESS_STRING = 'success (no divergences)' @@ -36,17 +33,22 @@ SUCCESS_STRING = 'success (no divergences)' NOT_FOUND = -1 def main(argv): + # Set up. cwd = os.path.dirname(os.path.realpath(__file__)) - cmd = [cwd + '/run_jfuzz_test.py'] + DEFAULT_ARGS + cmd = [cwd + '/run_jfuzz_test.py'] parser = argparse.ArgumentParser() parser.add_argument('--num_proc', default=8, type=int, help='number of processes to run') # Unknown arguments are passed to run_jfuzz_test.py. (args, unknown_args) = parser.parse_known_args() + # Run processes. + cmd = cmd + unknown_args + print('\n**** Running ****\n\n', cmd, '\n') output_files = [TemporaryFile('wb+') for _ in range(args.num_proc)] processes = [] - for output_file in output_files: - processes.append(subprocess.Popen(cmd + unknown_args, stdout=output_file, + for i, output_file in enumerate(output_files): + print('Tester', i) + processes.append(subprocess.Popen(cmd, stdout=output_file, stderr=subprocess.STDOUT)) try: # Wait for processes to terminate. @@ -56,6 +58,7 @@ def main(argv): for proc in processes: proc.kill() # Output results. + print('\n**** Results ****\n') output_dirs = [] for i, output_file in enumerate(output_files): output_file.seek(0) @@ -65,20 +68,24 @@ def main(argv): directory_match = re.search(r'Directory[^:]*: ([^\n]+)\n', output_str) if directory_match: output_dirs.append(directory_match.group(1)) - print('Tester', i) if output_str.find(SUCCESS_STRING) == NOT_FOUND: - print(output_str) + print('Tester', i, output_str) else: - print(SUCCESS_STRING) + print('Tester', i, SUCCESS_STRING) # Gather divergences. global_out_dir = mkdtemp('jfuzz_nightly') - divergence_nr = 1 + divergence_nr = 0 for out_dir in output_dirs: for divergence_dir in glob(out_dir + '/divergence*/'): + divergence_nr += 1 shutil.copytree(divergence_dir, global_out_dir + '/divergence' + str(divergence_nr)) - divergence_nr += 1 - print('Global output directory:', global_out_dir) + if divergence_nr > 0: + print('\n!!!! Divergences !!!!', divergence_nr) + else: + print ('\nSuccess') + print('\nGlobal output directory:', global_out_dir) + print() if __name__ == '__main__': main(sys.argv) |