summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.mk41
-rw-r--r--build/Android.bp7
-rw-r--r--build/Android.gtest.mk9
-rw-r--r--compiler/optimizing/loop_optimization.cc54
-rw-r--r--compiler/optimizing/loop_optimization.h5
-rw-r--r--compiler/utils/arm/assembler_thumb2.cc2
-rw-r--r--compiler/utils/mips/assembler_mips_test.cc2
-rw-r--r--dex2oat/Android.bp5
-rw-r--r--dex2oat/dex2oat.cc11
-rw-r--r--dex2oat/dex2oat_image_test.cc340
-rw-r--r--imgdiag/imgdiag.cc884
-rw-r--r--profman/Android.bp1
-rw-r--r--profman/boot_image_profile.cc138
-rw-r--r--profman/boot_image_profile.h55
-rw-r--r--profman/profile_assistant_test.cc95
-rw-r--r--profman/profman.cc131
-rw-r--r--runtime/Android.bp2
-rw-r--r--runtime/art_method.cc2
-rw-r--r--runtime/art_method.h4
-rw-r--r--runtime/base/logging.cc49
-rw-r--r--runtime/base/logging.h37
-rw-r--r--runtime/base/logging_test.cc59
-rw-r--r--runtime/common_runtime_test.cc16
-rw-r--r--runtime/common_runtime_test.h2
-rw-r--r--runtime/jit/profile_compilation_info.cc21
-rw-r--r--runtime/jit/profile_compilation_info.h12
-rw-r--r--runtime/openjdkjvmti/ti_heap.cc4
-rw-r--r--runtime/openjdkjvmti/ti_stack.cc266
-rw-r--r--runtime/parsed_options.cc8
-rw-r--r--runtime/read_barrier.cc24
-rw-r--r--runtime/read_barrier.h12
-rw-r--r--runtime/runtime_options.def2
-rw-r--r--runtime/stack_map.h13
-rw-r--r--runtime/thread.cc49
-rw-r--r--runtime/thread.h6
-rw-r--r--runtime/type_reference.h9
-rw-r--r--runtime/vdex_file.h3
-rw-r--r--test/004-JniTest/jni_test.cc13
-rw-r--r--test/004-JniTest/src/Main.java7
-rw-r--r--test/004-ReferenceMap/build26
-rw-r--r--test/004-ReferenceMap/stack_walk_refmap_jni.cc193
-rw-r--r--test/004-StackWalk/build26
-rw-r--r--test/004-StackWalk/stack_walk_jni.cc16
-rw-r--r--test/089-many-methods/build6
-rw-r--r--test/089-many-methods/expected.txt8
-rw-r--r--test/580-checker-string-fact-intrinsics/src-art/Main.java (renamed from test/580-checker-string-fact-intrinsics/src/Main.java)0
-rw-r--r--test/616-cha-interface-default/build17
-rw-r--r--test/652-deopt-intrinsic/src/Main.java16
-rw-r--r--test/656-loop-deopt/expected.txt1
-rw-r--r--test/656-loop-deopt/info.txt2
-rw-r--r--test/656-loop-deopt/src/Main.java104
-rw-r--r--test/907-get-loaded-classes/src/art/Cerr.java27
-rw-r--r--test/907-get-loaded-classes/src/art/Test907.java23
-rw-r--r--test/910-methods/check22
-rw-r--r--test/910-methods/expected.txt2
-rw-r--r--test/910-methods/expected_jack.diff4
-rw-r--r--test/911-get-stack-trace/check22
-rw-r--r--test/911-get-stack-trace/expected.txt16
-rw-r--r--test/911-get-stack-trace/expected_jack.diff32
-rw-r--r--test/913-heaps/check22
-rw-r--r--test/913-heaps/expected.txt30
-rw-r--r--test/913-heaps/expected_jack.diff50
-rw-r--r--test/958-methodhandle-stackframe/src-art/Main.java2
-rw-r--r--test/Android.run-test-jvmti-java-library.mk1
-rwxr-xr-xtest/etc/default-build16
-rwxr-xr-xtest/etc/run-test-jar3
-rw-r--r--test/knownfailures.json17
-rw-r--r--tools/art4
-rwxr-xr-xtools/run-jdwp-tests.sh2
-rwxr-xr-xtools/run-libcore-tests.sh2
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,
+ &region_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": [
diff --git a/tools/art b/tools/art
index 0bc08f0fce..2e5df91bfd 100644
--- a/tools/art
+++ b/tools/art
@@ -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