diff options
70 files changed, 2417 insertions, 695 deletions
diff --git a/Android.mk b/Android.mk index c0935a72bc..7de07a89a0 100644 --- a/Android.mk +++ b/Android.mk @@ -351,6 +351,47 @@ valgrind-test-art-target64: valgrind-test-art-target-gtest64 endif # art_test_bother +####################### +# Fake packages for ART + +# The art-runtime package depends on the core ART libraries and binaries. It exists so we can +# manipulate the set of things shipped, e.g., add debug versions and so on. + +include $(CLEAR_VARS) +LOCAL_MODULE := art-runtime + +# Base requirements. +LOCAL_REQUIRED_MODULES := \ + dalvikvm \ + dex2oat \ + dexoptanalyzer \ + libart \ + libart-compiler \ + libopenjdkjvm \ + libopenjdkjvmti \ + patchoat \ + profman \ + +# For nosy apps, we provide a fake library that avoids namespace issues and gives some warnings. +LOCAL_REQUIRED_MODULES += libart_fake + +include $(BUILD_PHONY_PACKAGE) + +# The art-tools package depends on helpers and tools that are useful for developers and on-device +# investigations. + +include $(CLEAR_VARS) +LOCAL_MODULE := art-tools +LOCAL_REQUIRED_MODULES := \ + ahat \ + dexdiag \ + dexdump \ + dexlist \ + hprof-conv \ + oatdump \ + +include $(BUILD_PHONY_PACKAGE) + #################################################################################################### # Fake packages to ensure generation of libopenjdkd when one builds with mm/mmm/mmma. # diff --git a/build/Android.bp b/build/Android.bp index 289834beb8..c5ff486709 100644 --- a/build/Android.bp +++ b/build/Android.bp @@ -59,9 +59,6 @@ art_global_defaults { "-Wunreachable-code-break", "-Wunreachable-code-return", - // Bug: http://b/29823425 Disable -Wconstant-conversion for Clang update to r271374 - "-Wno-constant-conversion", - // Enable thread annotations for std::mutex, etc. "-D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS", ], @@ -158,6 +155,10 @@ art_global_defaults { // The static analyzer treats DCHECK as always enabled; we sometimes get // false positives when we use DCHECKs with code that relies on NDEBUG. "-extra-arg=-UNDEBUG", + // clang-tidy complains about functions like: + // void foo() { CHECK(kIsFooEnabled); /* do foo... */ } + // not being marked noreturn if kIsFooEnabled is false. + "-extra-arg=-Wno-missing-noreturn", ], } diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk index 5b5c10fa5d..c87abe5664 100644 --- a/build/Android.gtest.mk +++ b/build/Android.gtest.mk @@ -106,6 +106,7 @@ ART_GTEST_dex_cache_test_DEX_DEPS := Main Packages MethodTypes ART_GTEST_dex_file_test_DEX_DEPS := GetMethodSignature Main Nested MultiDex ART_GTEST_dexlayout_test_DEX_DEPS := ManyMethods ART_GTEST_dex2oat_test_DEX_DEPS := $(ART_GTEST_dex2oat_environment_tests_DEX_DEPS) Statics VerifierDeps +ART_GTEST_dex2oat_image_test_DEX_DEPS := $(ART_GTEST_dex2oat_environment_tests_DEX_DEPS) Statics VerifierDeps ART_GTEST_exception_test_DEX_DEPS := ExceptionHandle ART_GTEST_image_test_DEX_DEPS := ImageLayoutA ImageLayoutB DefaultMethods ART_GTEST_imtable_test_DEX_DEPS := IMTA IMTB @@ -170,6 +171,11 @@ ART_GTEST_dex2oat_test_HOST_DEPS := \ ART_GTEST_dex2oat_test_TARGET_DEPS := \ $(ART_GTEST_dex2oat_environment_tests_TARGET_DEPS) +ART_GTEST_dex2oat_image_test_HOST_DEPS := \ + $(ART_GTEST_dex2oat_environment_tests_HOST_DEPS) +ART_GTEST_dex2oat_image_test_TARGET_DEPS := \ + $(ART_GTEST_dex2oat_environment_tests_TARGET_DEPS) + # TODO: document why this is needed. ART_GTEST_proxy_test_HOST_DEPS := $(HOST_CORE_IMAGE_DEFAULT_64) $(HOST_CORE_IMAGE_DEFAULT_32) @@ -664,6 +670,9 @@ ART_GTEST_image_space_test_TARGET_DEPS := ART_GTEST_dex2oat_test_DEX_DEPS := ART_GTEST_dex2oat_test_HOST_DEPS := ART_GTEST_dex2oat_test_TARGET_DEPS := +ART_GTEST_dex2oat_image_test_DEX_DEPS := +ART_GTEST_dex2oat_image_test_HOST_DEPS := +ART_GTEST_dex2oat_image_test_TARGET_DEPS := ART_GTEST_object_test_DEX_DEPS := ART_GTEST_proxy_test_DEX_DEPS := ART_GTEST_reflection_test_DEX_DEPS := diff --git a/compiler/optimizing/loop_optimization.cc b/compiler/optimizing/loop_optimization.cc index 9c8a632d40..d2493137fe 100644 --- a/compiler/optimizing/loop_optimization.cc +++ b/compiler/optimizing/loop_optimization.cc @@ -422,6 +422,23 @@ void HLoopOptimization::TraverseLoopsInnerToOuter(LoopNode* node) { // Optimization. // +bool HLoopOptimization::CanRemoveCycle() { + for (HInstruction* i : *iset_) { + // We can never remove instructions that have environment + // uses when we compile 'debuggable'. + if (i->HasEnvironmentUses() && graph_->IsDebuggable()) { + return false; + } + // A deoptimization should never have an environment input removed. + for (const HUseListNode<HEnvironment*>& use : i->GetEnvUses()) { + if (use.GetUser()->GetHolder()->IsDeoptimize()) { + return false; + } + } + } + return true; +} + void HLoopOptimization::SimplifyInduction(LoopNode* node) { HBasicBlock* header = node->loop_info->GetHeader(); HBasicBlock* preheader = node->loop_info->GetPreHeader(); @@ -435,10 +452,15 @@ void HLoopOptimization::SimplifyInduction(LoopNode* node) { iset_->clear(); // prepare phi induction if (TrySetPhiInduction(phi, /*restrict_uses*/ true) && TryAssignLastValue(node->loop_info, phi, preheader, /*collect_loop_uses*/ false)) { - for (HInstruction* i : *iset_) { - RemoveFromCycle(i); + // Note that it's ok to have replaced uses after the loop with the last value, without + // being able to remove the cycle. Environment uses (which are the reason we may not be + // able to remove the cycle) within the loop will still hold the right value. + if (CanRemoveCycle()) { + for (HInstruction* i : *iset_) { + RemoveFromCycle(i); + } + simplified_ = true; } - simplified_ = true; } } } @@ -1527,11 +1549,10 @@ bool HLoopOptimization::IsOnlyUsedAfterLoop(HLoopInformation* loop_info, return true; } -bool HLoopOptimization::TryReplaceWithLastValue(HInstruction* instruction, HBasicBlock* block) { - // Try to replace outside uses with the last value. Environment uses can consume this - // value too, since any first true use is outside the loop (although this may imply - // that de-opting may look "ahead" a bit on the phi value). If there are only environment - // uses, the value is dropped altogether, since the computations have no effect. +bool HLoopOptimization::TryReplaceWithLastValue(HLoopInformation* loop_info, + HInstruction* instruction, + HBasicBlock* block) { + // Try to replace outside uses with the last value. if (induction_range_.CanGenerateLastValue(instruction)) { HInstruction* replacement = induction_range_.GenerateLastValue(instruction, graph_, block); const HUseList<HInstruction*>& uses = instruction->GetUses(); @@ -1540,6 +1561,11 @@ bool HLoopOptimization::TryReplaceWithLastValue(HInstruction* instruction, HBasi size_t index = it->GetIndex(); ++it; // increment before replacing if (iset_->find(user) == iset_->end()) { // not excluded? + if (kIsDebugBuild) { + // We have checked earlier in 'IsOnlyUsedAfterLoop' that the use is after the loop. + HLoopInformation* other_loop_info = user->GetBlock()->GetLoopInformation(); + CHECK(other_loop_info == nullptr || !other_loop_info->IsIn(*loop_info)); + } user->ReplaceInput(replacement, index); induction_range_.Replace(user, instruction, replacement); // update induction } @@ -1550,9 +1576,13 @@ bool HLoopOptimization::TryReplaceWithLastValue(HInstruction* instruction, HBasi size_t index = it->GetIndex(); ++it; // increment before replacing if (iset_->find(user->GetHolder()) == iset_->end()) { // not excluded? - user->RemoveAsUserOfInput(index); - user->SetRawEnvAt(index, replacement); - replacement->AddEnvUseAt(user, index); + HLoopInformation* other_loop_info = user->GetHolder()->GetBlock()->GetLoopInformation(); + // Only update environment uses after the loop. + if (other_loop_info == nullptr || !other_loop_info->IsIn(*loop_info)) { + user->RemoveAsUserOfInput(index); + user->SetRawEnvAt(index, replacement); + replacement->AddEnvUseAt(user, index); + } } } induction_simplication_count_++; @@ -1571,7 +1601,7 @@ bool HLoopOptimization::TryAssignLastValue(HLoopInformation* loop_info, int32_t use_count = 0; return IsOnlyUsedAfterLoop(loop_info, instruction, collect_loop_uses, &use_count) && (use_count == 0 || - (!IsEarlyExit(loop_info) && TryReplaceWithLastValue(instruction, block))); + (!IsEarlyExit(loop_info) && TryReplaceWithLastValue(loop_info, instruction, block))); } void HLoopOptimization::RemoveDeadInstructions(const HInstructionList& list) { diff --git a/compiler/optimizing/loop_optimization.h b/compiler/optimizing/loop_optimization.h index 75a42f3297..cc6343aeb5 100644 --- a/compiler/optimizing/loop_optimization.h +++ b/compiler/optimizing/loop_optimization.h @@ -161,12 +161,15 @@ class HLoopOptimization : public HOptimization { /*out*/ int32_t* use_count); bool IsUsedOutsideLoop(HLoopInformation* loop_info, HInstruction* instruction); - bool TryReplaceWithLastValue(HInstruction* instruction, HBasicBlock* block); + bool TryReplaceWithLastValue(HLoopInformation* loop_info, + HInstruction* instruction, + HBasicBlock* block); bool TryAssignLastValue(HLoopInformation* loop_info, HInstruction* instruction, HBasicBlock* block, bool collect_loop_uses); void RemoveDeadInstructions(const HInstructionList& list); + bool CanRemoveCycle(); // Whether the current 'iset_' is removable. // Compiler driver (to query ISA features). const CompilerDriver* compiler_driver_; diff --git a/compiler/utils/arm/assembler_thumb2.cc b/compiler/utils/arm/assembler_thumb2.cc index d7096b3c87..abc36c6adc 100644 --- a/compiler/utils/arm/assembler_thumb2.cc +++ b/compiler/utils/arm/assembler_thumb2.cc @@ -324,7 +324,7 @@ void Thumb2Assembler::PatchCFI() { inline int16_t Thumb2Assembler::BEncoding16(int32_t offset, Condition cond) { DCHECK_ALIGNED(offset, 2); - int16_t encoding = B15 | B14; + int16_t encoding = static_cast<int16_t>(B15 | B14); if (cond != AL) { DCHECK(IsInt<9>(offset)); encoding |= B12 | (static_cast<int32_t>(cond) << 8) | ((offset >> 1) & 0xff); diff --git a/compiler/utils/mips/assembler_mips_test.cc b/compiler/utils/mips/assembler_mips_test.cc index c24e1b16fb..09175309f9 100644 --- a/compiler/utils/mips/assembler_mips_test.cc +++ b/compiler/utils/mips/assembler_mips_test.cc @@ -2851,7 +2851,7 @@ TEST_F(AssemblerMIPSTest, LongBranchReorder) { // Account for 5 extra instructions: ori, addu, lw, jalr, addiu. uint32_t offset_forward = (kAdduCount1 + 5) * sizeof(uint32_t); // Account for 5 extra instructions: subu, addiu, sw, nal, lui. - uint32_t offset_back = -(kAdduCount1 + 5) * sizeof(uint32_t); + uint32_t offset_back = static_cast<uint32_t>(-(kAdduCount1 + 5) * sizeof(uint32_t)); std::ostringstream oss; oss << diff --git a/dex2oat/Android.bp b/dex2oat/Android.bp index 048f36d76c..346f5a7ef5 100644 --- a/dex2oat/Android.bp +++ b/dex2oat/Android.bp @@ -138,6 +138,9 @@ art_cc_test { defaults: [ "art_gtest_defaults", ], - srcs: ["dex2oat_test.cc"], + srcs: [ + "dex2oat_test.cc", + "dex2oat_image_test.cc", + ], header_libs: ["dex2oat_headers"], } diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index f9267e2eb3..c0ab21d4a0 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -867,6 +867,15 @@ class Dex2Oat FINAL { Usage("Profile file should not be specified with both --profile-file-fd and --profile-file"); } + if (have_profile_file || have_profile_fd) { + if (compiled_classes_filename_ != nullptr || + compiled_classes_zip_filename_ != nullptr || + image_classes_filename_ != nullptr || + image_classes_zip_filename_ != nullptr) { + Usage("Profile based image creation is not supported with image or compiled classes"); + } + } + if (!parser_options->oat_symbols.empty()) { oat_unstripped_ = std::move(parser_options->oat_symbols); } @@ -1456,7 +1465,7 @@ class Dex2Oat FINAL { } void LoadClassProfileDescriptors() { - if (profile_compilation_info_ != nullptr && IsAppImage()) { + if (profile_compilation_info_ != nullptr && IsImage()) { Runtime* runtime = Runtime::Current(); CHECK(runtime != nullptr); // Filter out class path classes since we don't want to include these in the image. diff --git a/dex2oat/dex2oat_image_test.cc b/dex2oat/dex2oat_image_test.cc new file mode 100644 index 0000000000..148af0d04d --- /dev/null +++ b/dex2oat/dex2oat_image_test.cc @@ -0,0 +1,340 @@ +/* + * 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 <regex> +#include <sstream> +#include <string> +#include <vector> + +#include <sys/wait.h> +#include <unistd.h> + +#include "common_runtime_test.h" + +#include "base/logging.h" +#include "base/macros.h" +#include "base/unix_file/fd_file.h" +#include "dex_file-inl.h" +#include "jit/profile_compilation_info.h" +#include "method_reference.h" +#include "runtime.h" +#include "utils.h" + +namespace art { + +struct ImageSizes { + size_t art_size = 0; + size_t oat_size = 0; + size_t vdex_size = 0; +}; + +std::ostream& operator<<(std::ostream& os, const ImageSizes& sizes) { + os << "art=" << sizes.art_size << " oat=" << sizes.oat_size << " vdex=" << sizes.vdex_size; + return os; +} + +class Dex2oatImageTest : public CommonRuntimeTest { + public: + virtual void TearDown() OVERRIDE {} + + protected: + // Visitors take method and type references + template <typename MethodVisitor, typename ClassVisitor> + void VisitLibcoreDexes(const MethodVisitor& method_visitor, + const ClassVisitor& class_visitor, + size_t method_frequency = 1, + size_t class_frequency = 1) { + size_t method_counter = 0; + size_t class_counter = 0; + for (std::string dex : GetLibCoreDexFileNames()) { + std::vector<std::unique_ptr<const DexFile>> dex_files; + std::string error_msg; + CHECK(DexFile::Open(dex.c_str(), dex, /*verify_checksum*/ false, &error_msg, &dex_files)) + << error_msg; + for (const std::unique_ptr<const DexFile>& dex_file : dex_files) { + for (size_t i = 0; i < dex_file->NumMethodIds(); ++i) { + if (++method_counter % method_frequency == 0) { + method_visitor(MethodReference(dex_file.get(), i)); + } + } + for (size_t i = 0; i < dex_file->NumTypeIds(); ++i) { + if (++class_counter % class_frequency == 0) { + class_visitor(TypeReference(dex_file.get(), dex::TypeIndex(i))); + } + } + } + } + } + + static void WriteLine(File* file, std::string line) { + line += '\n'; + EXPECT_TRUE(file->WriteFully(&line[0], line.length())); + } + + void GenerateClasses(File* out_file, size_t frequency = 1) { + VisitLibcoreDexes(VoidFunctor(), + [out_file](TypeReference ref) { + WriteLine(out_file, + ref.dex_file->PrettyType(ref.type_index)); + }, frequency, frequency); + EXPECT_EQ(out_file->Flush(), 0); + } + + void GenerateMethods(File* out_file, size_t frequency = 1) { + VisitLibcoreDexes([out_file](MethodReference ref) { + WriteLine(out_file, + ref.dex_file->PrettyMethod(ref.dex_method_index)); + }, VoidFunctor(), frequency, frequency); + EXPECT_EQ(out_file->Flush(), 0); + } + + void AddRuntimeArg(std::vector<std::string>& args, const std::string& arg) { + args.push_back("--runtime-arg"); + args.push_back(arg); + } + + ImageSizes CompileImageAndGetSizes(const std::vector<std::string>& extra_args) { + ImageSizes ret; + ScratchFile scratch; + std::string scratch_dir = scratch.GetFilename(); + while (!scratch_dir.empty() && scratch_dir.back() != '/') { + scratch_dir.pop_back(); + } + CHECK(!scratch_dir.empty()) << "No directory " << scratch.GetFilename(); + std::string error_msg; + if (!CompileBootImage(extra_args, scratch.GetFilename(), &error_msg)) { + LOG(ERROR) << "Failed to compile image " << scratch.GetFilename() << error_msg; + } + std::string art_file = scratch.GetFilename() + ".art"; + std::string oat_file = scratch.GetFilename() + ".oat"; + std::string vdex_file = scratch.GetFilename() + ".vdex"; + ret.art_size = GetFileSizeBytes(art_file); + ret.oat_size = GetFileSizeBytes(oat_file); + ret.vdex_size = GetFileSizeBytes(vdex_file); + CHECK_GT(ret.art_size, 0u) << art_file; + CHECK_GT(ret.oat_size, 0u) << oat_file; + CHECK_GT(ret.vdex_size, 0u) << vdex_file; + scratch.Close(); + // Clear image files since we compile the image multiple times and don't want to leave any + // artifacts behind. + ClearDirectory(scratch_dir.c_str(), /*recursive*/ false); + return ret; + } + + bool CompileBootImage(const std::vector<std::string>& extra_args, + const std::string& image_file_name_prefix, + std::string* error_msg) { + Runtime* const runtime = Runtime::Current(); + std::vector<std::string> argv; + argv.push_back(runtime->GetCompilerExecutable()); + AddRuntimeArg(argv, "-Xms64m"); + AddRuntimeArg(argv, "-Xmx64m"); + std::vector<std::string> dex_files = GetLibCoreDexFileNames(); + for (const std::string& dex_file : dex_files) { + argv.push_back("--dex-file=" + dex_file); + argv.push_back("--dex-location=" + dex_file); + } + if (runtime->IsJavaDebuggable()) { + argv.push_back("--debuggable"); + } + runtime->AddCurrentRuntimeFeaturesAsDex2OatArguments(&argv); + + AddRuntimeArg(argv, "-Xverify:softfail"); + + if (!kIsTargetBuild) { + argv.push_back("--host"); + } + + ScratchFile file; + const std::string image_prefix = file.GetFilename(); + + argv.push_back("--image=" + image_file_name_prefix + ".art"); + argv.push_back("--oat-file=" + image_file_name_prefix + ".oat"); + argv.push_back("--oat-location=" + image_file_name_prefix + ".oat"); + argv.push_back("--base=0x60000000"); + + std::vector<std::string> compiler_options = runtime->GetCompilerOptions(); + argv.insert(argv.end(), compiler_options.begin(), compiler_options.end()); + + // We must set --android-root. + const char* android_root = getenv("ANDROID_ROOT"); + CHECK(android_root != nullptr); + argv.push_back("--android-root=" + std::string(android_root)); + argv.insert(argv.end(), extra_args.begin(), extra_args.end()); + + return RunDex2Oat(argv, error_msg); + } + + int RunDex2Oat(const std::vector<std::string>& args, std::string* error_msg) { + int link[2]; + + if (pipe(link) == -1) { + return false; + } + + pid_t pid = fork(); + if (pid == -1) { + return false; + } + + if (pid == 0) { + // We need dex2oat to actually log things. + setenv("ANDROID_LOG_TAGS", "*:f", 1); + dup2(link[1], STDERR_FILENO); + close(link[0]); + close(link[1]); + std::vector<const char*> c_args; + for (const std::string& str : args) { + c_args.push_back(str.c_str()); + } + c_args.push_back(nullptr); + execv(c_args[0], const_cast<char* const*>(c_args.data())); + exit(1); + UNREACHABLE(); + } else { + close(link[1]); + char buffer[128]; + memset(buffer, 0, 128); + ssize_t bytes_read = 0; + + while (TEMP_FAILURE_RETRY(bytes_read = read(link[0], buffer, 128)) > 0) { + *error_msg += std::string(buffer, bytes_read); + } + close(link[0]); + int status = -1; + if (waitpid(pid, &status, 0) != -1) { + return (status == 0); + } + return false; + } + } +}; + +TEST_F(Dex2oatImageTest, TestModesAndFilters) { + if (kIsTargetBuild) { + // This test is too slow for target builds. + return; + } + ImageSizes base_sizes = CompileImageAndGetSizes({}); + ImageSizes image_classes_sizes; + ImageSizes compiled_classes_sizes; + ImageSizes compiled_all_classes_sizes; + ImageSizes compiled_methods_sizes; + ImageSizes compiled_all_methods_sizes; + ImageSizes profile_sizes; + std::cout << "Base compile sizes " << base_sizes << std::endl; + // Test image classes + { + ScratchFile classes; + GenerateClasses(classes.GetFile(), /*frequency*/ 1u); + image_classes_sizes = CompileImageAndGetSizes( + {"--image-classes=" + classes.GetFilename()}); + classes.Close(); + std::cout << "Image classes sizes " << image_classes_sizes << std::endl; + // Putting all classes as image classes should increase art size + EXPECT_GE(image_classes_sizes.art_size, base_sizes.art_size); + // Sanity check that dex is the same size. + EXPECT_EQ(image_classes_sizes.vdex_size, base_sizes.vdex_size); + } + // Test compiled classes with all the classes. + { + ScratchFile classes; + // Only compile every even class. + GenerateClasses(classes.GetFile(), /*frequency*/ 1u); + compiled_all_classes_sizes = CompileImageAndGetSizes( + {"--compiled-classes=" + classes.GetFilename()}); + classes.Close(); + std::cout << "Compiled all classes sizes " << compiled_all_classes_sizes << std::endl; + // Check that oat size is smaller since we didn't compile everything. + EXPECT_EQ(compiled_all_classes_sizes.art_size, base_sizes.art_size); + EXPECT_EQ(compiled_all_classes_sizes.oat_size, base_sizes.oat_size); + EXPECT_EQ(compiled_all_classes_sizes.vdex_size, base_sizes.vdex_size); + } + // Test compiled classes. + { + ScratchFile classes; + // Only compile every even class. + GenerateClasses(classes.GetFile(), /*frequency*/ 2u); + compiled_classes_sizes = CompileImageAndGetSizes( + {"--image-classes=" + classes.GetFilename(), + "--compiled-classes=" + classes.GetFilename()}); + classes.Close(); + std::cout << "Compiled classes sizes " << compiled_classes_sizes << std::endl; + // Check that oat size is smaller since we didn't compile everything. + EXPECT_LT(compiled_classes_sizes.oat_size, base_sizes.oat_size); + // Art file should be smaller than image classes version since we included fewer classes in the + // list. + EXPECT_LT(compiled_classes_sizes.art_size, image_classes_sizes.art_size); + } + // Test compiled methods. + { + ScratchFile methods; + // Only compile every even class. + GenerateMethods(methods.GetFile(), /*frequency*/ 1u); + compiled_all_methods_sizes = CompileImageAndGetSizes( + {"--compiled-methods=" + methods.GetFilename()}); + methods.Close(); + std::cout << "Compiled all methods sizes " << compiled_all_methods_sizes << std::endl; + EXPECT_EQ(compiled_all_classes_sizes.art_size, base_sizes.art_size); + EXPECT_EQ(compiled_all_classes_sizes.oat_size, base_sizes.oat_size); + EXPECT_EQ(compiled_all_classes_sizes.vdex_size, base_sizes.vdex_size); + } + static size_t kMethodFrequency = 3; + static size_t kTypeFrequency = 4; + // Test compiling fewer methods and classes. + { + ScratchFile methods; + ScratchFile classes; + // Only compile every even class. + GenerateMethods(methods.GetFile(), kMethodFrequency); + GenerateClasses(classes.GetFile(), kTypeFrequency); + compiled_methods_sizes = CompileImageAndGetSizes( + {"--image-classes=" + classes.GetFilename(), + "--compiled-methods=" + methods.GetFilename()}); + methods.Close(); + classes.Close(); + std::cout << "Compiled fewer methods sizes " << compiled_methods_sizes << std::endl; + } + // Cross verify profile based image against image-classes and compiled-methods to make sure it + // matches. + { + ProfileCompilationInfo profile; + VisitLibcoreDexes([&profile](MethodReference ref) { + EXPECT_TRUE(profile.AddMethodIndex(ProfileCompilationInfo::MethodHotness::kFlagHot, ref)); + }, [&profile](TypeReference ref) { + EXPECT_TRUE(profile.AddClassesForDex(ref.dex_file, &ref.type_index, &ref.type_index + 1)); + }, kMethodFrequency, kTypeFrequency); + ScratchFile profile_file; + profile.Save(profile_file.GetFile()->Fd()); + EXPECT_EQ(profile_file.GetFile()->Flush(), 0); + profile_sizes = CompileImageAndGetSizes( + {"--profile-file=" + profile_file.GetFilename(), + "--compiler-filter=speed-profile"}); + profile_file.Close(); + std::cout << "Profile sizes " << profile_sizes << std::endl; + // Since there is some difference between profile vs image + methods due to layout, check that + // the range is within expected margins (+-5%). + const double kRatio = 0.95; + EXPECT_LE(profile_sizes.art_size * kRatio, compiled_methods_sizes.art_size); + EXPECT_LE(profile_sizes.oat_size * kRatio, compiled_methods_sizes.oat_size); + EXPECT_LE(profile_sizes.vdex_size * kRatio, compiled_methods_sizes.vdex_size); + EXPECT_GE(profile_sizes.art_size / kRatio, compiled_methods_sizes.art_size); + EXPECT_GE(profile_sizes.oat_size / kRatio, compiled_methods_sizes.oat_size); + EXPECT_GE(profile_sizes.vdex_size / kRatio, compiled_methods_sizes.vdex_size); + } +} + +} // namespace art diff --git a/imgdiag/imgdiag.cc b/imgdiag/imgdiag.cc index e3b11951e3..2763c072cb 100644 --- a/imgdiag/imgdiag.cc +++ b/imgdiag/imgdiag.cc @@ -64,61 +64,24 @@ class ImgDiagDumper { zygote_diff_pid_(zygote_diff_pid), zygote_pid_only_(false) {} - bool Dump() REQUIRES_SHARED(Locks::mutator_lock_) { + bool Init() { std::ostream& os = *os_; - os << "IMAGE LOCATION: " << image_location_ << "\n\n"; - - os << "MAGIC: " << image_header_.GetMagic() << "\n\n"; - - os << "IMAGE BEGIN: " << reinterpret_cast<void*>(image_header_.GetImageBegin()) << "\n\n"; - - PrintPidLine("IMAGE", image_diff_pid_); - os << "\n\n"; - PrintPidLine("ZYGOTE", zygote_diff_pid_); - bool ret = true; - if (image_diff_pid_ >= 0 || zygote_diff_pid_ >= 0) { - if (image_diff_pid_ < 0) { - image_diff_pid_ = zygote_diff_pid_; - zygote_diff_pid_ = -1; - zygote_pid_only_ = true; - } - ret = DumpImageDiff(); - os << "\n\n"; - } - os << std::flush; - - return ret; - } - - private: - void PrintPidLine(const std::string& kind, pid_t pid) { - if (pid < 0) { - *os_ << kind << " DIFF PID: disabled\n\n"; - } else { - *os_ << kind << " DIFF PID (" << pid << "): "; + if (image_diff_pid_ < 0 && zygote_diff_pid_ < 0) { + os << "Either --image-diff-pid or --zygote-diff-pid (or both) must be specified.\n"; + return false; } - } - static bool EndsWith(const std::string& str, const std::string& suffix) { - return str.size() >= suffix.size() && - str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; - } - - // Return suffix of the file path after the last /. (e.g. /foo/bar -> bar, bar -> bar) - static std::string BaseName(const std::string& str) { - size_t idx = str.rfind('/'); - if (idx == std::string::npos) { - return str; + // To avoid the combinations of command-line argument use cases: + // If the user invoked with only --zygote-diff-pid, shuffle that to + // image_diff_pid_, invalidate zygote_diff_pid_, and remember that + // image_diff_pid_ is now special. + if (image_diff_pid_ < 0) { + image_diff_pid_ = zygote_diff_pid_; + zygote_diff_pid_ = -1; + zygote_pid_only_ = true; } - return str.substr(idx + 1); - } - - bool DumpImageDiff() - REQUIRES_SHARED(Locks::mutator_lock_) { - std::ostream& os = *os_; - { struct stat sts; std::string proc_pid_str = @@ -130,19 +93,18 @@ class ImgDiagDumper { } // Open /proc/$pid/maps to view memory maps - auto proc_maps = std::unique_ptr<BacktraceMap>(BacktraceMap::Create(image_diff_pid_)); - if (proc_maps == nullptr) { + auto tmp_proc_maps = std::unique_ptr<BacktraceMap>(BacktraceMap::Create(image_diff_pid_)); + if (tmp_proc_maps == nullptr) { os << "Could not read backtrace maps"; return false; } bool found_boot_map = false; - backtrace_map_t boot_map = backtrace_map_t(); // Find the memory map only for boot.art - for (const backtrace_map_t& map : *proc_maps) { + for (const backtrace_map_t& map : *tmp_proc_maps) { if (EndsWith(map.name, GetImageLocationBaseName())) { if ((map.flags & PROT_WRITE) != 0) { - boot_map = map; + boot_map_ = map; found_boot_map = true; break; } @@ -157,9 +119,232 @@ class ImgDiagDumper { os << "Could not find map for " << GetImageLocationBaseName(); return false; } + // Sanity check boot_map_. + CHECK(boot_map_.end >= boot_map_.start); + boot_map_size_ = boot_map_.end - boot_map_.start; + + pointer_size_ = InstructionSetPointerSize(Runtime::Current()->GetInstructionSet()); + + // Open /proc/<image_diff_pid_>/mem and read as remote_contents_. + std::string image_file_name = + StringPrintf("/proc/%ld/mem", static_cast<long>(image_diff_pid_)); // NOLINT [runtime/int] + auto image_map_file = std::unique_ptr<File>(OS::OpenFileForReading(image_file_name.c_str())); + if (image_map_file == nullptr) { + os << "Failed to open " << image_file_name << " for reading"; + return false; + } + std::vector<uint8_t> tmp_remote_contents(boot_map_size_); + if (!image_map_file->PreadFully(&tmp_remote_contents[0], boot_map_size_, boot_map_.start)) { + os << "Could not fully read file " << image_file_name; + return false; + } + + // If zygote_diff_pid_ != -1, open /proc/<zygote_diff_pid_>/mem and read as zygote_contents_. + std::vector<uint8_t> tmp_zygote_contents; + if (zygote_diff_pid_ != -1) { + std::string zygote_file_name = + StringPrintf("/proc/%ld/mem", static_cast<long>(zygote_diff_pid_)); // NOLINT [runtime/int] + std::unique_ptr<File> zygote_map_file(OS::OpenFileForReading(zygote_file_name.c_str())); + if (zygote_map_file == nullptr) { + os << "Failed to open " << zygote_file_name << " for reading"; + return false; + } + // The boot map should be at the same address. + tmp_zygote_contents.reserve(boot_map_size_); + if (!zygote_map_file->PreadFully(&tmp_zygote_contents[0], boot_map_size_, boot_map_.start)) { + LOG(WARNING) << "Could not fully read zygote file " << zygote_file_name; + return false; + } + } + + // Open /proc/<image_diff_pid_>/pagemap. + std::string pagemap_file_name = StringPrintf( + "/proc/%ld/pagemap", static_cast<long>(image_diff_pid_)); // NOLINT [runtime/int] + auto tmp_pagemap_file = + std::unique_ptr<File>(OS::OpenFileForReading(pagemap_file_name.c_str())); + if (tmp_pagemap_file == nullptr) { + os << "Failed to open " << pagemap_file_name << " for reading: " << strerror(errno); + return false; + } + + // Not truly clean, mmap-ing boot.art again would be more pristine, but close enough + const char* clean_pagemap_file_name = "/proc/self/pagemap"; + auto tmp_clean_pagemap_file = std::unique_ptr<File>( + OS::OpenFileForReading(clean_pagemap_file_name)); + if (tmp_clean_pagemap_file == nullptr) { + os << "Failed to open " << clean_pagemap_file_name << " for reading: " << strerror(errno); + return false; + } + + auto tmp_kpageflags_file = std::unique_ptr<File>(OS::OpenFileForReading("/proc/kpageflags")); + if (tmp_kpageflags_file == nullptr) { + os << "Failed to open /proc/kpageflags for reading: " << strerror(errno); + return false; + } + + auto tmp_kpagecount_file = std::unique_ptr<File>(OS::OpenFileForReading("/proc/kpagecount")); + if (tmp_kpagecount_file == nullptr) { + os << "Failed to open /proc/kpagecount for reading:" << strerror(errno); + return false; + } + + // Commit the mappings, etc., to the object state. + proc_maps_ = std::move(tmp_proc_maps); + remote_contents_ = std::move(tmp_remote_contents); + zygote_contents_ = std::move(tmp_zygote_contents); + pagemap_file_ = std::move(*tmp_pagemap_file.release()); + clean_pagemap_file_ = std::move(*tmp_clean_pagemap_file.release()); + kpageflags_file_ = std::move(*tmp_kpageflags_file.release()); + kpagecount_file_ = std::move(*tmp_kpagecount_file.release()); + + return true; + } + + bool Dump() REQUIRES_SHARED(Locks::mutator_lock_) { + std::ostream& os = *os_; + os << "IMAGE LOCATION: " << image_location_ << "\n\n"; + + os << "MAGIC: " << image_header_.GetMagic() << "\n\n"; + + os << "IMAGE BEGIN: " << reinterpret_cast<void*>(image_header_.GetImageBegin()) << "\n\n"; + + PrintPidLine("IMAGE", image_diff_pid_); + os << "\n\n"; + PrintPidLine("ZYGOTE", zygote_diff_pid_); + bool ret = true; + if (image_diff_pid_ >= 0 || zygote_diff_pid_ >= 0) { + ret = DumpImageDiff(); + os << "\n\n"; + } - // Future idea: diff against zygote so we can ignore the shared dirty pages. - return DumpImageDiffMap(boot_map); + os << std::flush; + + return ret; + } + + private: + bool DumpImageDiff() + REQUIRES_SHARED(Locks::mutator_lock_) { + return DumpImageDiffMap(); + } + + bool ComputeDirtyBytes(const uint8_t* image_begin, + size_t* dirty_pages /*out*/, + size_t* different_pages /*out*/, + size_t* different_bytes /*out*/, + size_t* different_int32s /*out*/, + size_t* private_pages /*out*/, + size_t* private_dirty_pages /*out*/, + std::set<size_t>* dirty_page_set_local) { + std::ostream& os = *os_; + + size_t virtual_page_idx = 0; // Virtual page number (for an absolute memory address) + size_t page_idx = 0; // Page index relative to 0 + size_t previous_page_idx = 0; // Previous page index relative to 0 + + + // Iterate through one page at a time. Boot map begin/end already implicitly aligned. + for (uintptr_t begin = boot_map_.start; begin != boot_map_.end; begin += kPageSize) { + ptrdiff_t offset = begin - boot_map_.start; + + // We treat the image header as part of the memory map for now + // If we wanted to change this, we could pass base=start+sizeof(ImageHeader) + // But it might still be interesting to see if any of the ImageHeader data mutated + const uint8_t* local_ptr = reinterpret_cast<const uint8_t*>(&image_header_) + offset; + uint8_t* remote_ptr = &remote_contents_[offset]; + + if (memcmp(local_ptr, remote_ptr, kPageSize) != 0) { + ++*different_pages; + + // Count the number of 32-bit integers that are different. + for (size_t i = 0; i < kPageSize / sizeof(uint32_t); ++i) { + uint32_t* remote_ptr_int32 = reinterpret_cast<uint32_t*>(remote_ptr); + const uint32_t* local_ptr_int32 = reinterpret_cast<const uint32_t*>(local_ptr); + + if (remote_ptr_int32[i] != local_ptr_int32[i]) { + ++*different_int32s; + } + } + } + } + + // Iterate through one byte at a time. + ptrdiff_t page_off_begin = image_header_.GetImageBegin() - image_begin; + for (uintptr_t begin = boot_map_.start; begin != boot_map_.end; ++begin) { + previous_page_idx = page_idx; + ptrdiff_t offset = begin - boot_map_.start; + + // We treat the image header as part of the memory map for now + // If we wanted to change this, we could pass base=start+sizeof(ImageHeader) + // But it might still be interesting to see if any of the ImageHeader data mutated + const uint8_t* local_ptr = reinterpret_cast<const uint8_t*>(&image_header_) + offset; + uint8_t* remote_ptr = &remote_contents_[offset]; + + virtual_page_idx = reinterpret_cast<uintptr_t>(local_ptr) / kPageSize; + + // Calculate the page index, relative to the 0th page where the image begins + page_idx = (offset + page_off_begin) / kPageSize; + if (*local_ptr != *remote_ptr) { + // Track number of bytes that are different + ++*different_bytes; + } + + // Independently count the # of dirty pages on the remote side + size_t remote_virtual_page_idx = begin / kPageSize; + if (previous_page_idx != page_idx) { + uint64_t page_count = 0xC0FFEE; + // TODO: virtual_page_idx needs to be from the same process + std::string error_msg; + int dirtiness = (IsPageDirty(&pagemap_file_, // Image-diff-pid procmap + &clean_pagemap_file_, // Self procmap + &kpageflags_file_, + &kpagecount_file_, + remote_virtual_page_idx, // potentially "dirty" page + virtual_page_idx, // true "clean" page + &page_count, + &error_msg)); + if (dirtiness < 0) { + os << error_msg; + return false; + } else if (dirtiness > 0) { + ++*dirty_pages; + dirty_page_set_local->insert(dirty_page_set_local->end(), virtual_page_idx); + } + + bool is_dirty = dirtiness > 0; + bool is_private = page_count == 1; + + if (page_count == 1) { + ++*private_pages; + } + + if (is_dirty && is_private) { + ++*private_dirty_pages; + } + } + } + return true; + } + + bool ObjectIsOnDirtyPage(const uint8_t* item, + size_t size, + const std::set<size_t>& dirty_page_set_local) { + size_t page_off = 0; + size_t current_page_idx; + uintptr_t object_address = reinterpret_cast<uintptr_t>(item); + // Iterate every page this object belongs to + do { + current_page_idx = object_address / kPageSize + page_off; + + if (dirty_page_set_local.find(current_page_idx) != dirty_page_set_local.end()) { + // This object is on a dirty page + return true; + } + + page_off++; + } while ((current_page_idx * kPageSize) < RoundUp(object_address + size, kObjectAlignment)); + + return false; } static std::string PrettyFieldValue(ArtField* field, mirror::Object* obj) @@ -213,24 +398,24 @@ class ImgDiagDumper { // Aggregate and detail class data from an image diff. struct ClassData { - int dirty_object_count = 0; + size_t dirty_object_count = 0; // Track only the byte-per-byte dirtiness (in bytes) - int dirty_object_byte_count = 0; + size_t dirty_object_byte_count = 0; // Track the object-by-object dirtiness (in bytes) - int dirty_object_size_in_bytes = 0; + size_t dirty_object_size_in_bytes = 0; - int clean_object_count = 0; + size_t clean_object_count = 0; std::string descriptor; - int false_dirty_byte_count = 0; - int false_dirty_object_count = 0; - std::vector<mirror::Object*> false_dirty_objects; + size_t false_dirty_byte_count = 0; + size_t false_dirty_object_count = 0; + std::vector<const uint8_t*> false_dirty_objects; // Remote pointers to dirty objects - std::vector<mirror::Object*> dirty_objects; + std::vector<const uint8_t*> dirty_objects; }; void DiffObjectContents(mirror::Object* obj, @@ -297,238 +482,189 @@ class ImgDiagDumper { os << "\n"; } - // Look at /proc/$pid/mem and only diff the things from there - bool DumpImageDiffMap(const backtrace_map_t& boot_map) - REQUIRES_SHARED(Locks::mutator_lock_) { - std::ostream& os = *os_; - const PointerSize pointer_size = InstructionSetPointerSize( - Runtime::Current()->GetInstructionSet()); + struct ObjectRegionData { + // Count of objects that are different. + size_t different_objects = 0; - std::string file_name = - StringPrintf("/proc/%ld/mem", static_cast<long>(image_diff_pid_)); // NOLINT [runtime/int] + // Local objects that are dirty (differ in at least one byte). + size_t dirty_object_bytes = 0; + std::vector<const uint8_t*>* dirty_objects; - size_t boot_map_size = boot_map.end - boot_map.start; + // Local objects that are clean, but located on dirty pages. + size_t false_dirty_object_bytes = 0; + std::vector<const uint8_t*> false_dirty_objects; - // Open /proc/$pid/mem as a file - auto map_file = std::unique_ptr<File>(OS::OpenFileForReading(file_name.c_str())); - if (map_file == nullptr) { - os << "Failed to open " << file_name << " for reading"; - return false; + // Image dirty objects + // If zygote_pid_only_ == true, these are shared dirty objects in the zygote. + // If zygote_pid_only_ == false, these are private dirty objects in the application. + std::set<const uint8_t*> image_dirty_objects; + + // Zygote dirty objects (probably private dirty). + // We only add objects here if they differed in both the image and the zygote, so + // they are probably private dirty. + std::set<const uint8_t*> zygote_dirty_objects; + + std::map<off_t /* field offset */, size_t /* count */>* field_dirty_count; + }; + + void ComputeObjectDirty(const uint8_t* current, + const uint8_t* current_remote, + const uint8_t* current_zygote, + ClassData* obj_class_data, + size_t obj_size, + const std::set<size_t>& dirty_page_set_local, + ObjectRegionData* region_data /*out*/) { + bool different_image_object = memcmp(current, current_remote, obj_size) != 0; + if (different_image_object) { + bool different_zygote_object = false; + if (!zygote_contents_.empty()) { + different_zygote_object = memcmp(current, current_zygote, obj_size) != 0; + } + if (different_zygote_object) { + // Different from zygote. + region_data->zygote_dirty_objects.insert(current); + } else { + // Just different from image. + region_data->image_dirty_objects.insert(current); + } + + ++region_data->different_objects; + region_data->dirty_object_bytes += obj_size; + + ++obj_class_data->dirty_object_count; + + // Go byte-by-byte and figure out what exactly got dirtied + size_t dirty_byte_count_per_object = 0; + for (size_t i = 0; i < obj_size; ++i) { + if (current[i] != current_remote[i]) { + dirty_byte_count_per_object++; + } + } + obj_class_data->dirty_object_byte_count += dirty_byte_count_per_object; + obj_class_data->dirty_object_size_in_bytes += obj_size; + obj_class_data->dirty_objects.push_back(current_remote); + } else { + ++obj_class_data->clean_object_count; } - // Memory-map /proc/$pid/mem subset from the boot map - CHECK(boot_map.end >= boot_map.start); + if (different_image_object) { + if (region_data->dirty_objects != nullptr) { + // print the fields that are dirty + for (size_t i = 0; i < obj_size; ++i) { + if (current[i] != current_remote[i]) { + size_t dirty_count = 0; + if (region_data->field_dirty_count->find(i) != region_data->field_dirty_count->end()) { + dirty_count = (*region_data->field_dirty_count)[i]; + } + (*region_data->field_dirty_count)[i] = dirty_count + 1; + } + } + + region_data->dirty_objects->push_back(current); + } + /* + * TODO: Resurrect this stuff in the client when we add ArtMethod iterator. + } else { + std::string descriptor = GetClassDescriptor(klass); + if (strcmp(descriptor.c_str(), "Ljava/lang/reflect/ArtMethod;") == 0) { + // this is an ArtMethod + ArtMethod* art_method = reinterpret_cast<ArtMethod*>(remote_obj); + + // print the fields that are dirty + for (size_t i = 0; i < obj_size; ++i) { + if (current[i] != current_remote[i]) { + art_method_field_dirty_count[i]++; + } + } + art_method_dirty_objects.push_back(art_method); + } + } + */ + } else if (ObjectIsOnDirtyPage(current, obj_size, dirty_page_set_local)) { + // This object was either never mutated or got mutated back to the same value. + // TODO: Do I want to distinguish a "different" vs a "dirty" page here? + region_data->false_dirty_objects.push_back(current); + obj_class_data->false_dirty_objects.push_back(current); + region_data->false_dirty_object_bytes += obj_size; + obj_class_data->false_dirty_byte_count += obj_size; + obj_class_data->false_dirty_object_count += 1; + } + } + + // Look at /proc/$pid/mem and only diff the things from there + bool DumpImageDiffMap() + REQUIRES_SHARED(Locks::mutator_lock_) { + std::ostream& os = *os_; std::string error_msg; // Walk the bytes and diff against our boot image - const ImageHeader& boot_image_header = image_header_; - os << "\nObserving boot image header at address " - << reinterpret_cast<const void*>(&boot_image_header) + << reinterpret_cast<const void*>(&image_header_) << "\n\n"; - const uint8_t* image_begin_unaligned = boot_image_header.GetImageBegin(); + const uint8_t* image_begin_unaligned = image_header_.GetImageBegin(); const uint8_t* image_mirror_end_unaligned = image_begin_unaligned + - boot_image_header.GetImageSection(ImageHeader::kSectionObjects).Size(); - const uint8_t* image_end_unaligned = image_begin_unaligned + boot_image_header.GetImageSize(); + image_header_.GetImageSection(ImageHeader::kSectionObjects).Size(); + const uint8_t* image_end_unaligned = image_begin_unaligned + image_header_.GetImageSize(); // Adjust range to nearest page const uint8_t* image_begin = AlignDown(image_begin_unaligned, kPageSize); const uint8_t* image_end = AlignUp(image_end_unaligned, kPageSize); - ptrdiff_t page_off_begin = boot_image_header.GetImageBegin() - image_begin; - - if (reinterpret_cast<uintptr_t>(image_begin) > boot_map.start || - reinterpret_cast<uintptr_t>(image_end) < boot_map.end) { + if (reinterpret_cast<uintptr_t>(image_begin) > boot_map_.start || + reinterpret_cast<uintptr_t>(image_end) < boot_map_.end) { // Sanity check that we aren't trying to read a completely different boot image os << "Remote boot map is out of range of local boot map: " << "local begin " << reinterpret_cast<const void*>(image_begin) << ", local end " << reinterpret_cast<const void*>(image_end) << - ", remote begin " << reinterpret_cast<const void*>(boot_map.start) << - ", remote end " << reinterpret_cast<const void*>(boot_map.end); + ", remote begin " << reinterpret_cast<const void*>(boot_map_.start) << + ", remote end " << reinterpret_cast<const void*>(boot_map_.end); return false; // If we wanted even more validation we could map the ImageHeader from the file } - std::vector<uint8_t> remote_contents(boot_map_size); - if (!map_file->PreadFully(&remote_contents[0], boot_map_size, boot_map.start)) { - os << "Could not fully read file " << file_name; - return false; - } - - std::vector<uint8_t> zygote_contents; - std::unique_ptr<File> zygote_map_file; - if (zygote_diff_pid_ != -1) { - std::string zygote_file_name = - StringPrintf("/proc/%ld/mem", static_cast<long>(zygote_diff_pid_)); // NOLINT [runtime/int] - zygote_map_file.reset(OS::OpenFileForReading(zygote_file_name.c_str())); - // The boot map should be at the same address. - zygote_contents.resize(boot_map_size); - if (!zygote_map_file->PreadFully(&zygote_contents[0], boot_map_size, boot_map.start)) { - LOG(WARNING) << "Could not fully read zygote file " << zygote_file_name; - zygote_contents.clear(); - } - } - - std::string page_map_file_name = StringPrintf( - "/proc/%ld/pagemap", static_cast<long>(image_diff_pid_)); // NOLINT [runtime/int] - auto page_map_file = std::unique_ptr<File>(OS::OpenFileForReading(page_map_file_name.c_str())); - if (page_map_file == nullptr) { - os << "Failed to open " << page_map_file_name << " for reading: " << strerror(errno); - return false; - } - - // Not truly clean, mmap-ing boot.art again would be more pristine, but close enough - const char* clean_page_map_file_name = "/proc/self/pagemap"; - auto clean_page_map_file = std::unique_ptr<File>( - OS::OpenFileForReading(clean_page_map_file_name)); - if (clean_page_map_file == nullptr) { - os << "Failed to open " << clean_page_map_file_name << " for reading: " << strerror(errno); - return false; - } - - auto kpage_flags_file = std::unique_ptr<File>(OS::OpenFileForReading("/proc/kpageflags")); - if (kpage_flags_file == nullptr) { - os << "Failed to open /proc/kpageflags for reading: " << strerror(errno); - return false; - } - - auto kpage_count_file = std::unique_ptr<File>(OS::OpenFileForReading("/proc/kpagecount")); - if (kpage_count_file == nullptr) { - os << "Failed to open /proc/kpagecount for reading:" << strerror(errno); - return false; - } - - // Set of the remote virtual page indices that are dirty - std::set<size_t> dirty_page_set_remote; - // Set of the local virtual page indices that are dirty - std::set<size_t> dirty_page_set_local; - - size_t different_int32s = 0; - size_t different_bytes = 0; - size_t different_pages = 0; - size_t virtual_page_idx = 0; // Virtual page number (for an absolute memory address) - size_t page_idx = 0; // Page index relative to 0 - size_t previous_page_idx = 0; // Previous page index relative to 0 size_t dirty_pages = 0; + size_t different_pages = 0; + size_t different_bytes = 0; + size_t different_int32s = 0; size_t private_pages = 0; size_t private_dirty_pages = 0; - // Iterate through one page at a time. Boot map begin/end already implicitly aligned. - for (uintptr_t begin = boot_map.start; begin != boot_map.end; begin += kPageSize) { - ptrdiff_t offset = begin - boot_map.start; - - // We treat the image header as part of the memory map for now - // If we wanted to change this, we could pass base=start+sizeof(ImageHeader) - // But it might still be interesting to see if any of the ImageHeader data mutated - const uint8_t* local_ptr = reinterpret_cast<const uint8_t*>(&boot_image_header) + offset; - uint8_t* remote_ptr = &remote_contents[offset]; - - if (memcmp(local_ptr, remote_ptr, kPageSize) != 0) { - different_pages++; - - // Count the number of 32-bit integers that are different. - for (size_t i = 0; i < kPageSize / sizeof(uint32_t); ++i) { - uint32_t* remote_ptr_int32 = reinterpret_cast<uint32_t*>(remote_ptr); - const uint32_t* local_ptr_int32 = reinterpret_cast<const uint32_t*>(local_ptr); - - if (remote_ptr_int32[i] != local_ptr_int32[i]) { - different_int32s++; - } - } - } - } - - // Iterate through one byte at a time. - for (uintptr_t begin = boot_map.start; begin != boot_map.end; ++begin) { - previous_page_idx = page_idx; - ptrdiff_t offset = begin - boot_map.start; - - // We treat the image header as part of the memory map for now - // If we wanted to change this, we could pass base=start+sizeof(ImageHeader) - // But it might still be interesting to see if any of the ImageHeader data mutated - const uint8_t* local_ptr = reinterpret_cast<const uint8_t*>(&boot_image_header) + offset; - uint8_t* remote_ptr = &remote_contents[offset]; - - virtual_page_idx = reinterpret_cast<uintptr_t>(local_ptr) / kPageSize; - - // Calculate the page index, relative to the 0th page where the image begins - page_idx = (offset + page_off_begin) / kPageSize; - if (*local_ptr != *remote_ptr) { - // Track number of bytes that are different - different_bytes++; - } - - // Independently count the # of dirty pages on the remote side - size_t remote_virtual_page_idx = begin / kPageSize; - if (previous_page_idx != page_idx) { - uint64_t page_count = 0xC0FFEE; - // TODO: virtual_page_idx needs to be from the same process - int dirtiness = (IsPageDirty(page_map_file.get(), // Image-diff-pid procmap - clean_page_map_file.get(), // Self procmap - kpage_flags_file.get(), - kpage_count_file.get(), - remote_virtual_page_idx, // potentially "dirty" page - virtual_page_idx, // true "clean" page - &page_count, - &error_msg)); - if (dirtiness < 0) { - os << error_msg; - return false; - } else if (dirtiness > 0) { - dirty_pages++; - dirty_page_set_remote.insert(dirty_page_set_remote.end(), remote_virtual_page_idx); - dirty_page_set_local.insert(dirty_page_set_local.end(), virtual_page_idx); - } - - bool is_dirty = dirtiness > 0; - bool is_private = page_count == 1; - - if (page_count == 1) { - private_pages++; - } + // Set of the local virtual page indices that are dirty + std::set<size_t> dirty_page_set_local; - if (is_dirty && is_private) { - private_dirty_pages++; - } - } + if (!ComputeDirtyBytes(image_begin, + &dirty_pages, + &different_pages, + &different_bytes, + &different_int32s, + &private_pages, + &private_dirty_pages, + &dirty_page_set_local)) { + return false; } std::map<mirror::Class*, ClassData> class_data; // Walk each object in the remote image space and compare it against ours - size_t different_objects = 0; - std::map<off_t /* field offset */, int /* count */> art_method_field_dirty_count; std::vector<ArtMethod*> art_method_dirty_objects; - std::map<off_t /* field offset */, int /* count */> class_field_dirty_count; - std::vector<mirror::Class*> class_dirty_objects; + std::map<off_t /* field offset */, size_t /* count */> class_field_dirty_count; + std::vector<const uint8_t*> class_dirty_objects; - // List of local objects that are clean, but located on dirty pages. - std::vector<mirror::Object*> false_dirty_objects; - size_t false_dirty_object_bytes = 0; // Look up remote classes by their descriptor std::map<std::string, mirror::Class*> remote_class_map; // Look up local classes by their descriptor std::map<std::string, mirror::Class*> local_class_map; - // Image dirty objects - // If zygote_pid_only_ == true, these are shared dirty objects in the zygote. - // If zygote_pid_only_ == false, these are private dirty objects in the application. - std::set<mirror::Object*> image_dirty_objects; - - // Zygote dirty objects (probably private dirty). - // We only add objects here if they differed in both the image and the zygote, so - // they are probably private dirty. - std::set<mirror::Object*> zygote_dirty_objects; - - size_t dirty_object_bytes = 0; const uint8_t* begin_image_ptr = image_begin_unaligned; const uint8_t* end_image_ptr = image_mirror_end_unaligned; + ObjectRegionData region_data; + const uint8_t* current = begin_image_ptr + RoundUp(sizeof(ImageHeader), kObjectAlignment); while (reinterpret_cast<uintptr_t>(current) < reinterpret_cast<uintptr_t>(end_image_ptr)) { CHECK_ALIGNED(current, kObjectAlignment); @@ -540,125 +676,60 @@ class ImgDiagDumper { obj->AssertReadBarrierState(); } - // Iterate every page this object belongs to - bool on_dirty_page = false; - size_t page_off = 0; - size_t current_page_idx; - uintptr_t object_address; - do { - object_address = reinterpret_cast<uintptr_t>(current); - current_page_idx = object_address / kPageSize + page_off; - - if (dirty_page_set_local.find(current_page_idx) != dirty_page_set_local.end()) { - // This object is on a dirty page - on_dirty_page = true; - } - - page_off++; - } while ((current_page_idx * kPageSize) < - RoundUp(object_address + obj->SizeOf(), kObjectAlignment)); - mirror::Class* klass = obj->GetClass(); + size_t obj_size = obj->SizeOf(); + ClassData& obj_class_data = class_data[klass]; // Check against the other object and see if they are different ptrdiff_t offset = current - begin_image_ptr; - const uint8_t* current_remote = &remote_contents[offset]; - mirror::Object* remote_obj = reinterpret_cast<mirror::Object*>( - const_cast<uint8_t*>(current_remote)); - - bool different_image_object = memcmp(current, current_remote, obj->SizeOf()) != 0; - if (different_image_object) { - bool different_zygote_object = false; - if (!zygote_contents.empty()) { - const uint8_t* zygote_ptr = &zygote_contents[offset]; - different_zygote_object = memcmp(current, zygote_ptr, obj->SizeOf()) != 0; - } - if (different_zygote_object) { - // Different from zygote. - zygote_dirty_objects.insert(obj); - } else { - // Just different from image. - image_dirty_objects.insert(obj); - } - - different_objects++; - dirty_object_bytes += obj->SizeOf(); + const uint8_t* current_remote = &remote_contents_[offset]; + const uint8_t* current_zygote = + zygote_contents_.empty() ? nullptr : &zygote_contents_[offset]; - ++class_data[klass].dirty_object_count; - - // Go byte-by-byte and figure out what exactly got dirtied - size_t dirty_byte_count_per_object = 0; - for (size_t i = 0; i < obj->SizeOf(); ++i) { - if (current[i] != current_remote[i]) { - dirty_byte_count_per_object++; - } - } - class_data[klass].dirty_object_byte_count += dirty_byte_count_per_object; - class_data[klass].dirty_object_size_in_bytes += obj->SizeOf(); - class_data[klass].dirty_objects.push_back(remote_obj); + if (klass->IsClassClass()) { + region_data.field_dirty_count = &class_field_dirty_count; + region_data.dirty_objects = &class_dirty_objects; } else { - ++class_data[klass].clean_object_count; + region_data.field_dirty_count = nullptr; + region_data.dirty_objects = nullptr; } - std::string descriptor = GetClassDescriptor(klass); - if (different_image_object) { - if (klass->IsClassClass()) { - // this is a "Class" - mirror::Class* obj_as_class = reinterpret_cast<mirror::Class*>(remote_obj); - // print the fields that are dirty - for (size_t i = 0; i < obj->SizeOf(); ++i) { - if (current[i] != current_remote[i]) { - class_field_dirty_count[i]++; - } - } - - class_dirty_objects.push_back(obj_as_class); - } else if (strcmp(descriptor.c_str(), "Ljava/lang/reflect/ArtMethod;") == 0) { - // this is an ArtMethod - ArtMethod* art_method = reinterpret_cast<ArtMethod*>(remote_obj); - - // print the fields that are dirty - for (size_t i = 0; i < obj->SizeOf(); ++i) { - if (current[i] != current_remote[i]) { - art_method_field_dirty_count[i]++; - } - } - - art_method_dirty_objects.push_back(art_method); - } - } else if (on_dirty_page) { - // This object was either never mutated or got mutated back to the same value. - // TODO: Do I want to distinguish a "different" vs a "dirty" page here? - false_dirty_objects.push_back(obj); - class_data[klass].false_dirty_objects.push_back(obj); - false_dirty_object_bytes += obj->SizeOf(); - class_data[obj->GetClass()].false_dirty_byte_count += obj->SizeOf(); - class_data[obj->GetClass()].false_dirty_object_count += 1; - } + ComputeObjectDirty(current, + current_remote, + current_zygote, + &obj_class_data, + obj_size, + dirty_page_set_local, + ®ion_data); + // Object specific stuff. + std::string descriptor = GetClassDescriptor(klass); if (strcmp(descriptor.c_str(), "Ljava/lang/Class;") == 0) { local_class_map[descriptor] = reinterpret_cast<mirror::Class*>(obj); + mirror::Object* remote_obj = reinterpret_cast<mirror::Object*>( + const_cast<uint8_t*>(current_remote)); remote_class_map[descriptor] = reinterpret_cast<mirror::Class*>(remote_obj); } // Unconditionally store the class descriptor in case we need it later - class_data[klass].descriptor = descriptor; - current += RoundUp(obj->SizeOf(), kObjectAlignment); + obj_class_data.descriptor = descriptor; + + current += RoundUp(obj_size, kObjectAlignment); } // Looking at only dirty pages, figure out how many of those bytes belong to dirty objects. - float true_dirtied_percent = dirty_object_bytes * 1.0f / (dirty_pages * kPageSize); + float true_dirtied_percent = region_data.dirty_object_bytes * 1.0f / (dirty_pages * kPageSize); size_t false_dirty_pages = dirty_pages - different_pages; - os << "Mapping at [" << reinterpret_cast<void*>(boot_map.start) << ", " - << reinterpret_cast<void*>(boot_map.end) << ") had: \n " + os << "Mapping at [" << reinterpret_cast<void*>(boot_map_.start) << ", " + << reinterpret_cast<void*>(boot_map_.end) << ") had: \n " << different_bytes << " differing bytes, \n " << different_int32s << " differing int32s, \n " - << different_objects << " different objects, \n " - << dirty_object_bytes << " different object [bytes], \n " - << false_dirty_objects.size() << " false dirty objects,\n " - << false_dirty_object_bytes << " false dirty object [bytes], \n " + << region_data.different_objects << " different objects, \n " + << region_data.dirty_object_bytes << " different object [bytes], \n " + << region_data.false_dirty_objects.size() << " false dirty objects,\n " + << region_data.false_dirty_object_bytes << " false dirty object [bytes], \n " << true_dirtied_percent << " different objects-vs-total in a dirty page;\n " << different_pages << " different pages; \n " << dirty_pages << " pages are dirty; \n " @@ -673,17 +744,17 @@ class ImgDiagDumper { auto clean_object_class_values = SortByValueDesc<mirror::Class*, int, ClassData>( class_data, [](const ClassData& d) { return d.clean_object_count; }); - if (!zygote_dirty_objects.empty()) { + if (!region_data.zygote_dirty_objects.empty()) { // We only reach this point if both pids were specified. Furthermore, // objects are only displayed here if they differed in both the image // and the zygote, so they are probably private dirty. CHECK(image_diff_pid_ > 0 && zygote_diff_pid_ > 0); os << "\n" << " Zygote dirty objects (probably shared dirty): " - << zygote_dirty_objects.size() << "\n"; - for (mirror::Object* obj : zygote_dirty_objects) { - const uint8_t* obj_bytes = reinterpret_cast<const uint8_t*>(obj); + << region_data.zygote_dirty_objects.size() << "\n"; + for (const uint8_t* obj_bytes : region_data.zygote_dirty_objects) { + auto obj = const_cast<mirror::Object*>(reinterpret_cast<const mirror::Object*>(obj_bytes)); ptrdiff_t offset = obj_bytes - begin_image_ptr; - uint8_t* remote_bytes = &zygote_contents[offset]; + uint8_t* remote_bytes = &zygote_contents_[offset]; DiffObjectContents(obj, remote_bytes, os); } } @@ -699,11 +770,11 @@ class ImgDiagDumper { os << " Application dirty objects (unknown whether private or shared dirty): "; } } - os << image_dirty_objects.size() << "\n"; - for (mirror::Object* obj : image_dirty_objects) { - const uint8_t* obj_bytes = reinterpret_cast<const uint8_t*>(obj); + os << region_data.image_dirty_objects.size() << "\n"; + for (const uint8_t* obj_bytes : region_data.image_dirty_objects) { + auto obj = const_cast<mirror::Object*>(reinterpret_cast<const mirror::Object*>(obj_bytes)); ptrdiff_t offset = obj_bytes - begin_image_ptr; - uint8_t* remote_bytes = &remote_contents[offset]; + uint8_t* remote_bytes = &remote_contents_[offset]; DiffObjectContents(obj, remote_bytes, os); } @@ -747,27 +818,26 @@ class ImgDiagDumper { os << " field contents:\n"; const auto& dirty_objects_list = class_data[klass].dirty_objects; - for (mirror::Object* obj : dirty_objects_list) { + for (const uint8_t* uobj : dirty_objects_list) { + auto obj = const_cast<mirror::Object*>(reinterpret_cast<const mirror::Object*>(uobj)); // remote method auto art_method = reinterpret_cast<ArtMethod*>(obj); // remote class mirror::Class* remote_declaring_class = - FixUpRemotePointer(art_method->GetDeclaringClass(), remote_contents, boot_map); + FixUpRemotePointer(art_method->GetDeclaringClass(), remote_contents_, boot_map_); // local class mirror::Class* declaring_class = - RemoteContentsPointerToLocal(remote_declaring_class, - remote_contents, - boot_image_header); + RemoteContentsPointerToLocal(remote_declaring_class, remote_contents_, image_header_); os << " " << reinterpret_cast<void*>(obj) << " "; os << " entryPointFromJni: " << reinterpret_cast<const void*>( - art_method->GetDataPtrSize(pointer_size)) << ", "; + art_method->GetDataPtrSize(pointer_size_)) << ", "; os << " entryPointFromQuickCompiledCode: " << reinterpret_cast<const void*>( - art_method->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size)) + art_method->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size_)) << ", "; os << " isNative? " << (art_method->IsNative() ? "yes" : "no") << ", "; os << " class_status (local): " << declaring_class->GetStatus(); @@ -780,13 +850,13 @@ class ImgDiagDumper { for (size_t i = 0; i < class_dirty_objects.size() && i < kMaxAddressPrint; ++i) { auto class_ptr = class_dirty_objects[i]; - os << reinterpret_cast<void*>(class_ptr) << ", "; + os << reinterpret_cast<const void*>(class_ptr) << ", "; } os << "\n"; os << " dirty byte +offset:count list = "; auto class_field_dirty_count_sorted = - SortByValueDesc<off_t, int, int>(class_field_dirty_count); + SortByValueDesc<off_t, int, size_t>(class_field_dirty_count); for (auto pair : class_field_dirty_count_sorted) { off_t offset = pair.second; int count = pair.first; @@ -796,17 +866,19 @@ class ImgDiagDumper { os << "\n"; os << " field contents:\n"; + // TODO: templatize this to avoid the awful casts down to uint8_t* and back. const auto& dirty_objects_list = class_data[klass].dirty_objects; - for (mirror::Object* obj : dirty_objects_list) { + for (const uint8_t* uobj : dirty_objects_list) { + auto obj = const_cast<mirror::Object*>(reinterpret_cast<const mirror::Object*>(uobj)); // remote class object auto remote_klass = reinterpret_cast<mirror::Class*>(obj); // local class object auto local_klass = RemoteContentsPointerToLocal(remote_klass, - remote_contents, - boot_image_header); + remote_contents_, + image_header_); - os << " " << reinterpret_cast<void*>(obj) << " "; + os << " " << reinterpret_cast<const void*>(obj) << " "; os << " class_status (remote): " << remote_klass->GetStatus() << ", "; os << " class_status (local): " << local_klass->GetStatus(); os << "\n"; @@ -832,23 +904,25 @@ class ImgDiagDumper { << ")\n"; if (strcmp(descriptor.c_str(), "Ljava/lang/reflect/ArtMethod;") == 0) { + // TODO: templatize this to avoid the awful casts down to uint8_t* and back. auto& art_method_false_dirty_objects = class_data[klass].false_dirty_objects; os << " field contents:\n"; - for (mirror::Object* obj : art_method_false_dirty_objects) { + for (const uint8_t* uobj : art_method_false_dirty_objects) { + auto obj = const_cast<mirror::Object*>(reinterpret_cast<const mirror::Object*>(uobj)); // local method auto art_method = reinterpret_cast<ArtMethod*>(obj); // local class mirror::Class* declaring_class = art_method->GetDeclaringClass(); - os << " " << reinterpret_cast<void*>(obj) << " "; + os << " " << reinterpret_cast<const void*>(obj) << " "; os << " entryPointFromJni: " << reinterpret_cast<const void*>( - art_method->GetDataPtrSize(pointer_size)) << ", "; + art_method->GetDataPtrSize(pointer_size_)) << ", "; os << " entryPointFromQuickCompiledCode: " << reinterpret_cast<const void*>( - art_method->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size)) + art_method->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size_)) << ", "; os << " isNative? " << (art_method->IsNative() ? "yes" : "no") << ", "; os << " class_status (local): " << declaring_class->GetStatus(); @@ -963,18 +1037,18 @@ class ImgDiagDumper { } static int IsPageDirty(File* page_map_file, - File* clean_page_map_file, - File* kpage_flags_file, - File* kpage_count_file, + File* clean_pagemap_file, + File* kpageflags_file, + File* kpagecount_file, size_t virtual_page_idx, size_t clean_virtual_page_idx, // Out parameters: uint64_t* page_count, std::string* error_msg) { CHECK(page_map_file != nullptr); - CHECK(clean_page_map_file != nullptr); - CHECK_NE(page_map_file, clean_page_map_file); - CHECK(kpage_flags_file != nullptr); - CHECK(kpage_count_file != nullptr); + CHECK(clean_pagemap_file != nullptr); + CHECK_NE(page_map_file, clean_pagemap_file); + CHECK(kpageflags_file != nullptr); + CHECK(kpagecount_file != nullptr); CHECK(page_count != nullptr); CHECK(error_msg != nullptr); @@ -992,27 +1066,27 @@ class ImgDiagDumper { } uint64_t page_frame_number_clean = 0; - if (!GetPageFrameNumber(clean_page_map_file, clean_virtual_page_idx, &page_frame_number_clean, + if (!GetPageFrameNumber(clean_pagemap_file, clean_virtual_page_idx, &page_frame_number_clean, error_msg)) { return -1; } // Read 64-bit entry from /proc/kpageflags to get the dirty bit for a page uint64_t kpage_flags_entry = 0; - if (!kpage_flags_file->PreadFully(&kpage_flags_entry, + if (!kpageflags_file->PreadFully(&kpage_flags_entry, kPageFlagsEntrySize, page_frame_number * kPageFlagsEntrySize)) { *error_msg = StringPrintf("Failed to read the page flags from %s", - kpage_flags_file->GetPath().c_str()); + kpageflags_file->GetPath().c_str()); return -1; } // Read 64-bit entyry from /proc/kpagecount to get mapping counts for a page - if (!kpage_count_file->PreadFully(page_count /*out*/, + if (!kpagecount_file->PreadFully(page_count /*out*/, kPageCountEntrySize, page_frame_number * kPageCountEntrySize)) { *error_msg = StringPrintf("Failed to read the page count from %s", - kpage_count_file->GetPath().c_str()); + kpagecount_file->GetPath().c_str()); return -1; } @@ -1033,7 +1107,29 @@ class ImgDiagDumper { return page_frame_number != page_frame_number_clean; } - private: + void PrintPidLine(const std::string& kind, pid_t pid) { + if (pid < 0) { + *os_ << kind << " DIFF PID: disabled\n\n"; + } else { + *os_ << kind << " DIFF PID (" << pid << "): "; + } + } + + static bool EndsWith(const std::string& str, const std::string& suffix) { + return str.size() >= suffix.size() && + str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; + } + + // Return suffix of the file path after the last /. (e.g. /foo/bar -> bar, bar -> bar) + static std::string BaseName(const std::string& str) { + size_t idx = str.rfind('/'); + if (idx == std::string::npos) { + return str; + } + + return str.substr(idx + 1); + } + // Return the image location, stripped of any directories, e.g. "boot.art" or "core.art" std::string GetImageLocationBaseName() const { return BaseName(std::string(image_location_)); @@ -1046,6 +1142,27 @@ class ImgDiagDumper { pid_t zygote_diff_pid_; // Dump image diff against zygote boot.art if pid is non-negative bool zygote_pid_only_; // The user only specified a pid for the zygote. + // Pointer size constant for object fields, etc. + PointerSize pointer_size_; + // BacktraceMap used for finding the memory mapping of the image file. + std::unique_ptr<BacktraceMap> proc_maps_; + // Boot image mapping. + backtrace_map_t boot_map_{}; // NOLINT + // The size of the boot image mapping. + size_t boot_map_size_; + // The contents of /proc/<image_diff_pid_>/maps. + std::vector<uint8_t> remote_contents_; + // The contents of /proc/<zygote_diff_pid_>/maps. + std::vector<uint8_t> zygote_contents_; + // A File for reading /proc/<zygote_diff_pid_>/maps. + File pagemap_file_; + // A File for reading /proc/self/pagemap. + File clean_pagemap_file_; + // A File for reading /proc/kpageflags. + File kpageflags_file_; + // A File for reading /proc/kpagecount. + File kpagecount_file_; + DISALLOW_COPY_AND_ASSIGN(ImgDiagDumper); }; @@ -1069,6 +1186,9 @@ static int DumpImage(Runtime* runtime, image_space->GetImageLocation(), image_diff_pid, zygote_diff_pid); + if (!img_diag_dumper.Init()) { + return EXIT_FAILURE; + } if (!img_diag_dumper.Dump()) { return EXIT_FAILURE; } diff --git a/profman/Android.bp b/profman/Android.bp index a327ef2c16..2a45c462b0 100644 --- a/profman/Android.bp +++ b/profman/Android.bp @@ -19,6 +19,7 @@ cc_defaults { host_supported: true, defaults: ["art_defaults"], srcs: [ + "boot_image_profile.cc", "profman.cc", "profile_assistant.cc", ], diff --git a/profman/boot_image_profile.cc b/profman/boot_image_profile.cc new file mode 100644 index 0000000000..21de0831b8 --- /dev/null +++ b/profman/boot_image_profile.cc @@ -0,0 +1,138 @@ +/* + * 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 <memory> +#include <set> + +#include "boot_image_profile.h" +#include "dex_file-inl.h" +#include "method_reference.h" +#include "type_reference.h" + +namespace art { + +using Hotness = ProfileCompilationInfo::MethodHotness; + +void GenerateBootImageProfile( + const std::vector<std::unique_ptr<const DexFile>>& dex_files, + const std::vector<std::unique_ptr<const ProfileCompilationInfo>>& profiles, + const BootImageOptions& options, + bool verbose, + ProfileCompilationInfo* out_profile) { + for (const std::unique_ptr<const ProfileCompilationInfo>& profile : profiles) { + // Avoid merging classes since we may want to only add classes that fit a certain criteria. + // If we merged the classes, every single class in each profile would be in the out_profile, + // but we want to only included classes that are in at least a few profiles. + out_profile->MergeWith(*profile, /*merge_classes*/ false); + } + + // Image classes that were added because they are commonly used. + size_t class_count = 0; + // Image classes that were only added because they were clean. + size_t clean_class_count = 0; + // Total clean classes. + size_t clean_count = 0; + // Total dirty classes. + size_t dirty_count = 0; + + for (const std::unique_ptr<const DexFile>& dex_file : dex_files) { + // Inferred classes are classes inferred from method samples. + std::set<std::pair<const ProfileCompilationInfo*, dex::TypeIndex>> inferred_classes; + for (size_t i = 0; i < dex_file->NumMethodIds(); ++i) { + MethodReference ref(dex_file.get(), i); + // This counter is how many profiles contain the method as sampled or hot. + size_t counter = 0; + for (const std::unique_ptr<const ProfileCompilationInfo>& profile : profiles) { + Hotness hotness = profile->GetMethodHotness(ref); + if (hotness.IsInProfile()) { + ++counter; + out_profile->AddMethodHotness(ref, hotness); + inferred_classes.emplace(profile.get(), + dex_file->GetMethodId(ref.dex_method_index).class_idx_); + } + } + // If the counter is greater or equal to the compile threshold, mark the method as hot. + // Note that all hot methods are also marked as hot in the out profile during the merging + // process. + if (counter >= options.compiled_method_threshold) { + Hotness hotness; + hotness.AddFlag(Hotness::kFlagHot); + out_profile->AddMethodHotness(ref, hotness); + } + } + // Walk all of the classes and add them to the profile if they meet the requirements. + for (size_t i = 0; i < dex_file->NumClassDefs(); ++i) { + const DexFile::ClassDef& class_def = dex_file->GetClassDef(i); + TypeReference ref(dex_file.get(), class_def.class_idx_); + bool is_clean = true; + const uint8_t* class_data = dex_file->GetClassData(class_def); + if (class_data != nullptr) { + ClassDataItemIterator it(*dex_file, class_data); + while (it.HasNextStaticField()) { + const uint32_t flags = it.GetFieldAccessFlags(); + if ((flags & kAccFinal) == 0) { + // Not final static field will probably dirty the class. + is_clean = false; + break; + } + it.Next(); + } + it.SkipInstanceFields(); + while (it.HasNextDirectMethod() || it.HasNextVirtualMethod()) { + const uint32_t flags = it.GetMethodAccessFlags(); + if ((flags & kAccNative) != 0 || (flags & kAccFastNative) != 0) { + // Native method will get dirtied. + is_clean = false; + break; + } + if ((flags & kAccConstructor) != 0 && (flags & kAccStatic) != 0) { + // Class initializer, may get dirtied (not sure). + is_clean = false; + break; + } + it.Next(); + } + } + ++(is_clean ? clean_count : dirty_count); + // This counter is how many profiles contain the class. + size_t counter = 0; + for (const std::unique_ptr<const ProfileCompilationInfo>& profile : profiles) { + auto it = inferred_classes.find(std::make_pair(profile.get(), ref.type_index)); + if (it != inferred_classes.end() || + profile->ContainsClass(*ref.dex_file, ref.type_index)) { + ++counter; + } + } + if (counter == 0) { + continue; + } + if (counter >= options.image_class_theshold) { + ++class_count; + out_profile->AddClassesForDex(ref.dex_file, &ref.type_index, &ref.type_index + 1); + } else if (is_clean && counter >= options.image_class_clean_theshold) { + ++clean_class_count; + out_profile->AddClassesForDex(ref.dex_file, &ref.type_index, &ref.type_index + 1); + } + } + } + if (verbose) { + LOG(INFO) << "Image classes " << class_count + clean_class_count + << " added because clean " << clean_class_count + << " total clean " << clean_count << " total dirty " << dirty_count; + } +} + +} // namespace art diff --git a/profman/boot_image_profile.h b/profman/boot_image_profile.h new file mode 100644 index 0000000000..d02e408cd5 --- /dev/null +++ b/profman/boot_image_profile.h @@ -0,0 +1,55 @@ +/* + * 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_PROFMAN_BOOT_IMAGE_PROFILE_H_ +#define ART_PROFMAN_BOOT_IMAGE_PROFILE_H_ + +#include <limits> +#include <memory> +#include <vector> + +#include "dex_file.h" +#include "jit/profile_compilation_info.h" + +namespace art { + +struct BootImageOptions { + public: + // Threshold for classes that may be dirty or clean. The threshold specifies how + // many different profiles need to have the class before it gets added to the boot profile. + uint32_t image_class_theshold = 10; + + // Threshold for classes that are likely to remain clean. The threshold specifies how + // many different profiles need to have the class before it gets added to the boot profile. + uint32_t image_class_clean_theshold = 3; + + // Threshold for non-hot methods to be compiled. The threshold specifies how + // many different profiles need to have the method before it gets added to the boot profile. + uint32_t compiled_method_threshold = std::numeric_limits<uint32_t>::max(); +}; + +// Merge a bunch of profiles together to generate a boot profile. Classes and methods are added +// to the out_profile if they meet the options. +void GenerateBootImageProfile( + const std::vector<std::unique_ptr<const DexFile>>& dex_files, + const std::vector<std::unique_ptr<const ProfileCompilationInfo>>& profiles, + const BootImageOptions& options, + bool verbose, + ProfileCompilationInfo* out_profile); + +} // namespace art + +#endif // ART_PROFMAN_BOOT_IMAGE_PROFILE_H_ diff --git a/profman/profile_assistant_test.cc b/profman/profile_assistant_test.cc index 9e2ab39a48..75f8ec9e27 100644 --- a/profman/profile_assistant_test.cc +++ b/profman/profile_assistant_test.cc @@ -171,6 +171,7 @@ class ProfileAssistantTest : public CommonRuntimeTest { << file_path << " should be a valid file path"; return file_path; } + // Runs test with given arguments. int ProcessProfiles(const std::vector<int>& profiles_fd, int reference_profile_fd) { std::string profman_cmd = GetProfmanCmd(); @@ -620,6 +621,100 @@ TEST_F(ProfileAssistantTest, TestProfileCreationGenerateMethods) { EXPECT_GT(method_count, 0u); } +TEST_F(ProfileAssistantTest, TestBootImageProfile) { + const std::string core_dex = GetLibCoreDexFileNames()[0]; + + std::vector<ScratchFile> profiles; + + // In image with enough clean occurrences. + const std::string kCleanClass = "Ljava/lang/CharSequence;"; + // In image with enough dirty occurrences. + const std::string kDirtyClass = "Ljava/lang/Object;"; + // Not in image becauseof not enough occurrences. + const std::string kUncommonCleanClass = "Ljava/lang/Process;"; + const std::string kUncommonDirtyClass = "Ljava/lang/Package;"; + // Method that is hot. + // Also adds the class through inference since it is in each dex. + const std::string kHotMethod = "Ljava/lang/Comparable;->compareTo(Ljava/lang/Object;)I"; + // Method that doesn't add the class since its only in one profile. Should still show up in the + // boot profile. + const std::string kOtherMethod = "Ljava/util/HashMap;-><init>()V"; + + // Thresholds for this test. + static const size_t kDirtyThreshold = 3; + static const size_t kCleanThreshold = 2; + + // Create a bunch of boot profiles. + std::string dex1 = + kCleanClass + "\n" + + kDirtyClass + "\n" + + kUncommonCleanClass + "\n" + + "H" + kHotMethod + "\n" + + kUncommonDirtyClass; + profiles.emplace_back(ScratchFile()); + EXPECT_TRUE(CreateProfile(dex1, profiles.back().GetFilename(), core_dex)); + + // Create a bunch of boot profiles. + std::string dex2 = + kCleanClass + "\n" + + kDirtyClass + "\n" + + "P" + kHotMethod + "\n" + + kUncommonDirtyClass; + profiles.emplace_back(ScratchFile()); + EXPECT_TRUE(CreateProfile(dex2, profiles.back().GetFilename(), core_dex)); + + // Create a bunch of boot profiles. + std::string dex3 = + "S" + kHotMethod + "\n" + + "P" + kOtherMethod + "\n" + + kDirtyClass + "\n"; + profiles.emplace_back(ScratchFile()); + EXPECT_TRUE(CreateProfile(dex3, profiles.back().GetFilename(), core_dex)); + + // Generate the boot profile. + ScratchFile out_profile; + std::vector<std::string> args; + args.push_back(GetProfmanCmd()); + args.push_back("--generate-boot-image-profile"); + args.push_back("--boot-image-class-threshold=" + std::to_string(kDirtyThreshold)); + args.push_back("--boot-image-clean-class-threshold=" + std::to_string(kCleanThreshold)); + args.push_back("--reference-profile-file=" + out_profile.GetFilename()); + args.push_back("--apk=" + core_dex); + args.push_back("--dex-location=" + core_dex); + for (const ScratchFile& profile : profiles) { + args.push_back("--profile-file=" + profile.GetFilename()); + } + std::string error; + EXPECT_EQ(ExecAndReturnCode(args, &error), 0) << error; + ASSERT_EQ(0, out_profile.GetFile()->Flush()); + ASSERT_TRUE(out_profile.GetFile()->ResetOffset()); + + // Verify the boot profile contents. + std::string output_file_contents; + EXPECT_TRUE(DumpClassesAndMethods(out_profile.GetFilename(), &output_file_contents)); + // Common classes, should be in the classes of the profile. + EXPECT_NE(output_file_contents.find(kCleanClass + "\n"), std::string::npos) + << output_file_contents; + EXPECT_NE(output_file_contents.find(kDirtyClass + "\n"), std::string::npos) + << output_file_contents; + // Uncommon classes, should not fit preloaded class criteria and should not be in the profile. + EXPECT_EQ(output_file_contents.find(kUncommonCleanClass + "\n"), std::string::npos) + << output_file_contents; + EXPECT_EQ(output_file_contents.find(kUncommonDirtyClass + "\n"), std::string::npos) + << output_file_contents; + // Inferred class from a method common to all three profiles. + EXPECT_NE(output_file_contents.find("Ljava/lang/Comparable;\n"), std::string::npos) + << output_file_contents; + // Aggregated methods hotness information. + EXPECT_NE(output_file_contents.find("HSP" + kHotMethod), std::string::npos) + << output_file_contents; + EXPECT_NE(output_file_contents.find(kOtherMethod), std::string::npos) + << output_file_contents; + // Not inferred class, method is only in one profile. + EXPECT_EQ(output_file_contents.find("Ljava/util/HashMap;\n"), std::string::npos) + << output_file_contents; +} + TEST_F(ProfileAssistantTest, TestProfileCreationOneNotMatched) { // Class names put here need to be in sorted order. std::vector<std::string> class_names = { diff --git a/profman/profman.cc b/profman/profman.cc index f763b8ea05..14b026277f 100644 --- a/profman/profman.cc +++ b/profman/profman.cc @@ -36,6 +36,7 @@ #include "base/stringpiece.h" #include "base/time_utils.h" #include "base/unix_file/fd_file.h" +#include "boot_image_profile.h" #include "bytecode_utils.h" #include "dex_file.h" #include "jit/profile_compilation_info.h" @@ -133,6 +134,15 @@ NO_RETURN static void Usage(const char *fmt, ...) { UsageError(" search for dex files"); UsageError(" --apk-=<filename>: an APK to search for dex files"); UsageError(""); + UsageError(" --generate-boot-image-profile: Generate a boot image profile based on input"); + UsageError(" profiles. Requires passing in dex files to inspect properties of classes."); + UsageError(" --boot-image-class-threshold=<value>: specify minimum number of class occurrences"); + UsageError(" to include a class in the boot image profile. Default is 10."); + UsageError(" --boot-image-clean-class-threshold=<value>: specify minimum number of clean class"); + UsageError(" occurrences to include a class in the boot image profile. A clean class is a"); + UsageError(" class that doesn't have any static fields or native methods and is likely to"); + UsageError(" remain clean in the image. Default is 3."); + UsageError(""); exit(EXIT_FAILURE); } @@ -163,6 +173,7 @@ class ProfMan FINAL { reference_profile_file_fd_(kInvalidFd), dump_only_(false), dump_classes_and_methods_(false), + generate_boot_image_profile_(false), dump_output_to_fd_(kInvalidFd), test_profile_num_dex_(kDefaultTestProfileNumDex), test_profile_method_ratio_(kDefaultTestProfileMethodRatio), @@ -202,6 +213,18 @@ class ProfMan FINAL { create_profile_from_file_ = option.substr(strlen("--create-profile-from=")).ToString(); } else if (option.starts_with("--dump-output-to-fd=")) { ParseUintOption(option, "--dump-output-to-fd", &dump_output_to_fd_, Usage); + } else if (option == "--generate-boot-image-profile") { + generate_boot_image_profile_ = true; + } else if (option.starts_with("--boot-image-class-threshold=")) { + ParseUintOption(option, + "--boot-image-class-threshold", + &boot_image_options_.image_class_theshold, + Usage); + } else if (option.starts_with("--boot-image-clean-class-threshold=")) { + ParseUintOption(option, + "--boot-image-clean-class-threshold", + &boot_image_options_.image_class_clean_theshold, + Usage); } else if (option.starts_with("--profile-file=")) { profile_files_.push_back(option.substr(strlen("--profile-file=")).ToString()); } else if (option.starts_with("--profile-file-fd=")) { @@ -323,28 +346,33 @@ class ProfMan FINAL { } } - int DumpOneProfile(const std::string& banner, - const std::string& filename, - int fd, - const std::vector<std::unique_ptr<const DexFile>>* dex_files, - std::string* dump) { + std::unique_ptr<const ProfileCompilationInfo> LoadProfile(const std::string& filename, int fd) { if (!filename.empty()) { fd = open(filename.c_str(), O_RDWR); if (fd < 0) { LOG(ERROR) << "Cannot open " << filename << strerror(errno); - return -1; + return nullptr; } } - ProfileCompilationInfo info; - if (!info.Load(fd)) { + std::unique_ptr<ProfileCompilationInfo> info(new ProfileCompilationInfo); + if (!info->Load(fd)) { LOG(ERROR) << "Cannot load profile info from fd=" << fd << "\n"; - return -1; + return nullptr; } - std::string this_dump = banner + "\n" + info.DumpInfo(dex_files) + "\n"; - *dump += this_dump; - if (close(fd) < 0) { - PLOG(WARNING) << "Failed to close descriptor"; + return info; + } + + int DumpOneProfile(const std::string& banner, + const std::string& filename, + int fd, + const std::vector<std::unique_ptr<const DexFile>>* dex_files, + std::string* dump) { + std::unique_ptr<const ProfileCompilationInfo> info(LoadProfile(filename, fd)); + if (info == nullptr) { + LOG(ERROR) << "Cannot load profile info from filename=" << filename << " fd=" << fd; + return -1; } + *dump += banner + "\n" + info->DumpInfo(dex_files) + "\n"; return 0; } @@ -854,6 +882,19 @@ class ProfMan FINAL { return true; } + int OpenReferenceProfile() const { + int fd = reference_profile_file_fd_; + if (!FdIsValid(fd)) { + CHECK(!reference_profile_file_.empty()); + fd = open(reference_profile_file_.c_str(), O_CREAT | O_TRUNC | O_WRONLY, 0644); + if (fd < 0) { + LOG(ERROR) << "Cannot open " << reference_profile_file_ << strerror(errno); + return kInvalidFd; + } + } + return fd; + } + // Creates a profile from a human friendly textual representation. // The expected input format is: // # Classes @@ -881,14 +922,9 @@ class ProfMan FINAL { // for ZipArchive::OpenFromFd MemMap::Init(); // Open the profile output file if needed. - int fd = reference_profile_file_fd_; + int fd = OpenReferenceProfile(); if (!FdIsValid(fd)) { - CHECK(!reference_profile_file_.empty()); - fd = open(reference_profile_file_.c_str(), O_CREAT | O_TRUNC | O_WRONLY, 0644); - if (fd < 0) { - LOG(ERROR) << "Cannot open " << reference_profile_file_ << strerror(errno); return -1; - } } // Read the user-specified list of classes and methods. std::unique_ptr<std::unordered_set<std::string>> @@ -914,6 +950,57 @@ class ProfMan FINAL { return 0; } + bool ShouldCreateBootProfile() const { + return generate_boot_image_profile_; + } + + int CreateBootProfile() { + // Initialize memmap since it's required to open dex files. + MemMap::Init(); + // Open the profile output file. + const int reference_fd = OpenReferenceProfile(); + if (!FdIsValid(reference_fd)) { + PLOG(ERROR) << "Error opening reference profile"; + return -1; + } + // Open the dex files. + std::vector<std::unique_ptr<const DexFile>> dex_files; + OpenApkFilesFromLocations(&dex_files); + if (dex_files.empty()) { + PLOG(ERROR) << "Expected dex files for creating boot profile"; + return -2; + } + // Open the input profiles. + std::vector<std::unique_ptr<const ProfileCompilationInfo>> profiles; + if (!profile_files_fd_.empty()) { + for (int profile_file_fd : profile_files_fd_) { + std::unique_ptr<const ProfileCompilationInfo> profile(LoadProfile("", profile_file_fd)); + if (profile == nullptr) { + return -3; + } + profiles.emplace_back(std::move(profile)); + } + } + if (!profile_files_.empty()) { + for (const std::string& profile_file : profile_files_) { + std::unique_ptr<const ProfileCompilationInfo> profile(LoadProfile(profile_file, kInvalidFd)); + if (profile == nullptr) { + return -4; + } + profiles.emplace_back(std::move(profile)); + } + } + ProfileCompilationInfo out_profile; + GenerateBootImageProfile(dex_files, + profiles, + boot_image_options_, + VLOG_IS_ON(profiler), + &out_profile); + out_profile.Save(reference_fd); + close(reference_fd); + return 0; + } + bool ShouldCreateProfile() { return !create_profile_from_file_.empty(); } @@ -1001,7 +1088,9 @@ class ProfMan FINAL { int reference_profile_file_fd_; bool dump_only_; bool dump_classes_and_methods_; + bool generate_boot_image_profile_; int dump_output_to_fd_; + BootImageOptions boot_image_options_; std::string test_profile_; std::string create_profile_from_file_; uint16_t test_profile_num_dex_; @@ -1030,6 +1119,10 @@ static int profman(int argc, char** argv) { if (profman.ShouldCreateProfile()) { return profman.CreateProfile(); } + + if (profman.ShouldCreateBootProfile()) { + return profman.CreateBootProfile(); + } // Process profile information and assess if we need to do a profile guided compilation. // This operation involves I/O. return profman.ProcessProfiles(); diff --git a/runtime/Android.bp b/runtime/Android.bp index 20f95c0c74..46307ddde8 100644 --- a/runtime/Android.bp +++ b/runtime/Android.bp @@ -193,6 +193,7 @@ cc_defaults { "plugin.cc", "primitive.cc", "quick_exception_handler.cc", + "read_barrier.cc", "reference_table.cc", "reflection.cc", "runtime.cc", @@ -528,6 +529,7 @@ art_cc_test { "base/hash_set_test.cc", "base/hex_dump_test.cc", "base/histogram_test.cc", + "base/logging_test.cc", "base/mutex_test.cc", "base/safe_copy_test.cc", "base/scoped_flock_test.cc", diff --git a/runtime/art_method.cc b/runtime/art_method.cc index 32946ef0b4..ac433dd403 100644 --- a/runtime/art_method.cc +++ b/runtime/art_method.cc @@ -55,6 +55,8 @@ 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*); +DEFINE_RUNTIME_DEBUG_FLAG(ArtMethod, kCheckDeclaringClassState); + // Enforce that we he have the right index for runtime methods. static_assert(ArtMethod::kRuntimeMethodDexMethodIndex == DexFile::kDexNoIndex, "Wrong runtime-method dex method index"); diff --git a/runtime/art_method.h b/runtime/art_method.h index 3a8d279606..d537764cac 100644 --- a/runtime/art_method.h +++ b/runtime/art_method.h @@ -22,6 +22,7 @@ #include "base/bit_utils.h" #include "base/casts.h" #include "base/enums.h" +#include "base/logging.h" #include "dex_file.h" #include "gc_root.h" #include "modifiers.h" @@ -56,7 +57,8 @@ class String; class ArtMethod FINAL { public: - static constexpr bool kCheckDeclaringClassState = kIsDebugBuild; + // Should the class state be checked on sensitive operations? + DECLARE_RUNTIME_DEBUG_FLAG(kCheckDeclaringClassState); // The runtime dex_method_index is kDexNoIndex. To lower dependencies, we use this // constexpr, and ensure that the value is correct in art_method.cc. diff --git a/runtime/base/logging.cc b/runtime/base/logging.cc index adfd7d323c..2be9067e64 100644 --- a/runtime/base/logging.cc +++ b/runtime/base/logging.cc @@ -34,6 +34,55 @@ namespace art { +// We test here that the runtime-debug-checks are actually a no-op constexpr false in release +// builds, as we can't check that in gtests (which are always debug). + +#ifdef NDEBUG +namespace { +DECLARE_RUNTIME_DEBUG_FLAG(kTestForConstexpr); +static_assert(!kTestForConstexpr, "Issue with DECLARE_RUNTIME_DEBUG_FLAG in NDEBUG."); +} +#endif + +// Implementation of runtime debug flags. This should be compile-time optimized away in release +// builds. +namespace { +bool gSlowEnabled = false; // Default for slow flags is "off." + +// Use a function with a static to ensure our vector storage doesn't have initialization order +// issues. +std::vector<bool*>& GetFlagPtrs() { + static std::vector<bool*> g_flag_ptrs; + return g_flag_ptrs; +} + +bool RegisterRuntimeDebugFlagImpl(bool* flag_ptr) { + GetFlagPtrs().push_back(flag_ptr); + return gSlowEnabled; +} + +void SetRuntimeDebugFlagsEnabledImpl(bool enabled) { + gSlowEnabled = enabled; + for (bool* flag_ptr : GetFlagPtrs()) { + *flag_ptr = enabled; + } +} + +} // namespace + +bool RegisterRuntimeDebugFlag(bool* flag_ptr) { + if (kIsDebugBuild) { + return RegisterRuntimeDebugFlagImpl(flag_ptr); + } + return false; +} + +void SetRuntimeDebugFlagsEnabled(bool enabled) { + if (kIsDebugBuild) { + SetRuntimeDebugFlagsEnabledImpl(enabled); + } +} + LogVerbosity gLogVerbosity; unsigned int gAborting = 0; diff --git a/runtime/base/logging.h b/runtime/base/logging.h index 7a9184e07e..d8954e59d0 100644 --- a/runtime/base/logging.h +++ b/runtime/base/logging.h @@ -62,6 +62,43 @@ struct LogVerbosity { // Global log verbosity setting, initialized by InitLogging. extern LogVerbosity gLogVerbosity; +// Runtime debug flags are flags that have a runtime component, that is, their value can be changed. +// This is meant to implement fast vs slow debug builds, in that certain debug flags can be turned +// on and off. To that effect, expose two macros to help implement and globally drive these flags: +// +// In the header, declare a (class) flag like this: +// +// class C { +// DECLARE_RUNTIME_DEBUG_FLAG(kFlag); +// }; +// +// This will declare a flag kFlag that is a constexpr false in release builds, and a static field +// in debug builds. Usage is than uniform as C::kFlag. +// +// In the cc file, define the flag like this: +// +// DEFINE_RUNTIME_DEBUG_FLAG(C, kFlag); +// +// This will define the static storage, as necessary, and register the flag with the runtime +// infrastructure to toggle the value. + +#ifdef NDEBUG +#define DECLARE_RUNTIME_DEBUG_FLAG(x) \ + static constexpr bool x = false; +// Note: the static_assert in the following only works for public flags. Fix this when we cross +// the line at some point. +#define DEFINE_RUNTIME_DEBUG_FLAG(C, x) \ + static_assert(!C::x, "Unexpected enabled flag in release build"); +#else +#define DECLARE_RUNTIME_DEBUG_FLAG(x) \ + static bool x; +#define DEFINE_RUNTIME_DEBUG_FLAG(C, x) \ + bool C::x = RegisterRuntimeDebugFlag(&C::x); +#endif // NDEBUG + +bool RegisterRuntimeDebugFlag(bool* runtime_debug_flag); +void SetRuntimeDebugFlagsEnabled(bool enabled); + // 0 if not abort, non-zero if an abort is in progress. Used on fatal exit to prevents recursive // aborts. Global declaration allows us to disable some error checking to ensure fatal shutdown // makes forward progress. diff --git a/runtime/base/logging_test.cc b/runtime/base/logging_test.cc new file mode 100644 index 0000000000..d380b9eccc --- /dev/null +++ b/runtime/base/logging_test.cc @@ -0,0 +1,59 @@ +/* + * 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 "logging.h" + +#include <type_traits> + +#include "android-base/logging.h" +#include "base/bit_utils.h" +#include "base/macros.h" +#include "common_runtime_test.h" + +namespace art { + +static void SimpleAborter(const char* msg) { + LOG(FATAL_WITHOUT_ABORT) << msg; + _exit(1); +} + +class LoggingTest : public CommonRuntimeTest { + protected: + void PostRuntimeCreate() OVERRIDE { + // In our abort tests we really don't want the runtime to create a real dump. + android::base::SetAborter(SimpleAborter); + } +}; + +#ifdef NDEBUG +#error Unexpected NDEBUG +#endif + +class TestClass { + public: + DECLARE_RUNTIME_DEBUG_FLAG(kFlag); +}; +DEFINE_RUNTIME_DEBUG_FLAG(TestClass, kFlag); + +TEST_F(LoggingTest, DECL_DEF) { + SetRuntimeDebugFlagsEnabled(true); + EXPECT_TRUE(TestClass::kFlag); + + SetRuntimeDebugFlagsEnabled(false); + EXPECT_FALSE(TestClass::kFlag); +} + +} // namespace art diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc index f9259944b4..5beac1aa37 100644 --- a/runtime/common_runtime_test.cc +++ b/runtime/common_runtime_test.cc @@ -404,6 +404,9 @@ void CommonRuntimeTestImpl::SetUp() { options.push_back(std::make_pair("-Xcheck:jni", nullptr)); options.push_back(std::make_pair(min_heap_string, nullptr)); options.push_back(std::make_pair(max_heap_string, nullptr)); + options.push_back(std::make_pair("-XX:SlowDebug=true", nullptr)); + static bool gSlowDebugTestFlag = false; + RegisterRuntimeDebugFlag(&gSlowDebugTestFlag); callbacks_.reset(new NoopCompilerCallbacks()); @@ -434,6 +437,9 @@ void CommonRuntimeTestImpl::SetUp() { java_lang_dex_file_ = boot_class_path_[0]; FinalizeSetup(); + + // Ensure that we're really running with debug checks enabled. + CHECK(gSlowDebugTestFlag); } void CommonRuntimeTestImpl::FinalizeSetup() { @@ -460,7 +466,7 @@ void CommonRuntimeTestImpl::FinalizeSetup() { runtime_->GetHeap()->SetMinIntervalHomogeneousSpaceCompactionByOom(0U); } -void CommonRuntimeTestImpl::ClearDirectory(const char* dirpath) { +void CommonRuntimeTestImpl::ClearDirectory(const char* dirpath, bool recursive) { ASSERT_TRUE(dirpath != nullptr); DIR* dir = opendir(dirpath); ASSERT_TRUE(dir != nullptr); @@ -476,9 +482,11 @@ void CommonRuntimeTestImpl::ClearDirectory(const char* dirpath) { int stat_result = lstat(filename.c_str(), &s); ASSERT_EQ(0, stat_result) << "unable to stat " << filename; if (S_ISDIR(s.st_mode)) { - ClearDirectory(filename.c_str()); - int rmdir_result = rmdir(filename.c_str()); - ASSERT_EQ(0, rmdir_result) << filename; + if (recursive) { + ClearDirectory(filename.c_str()); + int rmdir_result = rmdir(filename.c_str()); + ASSERT_EQ(0, rmdir_result) << filename; + } } else { int unlink_result = unlink(filename.c_str()); ASSERT_EQ(0, unlink_result) << filename; diff --git a/runtime/common_runtime_test.h b/runtime/common_runtime_test.h index 019770302d..3b3e6c5321 100644 --- a/runtime/common_runtime_test.h +++ b/runtime/common_runtime_test.h @@ -126,7 +126,7 @@ class CommonRuntimeTestImpl { std::unique_ptr<const DexFile> LoadExpectSingleDexFile(const char* location); - void ClearDirectory(const char* dirpath); + void ClearDirectory(const char* dirpath, bool recursive = true); std::string GetTestAndroidRoot(); diff --git a/runtime/jit/profile_compilation_info.cc b/runtime/jit/profile_compilation_info.cc index 960030d577..147173c5a3 100644 --- a/runtime/jit/profile_compilation_info.cc +++ b/runtime/jit/profile_compilation_info.cc @@ -1148,7 +1148,8 @@ int ProfileCompilationInfo::InflateBuffer(const uint8_t* in_buffer, return ret; } -bool ProfileCompilationInfo::MergeWith(const ProfileCompilationInfo& other) { +bool ProfileCompilationInfo::MergeWith(const ProfileCompilationInfo& other, + bool merge_classes) { // First verify that all checksums match. This will avoid adding garbage to // the current profile info. // Note that the number of elements should be very small, so this should not @@ -1194,8 +1195,10 @@ bool ProfileCompilationInfo::MergeWith(const ProfileCompilationInfo& other) { DCHECK(dex_data != nullptr); // Merge the classes. - dex_data->class_set.insert(other_dex_data->class_set.begin(), - other_dex_data->class_set.end()); + if (merge_classes) { + dex_data->class_set.insert(other_dex_data->class_set.begin(), + other_dex_data->class_set.end()); + } // Merge the methods and the inline caches. for (const auto& other_method_it : other_dex_data->method_map) { @@ -1239,6 +1242,18 @@ ProfileCompilationInfo::MethodHotness ProfileCompilationInfo::GetMethodHotness( : MethodHotness(); } +bool ProfileCompilationInfo::AddMethodHotness(const MethodReference& method_ref, + const MethodHotness& hotness) { + DexFileData* dex_data = GetOrAddDexFileData(method_ref.dex_file); + if (dex_data != nullptr) { + // TODO: Add inline caches. + dex_data->AddMethod(static_cast<MethodHotness::Flag>(hotness.GetFlags()), + method_ref.dex_method_index); + return true; + } + return false; +} + ProfileCompilationInfo::MethodHotness ProfileCompilationInfo::GetMethodHotness( const std::string& dex_location, uint32_t dex_checksum, diff --git a/runtime/jit/profile_compilation_info.h b/runtime/jit/profile_compilation_info.h index 8d1e578875..079ce8d117 100644 --- a/runtime/jit/profile_compilation_info.h +++ b/runtime/jit/profile_compilation_info.h @@ -285,6 +285,9 @@ class ProfileCompilationInfo { return true; } + // Add hotness flags for a simple method. + bool AddMethodHotness(const MethodReference& method_ref, const MethodHotness& hotness); + // Load profile information from the given file descriptor. // If the current profile is non-empty the load will fail. bool Load(int fd); @@ -295,8 +298,10 @@ class ProfileCompilationInfo { // the file and returns true. bool Load(const std::string& filename, bool clear_if_invalid); - // Merge the data from another ProfileCompilationInfo into the current object. - bool MergeWith(const ProfileCompilationInfo& info); + // Merge the data from another ProfileCompilationInfo into the current object. Only merges + // classes if merge_classes is true. This is used for creating the boot profile since + // we don't want all of the classes to be image classes. + bool MergeWith(const ProfileCompilationInfo& info, bool merge_classes = true); // Save the profile data to the given file descriptor. bool Save(int fd); @@ -375,9 +380,6 @@ class ProfileCompilationInfo { ArenaAllocator* GetArena() { return &arena_; } - // Add a method index to the profile (without inline caches). - bool AddMethodIndex(const std::string& dex_location, uint32_t checksum, uint16_t method_idx); - private: enum ProfileLoadSatus { kProfileLoadWouldOverwiteData, diff --git a/runtime/openjdkjvmti/ti_heap.cc b/runtime/openjdkjvmti/ti_heap.cc index b3bc6764c9..29658d9154 100644 --- a/runtime/openjdkjvmti/ti_heap.cc +++ b/runtime/openjdkjvmti/ti_heap.cc @@ -1392,7 +1392,9 @@ jvmtiError HeapUtil::GetLoadedClasses(jvmtiEnv* env, bool operator()(art::ObjPtr<art::mirror::Class> klass) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) { - classes_.push_back(self_->GetJniEnv()->AddLocalReference<jclass>(klass)); + if (klass->IsLoaded() || klass->IsErroneous()) { + classes_.push_back(self_->GetJniEnv()->AddLocalReference<jclass>(klass)); + } return true; } diff --git a/runtime/openjdkjvmti/ti_stack.cc b/runtime/openjdkjvmti/ti_stack.cc index 550b97272d..a17226c55a 100644 --- a/runtime/openjdkjvmti/ti_stack.cc +++ b/runtime/openjdkjvmti/ti_stack.cc @@ -205,7 +205,12 @@ struct GetStackTraceDirectClosure : public art::Closure { size_t index = 0; }; -static jvmtiError GetThread(JNIEnv* env, jthread java_thread, art::Thread** thread) { +static jvmtiError GetThread(JNIEnv* env, + art::ScopedObjectAccessAlreadyRunnable& soa, + jthread java_thread, + art::Thread** thread) + REQUIRES_SHARED(art::Locks::mutator_lock_) // Needed for FromManagedThread. + REQUIRES(art::Locks::thread_list_lock_) { // Needed for FromManagedThread. if (java_thread == nullptr) { *thread = art::Thread::Current(); if (*thread == nullptr) { @@ -220,8 +225,6 @@ static jvmtiError GetThread(JNIEnv* env, jthread java_thread, art::Thread** thre } // TODO: Need non-aborting call here, to return JVMTI_ERROR_INVALID_THREAD. - art::ScopedObjectAccess soa(art::Thread::Current()); - art::MutexLock mu(soa.Self(), *art::Locks::thread_list_lock_); *thread = art::Thread::FromManagedThread(soa, java_thread); if (*thread == nullptr) { return ERR(THREAD_NOT_ALIVE); @@ -236,8 +239,16 @@ jvmtiError StackUtil::GetStackTrace(jvmtiEnv* jvmti_env ATTRIBUTE_UNUSED, jint max_frame_count, jvmtiFrameInfo* frame_buffer, jint* count_ptr) { + // It is not great that we have to hold these locks for so long, but it is necessary to ensure + // that the thread isn't dying on us. + art::ScopedObjectAccess soa(art::Thread::Current()); + art::MutexLock mu(soa.Self(), *art::Locks::thread_list_lock_); + art::Thread* thread; - jvmtiError thread_error = GetThread(art::Thread::Current()->GetJniEnv(), java_thread, &thread); + jvmtiError thread_error = GetThread(art::Thread::Current()->GetJniEnv(), + soa, + java_thread, + &thread); if (thread_error != ERR(NONE)) { return thread_error; } @@ -286,6 +297,37 @@ jvmtiError StackUtil::GetStackTrace(jvmtiEnv* jvmti_env ATTRIBUTE_UNUSED, count_ptr); } +template <typename Data> +struct GetAllStackTracesVectorClosure : public art::Closure { + GetAllStackTracesVectorClosure(size_t stop, Data* data_) : stop_input(stop), data(data_) {} + + void Run(art::Thread* thread) OVERRIDE + REQUIRES_SHARED(art::Locks::mutator_lock_) + REQUIRES(!data->mutex) { + art::Thread* self = art::Thread::Current(); + + // Skip threads that are still starting. + if (thread->IsStillStarting()) { + return; + } + + std::vector<jvmtiFrameInfo>* thread_frames = data->GetFrameStorageFor(self, thread); + if (thread_frames == nullptr) { + return; + } + + // Now collect the data. + auto frames_fn = [&](jvmtiFrameInfo info) { + thread_frames->push_back(info); + }; + auto visitor = MakeStackTraceVisitor(thread, 0u, stop_input, frames_fn); + visitor.WalkStack(/* include_transitions */ false); + } + + const size_t stop_input; + Data* data; +}; + jvmtiError StackUtil::GetAllStackTraces(jvmtiEnv* env, jint max_frame_count, jvmtiStackInfo** stack_info_ptr, @@ -297,58 +339,66 @@ jvmtiError StackUtil::GetAllStackTraces(jvmtiEnv* env, return ERR(NULL_POINTER); } - - art::Thread* current = art::Thread::Current(); - art::ScopedObjectAccess soa(current); // Now we know we have the shared lock. - art::ScopedThreadSuspension sts(current, art::kWaitingForDebuggerSuspension); - art::ScopedSuspendAll ssa("GetAllStackTraces"); - - std::vector<art::Thread*> threads; - std::vector<std::vector<jvmtiFrameInfo>> frames; - { - std::list<art::Thread*> thread_list; - { - art::MutexLock mu(current, *art::Locks::thread_list_lock_); - thread_list = art::Runtime::Current()->GetThreadList()->GetList(); - } - - for (art::Thread* thread : thread_list) { - // Skip threads that are still starting. - if (thread->IsStillStarting()) { - continue; + struct AllStackTracesData { + AllStackTracesData() : mutex("GetAllStackTraces", art::LockLevel::kAbortLock) {} + ~AllStackTracesData() { + JNIEnv* jni_env = art::Thread::Current()->GetJniEnv(); + for (jthread global_thread_ref : thread_peers) { + jni_env->DeleteGlobalRef(global_thread_ref); } + } - GetStackTraceVectorClosure closure(0u, static_cast<size_t>(max_frame_count)); - thread->RequestSynchronousCheckpoint(&closure); + std::vector<jvmtiFrameInfo>* GetFrameStorageFor(art::Thread* self, art::Thread* thread) + REQUIRES_SHARED(art::Locks::mutator_lock_) + REQUIRES(!mutex) { + art::MutexLock mu(self, mutex); threads.push_back(thread); - frames.emplace_back(); - frames.back().swap(closure.frames); + + jthread peer = art::Runtime::Current()->GetJavaVM()->AddGlobalRef( + self, thread->GetPeerFromOtherThread()); + thread_peers.push_back(peer); + + frames.emplace_back(new std::vector<jvmtiFrameInfo>()); + return frames.back().get(); } - } - // Convert the data into our output format. Note: we need to keep the threads suspended, - // as we need to access them for their peers. + art::Mutex mutex; + + // Storage. Only access directly after completion. + + std::vector<art::Thread*> threads; + // "thread_peers" contains global references to their peers. + std::vector<jthread> thread_peers; + + std::vector<std::unique_ptr<std::vector<jvmtiFrameInfo>>> frames; + }; + + AllStackTracesData data; + GetAllStackTracesVectorClosure<AllStackTracesData> closure( + static_cast<size_t>(max_frame_count), &data); + art::Runtime::Current()->GetThreadList()->RunCheckpoint(&closure, nullptr); + + art::Thread* current = art::Thread::Current(); + + // Convert the data into our output format. // Note: we use an array of jvmtiStackInfo for convenience. The spec says we need to // allocate one big chunk for this and the actual frames, which means we need // to either be conservative or rearrange things later (the latter is implemented). - std::unique_ptr<jvmtiStackInfo[]> stack_info_array(new jvmtiStackInfo[frames.size()]); + std::unique_ptr<jvmtiStackInfo[]> stack_info_array(new jvmtiStackInfo[data.frames.size()]); std::vector<std::unique_ptr<jvmtiFrameInfo[]>> frame_infos; - frame_infos.reserve(frames.size()); + frame_infos.reserve(data.frames.size()); // Now run through and add data for each thread. size_t sum_frames = 0; - for (size_t index = 0; index < frames.size(); ++index) { + for (size_t index = 0; index < data.frames.size(); ++index) { jvmtiStackInfo& stack_info = stack_info_array.get()[index]; memset(&stack_info, 0, sizeof(jvmtiStackInfo)); - art::Thread* self = threads[index]; - const std::vector<jvmtiFrameInfo>& thread_frames = frames[index]; + const std::vector<jvmtiFrameInfo>& thread_frames = *data.frames[index].get(); - // For the time being, set the thread to null. We don't have good ScopedLocalRef - // infrastructure. - DCHECK(self->GetPeerFromOtherThread() != nullptr); + // For the time being, set the thread to null. We'll fix it up in the second stage. stack_info.thread = nullptr; stack_info.state = JVMTI_THREAD_STATE_SUSPENDED; @@ -377,7 +427,7 @@ jvmtiError StackUtil::GetAllStackTraces(jvmtiEnv* env, } // No errors, yet. Now put it all into an output buffer. - size_t rounded_stack_info_size = art::RoundUp(sizeof(jvmtiStackInfo) * frames.size(), + size_t rounded_stack_info_size = art::RoundUp(sizeof(jvmtiStackInfo) * data.frames.size(), alignof(jvmtiFrameInfo)); size_t chunk_size = rounded_stack_info_size + sum_frames * sizeof(jvmtiFrameInfo); unsigned char* chunk_data; @@ -388,18 +438,18 @@ jvmtiError StackUtil::GetAllStackTraces(jvmtiEnv* env, jvmtiStackInfo* stack_info = reinterpret_cast<jvmtiStackInfo*>(chunk_data); // First copy in all the basic data. - memcpy(stack_info, stack_info_array.get(), sizeof(jvmtiStackInfo) * frames.size()); + memcpy(stack_info, stack_info_array.get(), sizeof(jvmtiStackInfo) * data.frames.size()); // Now copy the frames and fix up the pointers. jvmtiFrameInfo* frame_info = reinterpret_cast<jvmtiFrameInfo*>( chunk_data + rounded_stack_info_size); - for (size_t i = 0; i < frames.size(); ++i) { + for (size_t i = 0; i < data.frames.size(); ++i) { 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]->GetPeerFromOtherThread()); - new_stack_info.thread = thread_peer; + // Translate the global ref into a local ref. + new_stack_info.thread = + static_cast<JNIEnv*>(current->GetJniEnv())->NewLocalRef(data.thread_peers[i]); if (old_stack_info.frame_count > 0) { // Only copy when there's data - leave the nullptr alone. @@ -411,7 +461,7 @@ jvmtiError StackUtil::GetAllStackTraces(jvmtiEnv* env, } *stack_info_ptr = stack_info; - *thread_count_ptr = static_cast<jint>(frames.size()); + *thread_count_ptr = static_cast<jint>(data.frames.size()); return ERR(NONE); } @@ -438,9 +488,46 @@ jvmtiError StackUtil::GetThreadListStackTraces(jvmtiEnv* env, art::Thread* current = art::Thread::Current(); art::ScopedObjectAccess soa(current); // Now we know we have the shared lock. + struct SelectStackTracesData { + SelectStackTracesData() : mutex("GetSelectStackTraces", art::LockLevel::kAbortLock) {} + + std::vector<jvmtiFrameInfo>* GetFrameStorageFor(art::Thread* self, art::Thread* thread) + REQUIRES_SHARED(art::Locks::mutator_lock_) + REQUIRES(!mutex) { + 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. + art::MutexLock mu(self, mutex); + + threads.push_back(thread); + thread_list_indices.push_back(index); + + frames.emplace_back(new std::vector<jvmtiFrameInfo>()); + return frames.back().get(); + } + } + return nullptr; + } + + art::Mutex mutex; + + // Selection data. + + std::vector<art::Handle<art::mirror::Object>> handles; + + // Storage. Only access directly after completion. + + std::vector<art::Thread*> threads; + std::vector<size_t> thread_list_indices; + + std::vector<std::unique_ptr<std::vector<jvmtiFrameInfo>>> frames; + }; + + SelectStackTracesData data; + // Decode all threads to raw pointers. Put them into a handle scope to avoid any moving GC bugs. art::VariableSizedHandleScope hs(current); - std::vector<art::Handle<art::mirror::Object>> handles; for (jint i = 0; i != thread_count; ++i) { if (thread_list[i] == nullptr) { return ERR(INVALID_THREAD); @@ -448,70 +535,30 @@ jvmtiError StackUtil::GetThreadListStackTraces(jvmtiEnv* env, if (!soa.Env()->IsInstanceOf(thread_list[i], art::WellKnownClasses::java_lang_Thread)) { return ERR(INVALID_THREAD); } - handles.push_back(hs.NewHandle(soa.Decode<art::mirror::Object>(thread_list[i]))); + data.handles.push_back(hs.NewHandle(soa.Decode<art::mirror::Object>(thread_list[i]))); } - std::vector<art::Thread*> threads; - std::vector<size_t> thread_list_indices; - std::vector<std::vector<jvmtiFrameInfo>> frames; - - { - art::ScopedThreadSuspension sts(current, art::kWaitingForDebuggerSuspension); - art::ScopedSuspendAll ssa("GetThreadListStackTraces"); - - { - std::list<art::Thread*> art_thread_list; - { - art::MutexLock mu(current, *art::Locks::thread_list_lock_); - art_thread_list = art::Runtime::Current()->GetThreadList()->GetList(); - } - - for (art::Thread* thread : art_thread_list) { - if (thread->IsStillStarting()) { - // Skip this. We can't get the jpeer, and if it is for a thread in the thread_list, - // we'll just report STARTING. - continue; - } - - // Get the peer, and check whether we know it. - 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. - GetStackTraceVectorClosure closure(0u, static_cast<size_t>(max_frame_count)); - thread->RequestSynchronousCheckpoint(&closure); - - threads.push_back(thread); - thread_list_indices.push_back(index); - frames.emplace_back(); - frames.back().swap(closure.frames); - - continue; - } - } - - // Must be not started, or dead. We'll deal with it at the end. - } - } - } + GetAllStackTracesVectorClosure<SelectStackTracesData> closure( + static_cast<size_t>(max_frame_count), &data); + art::Runtime::Current()->GetThreadList()->RunCheckpoint(&closure, nullptr); // Convert the data into our output format. // Note: we use an array of jvmtiStackInfo for convenience. The spec says we need to // allocate one big chunk for this and the actual frames, which means we need // to either be conservative or rearrange things later (the latter is implemented). - std::unique_ptr<jvmtiStackInfo[]> stack_info_array(new jvmtiStackInfo[frames.size()]); + std::unique_ptr<jvmtiStackInfo[]> stack_info_array(new jvmtiStackInfo[data.frames.size()]); std::vector<std::unique_ptr<jvmtiFrameInfo[]>> frame_infos; - frame_infos.reserve(frames.size()); + frame_infos.reserve(data.frames.size()); // Now run through and add data for each thread. size_t sum_frames = 0; - for (size_t index = 0; index < frames.size(); ++index) { + for (size_t index = 0; index < data.frames.size(); ++index) { jvmtiStackInfo& stack_info = stack_info_array.get()[index]; memset(&stack_info, 0, sizeof(jvmtiStackInfo)); - art::Thread* self = threads[index]; - const std::vector<jvmtiFrameInfo>& thread_frames = frames[index]; + art::Thread* self = data.threads[index]; + const std::vector<jvmtiFrameInfo>& thread_frames = *data.frames[index].get(); // For the time being, set the thread to null. We don't have good ScopedLocalRef // infrastructure. @@ -562,8 +609,8 @@ jvmtiError StackUtil::GetThreadListStackTraces(jvmtiEnv* env, // Check whether we found a running thread for this. // Note: For simplicity, and with the expectation that the list is usually small, use a simple // search. (The list is *not* sorted!) - auto it = std::find(thread_list_indices.begin(), thread_list_indices.end(), i); - if (it == thread_list_indices.end()) { + auto it = std::find(data.thread_list_indices.begin(), data.thread_list_indices.end(), i); + if (it == data.thread_list_indices.end()) { // No native thread. Must be new or dead. We need to fill out the stack info now. // (Need to read the Java "started" field to know whether this is starting or terminated.) art::ObjPtr<art::mirror::Object> peer = soa.Decode<art::mirror::Object>(thread_list[i]); @@ -580,7 +627,7 @@ jvmtiError StackUtil::GetThreadListStackTraces(jvmtiEnv* env, stack_info[i].frame_buffer = nullptr; } else { // Had a native thread and frames. - size_t f_index = it - thread_list_indices.begin(); + size_t f_index = it - data.thread_list_indices.begin(); jvmtiStackInfo& old_stack_info = stack_info_array.get()[f_index]; jvmtiStackInfo& new_stack_info = stack_info[i]; @@ -598,7 +645,7 @@ jvmtiError StackUtil::GetThreadListStackTraces(jvmtiEnv* env, } } - * stack_info_ptr = stack_info; + *stack_info_ptr = stack_info; return ERR(NONE); } @@ -639,8 +686,17 @@ struct GetFrameCountClosure : public art::Closure { jvmtiError StackUtil::GetFrameCount(jvmtiEnv* env ATTRIBUTE_UNUSED, jthread java_thread, jint* count_ptr) { + // It is not great that we have to hold these locks for so long, but it is necessary to ensure + // that the thread isn't dying on us. + art::ScopedObjectAccess soa(art::Thread::Current()); + art::MutexLock mu(soa.Self(), *art::Locks::thread_list_lock_); + art::Thread* thread; - jvmtiError thread_error = GetThread(art::Thread::Current()->GetJniEnv(), java_thread, &thread); + jvmtiError thread_error = GetThread(art::Thread::Current()->GetJniEnv(), + soa, + java_thread, + &thread); + if (thread_error != ERR(NONE)) { return thread_error; } @@ -709,8 +765,16 @@ jvmtiError StackUtil::GetFrameLocation(jvmtiEnv* env ATTRIBUTE_UNUSED, jint depth, jmethodID* method_ptr, jlocation* location_ptr) { + // It is not great that we have to hold these locks for so long, but it is necessary to ensure + // that the thread isn't dying on us. + art::ScopedObjectAccess soa(art::Thread::Current()); + art::MutexLock mu(soa.Self(), *art::Locks::thread_list_lock_); + art::Thread* thread; - jvmtiError thread_error = GetThread(art::Thread::Current()->GetJniEnv(), java_thread, &thread); + jvmtiError thread_error = GetThread(art::Thread::Current()->GetJniEnv(), + soa, + java_thread, + &thread); if (thread_error != ERR(NONE)) { return thread_error; } diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc index abb6f8c018..b1eb506209 100644 --- a/runtime/parsed_options.cc +++ b/runtime/parsed_options.cc @@ -18,6 +18,7 @@ #include <sstream> +#include "base/logging.h" #include "base/stringpiece.h" #include "debugger.h" #include "gc/heap.h" @@ -306,6 +307,10 @@ std::unique_ptr<RuntimeParser> ParsedOptions::MakeParser(bool ignore_unrecognize .Define("-XX:ThreadSuspendTimeout=_") // in ms .WithType<MillisecondsToNanoseconds>() // store as ns .IntoKey(M::ThreadSuspendTimeout) + .Define("-XX:SlowDebug=_") + .WithType<bool>() + .WithValueMap({{"false", false}, {"true", true}}) + .IntoKey(M::SlowDebug) .Ignore({ "-ea", "-da", "-enableassertions", "-disableassertions", "--runtime-arg", "-esa", "-dsa", "-enablesystemassertions", "-disablesystemassertions", "-Xrs", "-Xint:_", @@ -517,6 +522,8 @@ bool ParsedOptions::DoParse(const RuntimeOptions& options, MaybeOverrideVerbosity(); + SetRuntimeDebugFlagsEnabled(args.Get(M::SlowDebug)); + // -Xprofile: Trace::SetDefaultClockSource(args.GetOrDefault(M::ProfileClock)); @@ -704,6 +711,7 @@ void ParsedOptions::Usage(const char* fmt, ...) { UsageMessage(stream, " -XX:LargeObjectSpace={disabled,map,freelist}\n"); UsageMessage(stream, " -XX:LargeObjectThreshold=N\n"); UsageMessage(stream, " -XX:DumpNativeStackOnSigQuit=booleanvalue\n"); + UsageMessage(stream, " -XX:SlowDebug={false,true}\n"); UsageMessage(stream, " -Xmethod-trace\n"); UsageMessage(stream, " -Xmethod-trace-file:filename"); UsageMessage(stream, " -Xmethod-trace-file-size:integervalue\n"); diff --git a/runtime/read_barrier.cc b/runtime/read_barrier.cc new file mode 100644 index 0000000000..89ae91040a --- /dev/null +++ b/runtime/read_barrier.cc @@ -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. + */ + +#include "read_barrier.h" + +namespace art { + +DEFINE_RUNTIME_DEBUG_FLAG(ReadBarrier, kEnableToSpaceInvariantChecks); +DEFINE_RUNTIME_DEBUG_FLAG(ReadBarrier, kEnableReadBarrierInvariantChecks); + +} // namespace art diff --git a/runtime/read_barrier.h b/runtime/read_barrier.h index 296409014e..ca776854cb 100644 --- a/runtime/read_barrier.h +++ b/runtime/read_barrier.h @@ -17,6 +17,7 @@ #ifndef ART_RUNTIME_READ_BARRIER_H_ #define ART_RUNTIME_READ_BARRIER_H_ +#include "base/logging.h" #include "base/mutex.h" #include "base/macros.h" #include "gc_root.h" @@ -37,10 +38,13 @@ class ArtMethod; class ReadBarrier { public: - // Enable the to-space invariant checks. - static constexpr bool kEnableToSpaceInvariantChecks = kIsDebugBuild; - // Enable the read barrier checks. - static constexpr bool kEnableReadBarrierInvariantChecks = kIsDebugBuild; + // Enable the to-space invariant checks. This is slow and happens very often. Do not enable in + // fast-debug environment. + DECLARE_RUNTIME_DEBUG_FLAG(kEnableToSpaceInvariantChecks); + + // Enable the read barrier checks. This is slow and happens very often. Do not enable in + // fast-debug environment. + DECLARE_RUNTIME_DEBUG_FLAG(kEnableReadBarrierInvariantChecks); // It's up to the implementation whether the given field gets updated whereas the return value // must be an updated reference unless kAlwaysUpdateField is true. diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def index cfc681f23f..3d23e20e1e 100644 --- a/runtime/runtime_options.def +++ b/runtime/runtime_options.def @@ -142,4 +142,6 @@ RUNTIME_OPTIONS_KEY (void (*)(int32_t status), \ // Runtime::Abort. RUNTIME_OPTIONS_KEY (void (*)(), HookAbort, nullptr) +RUNTIME_OPTIONS_KEY (bool, SlowDebug, false) + #undef RUNTIME_OPTIONS_KEY diff --git a/runtime/stack_map.h b/runtime/stack_map.h index a22498661e..21780a1bc9 100644 --- a/runtime/stack_map.h +++ b/runtime/stack_map.h @@ -17,6 +17,8 @@ #ifndef ART_RUNTIME_STACK_MAP_H_ #define ART_RUNTIME_STACK_MAP_H_ +#include <limits> + #include "arch/code_offset.h" #include "base/bit_vector.h" #include "base/bit_utils.h" @@ -1259,7 +1261,10 @@ class InvokeInfo { // Most of the fields are encoded as ULEB128 to save space. struct CodeInfoEncoding { - static constexpr uint32_t kInvalidSize = static_cast<size_t>(-1); + using SizeType = uint32_t; + + static constexpr SizeType kInvalidSize = std::numeric_limits<SizeType>::max(); + // Byte sized tables go first to avoid unnecessary alignment bits. ByteSizedTable dex_register_map; ByteSizedTable location_catalog; @@ -1285,7 +1290,7 @@ struct CodeInfoEncoding { inline_info = BitEncodingTable<InlineInfoEncoding>(); } cache_header_size = - dchecked_integral_cast<uint32_t>(ptr - reinterpret_cast<const uint8_t*>(data)); + dchecked_integral_cast<SizeType>(ptr - reinterpret_cast<const uint8_t*>(data)); ComputeTableOffsets(); } @@ -1332,9 +1337,9 @@ struct CodeInfoEncoding { private: // Computed fields (not serialized). // Header size in bytes, cached to avoid needing to re-decoding the encoding in HeaderSize. - uint32_t cache_header_size = kInvalidSize; + SizeType cache_header_size = kInvalidSize; // Non header size in bytes, cached to avoid needing to re-decoding the encoding in NonHeaderSize. - uint32_t cache_non_header_size = kInvalidSize; + SizeType cache_non_header_size = kInvalidSize; }; /** diff --git a/runtime/thread.cc b/runtime/thread.cc index 4ddf217ca1..9f8c90acc3 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -1402,16 +1402,36 @@ class BarrierClosure : public Closure { Barrier barrier_; }; -void Thread::RequestSynchronousCheckpoint(Closure* function) { +bool Thread::RequestSynchronousCheckpoint(Closure* function) { if (this == Thread::Current()) { // Asked to run on this thread. Just run. function->Run(this); - return; + return true; } Thread* self = Thread::Current(); // The current thread is not this thread. + if (GetState() == ThreadState::kTerminated) { + return false; + } + + // Note: we're holding the thread-list lock. The thread cannot die at this point. + struct ScopedThreadListLockUnlock { + explicit ScopedThreadListLockUnlock(Thread* self_in) RELEASE(*Locks::thread_list_lock_) + : self_thread(self_in) { + Locks::thread_list_lock_->AssertHeld(self_thread); + Locks::thread_list_lock_->Unlock(self_thread); + } + + ~ScopedThreadListLockUnlock() ACQUIRE(*Locks::thread_list_lock_) { + Locks::thread_list_lock_->AssertNotHeld(self_thread); + Locks::thread_list_lock_->Lock(self_thread); + } + + Thread* self_thread; + }; + for (;;) { // If this thread is runnable, try to schedule a checkpoint. Do some gymnastics to not hold the // suspend-count lock for too long. @@ -1423,8 +1443,11 @@ void Thread::RequestSynchronousCheckpoint(Closure* function) { installed = RequestCheckpoint(&barrier_closure); } if (installed) { + // Relinquish the thread-list lock, temporarily. We should not wait holding any locks. + ScopedThreadListLockUnlock stllu(self); + ScopedThreadSuspension sts(self, ThreadState::kWaiting); barrier_closure.Wait(self); - return; + return true; } // Fall-through. } @@ -1433,7 +1456,6 @@ void Thread::RequestSynchronousCheckpoint(Closure* function) { // Note: ModifySuspendCountInternal also expects the thread_list_lock to be held in // certain situations. { - MutexLock mu(self, *Locks::thread_list_lock_); MutexLock mu2(self, *Locks::thread_suspend_count_lock_); if (!ModifySuspendCount(self, +1, nullptr, false)) { @@ -1443,16 +1465,19 @@ void Thread::RequestSynchronousCheckpoint(Closure* function) { } } - while (GetState() == ThreadState::kRunnable) { - // We became runnable again. Wait till the suspend triggered in ModifySuspendCount - // moves us to suspended. - sched_yield(); - } + { + ScopedThreadListLockUnlock stllu(self); + ScopedThreadSuspension sts(self, ThreadState::kWaiting); + while (GetState() == ThreadState::kRunnable) { + // We became runnable again. Wait till the suspend triggered in ModifySuspendCount + // moves us to suspended. + sched_yield(); + } - function->Run(this); + function->Run(this); + } { - MutexLock mu(self, *Locks::thread_list_lock_); MutexLock mu2(self, *Locks::thread_suspend_count_lock_); DCHECK_NE(GetState(), ThreadState::kRunnable); @@ -1460,7 +1485,7 @@ void Thread::RequestSynchronousCheckpoint(Closure* function) { DCHECK(updated); } - return; // We're done, break out of the loop. + return true; // We're done, break out of the loop. } } diff --git a/runtime/thread.h b/runtime/thread.h index e85ee0d2f3..770173e47e 100644 --- a/runtime/thread.h +++ b/runtime/thread.h @@ -250,8 +250,10 @@ class Thread { bool RequestCheckpoint(Closure* function) REQUIRES(Locks::thread_suspend_count_lock_); - void RequestSynchronousCheckpoint(Closure* function) - REQUIRES(!Locks::thread_suspend_count_lock_, !Locks::thread_list_lock_); + bool RequestSynchronousCheckpoint(Closure* function) + REQUIRES_SHARED(Locks::mutator_lock_) + REQUIRES(Locks::thread_list_lock_) + REQUIRES(!Locks::thread_suspend_count_lock_); bool RequestEmptyCheckpoint() REQUIRES(Locks::thread_suspend_count_lock_); diff --git a/runtime/type_reference.h b/runtime/type_reference.h index b7e964b3ad..c44019dde3 100644 --- a/runtime/type_reference.h +++ b/runtime/type_reference.h @@ -37,6 +37,15 @@ struct TypeReference { dex::TypeIndex type_index; }; +struct TypeReferenceComparator { + bool operator()(TypeReference mr1, TypeReference mr2) const { + if (mr1.dex_file != mr2.dex_file) { + return mr1.dex_file < mr2.dex_file; + } + return mr1.type_index < mr2.type_index; + } +}; + // Compare the actual referenced type names. Used for type reference deduplication. struct TypeReferenceValueComparator { bool operator()(TypeReference tr1, TypeReference tr2) const { diff --git a/runtime/vdex_file.h b/runtime/vdex_file.h index ece5491472..93d282b9bb 100644 --- a/runtime/vdex_file.h +++ b/runtime/vdex_file.h @@ -65,7 +65,8 @@ class VdexFile { private: static constexpr uint8_t kVdexMagic[] = { 'v', 'd', 'e', 'x' }; - static constexpr uint8_t kVdexVersion[] = { '0', '0', '5', '\0' }; // access flags + // Last update: Disable in-place vdex update + static constexpr uint8_t kVdexVersion[] = { '0', '0', '6', '\0' }; uint8_t magic_[4]; uint8_t version_[4]; diff --git a/test/004-JniTest/jni_test.cc b/test/004-JniTest/jni_test.cc index 81be531e44..f2edd0f688 100644 --- a/test/004-JniTest/jni_test.cc +++ b/test/004-JniTest/jni_test.cc @@ -775,5 +775,18 @@ static jint Java_Main_intCriticalNativeMethod(jint a, jint b, jint c) { return a + b + c; } +extern "C" JNIEXPORT jboolean JNICALL Java_Main_isSlowDebug(JNIEnv*, jclass) { + // Return whether slow-debug is on. Only relevant for debug builds. + if (kIsDebugBuild) { + // Register a dummy flag and get the default value it should be initialized with. + static bool dummy_flag = false; + dummy_flag = RegisterRuntimeDebugFlag(&dummy_flag); + + return dummy_flag ? JNI_TRUE : JNI_FALSE; + } + // To pass the Java-side test, just so "on" for release builds. + return JNI_TRUE; +} + } // namespace art diff --git a/test/004-JniTest/src/Main.java b/test/004-JniTest/src/Main.java index bb098e44dc..0c4ed89f81 100644 --- a/test/004-JniTest/src/Main.java +++ b/test/004-JniTest/src/Main.java @@ -24,6 +24,11 @@ import dalvik.annotation.optimization.FastNative; public class Main { public static void main(String[] args) { System.loadLibrary(args[0]); + + if (!isSlowDebug()) { + throw new RuntimeException("Slow-debug flags unexpectedly off."); + } + testFindClassOnAttachedNativeThread(); testFindFieldOnAttachedNativeThread(); testReflectFieldGetFromAttachedNativeThreadNative(); @@ -307,6 +312,8 @@ public class Main { } } } + + private static native boolean isSlowDebug(); } @FunctionalInterface diff --git a/test/004-ReferenceMap/build b/test/004-ReferenceMap/build new file mode 100644 index 0000000000..08987b556c --- /dev/null +++ b/test/004-ReferenceMap/build @@ -0,0 +1,26 @@ +#!/bin/bash +# +# Copyright (C) 2015 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Stop if something fails. +set -e + +# The test relies on DEX file produced by javac+dx so keep building with them for now +# (see b/19467889) +mkdir classes +${JAVAC} -d classes `find src -name '*.java'` +${DX} -JXmx256m --debug --dex --dump-to=classes.lst --output=classes.dex \ + --dump-width=1000 ${DX_FLAGS} classes +zip $TEST_NAME.jar classes.dex diff --git a/test/004-ReferenceMap/stack_walk_refmap_jni.cc b/test/004-ReferenceMap/stack_walk_refmap_jni.cc index 6c1610011f..1ce20e2b3d 100644 --- a/test/004-ReferenceMap/stack_walk_refmap_jni.cc +++ b/test/004-ReferenceMap/stack_walk_refmap_jni.cc @@ -60,10 +60,10 @@ struct ReferenceMap2Visitor : public CheckReferenceMapVisitor { // We eliminate the non-live registers at a return, so only v3 is live. // Note that it is OK for a compiler to not have a dex map at this dex PC because // a return is not necessarily a safepoint. - CHECK_REGS_CONTAIN_REFS(0x14U, false, 2); // v2: y + CHECK_REGS_CONTAIN_REFS(0x13U, false, 3); // v3: y // Note that v0: ex can be eliminated because it's a dead merge of two different exceptions. CHECK_REGS_CONTAIN_REFS(0x18U, true, 8, 2, 1); // v8: this, v2: y, v1: x (dead v0: ex) - CHECK_REGS_CONTAIN_REFS(0x22U, true, 8, 2, 1); // v8: this, v2: y, v1: x (dead v0: ex) + CHECK_REGS_CONTAIN_REFS(0x21U, true, 8, 2, 1); // v8: this, v2: y, v1: x (dead v0: ex) if (!GetCurrentOatQuickMethodHeader()->IsOptimized()) { CHECK_REGS_CONTAIN_REFS(0x27U, true, 8, 4, 2, 1); // v8: this, v4: ex, v2: y, v1: x @@ -80,79 +80,124 @@ struct ReferenceMap2Visitor : public CheckReferenceMapVisitor { } }; -// DEX code +// Dex instructions for the function 'f' in ReferenceMap.java +// Virtual methods - +// #0 : (in LReferenceMap;) +// name : 'f' +// type : '()Ljava/lang/Object;' +// access : 0x0000 () +// code - +// registers : 9 +// ins : 1 +// outs : 2 +// insns size : 51 16-bit code units +// |[0001e8] ReferenceMap.f:()Ljava/lang/Object; +// |0000: const/4 v4, #int 2 // #2 +// |0001: const/4 v7, #int 0 // #0 +// |0002: const/4 v6, #int 1 // #1 // -// 0000: const/4 v4, #int 2 // #2 -// 0001: const/4 v7, #int 0 // #0 -// 0002: const/4 v6, #int 1 // #1 -// 0003: new-array v1, v4, [Ljava/lang/Object; // type@0007 -// 0005: const/4 v2, #int 0 // #0 -// 0006: new-instance v3, Ljava/lang/Object; // type@0003 -// 0008: invoke-direct {v3}, Ljava/lang/Object;.<init>:()V // method@0004 -// 000b: const/4 v4, #int 2 // #2 -// 000c: aput-object v3, v1, v4 -// 000e: aput-object v3, v1, v6 -// 0010: invoke-virtual {v8, v7}, LMain;.refmap:(I)I // method@0003 -// 0013: move-object v2, v3 -// 0014: return-object v2 -// 0015: move-exception v0 -// 0016: if-nez v2, 0020 // +000a -// 0018: new-instance v4, Ljava/lang/Object; // type@0003 -// 001a: invoke-direct {v4}, Ljava/lang/Object;.<init>:()V // method@0004 -// 001d: const/4 v5, #int 1 // #1 -// 001e: aput-object v4, v1, v5 -// 0020: aput-object v2, v1, v6 -// 0022: invoke-virtual {v8, v7}, LMain;.refmap:(I)I // method@0003 -// 0025: goto 0014 // -0011 -// 0026: move-exception v4 -// 0027: aput-object v2, v1, v6 -// 0029: invoke-virtual {v8, v7}, LMain;.refmap:(I)I // method@0003 -// 002c: throw v4 -// 002d: move-exception v4 -// 002e: move-object v2, v3 -// 002f: goto 0027 // -0008 -// 0030: move-exception v0 -// 0031: move-object v2, v3 -// 0032: goto 0016 // -001c -// catches : 3 -// 0x0006 - 0x000b -// Ljava/lang/Exception; -> 0x0015 -// <any> -> 0x0026 -// 0x000c - 0x000e -// Ljava/lang/Exception; -> 0x0030 -// <any> -> 0x002d -// 0x0018 - 0x0020 -// <any> -> 0x0026 -// positions : -// 0x0003 line=22 -// 0x0005 line=23 -// 0x0006 line=25 -// 0x000b line=26 -// 0x000e line=32 -// 0x0010 line=33 -// 0x0014 line=35 -// 0x0015 line=27 -// 0x0016 line=28 -// 0x0018 line=29 -// 0x0020 line=32 -// 0x0022 line=33 -// 0x0026 line=31 -// 0x0027 line=32 -// 0x0029 line=33 -// 0x002c line=31 -// 0x0030 line=27 -// locals : -// 0x0006 - 0x000b reg=2 y Ljava/lang/Object; -// 0x000b - 0x0014 reg=3 y Ljava/lang/Object; -// 0x0015 - 0x0016 reg=2 y Ljava/lang/Object; -// 0x0016 - 0x0026 reg=0 ex Ljava/lang/Exception; -// 0x002d - 0x002f reg=3 y Ljava/lang/Object; -// 0x002f - 0x0030 reg=2 y Ljava/lang/Object; -// 0x0030 - 0x0032 reg=3 y Ljava/lang/Object; -// 0x0031 - 0x0033 reg=0 ex Ljava/lang/Exception; -// 0x0005 - 0x0033 reg=1 x [Ljava/lang/Object; -// 0x0032 - 0x0033 reg=2 y Ljava/lang/Object; -// 0x0000 - 0x0033 reg=8 this LMain; +// 0:[Unknown],1:[Unknown],2:[Unknown],3:[Unknown],4:[32-bit Constant: 2],5:[Unknown],6:[32-bit Constant: 1],7:[Zero],8:[Reference: ReferenceMap], +// |0003: new-array v1, v4, [Ljava/lang/Object; // type@0007 +// |0005: const/4 v2, #int 0 // #0 + +// 0:[Unknown],1:[Reference: java.lang.Object[]],2:[Zero],3:[Unknown],4:[32-bit Constant: 2],5:[Unknown],6:[32-bit Constant: 1],7:[Zero],8:[Reference: ReferenceMap], +// |0006: new-instance v3, Ljava/lang/Object; // type@0003 + +// [Unknown],1:[Reference: java.lang.Object[]],2:[Zero],3:[Uninitialized Reference: java.lang.Object],4:[32-bit Constant: 2],5:[Unknown],6:[32-bit Constant: 1],7:[Zero],8:[Reference: ReferenceMap], +// |0008: +invoke-object-init/range {}, Ljava/lang/Object;.<init>:()V // method@0005 +// |000b: const/4 v4, #int 2 // #2 + +// 0:[Unknown],1:[Reference: java.lang.Object[]],2:[Zero],3:[Reference: java.lang.Object],4:[32-bit Constant: 2],5:[Unknown],6:[32-bit Constant: 1],7:[Zero],8:[Reference: ReferenceMap], +// |000c: aput-object v3, v1, v4 + +// 0:[Unknown],1:[Reference: java.lang.Object[]],2:[Zero],3:[Reference: java.lang.Object],4:[32-bit Constant: 2],5:[Unknown],6:[32-bit Constant: 1],7:[Zero],8:[Reference: ReferenceMap], +// |000e: aput-object v3, v1, v6 + +// 0:[Unknown],1:[Reference: java.lang.Object[]],2:[Zero],3:[Reference: java.lang.Object],4:[32-bit Constant: 2],5:[Unknown],6:[32-bit Constant: 1],7:[Zero],8:[Reference: ReferenceMap], +// |0010: +invoke-virtual-quick {v8, v7}, [000c] // vtable #000c + +// 0:[Conflict],1:[Conflict],2:[Conflict],3:[Reference: java.lang.Object],4:[Conflict],5:[Conflict],6:[Conflict],7:[Conflict],8:[Conflict], +// |0013: return-object v3 +// |0014: move-exception v0 + +// 0:[Reference: java.lang.Exception],1:[Reference: java.lang.Object[]],2:[Reference: java.lang.Object],3:[Conflict],4:[32-bit Constant: 2],5:[Unknown],6:[32-bit Constant: 1],7:[Zero],8:[Reference: ReferenceMap], +// |0015: if-nez v2, 001f // +000a +// |0017: const/4 v4, #int 1 // #1 + +// 0:[Reference: java.lang.Exception],1:[Reference: java.lang.Object[]],2:[Reference: java.lang.Object],3:[Conflict],4:[32-bit Constant: 1],5:[Unknown],6:[32-bit Constant: 1],7:[Zero],8:[Reference: ReferenceMap], +// |0018: new-instance v5, Ljava/lang/Object; // type@0003 + +// 0:[Reference: java.lang.Exception],1:[Reference: java.lang.Object[]],2:[Reference: java.lang.Object],3:[Conflict],4:[32-bit Constant: 1],5:[Uninitialized Reference: java.lang.Object],6:[32-bit Constant: 1],7:[Zero],8:[Reference: ReferenceMap], +// |001a: +invoke-object-init/range {}, Ljava/lang/Object;.<init>:()V // method@0005 + +// 0:[Reference: java.lang.Exception],1:[Reference: java.lang.Object[]],2:[Reference: java.lang.Object],3:[Conflict],4:[32-bit Constant: 1],5:[Reference: java.lang.Object],6:[32-bit Constant: 1],7:[Zero],8:[Reference: ReferenceMap], +// |001d: aput-object v5, v1, v4 + +// 0:[Reference: java.lang.Exception],1:[Reference: java.lang.Object[]],2:[Reference: java.lang.Object],3:[Conflict],4:[32-bit Constant: 2],5:[Conflict],6:[32-bit Constant: 1],7:[Zero],8:[Reference: ReferenceMap], +// |001f: aput-object v2, v1, v6 + +// 0:[Reference: java.lang.Exception],1:[Reference: java.lang.Object[]],2:[Reference: java.lang.Object],3:[Conflict],4:[32-bit Constant: 2],5:[Conflict],6:[32-bit Constant: 1],7:[Zero],8:[Reference: ReferenceMap], +// |0021: +invoke-virtual-quick {v8, v7}, [000c] // vtable #000c +// |0024: move-object v3, v2 + +// 0:[Reference: java.lang.Exception],1:[Reference: java.lang.Object[]],2:[Reference: java.lang.Object],3:[Reference: java.lang.Object],4:[32-bit Constant: 2],5:[Conflict],6:[32-bit Constant: 1],7:[Zero],8:[Reference: ReferenceMap], +// |0025: goto 0013 // -0012 +// |0026: move-exception v4 + +// 0:[Conflict],1:[Reference: java.lang.Object[]],2:[Reference: java.lang.Object],3:[Conflict],4:[Reference: java.lang.Throwable],5:[Conflict],6:[32-bit Constant: 1],7:[Zero],8:[Reference: ReferenceMap], +// |0027: aput-object v2, v1, v6 + +// 0:[Conflict],1:[Reference: java.lang.Object[]],2:[Reference: java.lang.Object],3:[Conflict],4:[Reference: java.lang.Throwable],5:[Conflict],6:[32-bit Constant: 1],7:[Zero],8:[Reference: ReferenceMap], +// |0029: +invoke-virtual-quick {v8, v7}, [000c] // vtable #000c + +// 0:[Conflict],1:[Reference: java.lang.Object[]],2:[Reference: java.lang.Object],3:[Conflict],4:[Reference: java.lang.Throwable],5:[Conflict],6:[32-bit Constant: 1],7:[Zero],8:[Reference: ReferenceMap], +// |002c: throw v4 +// |002d: move-exception v4 +// |002e: move-object v2, v3 + +// 0:[Unknown],1:[Reference: java.lang.Object[]],2:[Reference: java.lang.Object],3:[Reference: java.lang.Object],4:[Reference: java.lang.Throwable],5:[Unknown],6:[32-bit Constant: 1],7:[Zero],8:[Reference: ReferenceMap], +// |002f: goto 0027 // -0008 +// |0030: move-exception v0 +// |0031: move-object v2, v3 + +// 0:[Reference: java.lang.Exception],1:[Reference: java.lang.Object[]],2:[Reference: java.lang.Object],3:[Reference: java.lang.Object],4:[32-bit Constant: 2],5:[Unknown],6:[32-bit Constant: 1],7:[Zero],8:[Reference: ReferenceMap], +// |0032: goto 0015 // -001d +// catches : 3 +// 0x0006 - 0x000b +// Ljava/lang/Exception; -> 0x0014 +// <any> -> 0x0026 +// 0x000c - 0x000e +// Ljava/lang/Exception; -> 0x0030 +// <any> -> 0x002d +// 0x0018 - 0x001f +// <any> -> 0x0026 +// positions : +// 0x0003 line=8 +// 0x0005 line=9 +// 0x0006 line=11 +// 0x000b line=12 +// 0x000e line=18 +// 0x0010 line=19 +// 0x0013 line=21 +// 0x0014 line=13 +// 0x0015 line=14 +// 0x0017 line=15 +// 0x001f line=18 +// 0x0021 line=19 +// 0x0025 line=20 +// 0x0026 line=18 +// 0x0029 line=19 +// 0x002d line=18 +// 0x0030 line=13 +// locals : +// 0x0006 - 0x000b reg=2 y Ljava/lang/Object; +// 0x000b - 0x0013 reg=3 y Ljava/lang/Object; +// 0x0014 - 0x0015 reg=2 y Ljava/lang/Object; +// 0x0015 - 0x0026 reg=0 ex Ljava/lang/Exception; +// 0x002d - 0x0032 reg=3 y Ljava/lang/Object; +// 0x0005 - 0x0033 reg=1 x [Ljava/lang/Object; +// 0x0032 - 0x0033 reg=2 y Ljava/lang/Object; +// 0x0000 - 0x0033 reg=8 this LReferenceMap; extern "C" JNIEXPORT jint JNICALL Java_Main_refmap(JNIEnv*, jobject, jint count) { // Visitor diff --git a/test/004-StackWalk/build b/test/004-StackWalk/build new file mode 100644 index 0000000000..08987b556c --- /dev/null +++ b/test/004-StackWalk/build @@ -0,0 +1,26 @@ +#!/bin/bash +# +# Copyright (C) 2015 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Stop if something fails. +set -e + +# The test relies on DEX file produced by javac+dx so keep building with them for now +# (see b/19467889) +mkdir classes +${JAVAC} -d classes `find src -name '*.java'` +${DX} -JXmx256m --debug --dex --dump-to=classes.lst --output=classes.dex \ + --dump-width=1000 ${DX_FLAGS} classes +zip $TEST_NAME.jar classes.dex diff --git a/test/004-StackWalk/stack_walk_jni.cc b/test/004-StackWalk/stack_walk_jni.cc index 795f168055..89e2e660d9 100644 --- a/test/004-StackWalk/stack_walk_jni.cc +++ b/test/004-StackWalk/stack_walk_jni.cc @@ -43,31 +43,31 @@ class TestReferenceMapVisitor : public CheckReferenceMapVisitor { // Given the method name and the number of times the method has been called, // we know the Dex registers with live reference values. Assert that what we // find is what is expected. - if (m_name == "$noinline$f") { + if (m_name == "f") { if (gJava_StackWalk_refmap_calls == 1) { CHECK_EQ(1U, GetDexPc()); - CHECK_REGS(1); // v1: this + CHECK_REGS(4); } else { CHECK_EQ(gJava_StackWalk_refmap_calls, 2); CHECK_EQ(5U, GetDexPc()); - CHECK_REGS(1); // v1: this + CHECK_REGS(4); } } else if (m_name == "g") { if (gJava_StackWalk_refmap_calls == 1) { - CHECK_EQ(0xdU, GetDexPc()); - CHECK_REGS(0, 2); // v2: this (Note that v1 is not in the minimal root set) + CHECK_EQ(0xcU, GetDexPc()); + CHECK_REGS(0, 2); // Note that v1 is not in the minimal root set } else { CHECK_EQ(gJava_StackWalk_refmap_calls, 2); - CHECK_EQ(0xdU, GetDexPc()); + CHECK_EQ(0xcU, GetDexPc()); CHECK_REGS(0, 2); } } else if (m_name == "shlemiel") { if (gJava_StackWalk_refmap_calls == 1) { - CHECK_EQ(0x393U, GetDexPc()); + CHECK_EQ(0x380U, GetDexPc()); CHECK_REGS(2, 4, 5, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 21, 25); } else { CHECK_EQ(gJava_StackWalk_refmap_calls, 2); - CHECK_EQ(0x393U, GetDexPc()); + CHECK_EQ(0x380U, GetDexPc()); CHECK_REGS(2, 4, 5, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 21, 25); } } diff --git a/test/089-many-methods/build b/test/089-many-methods/build index 58144e1dc8..ff77c60f64 100644 --- a/test/089-many-methods/build +++ b/test/089-many-methods/build @@ -43,4 +43,8 @@ function writeFileMethod(name) { printf("}\n") > fileName; }' -./default-build +# The test relies on the error message produced by dx, not jack, so keep building with dx for now +# (b/19467889). +mkdir classes +${JAVAC} -d classes `find src -name '*.java'` +${DX} -JXmx1024m --dex --no-optimize classes diff --git a/test/089-many-methods/expected.txt b/test/089-many-methods/expected.txt index bfee8b3874..786df7c76d 100644 --- a/test/089-many-methods/expected.txt +++ b/test/089-many-methods/expected.txt @@ -1,2 +1,6 @@ -ERROR: Dex writing phase: classes.dex has too many IDs. Try using multi-dex -build exit status: 4 + +trouble writing output: Too many field references to fit in one dex file: 131000; max is 65536. +You may try using multi-dex. If multi-dex is enabled then the list of classes for the main dex list is too large. +References by package: +131000 default +build exit status: 2 diff --git a/test/580-checker-string-fact-intrinsics/src/Main.java b/test/580-checker-string-fact-intrinsics/src-art/Main.java index a2e34bffd0..a2e34bffd0 100644 --- a/test/580-checker-string-fact-intrinsics/src/Main.java +++ b/test/580-checker-string-fact-intrinsics/src-art/Main.java diff --git a/test/616-cha-interface-default/build b/test/616-cha-interface-default/build new file mode 100644 index 0000000000..d9654f8d38 --- /dev/null +++ b/test/616-cha-interface-default/build @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 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. + +./default-build "$@" --experimental default-methods diff --git a/test/652-deopt-intrinsic/src/Main.java b/test/652-deopt-intrinsic/src/Main.java index a82580c8a1..8c583c0262 100644 --- a/test/652-deopt-intrinsic/src/Main.java +++ b/test/652-deopt-intrinsic/src/Main.java @@ -27,16 +27,22 @@ public class Main { for (int i = 0; i < 5000; i++) { $noinline$doCall("foo"); $noinline$doCall(m); - if (numberOfDeoptimizations() != 0) { - throw new Error("Unexpected deoptimizations"); - } } } public static boolean $noinline$doCall(Object foo) { - return foo.equals(Main.class); + boolean isCompiledAtEntry = !isInterpreted(); + boolean result = foo.equals(Main.class); + + // Test that the 'equals' above did not lead to a deoptimization. + if (isCompiledAtEntry) { + if (isInterpreted()) { + throw new Error("Unexpected deoptimization"); + } + } + return result; } - public static native int numberOfDeoptimizations(); + public static native boolean isInterpreted(); public static native void ensureJitCompiled(Class<?> cls, String methodName); } diff --git a/test/656-loop-deopt/expected.txt b/test/656-loop-deopt/expected.txt new file mode 100644 index 0000000000..6a5618ebc6 --- /dev/null +++ b/test/656-loop-deopt/expected.txt @@ -0,0 +1 @@ +JNI_OnLoad called diff --git a/test/656-loop-deopt/info.txt b/test/656-loop-deopt/info.txt new file mode 100644 index 0000000000..31e4052bd2 --- /dev/null +++ b/test/656-loop-deopt/info.txt @@ -0,0 +1,2 @@ +Regression test for the compiler, whose loop optimization used to wrongly +remove environment uses of HDeoptimize instructions. diff --git a/test/656-loop-deopt/src/Main.java b/test/656-loop-deopt/src/Main.java new file mode 100644 index 0000000000..c99cccf4f1 --- /dev/null +++ b/test/656-loop-deopt/src/Main.java @@ -0,0 +1,104 @@ +/* + * 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 { + public static void main(String[] args) throws Exception { + System.loadLibrary(args[0]); + + $noinline$intUpdate(new Main()); + ensureJitCompiled(Main.class, "$noinline$intUpdate"); + $noinline$intUpdate(new SubMain()); + if (myIntStatic != 5000) { + throw new Error("Expected 5000, got " + myIntStatic); + } + + $noinline$objectUpdate(new Main()); + ensureJitCompiled(Main.class, "$noinline$objectUpdate"); + $noinline$objectUpdate(new SubMain()); + + $noinline$loopIncrement(new Main()); + ensureJitCompiled(Main.class, "$noinline$loopIncrement"); + $noinline$loopIncrement(new SubMain()); + } + + public boolean doCheck() { + return false; + } + + public static void $noinline$intUpdate(Main m) { + int a = 0; + // We used to kill 'a' when the inline cache of 'doCheck' only + // contains 'Main' (which makes the only branch using 'a' dead). + // So the deoptimization at the inline cache was incorrectly assuming + // 'a' was dead. + for (int i = 0; i < 5000; i++) { + if (m.doCheck()) { + a++; + // We make this branch the only true user of the 'a' phi. All other uses + // of 'a' are phi updates. + myIntStatic = a; + } else if (myIntStatic == 42) { + a = 1; + } + } + } + + public static void $noinline$objectUpdate(Main m) { + Object o = new Object(); + // We used to kill 'o' when the inline cache of 'doCheck' only + // contains 'Main' (which makes the only branch using 'a' dead). + // So the deoptimization at the inline cache was incorrectly assuming + // 'o' was dead. + // This lead to a NPE on the 'toString' call just after deoptimizing. + for (int i = 0; i < 5000; i++) { + if (m.doCheck()) { + // We make this branch the only true user of the 'o' phi. All other uses + // of 'o' are phi updates. + o.toString(); + } else if (myIntStatic == 42) { + o = m; + } + } + } + + public static void $noinline$loopIncrement(Main m) { + int k = 0; + // We used to kill 'k' and replace it with 5000 when the inline cache + // of 'doCheck' only contains 'Main'. + // So the deoptimization at the inline cache was incorrectly assuming + // 'k' was 5000. + for (int i = 0; i < 5000; i++, k++) { + if (m.doCheck()) { + // We make this branch the only true user of the 'a' phi. All other uses + // of 'a' are phi updates. + myIntStatic = k; + } + } + if (k != 5000) { + throw new Error("Expected 5000, got " + k); + } + } + + public static int myIntStatic = 0; + + public static native void ensureJitCompiled(Class<?> itf, String name); +} + +class SubMain extends Main { + public boolean doCheck() { + return true; + } +} diff --git a/test/907-get-loaded-classes/src/art/Cerr.java b/test/907-get-loaded-classes/src/art/Cerr.java new file mode 100644 index 0000000000..807674ddf6 --- /dev/null +++ b/test/907-get-loaded-classes/src/art/Cerr.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. + */ + +package art; + +public class Cerr { + static { + throwError(); + } + + public static void throwError() { + throw new Error("Error!"); + } +} diff --git a/test/907-get-loaded-classes/src/art/Test907.java b/test/907-get-loaded-classes/src/art/Test907.java index df9ce7ae9f..346e042657 100644 --- a/test/907-get-loaded-classes/src/art/Test907.java +++ b/test/907-get-loaded-classes/src/art/Test907.java @@ -25,21 +25,42 @@ public class Test907 { doTest(); } + static final String NOT_A_REAL_CLASS = "art.NotARealClass_Foo_Bar_Baz"; + public static void doTest() throws Exception { // Ensure some classes are loaded. A a = new A(); B b = new B(); A[] aArray = new A[5]; + int[] iArray = new int[4]; + Object c; + Object d; + try { + // Cerr throws in it's clinit. + c = new Cerr(); + } catch (Error e) { } + try { + d = Class.forName(NOT_A_REAL_CLASS).getDeclaredConstructor().newInstance(); + } catch (Exception e) {} String[] classes = getLoadedClasses(); HashSet<String> classesSet = new HashSet<>(Arrays.asList(classes)); String[] shouldBeLoaded = new String[] { "java.lang.Object", "java.lang.Class", "java.lang.String", "art.Test907$A", - "art.Test907$B", "[Lart.Test907$A;" + "art.Test907$B", "[Lart.Test907$A;", "[I", "art.Cerr", + }; + String[] shouldNotBeLoaded = new String[] { + "I", "J", "B", "Z", "V", "J", "F", "D", "C", "S", NOT_A_REAL_CLASS, }; boolean error = false; + for (String s : shouldNotBeLoaded) { + if (classesSet.contains(s)) { + System.out.println("Found" + s); + error = true; + } + } for (String s : shouldBeLoaded) { if (!classesSet.contains(s)) { System.out.println("Did not find " + s); diff --git a/test/910-methods/check b/test/910-methods/check new file mode 100644 index 0000000000..835850004a --- /dev/null +++ b/test/910-methods/check @@ -0,0 +1,22 @@ +#!/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. + +# Jack has a different set of bytecode offsets/method IDs in the expected.txt +if [[ "$USE_JACK" == true ]]; then + patch -p0 expected.txt < expected_jack.diff +fi + +./default-check "$@" diff --git a/test/910-methods/expected.txt b/test/910-methods/expected.txt index c14c6c49e1..45de3db1fb 100644 --- a/test/910-methods/expected.txt +++ b/test/910-methods/expected.txt @@ -4,7 +4,7 @@ class java.lang.Object Max locals: 3 Argument size: 1 Location start: 0 -Location end: 40 +Location end: 39 Is native: false Is obsolete: false Is synthetic: false diff --git a/test/910-methods/expected_jack.diff b/test/910-methods/expected_jack.diff new file mode 100644 index 0000000000..2fe6953f6c --- /dev/null +++ b/test/910-methods/expected_jack.diff @@ -0,0 +1,4 @@ +7c7 +< Location end: 39 +--- +> Location end: 40 diff --git a/test/911-get-stack-trace/check b/test/911-get-stack-trace/check new file mode 100644 index 0000000000..835850004a --- /dev/null +++ b/test/911-get-stack-trace/check @@ -0,0 +1,22 @@ +#!/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. + +# Jack has a different set of bytecode offsets/method IDs in the expected.txt +if [[ "$USE_JACK" == true ]]; then + patch -p0 expected.txt < expected_jack.diff +fi + +./default-check "$@" diff --git a/test/911-get-stack-trace/expected.txt b/test/911-get-stack-trace/expected.txt index 8510ff90ad..8177f494ac 100644 --- a/test/911-get-stack-trace/expected.txt +++ b/test/911-get-stack-trace/expected.txt @@ -21,7 +21,7 @@ From top baz (IIILart/ControlData;)Ljava/lang/Object; 9 34 bar (IIILart/ControlData;)J 0 26 foo (IIILart/ControlData;)I 0 21 - doTest ()V 38 25 + doTest ()V 34 25 run ()V 0 25 --------- print (Ljava/lang/Thread;II)V 0 38 @@ -41,7 +41,7 @@ From top baz (IIILart/ControlData;)Ljava/lang/Object; 9 34 bar (IIILart/ControlData;)J 0 26 foo (IIILart/ControlData;)I 0 21 - doTest ()V 42 26 + doTest ()V 38 26 run ()V 0 25 --------- getStackTrace (Ljava/lang/Thread;II)[[Ljava/lang/String; -1 -2 @@ -62,7 +62,7 @@ From bottom baz (IIILart/ControlData;)Ljava/lang/Object; 9 34 bar (IIILart/ControlData;)J 0 26 foo (IIILart/ControlData;)I 0 21 - doTest ()V 65 32 + doTest ()V 60 32 run ()V 0 25 --------- bar (IIILart/ControlData;)J 0 26 @@ -360,7 +360,7 @@ Signal Catcher Test911 getAllStackTraces (I)[[Ljava/lang/Object; -1 -2 printAll (I)V 0 75 - doTest ()V 128 59 + doTest ()V 122 59 run ()V 24 37 --------- @@ -595,7 +595,7 @@ Signal Catcher Test911 getAllStackTraces (I)[[Ljava/lang/Object; -1 -2 printAll (I)V 0 75 - doTest ()V 133 61 + doTest ()V 127 61 run ()V 24 37 --------- @@ -627,7 +627,7 @@ ThreadListTraces Thread 8 Test911 getThreadListStackTraces ([Ljava/lang/Thread;I)[[Ljava/lang/Object; -1 -2 printList ([Ljava/lang/Thread;I)V 0 68 - doTest ()V 116 54 + doTest ()V 112 54 run ()V 32 41 --------- @@ -674,7 +674,7 @@ ThreadListTraces Thread 8 Test911 getThreadListStackTraces ([Ljava/lang/Thread;I)[[Ljava/lang/Object; -1 -2 printList ([Ljava/lang/Thread;I)V 0 68 - doTest ()V 121 56 + doTest ()V 117 56 run ()V 32 41 --------- @@ -789,7 +789,7 @@ ThreadListTraces Thread 8 4 JVMTI_ERROR_ILLEGAL_ARGUMENT [public static native java.lang.Object[] art.Frames.getFrameLocation(java.lang.Thread,int), ffffffff] -[public static void art.Frames.doTestSameThread(), 38] +[public static void art.Frames.doTestSameThread(), 35] [public static void art.Frames.doTest() throws java.lang.Exception, 0] [public void art.Test911$1.run(), 28] JVMTI_ERROR_NO_MORE_FRAMES diff --git a/test/911-get-stack-trace/expected_jack.diff b/test/911-get-stack-trace/expected_jack.diff new file mode 100644 index 0000000000..b7481941c7 --- /dev/null +++ b/test/911-get-stack-trace/expected_jack.diff @@ -0,0 +1,32 @@ +24c24 +< doTest ()V 34 25 +--- +> doTest ()V 38 25 +44c44 +< doTest ()V 38 26 +--- +> doTest ()V 42 26 +65c65 +< doTest ()V 60 32 +--- +> doTest ()V 65 32 +363c363 +< doTest ()V 122 59 +--- +> doTest ()V 128 59 +598c598 +< doTest ()V 127 61 +--- +> doTest ()V 133 61 +630c630 +< doTest ()V 112 54 +--- +> doTest ()V 116 54 +677c677 +< doTest ()V 117 56 +--- +> doTest ()V 121 56 +792c792 +< [public static void art.Frames.doTestSameThread(), 35] +--- +> [public static void art.Frames.doTestSameThread(), 38] diff --git a/test/913-heaps/check b/test/913-heaps/check new file mode 100644 index 0000000000..835850004a --- /dev/null +++ b/test/913-heaps/check @@ -0,0 +1,22 @@ +#!/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. + +# Jack has a different set of bytecode offsets/method IDs in the expected.txt +if [[ "$USE_JACK" == true ]]; then + patch -p0 expected.txt < expected_jack.diff +fi + +./default-check "$@" diff --git a/test/913-heaps/expected.txt b/test/913-heaps/expected.txt index 80f8b9e947..6144881a55 100644 --- a/test/913-heaps/expected.txt +++ b/test/913-heaps/expected.txt @@ -1,7 +1,7 @@ --- true true root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 3000@0 [size=136, length=-1] -root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=13,location= 32])--> 1@1000 [size=16, length=-1] +root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=13,location= 30])--> 1@1000 [size=16, length=-1] root@root --(stack-local[id=1,tag=3000,depth=3,method=doFollowReferencesTest,vreg=1,location= 28])--> 3000@0 [size=136, length=-1] root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=136, length=-1] root@root --(thread)--> 3000@0 [size=136, length=-1] @@ -46,8 +46,8 @@ root@root --(thread)--> 3000@0 [size=136, length=-1] root@root --(jni-global)--> 1@1000 [size=16, length=-1] root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 1@1000 [size=16, length=-1] root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 3000@0 [size=136, length=-1] -root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=13,location= 10])--> 1@1000 [size=16, length=-1] -root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 10])--> 1@1000 [size=16, length=-1] +root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=11,location= 8])--> 1@1000 [size=16, length=-1] +root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 8])--> 1@1000 [size=16, length=-1] root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestRoot,vreg=4,location= 19])--> 1@1000 [size=16, length=-1] root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=136, length=-1] root@root --(thread)--> 1@1000 [size=16, length=-1] @@ -99,7 +99,7 @@ root@root --(thread)--> 3000@0 [size=136, length=-1] 3@1001 --(class)--> 1001@0 [size=123, length=-1] --- root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 3000@0 [size=136, length=-1] -root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=13,location= 32])--> 1@1000 [size=16, length=-1] +root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=13,location= 30])--> 1@1000 [size=16, length=-1] root@root --(stack-local[id=1,tag=3000,depth=3,method=doFollowReferencesTest,vreg=1,location= 28])--> 3000@0 [size=136, length=-1] root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=136, length=-1] root@root --(thread)--> 3000@0 [size=136, length=-1] @@ -112,8 +112,8 @@ root@root --(thread)--> 3000@0 [size=136, length=-1] root@root --(jni-global)--> 1@1000 [size=16, length=-1] root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 1@1000 [size=16, length=-1] root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 3000@0 [size=136, length=-1] -root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=13,location= 10])--> 1@1000 [size=16, length=-1] -root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 10])--> 1@1000 [size=16, length=-1] +root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=11,location= 8])--> 1@1000 [size=16, length=-1] +root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 8])--> 1@1000 [size=16, length=-1] root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestRoot,vreg=4,location= 19])--> 1@1000 [size=16, length=-1] root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=136, length=-1] root@root --(thread)--> 1@1000 [size=16, length=-1] @@ -159,7 +159,7 @@ root@root --(thread)--> 3000@0 [size=136, length=-1] 10007@0 (instance, float, index=13) 000000003f9d70a4 10008 --- klass --- -root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=13,location= 32])--> 1@1000 [size=16, length=-1] +root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=13,location= 30])--> 1@1000 [size=16, length=-1] 1@1000 --(field@2)--> 2@1000 [size=16, length=-1] 3@1001 --(field@4)--> 4@1000 [size=16, length=-1] 500@0 --(array-element@1)--> 2@1000 [size=16, length=-1] @@ -174,8 +174,8 @@ root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonR --- root@root --(jni-global)--> 1@1000 [size=16, length=-1] root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 1@1000 [size=16, length=-1] -root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=13,location= 10])--> 1@1000 [size=16, length=-1] -root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 10])--> 1@1000 [size=16, length=-1] +root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=11,location= 8])--> 1@1000 [size=16, length=-1] +root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 8])--> 1@1000 [size=16, length=-1] root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestRoot,vreg=4,location= 19])--> 1@1000 [size=16, length=-1] root@root --(thread)--> 1@1000 [size=16, length=-1] 1@1000 --(field@2)--> 2@1000 [size=16, length=-1] @@ -198,7 +198,7 @@ root@root --(thread)--> 1@1000 [size=16, length=-1] --- ---- untagged objects root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 3000@0 [size=136, length=-1] -root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=13,location= 32])--> 1@1000 [size=16, length=-1] +root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=13,location= 30])--> 1@1000 [size=16, length=-1] root@root --(stack-local[id=1,tag=3000,depth=3,method=doFollowReferencesTest,vreg=1,location= 28])--> 3000@0 [size=136, length=-1] root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=136, length=-1] root@root --(thread)--> 3000@0 [size=136, length=-1] @@ -243,8 +243,8 @@ root@root --(thread)--> 3000@0 [size=136, length=-1] root@root --(jni-global)--> 1@1000 [size=16, length=-1] root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 1@1000 [size=16, length=-1] root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 3000@0 [size=136, length=-1] -root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=13,location= 10])--> 1@1000 [size=16, length=-1] -root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 10])--> 1@1000 [size=16, length=-1] +root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=11,location= 8])--> 1@1000 [size=16, length=-1] +root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 8])--> 1@1000 [size=16, length=-1] root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestRoot,vreg=4,location= 19])--> 1@1000 [size=16, length=-1] root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=136, length=-1] root@root --(thread)--> 1@1000 [size=16, length=-1] @@ -344,7 +344,7 @@ root@root --(thread)--> 3000@0 [size=136, length=-1] 6@1000 --(class)--> 1000@0 [size=123, length=-1] --- ---- untagged classes -root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=13,location= 32])--> 1@1000 [size=16, length=-1] +root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=13,location= 30])--> 1@1000 [size=16, length=-1] 1@1000 --(field@2)--> 2@1000 [size=16, length=-1] 1@1000 --(field@3)--> 3@1001 [size=24, length=-1] 3@1001 --(field@4)--> 4@1000 [size=16, length=-1] @@ -363,8 +363,8 @@ root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonR --- root@root --(jni-global)--> 1@1000 [size=16, length=-1] root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 1@1000 [size=16, length=-1] -root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=13,location= 10])--> 1@1000 [size=16, length=-1] -root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 10])--> 1@1000 [size=16, length=-1] +root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=11,location= 8])--> 1@1000 [size=16, length=-1] +root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 8])--> 1@1000 [size=16, length=-1] root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestRoot,vreg=4,location= 19])--> 1@1000 [size=16, length=-1] root@root --(thread)--> 1@1000 [size=16, length=-1] 1@1000 --(field@2)--> 2@1000 [size=16, length=-1] diff --git a/test/913-heaps/expected_jack.diff b/test/913-heaps/expected_jack.diff new file mode 100644 index 0000000000..32fef02234 --- /dev/null +++ b/test/913-heaps/expected_jack.diff @@ -0,0 +1,50 @@ +4c4 +< root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=13,location= 30])--> 1@1000 [size=16, length=-1] +--- +> root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=13,location= 32])--> 1@1000 [size=16, length=-1] +49,50c49,50 +< root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=11,location= 8])--> 1@1000 [size=16, length=-1] +< root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 8])--> 1@1000 [size=16, length=-1] +--- +> root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=13,location= 10])--> 1@1000 [size=16, length=-1] +> root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 10])--> 1@1000 [size=16, length=-1] +102c102 +< root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=13,location= 30])--> 1@1000 [size=16, length=-1] +--- +> root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=13,location= 32])--> 1@1000 [size=16, length=-1] +115,116c115,116 +< root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=11,location= 8])--> 1@1000 [size=16, length=-1] +< root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 8])--> 1@1000 [size=16, length=-1] +--- +> root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=13,location= 10])--> 1@1000 [size=16, length=-1] +> root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 10])--> 1@1000 [size=16, length=-1] +162c162 +< root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=13,location= 30])--> 1@1000 [size=16, length=-1] +--- +> root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=13,location= 32])--> 1@1000 [size=16, length=-1] +177,178c177,178 +< root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=11,location= 8])--> 1@1000 [size=16, length=-1] +< root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 8])--> 1@1000 [size=16, length=-1] +--- +> root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=13,location= 10])--> 1@1000 [size=16, length=-1] +> root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 10])--> 1@1000 [size=16, length=-1] +201c201 +< root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=13,location= 30])--> 1@1000 [size=16, length=-1] +--- +> root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=13,location= 32])--> 1@1000 [size=16, length=-1] +246,247c246,247 +< root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=11,location= 8])--> 1@1000 [size=16, length=-1] +< root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 8])--> 1@1000 [size=16, length=-1] +--- +> root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=13,location= 10])--> 1@1000 [size=16, length=-1] +> root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 10])--> 1@1000 [size=16, length=-1] +347c347 +< root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=13,location= 30])--> 1@1000 [size=16, length=-1] +--- +> root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=13,location= 32])--> 1@1000 [size=16, length=-1] +366,367c366,367 +< root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=11,location= 8])--> 1@1000 [size=16, length=-1] +< root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 8])--> 1@1000 [size=16, length=-1] +--- +> root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=13,location= 10])--> 1@1000 [size=16, length=-1] +> root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 10])--> 1@1000 [size=16, length=-1] diff --git a/test/958-methodhandle-stackframe/src-art/Main.java b/test/958-methodhandle-stackframe/src-art/Main.java index f739d47d08..bb3ce9a760 100644 --- a/test/958-methodhandle-stackframe/src-art/Main.java +++ b/test/958-methodhandle-stackframe/src-art/Main.java @@ -86,7 +86,7 @@ public class Main { new Class<?>[] { boolean.class, char.class, short.class, int.class, long.class, float.class, double.class, String.class, Object.class })); - DelegatingTransformer delegate = new DelegatingTransformer(specialFunctionHandle); + MethodHandle delegate = new DelegatingTransformer(specialFunctionHandle); // Test an exact invoke. // diff --git a/test/Android.run-test-jvmti-java-library.mk b/test/Android.run-test-jvmti-java-library.mk index 55553226b3..60ce6c7003 100644 --- a/test/Android.run-test-jvmti-java-library.mk +++ b/test/Android.run-test-jvmti-java-library.mk @@ -34,6 +34,7 @@ LOCAL_SRC_FILES += \ 905-object-free/src/art/Test905.java \ 906-iterate-heap/src/art/Test906.java \ 907-get-loaded-classes/src/art/Test907.java \ + 907-get-loaded-classes/src/art/Cerr.java \ 908-gc-start-finish/src/art/Test908.java \ 910-methods/src/art/Test910.java \ 911-get-stack-trace/src/art/Test911.java \ diff --git a/test/etc/default-build b/test/etc/default-build index 977b07189b..13f430185e 100755 --- a/test/etc/default-build +++ b/test/etc/default-build @@ -349,7 +349,7 @@ else if [ "${HAS_SRC_MULTIDEX}" = "true" ]; then mkdir classes2 - ${JAVAC} -implicit:none -classpath src -d classes2 `find src-multidex -name '*.java'` + ${JAVAC} ${JAVAC_ARGS} -implicit:none -classpath src -d classes2 `find src-multidex -name '*.java'` if [ ${NEED_DEX} = "true" ]; then make_dex classes2 fi @@ -403,8 +403,20 @@ if [ ${HAS_SRC_EX} = "true" ]; then # Restore previous "classes.dex" so it can be zipped. mv classes-1.dex classes.dex else + # Build src-ex into classes-ex. + # Includes 'src', 'src-art' source when compiling classes-ex, but exclude their .class files. + if [[ "${HAS_SRC}" == "true" ]]; then + mkdir -p classes-tmp-for-ex + ${JAVAC} ${JAVAC_ARGS} -d classes-tmp-for-ex `find src -name '*.java'` + src_tmp_for_ex="-cp classes-tmp-for-ex" + fi + if [[ "${HAS_SRC_ART}" == "true" ]]; then + mkdir -p classes-tmp-for-ex + javac_with_bootclasspath ${JAVAC_ARGS} -d classes-tmp-for-ex `find src-art -name '*.java'` + src_tmp_for_ex="-cp classes-tmp-for-ex" + fi mkdir classes-ex - ${JAVAC} ${JAVAC_ARGS} -d classes-ex -cp classes `find src-ex -name '*.java'` + ${JAVAC} ${JAVAC_ARGS} -d classes-ex $src_tmp_for_ex `find src-ex -name '*.java'` if [ ${NEED_DEX} = "true" ]; then make_dex classes-ex diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar index c44fb97c50..d24edccd8b 100755 --- a/test/etc/run-test-jar +++ b/test/etc/run-test-jar @@ -79,6 +79,9 @@ SET_STACK_TRACE_DUMP_DIR="n" # build step (e.g. dex2oat) were finished writing. SYNC_BEFORE_RUN="n" +# When running a debug build, we want to run with all checks. +ANDROID_FLAGS="${ANDROID_FLAGS} -XX:SlowDebug=true" + while true; do if [ "x$1" = "x--quiet" ]; then QUIET="y" diff --git a/test/knownfailures.json b/test/knownfailures.json index a3b8dd6cf0..8bdaeaaf5a 100644 --- a/test/knownfailures.json +++ b/test/knownfailures.json @@ -603,22 +603,11 @@ }, { "tests": [ - "004-ReferenceMap", - "004-StackWalk", - "089-many-methods", - "580-checker-string-fact-intrinsics", - "616-cha-interface-default", - "636-wrong-static-access", - "910-methods", - "911-get-stack-trace", - "913-heaps", - "948-change-annotations", - "953-invoke-polymorphic-compiler", - "958-methodhandle-stackframe" + "953-invoke-polymorphic-compiler" ], - "description": "The tests above fail with --build-with-javac-dx.", + "description": "Test throws VerifyError when run with --build-with-javac-dx.", "env_vars": {"ANDROID_COMPILE_WITH_JACK": "false"}, - "bug": "b/37636792" + "bug": "b/62722425" }, { "tests": [ @@ -24,6 +24,7 @@ LAUNCH_WRAPPER= LIBART=libart.so JIT_PROFILE="no" VERBOSE="no" +EXTRA_OPTIONS="" # Follow all sym links to get the program name. if [ z"$BASH_SOURCE" != z ]; then @@ -147,6 +148,8 @@ while [[ "$1" = "-"* ]]; do ;& # Fallthrough --debug) LIBART="libartd.so" + # Expect that debug mode wants all checks. + EXTRA_OPTIONS="${EXTRA_OPTIONS} -XX:SlowDebug=true" ;; --gdb) LIBART="libartd.so" @@ -203,7 +206,6 @@ fi LIBDIR="$(find_libdir $ART_BINARY_PATH)" LD_LIBRARY_PATH=$ANDROID_ROOT/$LIBDIR -EXTRA_OPTIONS="" # If ANDROID_DATA is the system ANDROID_DATA or is not set, use our own, # and ensure we delete it at the end. diff --git a/tools/run-jdwp-tests.sh b/tools/run-jdwp-tests.sh index f7427676eb..225fb394d1 100755 --- a/tools/run-jdwp-tests.sh +++ b/tools/run-jdwp-tests.sh @@ -154,7 +154,7 @@ debuggee_args="$debuggee_args -Xusejit:$use_jit" if [[ $debug == "yes" ]]; then art="$art -d" art_debugee="$art_debugee -d" - vm_args="$vm_args --vm-arg -XXlib:libartd.so" + vm_args="$vm_args --vm-arg -XXlib:libartd.so --vm-arg -XX:SlowDebug=true" fi if [[ $verbose == "yes" ]]; then # Enable JDWP logs in the debuggee. diff --git a/tools/run-libcore-tests.sh b/tools/run-libcore-tests.sh index 8b3df3a28c..6dcc23a9fc 100755 --- a/tools/run-libcore-tests.sh +++ b/tools/run-libcore-tests.sh @@ -127,7 +127,7 @@ while true; do elif [[ "$1" == "--debug" ]]; then # Remove the --debug from the arguments. vogar_args=${vogar_args/$1} - vogar_args="$vogar_args --vm-arg -XXlib:libartd.so" + vogar_args="$vogar_args --vm-arg -XXlib:libartd.so --vm-arg -XX:SlowDebug=true" debug=true shift elif [[ "$1" == "-Xgc:gcstress" ]]; then |