Deduplicate interned image strings.
Also fix a bug in relocation; even for -Xnorelocate we need
to relocate second and later extension if it's not compiled
against all previous boot image components.
Also clean up InternTable includes.
Test: New tests in image_space_test.
Test: m test-art-host-gtest
Test: testrunner.py --host --optimizing
Test: aosp_taimen-userdebug boots.
Bug: 152037801
Change-Id: Ie6ae70721f4ffb48950bd248ffa123dee460bcd7
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index 86e9494..b3cac26 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -30,6 +30,8 @@
ErroneousA \
ErroneousB \
ErroneousInit \
+ Extension1 \
+ Extension2 \
ForClassLoaderA \
ForClassLoaderB \
ForClassLoaderC \
@@ -227,7 +229,7 @@
ART_GTEST_jni_internal_test_DEX_DEPS := AllFields StaticLeafMethods MyClassNatives
ART_GTEST_oat_file_assistant_test_DEX_DEPS := $(ART_GTEST_dex2oat_environment_tests_DEX_DEPS)
ART_GTEST_dexoptanalyzer_test_DEX_DEPS := $(ART_GTEST_dex2oat_environment_tests_DEX_DEPS)
-ART_GTEST_image_space_test_DEX_DEPS := $(ART_GTEST_dex2oat_environment_tests_DEX_DEPS)
+ART_GTEST_image_space_test_DEX_DEPS := $(ART_GTEST_dex2oat_environment_tests_DEX_DEPS) Extension1 Extension2
ART_GTEST_oat_file_test_DEX_DEPS := Main MultiDex MainUncompressedAligned MultiDexUncompressedAligned MainStripped Nested MultiDexModifiedSecondary
ART_GTEST_oat_test_DEX_DEPS := Main
ART_GTEST_oat_writer_test_DEX_DEPS := Main
diff --git a/dex2oat/dex2oat_image_test.cc b/dex2oat/dex2oat_image_test.cc
index 9fd632f..d01b64f 100644
--- a/dex2oat/dex2oat_image_test.cc
+++ b/dex2oat/dex2oat_image_test.cc
@@ -41,8 +41,8 @@
#include "dex/dex_file-inl.h"
#include "dex/dex_file_loader.h"
#include "dex/method_reference.h"
+#include "dex/type_reference.h"
#include "gc/space/image_space.h"
-#include "profile/profile_compilation_info.h"
#include "runtime.h"
#include "scoped_thread_state_change-inl.h"
#include "thread-current-inl.h"
@@ -73,89 +73,11 @@
options->emplace_back("-Xnoimage-dex2oat", nullptr);
}
- // 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) {
- std::vector<std::string> dexes = GetLibCoreDexFileNames();
- ArrayRef<const std::string> dexes_array(dexes);
- VisitDexes(dexes_array, method_visitor, class_visitor, method_frequency, class_frequency);
- }
-
- // Visitors take method and type references
- template <typename MethodVisitor, typename ClassVisitor>
- void VisitDexes(ArrayRef<const std::string> dexes,
- 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 (const std::string& dex : dexes) {
- std::vector<std::unique_ptr<const DexFile>> dex_files;
- std::string error_msg;
- const ArtDexFileLoader dex_file_loader;
- CHECK(dex_file_loader.Open(dex.c_str(),
- dex,
- /*verify*/ true,
- /*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 GenerateProfile(ArrayRef<const std::string> dexes,
- File* out_file,
- size_t method_frequency,
- size_t type_frequency) {
- ProfileCompilationInfo profile;
- VisitDexes(
- dexes,
- [&profile](MethodReference ref) {
- uint32_t flags = ProfileCompilationInfo::MethodHotness::kFlagHot |
- ProfileCompilationInfo::MethodHotness::kFlagStartup;
- EXPECT_TRUE(profile.AddMethod(
- ProfileMethodInfo(ref),
- static_cast<ProfileCompilationInfo::MethodHotness::Flag>(flags)));
- },
- [&profile](TypeReference ref) {
- std::set<dex::TypeIndex> classes;
- classes.insert(ref.TypeIndex());
- EXPECT_TRUE(profile.AddClassesForDex(ref.dex_file, classes.begin(), classes.end()));
- },
- method_frequency,
- type_frequency);
- profile.Save(out_file->Fd());
- EXPECT_EQ(out_file->Flush(), 0);
- }
-
- void GenerateMethods(File* out_file, size_t frequency = 1) {
- VisitLibcoreDexes([out_file](MethodReference ref) {
- WriteLine(out_file, ref.PrettyMethod());
- }, 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);
@@ -316,8 +238,9 @@
// Test dirty image objects.
{
ScratchFile classes;
- VisitLibcoreDexes(VoidFunctor(),
- [&](TypeReference ref) {
+ VisitDexes(libcore_dexes_array,
+ VoidFunctor(),
+ [&](TypeReference ref) {
WriteLine(classes.GetFile(), ref.dex_file->PrettyType(ref.TypeIndex()));
}, /*method_frequency=*/ 1u, /*class_frequency=*/ 1u);
ImageSizes image_classes_sizes = CompileImageAndGetSizes(
diff --git a/dex2oat/linker/image_writer.h b/dex2oat/linker/image_writer.h
index 22fd8f4..769f2ff 100644
--- a/dex2oat/linker/image_writer.h
+++ b/dex2oat/linker/image_writer.h
@@ -27,6 +27,7 @@
#include <stack>
#include <string>
#include <unordered_map>
+#include <unordered_set>
#include "art_method.h"
#include "base/bit_utils.h"
diff --git a/libartbase/base/common_art_test.h b/libartbase/base/common_art_test.h
index 8d2693f..8cd25c3 100644
--- a/libartbase/base/common_art_test.h
+++ b/libartbase/base/common_art_test.h
@@ -28,6 +28,7 @@
#include "base/file_utils.h"
#include "base/globals.h"
+#include "base/memory_tool.h"
#include "base/mutex.h"
#include "base/os.h"
#include "base/unix_file/fd_file.h"
diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc
index 640e182..a846346 100644
--- a/runtime/common_runtime_test.cc
+++ b/runtime/common_runtime_test.cc
@@ -41,7 +41,9 @@
#include "dex/art_dex_file_loader.h"
#include "dex/dex_file-inl.h"
#include "dex/dex_file_loader.h"
+#include "dex/method_reference.h"
#include "dex/primitive.h"
+#include "dex/type_reference.h"
#include "gc/heap.h"
#include "gc/space/image_space.h"
#include "gc_root-inl.h"
@@ -56,6 +58,7 @@
#include "mirror/object_array-alloc-inl.h"
#include "native/dalvik_system_DexFile.h"
#include "noop_compiler_callbacks.h"
+#include "profile/profile_compilation_info.h"
#include "runtime-inl.h"
#include "scoped_thread_state_change-inl.h"
#include "thread.h"
@@ -408,18 +411,16 @@
}
bool CommonRuntimeTestImpl::StartDex2OatCommandLine(/*out*/std::vector<std::string>* argv,
- /*out*/std::string* error_msg) {
+ /*out*/std::string* error_msg,
+ bool use_runtime_bcp_and_image) {
DCHECK(argv != nullptr);
DCHECK(argv->empty());
Runtime* runtime = Runtime::Current();
- const std::vector<gc::space::ImageSpace*>& image_spaces =
- runtime->GetHeap()->GetBootImageSpaces();
- if (image_spaces.empty()) {
+ if (use_runtime_bcp_and_image && runtime->GetHeap()->GetBootImageSpaces().empty()) {
*error_msg = "No image location found for Dex2Oat.";
return false;
}
- std::string image_location = image_spaces[0]->GetImageLocation();
argv->push_back(runtime->GetCompilerExecutable());
if (runtime->IsJavaDebuggable()) {
@@ -427,12 +428,17 @@
}
runtime->AddCurrentRuntimeFeaturesAsDex2OatArguments(argv);
- argv->push_back("--runtime-arg");
- argv->push_back(GetClassPathOption("-Xbootclasspath:", GetLibCoreDexFileNames()));
- argv->push_back("--runtime-arg");
- argv->push_back(GetClassPathOption("-Xbootclasspath-locations:", GetLibCoreDexLocations()));
+ if (use_runtime_bcp_and_image) {
+ argv->push_back("--runtime-arg");
+ argv->push_back(GetClassPathOption("-Xbootclasspath:", GetLibCoreDexFileNames()));
+ argv->push_back("--runtime-arg");
+ argv->push_back(GetClassPathOption("-Xbootclasspath-locations:", GetLibCoreDexLocations()));
- argv->push_back("--boot-image=" + image_location);
+ const std::vector<gc::space::ImageSpace*>& image_spaces =
+ runtime->GetHeap()->GetBootImageSpaces();
+ DCHECK(!image_spaces.empty());
+ argv->push_back("--boot-image=" + image_spaces[0]->GetImageLocation());
+ }
std::vector<std::string> compiler_options = runtime->GetCompilerOptions();
argv->insert(argv->end(), compiler_options.begin(), compiler_options.end());
@@ -560,6 +566,64 @@
return Runtime::Current()->IsTransactionAborted();
}
+void CommonRuntimeTestImpl::VisitDexes(ArrayRef<const std::string> dexes,
+ const std::function<void(MethodReference)>& method_visitor,
+ const std::function<void(TypeReference)>& class_visitor,
+ size_t method_frequency,
+ size_t class_frequency) {
+ size_t method_counter = 0;
+ size_t class_counter = 0;
+ for (const std::string& dex : dexes) {
+ std::vector<std::unique_ptr<const DexFile>> dex_files;
+ std::string error_msg;
+ const ArtDexFileLoader dex_file_loader;
+ CHECK(dex_file_loader.Open(dex.c_str(),
+ dex,
+ /*verify*/ true,
+ /*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)));
+ }
+ }
+ }
+ }
+}
+
+void CommonRuntimeTestImpl::GenerateProfile(ArrayRef<const std::string> dexes,
+ File* out_file,
+ size_t method_frequency,
+ size_t type_frequency) {
+ ProfileCompilationInfo profile;
+ VisitDexes(
+ dexes,
+ [&profile](MethodReference ref) {
+ uint32_t flags = ProfileCompilationInfo::MethodHotness::kFlagHot |
+ ProfileCompilationInfo::MethodHotness::kFlagStartup;
+ EXPECT_TRUE(profile.AddMethod(
+ ProfileMethodInfo(ref),
+ static_cast<ProfileCompilationInfo::MethodHotness::Flag>(flags)));
+ },
+ [&profile](TypeReference ref) {
+ std::set<dex::TypeIndex> classes;
+ classes.insert(ref.TypeIndex());
+ EXPECT_TRUE(profile.AddClassesForDex(ref.dex_file, classes.begin(), classes.end()));
+ },
+ method_frequency,
+ type_frequency);
+ profile.Save(out_file->Fd());
+ EXPECT_EQ(out_file->Flush(), 0);
+}
+
CheckJniAbortCatcher::CheckJniAbortCatcher() : vm_(Runtime::Current()->GetJavaVM()) {
vm_->SetCheckJniAbortHook(Hook, &actual_);
}
diff --git a/runtime/common_runtime_test.h b/runtime/common_runtime_test.h
index 2dc8744..711bc59 100644
--- a/runtime/common_runtime_test.h
+++ b/runtime/common_runtime_test.h
@@ -20,6 +20,7 @@
#include <gtest/gtest.h>
#include <jni.h>
+#include <functional>
#include <string>
#include <android-base/logging.h>
@@ -38,6 +39,9 @@
namespace art {
+class MethodReference;
+class TypeReference;
+
using LogSeverity = android::base::LogSeverity;
using ScopedLogSeverity = android::base::ScopedLogSeverity;
@@ -111,7 +115,8 @@
REQUIRES_SHARED(Locks::mutator_lock_);
bool StartDex2OatCommandLine(/*out*/std::vector<std::string>* argv,
- /*out*/std::string* error_msg);
+ /*out*/std::string* error_msg,
+ bool use_runtime_bcp_and_image = true);
bool CompileBootImage(const std::vector<std::string>& extra_args,
const std::string& image_file_name_prefix,
@@ -162,6 +167,17 @@
jobject parent_loader,
jobject shared_libraries = nullptr);
+ void VisitDexes(ArrayRef<const std::string> dexes,
+ const std::function<void(MethodReference)>& method_visitor,
+ const std::function<void(TypeReference)>& class_visitor,
+ size_t method_frequency = 1u,
+ size_t class_frequency = 1u);
+
+ void GenerateProfile(ArrayRef<const std::string> dexes,
+ File* out_file,
+ size_t method_frequency = 1u,
+ size_t type_frequency = 1u);
+
std::unique_ptr<Runtime> runtime_;
// The class_linker_, java_lang_dex_file_, and boot_class_path_ are all
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index 147c6b4..edec8a5 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -675,6 +675,52 @@
ReferenceVisitor reference_visitor_;
};
+class ImageSpace::RemapInternedStringsVisitor {
+ public:
+ explicit RemapInternedStringsVisitor(SafeMap<mirror::String*, mirror::String*> intern_remap)
+ REQUIRES_SHARED(Locks::mutator_lock_)
+ : intern_remap_(std::move(intern_remap)),
+ string_class_(GetStringClass()) {}
+
+ // Visitor for VisitReferences().
+ ALWAYS_INLINE void operator()(ObjPtr<mirror::Object> object,
+ MemberOffset field_offset,
+ bool is_static ATTRIBUTE_UNUSED)
+ const REQUIRES_SHARED(Locks::mutator_lock_) {
+ ObjPtr<mirror::Object> old_value =
+ object->GetFieldObject<mirror::Object, kVerifyNone, kWithoutReadBarrier>(field_offset);
+ if (old_value != nullptr &&
+ old_value->GetClass<kVerifyNone, kWithoutReadBarrier>() == string_class_) {
+ auto it = intern_remap_.find(old_value->AsString().Ptr());
+ if (it != intern_remap_.end()) {
+ mirror::String* new_value = it->second;
+ object->SetFieldObjectWithoutWriteBarrier</*kTransactionActive=*/ false,
+ /*kCheckTransaction=*/ true,
+ kVerifyNone>(field_offset, new_value);
+ }
+ }
+ }
+ // Visitor for VisitReferences(), java.lang.ref.Reference case.
+ ALWAYS_INLINE void operator()(ObjPtr<mirror::Class> klass, ObjPtr<mirror::Reference> ref) const
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK(klass->IsTypeOfReferenceClass());
+ this->operator()(ref, mirror::Reference::ReferentOffset(), /*is_static=*/ false);
+ }
+ // Ignore class native roots; not called from VisitReferences() for kVisitNativeRoots == false.
+ void VisitRootIfNonNull(mirror::CompressedReference<mirror::Object>* root ATTRIBUTE_UNUSED)
+ const {}
+ void VisitRoot(mirror::CompressedReference<mirror::Object>* root ATTRIBUTE_UNUSED) const {}
+
+ private:
+ mirror::Class* GetStringClass() REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK(!intern_remap_.empty());
+ return intern_remap_.begin()->first->GetClass<kVerifyNone, kWithoutReadBarrier>();
+ }
+
+ const SafeMap<mirror::String*, mirror::String*> intern_remap_;
+ mirror::Class* const string_class_;
+};
+
// Helper class encapsulating loading, so we can access private ImageSpace members (this is a
// nested class), but not declare functions in the header.
class ImageSpace::Loader {
@@ -682,64 +728,91 @@
static std::unique_ptr<ImageSpace> InitAppImage(const char* image_filename,
const char* image_location,
const OatFile* oat_file,
- /*inout*/MemMap* image_reservation,
+ ArrayRef<ImageSpace* const> boot_image_spaces,
/*out*/std::string* error_msg)
REQUIRES_SHARED(Locks::mutator_lock_) {
TimingLogger logger(__PRETTY_FUNCTION__, /*precise=*/ true, VLOG_IS_ON(image));
std::unique_ptr<ImageSpace> space = Init(image_filename,
image_location,
- oat_file,
&logger,
- image_reservation,
+ /*image_reservation=*/ nullptr,
error_msg);
if (space != nullptr) {
- uint32_t expected_reservation_size =
- RoundUp(space->GetImageHeader().GetImageSize(), kPageSize);
+ space->oat_file_non_owned_ = oat_file;
+ const ImageHeader& image_header = space->GetImageHeader();
+
+ // Check the oat file checksum.
+ const uint32_t oat_checksum = oat_file->GetOatHeader().GetChecksum();
+ const uint32_t image_oat_checksum = image_header.GetOatChecksum();
+ if (oat_checksum != image_oat_checksum) {
+ *error_msg = StringPrintf("Oat checksum 0x%x does not match the image one 0x%x in image %s",
+ oat_checksum,
+ image_oat_checksum,
+ image_filename);
+ return nullptr;
+ }
+ size_t boot_image_space_dependencies;
+ if (!ValidateBootImageChecksum(image_filename,
+ image_header,
+ oat_file,
+ boot_image_spaces,
+ &boot_image_space_dependencies,
+ error_msg)) {
+ DCHECK(!error_msg->empty());
+ return nullptr;
+ }
+
+ uint32_t expected_reservation_size = RoundUp(image_header.GetImageSize(), kPageSize);
if (!CheckImageReservationSize(*space, expected_reservation_size, error_msg) ||
!CheckImageComponentCount(*space, /*expected_component_count=*/ 1u, error_msg)) {
return nullptr;
}
- TimingLogger::ScopedTiming timing("RelocateImage", &logger);
- ImageHeader* image_header = reinterpret_cast<ImageHeader*>(space->GetMemMap()->Begin());
- const PointerSize pointer_size = image_header->GetPointerSize();
- bool result;
- if (pointer_size == PointerSize::k64) {
- result = RelocateInPlace<PointerSize::k64>(*image_header,
- space->GetMemMap()->Begin(),
- space->GetLiveBitmap(),
- oat_file,
- error_msg);
- } else {
- result = RelocateInPlace<PointerSize::k32>(*image_header,
- space->GetMemMap()->Begin(),
- space->GetLiveBitmap(),
- oat_file,
- error_msg);
+ {
+ TimingLogger::ScopedTiming timing("RelocateImage", &logger);
+ const PointerSize pointer_size = image_header.GetPointerSize();
+ uint32_t boot_image_begin =
+ reinterpret_cast32<uint32_t>(boot_image_spaces.front()->Begin());
+ bool result;
+ if (pointer_size == PointerSize::k64) {
+ result = RelocateInPlace<PointerSize::k64>(boot_image_begin,
+ space->GetMemMap()->Begin(),
+ space->GetLiveBitmap(),
+ oat_file,
+ error_msg);
+ } else {
+ result = RelocateInPlace<PointerSize::k32>(boot_image_begin,
+ space->GetMemMap()->Begin(),
+ space->GetLiveBitmap(),
+ oat_file,
+ error_msg);
+ }
+ if (!result) {
+ return nullptr;
+ }
}
- if (!result) {
- return nullptr;
+
+ DCHECK_LE(boot_image_space_dependencies, boot_image_spaces.size());
+ if (boot_image_space_dependencies != boot_image_spaces.size()) {
+ TimingLogger::ScopedTiming timing("DeduplicateInternedStrings", &logger);
+ // There shall be no duplicates with boot image spaces this app image depends on.
+ ArrayRef<ImageSpace* const> old_spaces =
+ boot_image_spaces.SubArray(/*pos=*/ boot_image_space_dependencies);
+ SafeMap<mirror::String*, mirror::String*> intern_remap;
+ RemoveInternTableDuplicates(old_spaces, space.get(), &intern_remap);
+ if (!intern_remap.empty()) {
+ RemapInternedStringDuplicates(std::move(intern_remap), space.get());
+ }
}
- Runtime* runtime = Runtime::Current();
- CHECK_EQ(runtime->GetResolutionMethod(),
- image_header->GetImageMethod(ImageHeader::kResolutionMethod));
- CHECK_EQ(runtime->GetImtConflictMethod(),
- image_header->GetImageMethod(ImageHeader::kImtConflictMethod));
- CHECK_EQ(runtime->GetImtUnimplementedMethod(),
- image_header->GetImageMethod(ImageHeader::kImtUnimplementedMethod));
- CHECK_EQ(runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveAllCalleeSaves),
- image_header->GetImageMethod(ImageHeader::kSaveAllCalleeSavesMethod));
- CHECK_EQ(runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveRefsOnly),
- image_header->GetImageMethod(ImageHeader::kSaveRefsOnlyMethod));
- CHECK_EQ(runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveRefsAndArgs),
- image_header->GetImageMethod(ImageHeader::kSaveRefsAndArgsMethod));
- CHECK_EQ(runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveEverything),
- image_header->GetImageMethod(ImageHeader::kSaveEverythingMethod));
- CHECK_EQ(runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveEverythingForClinit),
- image_header->GetImageMethod(ImageHeader::kSaveEverythingMethodForClinit));
- CHECK_EQ(runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveEverythingForSuspendCheck),
- image_header->GetImageMethod(ImageHeader::kSaveEverythingMethodForSuspendCheck));
+
+ const ImageHeader& primary_header = boot_image_spaces.front()->GetImageHeader();
+ static_assert(static_cast<size_t>(ImageHeader::kResolutionMethod) == 0u);
+ for (size_t i = 0u; i != static_cast<size_t>(ImageHeader::kImageMethodsCount); ++i) {
+ ImageHeader::ImageMethod method = static_cast<ImageHeader::ImageMethod>(i);
+ CHECK_EQ(primary_header.GetImageMethod(method), image_header.GetImageMethod(method))
+ << method;
+ }
VLOG(image) << "ImageSpace::Loader::InitAppImage exiting " << *space.get();
}
@@ -751,7 +824,6 @@
static std::unique_ptr<ImageSpace> Init(const char* image_filename,
const char* image_location,
- const OatFile* oat_file,
TimingLogger* logger,
/*inout*/MemMap* image_reservation,
/*out*/std::string* error_msg)
@@ -772,7 +844,6 @@
image_filename,
image_location,
/* profile_file=*/ "",
- oat_file,
/*allow_direct_mapping=*/ true,
logger,
image_reservation,
@@ -783,7 +854,6 @@
const char* image_filename,
const char* image_location,
const char* profile_file,
- const OatFile* oat_file,
bool allow_direct_mapping,
TimingLogger* logger,
/*inout*/MemMap* image_reservation,
@@ -794,60 +864,41 @@
VLOG(image) << "ImageSpace::Init entering image_filename=" << image_filename;
- ImageHeader temp_image_header;
- ImageHeader* image_header = &temp_image_header;
+ ImageHeader image_header;
{
TimingLogger::ScopedTiming timing("ReadImageHeader", logger);
- bool success = file->PreadFully(image_header, sizeof(*image_header), /*offset=*/ 0u);
- if (!success || !image_header->IsValid()) {
+ bool success = file->PreadFully(&image_header, sizeof(image_header), /*offset=*/ 0u);
+ if (!success || !image_header.IsValid()) {
*error_msg = StringPrintf("Invalid image header in '%s'", image_filename);
return nullptr;
}
}
// Check that the file is larger or equal to the header size + data size.
const uint64_t image_file_size = static_cast<uint64_t>(file->GetLength());
- if (image_file_size < sizeof(ImageHeader) + image_header->GetDataSize()) {
+ if (image_file_size < sizeof(ImageHeader) + image_header.GetDataSize()) {
*error_msg = StringPrintf(
"Image file truncated: %" PRIu64 " vs. %" PRIu64 ".",
image_file_size,
- static_cast<uint64_t>(sizeof(ImageHeader) + image_header->GetDataSize()));
+ static_cast<uint64_t>(sizeof(ImageHeader) + image_header.GetDataSize()));
return nullptr;
}
- if (oat_file != nullptr) {
- // If we have an oat file (i.e. for app image), check the oat file checksum.
- // Otherwise, we open the oat file after the image and check the checksum there.
- const uint32_t oat_checksum = oat_file->GetOatHeader().GetChecksum();
- const uint32_t image_oat_checksum = image_header->GetOatChecksum();
- if (oat_checksum != image_oat_checksum) {
- *error_msg = StringPrintf("Oat checksum 0x%x does not match the image one 0x%x in image %s",
- oat_checksum,
- image_oat_checksum,
- image_filename);
- return nullptr;
- }
- if (!ValidateBootImageChecksum(image_filename, *image_header, oat_file, error_msg)) {
- DCHECK(!error_msg->empty());
- return nullptr;
- }
- }
-
if (VLOG_IS_ON(startup)) {
LOG(INFO) << "Dumping image sections";
for (size_t i = 0; i < ImageHeader::kSectionCount; ++i) {
const auto section_idx = static_cast<ImageHeader::ImageSections>(i);
- auto& section = image_header->GetImageSection(section_idx);
+ auto& section = image_header.GetImageSection(section_idx);
LOG(INFO) << section_idx << " start="
- << reinterpret_cast<void*>(image_header->GetImageBegin() + section.Offset()) << " "
+ << reinterpret_cast<void*>(image_header.GetImageBegin() + section.Offset()) << " "
<< section;
}
}
- const auto& bitmap_section = image_header->GetImageBitmapSection();
+ const auto& bitmap_section = image_header.GetImageBitmapSection();
// The location we want to map from is the first aligned page after the end of the stored
// (possibly compressed) data.
- const size_t image_bitmap_offset = RoundUp(sizeof(ImageHeader) + image_header->GetDataSize(),
- kPageSize);
+ const size_t image_bitmap_offset =
+ RoundUp(sizeof(ImageHeader) + image_header.GetDataSize(), kPageSize);
const size_t end_of_bitmap = image_bitmap_offset + bitmap_section.Size();
if (end_of_bitmap != image_file_size) {
*error_msg = StringPrintf(
@@ -866,7 +917,7 @@
MemMap map = LoadImageFile(
image_filename,
image_location,
- *image_header,
+ image_header,
file->Fd(),
allow_direct_mapping,
logger,
@@ -876,7 +927,7 @@
DCHECK(!error_msg->empty());
return nullptr;
}
- DCHECK_EQ(0, memcmp(image_header, map.Begin(), sizeof(ImageHeader)));
+ DCHECK_EQ(0, memcmp(&image_header, map.Begin(), sizeof(ImageHeader)));
MemMap image_bitmap_map = MemMap::MapFile(bitmap_section.Size(),
PROT_READ,
@@ -890,15 +941,12 @@
*error_msg = StringPrintf("Failed to map image bitmap: %s", error_msg->c_str());
return nullptr;
}
- // Loaded the map, use the image header from the file now in case we patch it with
- // RelocateInPlace.
- image_header = reinterpret_cast<ImageHeader*>(map.Begin());
const uint32_t bitmap_index = ImageSpace::bitmap_index_.fetch_add(1);
std::string bitmap_name(StringPrintf("imagespace %s live-bitmap %u",
image_filename,
bitmap_index));
// Bitmap only needs to cover until the end of the mirror objects section.
- const ImageSection& image_objects = image_header->GetObjectsSection();
+ const ImageSection& image_objects = image_header.GetObjectsSection();
// We only want the mirror object, not the ArtFields and ArtMethods.
uint8_t* const image_end = map.Begin() + image_objects.End();
accounting::ContinuousSpaceBitmap bitmap;
@@ -922,7 +970,6 @@
std::move(map),
std::move(bitmap),
image_end));
- space->oat_file_non_owned_ = oat_file;
return space;
}
@@ -954,21 +1001,91 @@
return true;
}
+ template <typename Container>
+ static void RemoveInternTableDuplicates(
+ const Container& old_spaces,
+ /*inout*/ImageSpace* new_space,
+ /*inout*/SafeMap<mirror::String*, mirror::String*>* intern_remap)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ const ImageSection& new_interns = new_space->GetImageHeader().GetInternedStringsSection();
+ if (new_interns.Size() != 0u) {
+ const uint8_t* new_data = new_space->Begin() + new_interns.Offset();
+ size_t new_read_count;
+ InternTable::UnorderedSet new_set(new_data, /*make_copy_of_data=*/ false, &new_read_count);
+ for (const auto& old_space : old_spaces) {
+ const ImageSection& old_interns = old_space->GetImageHeader().GetInternedStringsSection();
+ if (old_interns.Size() != 0u) {
+ const uint8_t* old_data = old_space->Begin() + old_interns.Offset();
+ size_t old_read_count;
+ InternTable::UnorderedSet old_set(
+ old_data, /*make_copy_of_data=*/ false, &old_read_count);
+ RemoveDuplicates(old_set, &new_set, intern_remap);
+ }
+ }
+ }
+ }
+
+ static void RemapInternedStringDuplicates(
+ SafeMap<mirror::String*, mirror::String*>&& intern_remap,
+ ImageSpace* new_space) REQUIRES_SHARED(Locks::mutator_lock_) {
+ RemapInternedStringsVisitor visitor(std::move(intern_remap));
+ static_assert(IsAligned<kObjectAlignment>(sizeof(ImageHeader)), "Header alignment check");
+ uint32_t objects_end = new_space->GetImageHeader().GetObjectsSection().Size();
+ DCHECK_ALIGNED(objects_end, kObjectAlignment);
+ for (uint32_t pos = sizeof(ImageHeader); pos != objects_end; ) {
+ mirror::Object* object = reinterpret_cast<mirror::Object*>(new_space->Begin() + pos);
+ object->VisitReferences</*kVisitNativeRoots=*/ false,
+ kVerifyNone,
+ kWithoutReadBarrier>(visitor, visitor);
+ pos += RoundUp(object->SizeOf<kVerifyNone>(), kObjectAlignment);
+ }
+ }
+
private:
+ // Remove duplicates found in the `old_set` from the `new_set`.
+ // Record the removed Strings for remapping. No read barriers are needed as the
+ // tables are either just being loaded and not yet a part of the heap, or boot
+ // image intern tables with non-moveable Strings used when loading an app image.
+ static void RemoveDuplicates(const InternTable::UnorderedSet& old_set,
+ /*inout*/InternTable::UnorderedSet* new_set,
+ /*inout*/SafeMap<mirror::String*, mirror::String*>* intern_remap)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (old_set.size() < new_set->size()) {
+ for (const GcRoot<mirror::String>& old_s : old_set) {
+ auto new_it = new_set->find(old_s);
+ if (UNLIKELY(new_it != new_set->end())) {
+ intern_remap->Put(new_it->Read<kWithoutReadBarrier>(), old_s.Read<kWithoutReadBarrier>());
+ new_set->erase(new_it);
+ }
+ }
+ } else {
+ for (auto new_it = new_set->begin(), end = new_set->end(); new_it != end; ) {
+ auto old_it = old_set.find(*new_it);
+ if (UNLIKELY(old_it != old_set.end())) {
+ intern_remap->Put(new_it->Read<kWithoutReadBarrier>(),
+ old_it->Read<kWithoutReadBarrier>());
+ new_it = new_set->erase(new_it);
+ } else {
+ ++new_it;
+ }
+ }
+ }
+ }
+
static bool ValidateBootImageChecksum(const char* image_filename,
const ImageHeader& image_header,
const OatFile* oat_file,
+ ArrayRef<ImageSpace* const> boot_image_spaces,
+ /*out*/size_t* boot_image_space_dependencies,
/*out*/std::string* error_msg) {
// Use the boot image component count to calculate the checksum from
// the appropriate number of boot image chunks.
- const std::vector<ImageSpace*>& image_spaces =
- Runtime::Current()->GetHeap()->GetBootImageSpaces();
uint32_t boot_image_component_count = image_header.GetBootImageComponentCount();
- size_t image_spaces_size = image_spaces.size();
- if (boot_image_component_count > image_spaces_size) {
+ size_t boot_image_spaces_size = boot_image_spaces.size();
+ if (boot_image_component_count > boot_image_spaces_size) {
*error_msg = StringPrintf("Too many boot image dependencies (%u > %zu) in image %s",
boot_image_component_count,
- image_spaces_size,
+ boot_image_spaces_size,
image_filename);
return false;
}
@@ -977,7 +1094,7 @@
size_t space_pos = 0u;
uint64_t boot_image_size = 0u;
for (size_t component_count = 0u; component_count != boot_image_component_count; ) {
- const ImageHeader& current_header = image_spaces[space_pos]->GetImageHeader();
+ const ImageHeader& current_header = boot_image_spaces[space_pos]->GetImageHeader();
if (current_header.GetComponentCount() > boot_image_component_count - component_count) {
*error_msg = StringPrintf("Boot image component count in %s ends in the middle of a chunk, "
"%u is between %zu and %zu",
@@ -1001,7 +1118,7 @@
return false;
}
if (image_header.GetBootImageSize() != boot_image_size) {
- *error_msg = StringPrintf("Boot image size (0x%08x != 0x%08" PRIx64 ") in image %s",
+ *error_msg = StringPrintf("Boot image size mismatch (0x%08x != 0x%08" PRIx64 ") in image %s",
image_header.GetBootImageSize(),
boot_image_size,
image_filename);
@@ -1029,6 +1146,7 @@
return false;
}
}
+ *boot_image_space_dependencies = space_pos;
return true;
}
@@ -1263,38 +1381,37 @@
// address. In place means modifying a single ImageSpace in place rather than relocating from
// one ImageSpace to another.
template <PointerSize kPointerSize>
- static bool RelocateInPlace(ImageHeader& image_header,
+ static bool RelocateInPlace(uint32_t boot_image_begin,
uint8_t* target_base,
accounting::ContinuousSpaceBitmap* bitmap,
const OatFile* app_oat_file,
std::string* error_msg) {
DCHECK(error_msg != nullptr);
// Set up sections.
- gc::Heap* const heap = Runtime::Current()->GetHeap();
- uint32_t boot_image_begin = heap->GetBootImagesStartAddress();
- const uint32_t boot_image_size = image_header.GetBootImageSize();
- const ImageSection& objects_section = image_header.GetObjectsSection();
+ ImageHeader* image_header = reinterpret_cast<ImageHeader*>(target_base);
+ const uint32_t boot_image_size = image_header->GetBootImageSize();
+ const ImageSection& objects_section = image_header->GetObjectsSection();
// Where the app image objects are mapped to.
uint8_t* objects_location = target_base + objects_section.Offset();
TimingLogger logger(__FUNCTION__, true, false);
- RelocationRange boot_image(image_header.GetBootImageBegin(),
+ RelocationRange boot_image(image_header->GetBootImageBegin(),
boot_image_begin,
boot_image_size);
// Metadata is everything after the objects section, use exclusion to be safe.
RelocationRange app_image_metadata(
- reinterpret_cast<uintptr_t>(image_header.GetImageBegin()) + objects_section.End(),
+ reinterpret_cast<uintptr_t>(image_header->GetImageBegin()) + objects_section.End(),
reinterpret_cast<uintptr_t>(target_base) + objects_section.End(),
- image_header.GetImageSize() - objects_section.End());
+ image_header->GetImageSize() - objects_section.End());
// App image heap objects, may be mapped in the heap.
RelocationRange app_image_objects(
- reinterpret_cast<uintptr_t>(image_header.GetImageBegin()) + objects_section.Offset(),
+ reinterpret_cast<uintptr_t>(image_header->GetImageBegin()) + objects_section.Offset(),
reinterpret_cast<uintptr_t>(objects_location),
objects_section.Size());
// Use the oat data section since this is where the OatFile::Begin is.
- RelocationRange app_oat(reinterpret_cast<uintptr_t>(image_header.GetOatDataBegin()),
+ RelocationRange app_oat(reinterpret_cast<uintptr_t>(image_header->GetOatDataBegin()),
// Not necessarily in low 4GB.
reinterpret_cast<uintptr_t>(app_oat_file->Begin()),
- image_header.GetOatDataEnd() - image_header.GetOatDataBegin());
+ image_header->GetOatDataEnd() - image_header->GetOatDataBegin());
VLOG(image) << "App image metadata " << app_image_metadata;
VLOG(image) << "App image objects " << app_image_objects;
VLOG(image) << "App oat " << app_oat;
@@ -1322,12 +1439,12 @@
gc::accounting::ContinuousSpaceBitmap visited_bitmap(
gc::accounting::ContinuousSpaceBitmap::Create("Relocate bitmap",
target_base,
- image_header.GetImageSize()));
+ image_header->GetImageSize()));
{
TimingLogger::ScopedTiming timing("Fixup classes", &logger);
ObjPtr<mirror::Class> class_class = [&]() NO_THREAD_SAFETY_ANALYSIS {
ObjPtr<mirror::ObjectArray<mirror::Object>> image_roots = app_image_objects.ToDest(
- image_header.GetImageRoots<kWithoutReadBarrier>().Ptr());
+ image_header->GetImageRoots<kWithoutReadBarrier>().Ptr());
int32_t class_roots_index = enum_cast<int32_t>(ImageHeader::kClassRoots);
DCHECK_LT(class_roots_index, image_roots->GetLength<kVerifyNone>());
ObjPtr<mirror::ObjectArray<mirror::Class>> class_roots =
@@ -1335,7 +1452,7 @@
image_roots->GetWithoutChecks<kVerifyNone>(class_roots_index).Ptr()));
return GetClassRoot<mirror::Class, kWithoutReadBarrier>(class_roots);
}();
- const auto& class_table_section = image_header.GetClassTableSection();
+ const auto& class_table_section = image_header->GetClassTableSection();
if (class_table_section.Size() > 0u) {
ScopedObjectAccess soa(Thread::Current());
ClassTableVisitor class_table_visitor(forward_object);
@@ -1394,13 +1511,13 @@
bitmap->VisitMarkedRange(objects_begin, objects_end, fixup_object_visitor);
// Fixup image roots.
CHECK(app_image_objects.InSource(reinterpret_cast<uintptr_t>(
- image_header.GetImageRoots<kWithoutReadBarrier>().Ptr())));
- image_header.RelocateImageReferences(app_image_objects.Delta());
- image_header.RelocateBootImageReferences(boot_image.Delta());
- CHECK_EQ(image_header.GetImageBegin(), target_base);
+ image_header->GetImageRoots<kWithoutReadBarrier>().Ptr())));
+ image_header->RelocateImageReferences(app_image_objects.Delta());
+ image_header->RelocateBootImageReferences(boot_image.Delta());
+ CHECK_EQ(image_header->GetImageBegin(), target_base);
// Fix up dex cache DexFile pointers.
ObjPtr<mirror::ObjectArray<mirror::DexCache>> dex_caches =
- image_header.GetImageRoot<kWithoutReadBarrier>(ImageHeader::kDexCaches)
+ image_header->GetImageRoot<kWithoutReadBarrier>(ImageHeader::kDexCaches)
->AsObjectArray<mirror::DexCache, kVerifyNone>();
for (int32_t i = 0, count = dex_caches->GetLength(); i < count; ++i) {
ObjPtr<mirror::DexCache> dex_cache = dex_caches->Get<kVerifyNone, kWithoutReadBarrier>(i);
@@ -1411,7 +1528,7 @@
{
// Only touches objects in the app image, no need for mutator lock.
TimingLogger::ScopedTiming timing("Fixup methods", &logger);
- image_header.VisitPackedArtMethods([&](ArtMethod& method) NO_THREAD_SAFETY_ANALYSIS {
+ image_header->VisitPackedArtMethods([&](ArtMethod& method) NO_THREAD_SAFETY_ANALYSIS {
// TODO: Consider a separate visitor for runtime vs normal methods.
if (UNLIKELY(method.IsRuntimeMethod())) {
ImtConflictTable* table = method.GetImtConflictTable(kPointerSize);
@@ -1436,21 +1553,21 @@
{
// Only touches objects in the app image, no need for mutator lock.
TimingLogger::ScopedTiming timing("Fixup fields", &logger);
- image_header.VisitPackedArtFields([&](ArtField& field) NO_THREAD_SAFETY_ANALYSIS {
+ image_header->VisitPackedArtFields([&](ArtField& field) NO_THREAD_SAFETY_ANALYSIS {
patch_object_visitor.template PatchGcRoot</*kMayBeNull=*/ false>(
&field.DeclaringClassRoot());
}, target_base);
}
{
TimingLogger::ScopedTiming timing("Fixup imt", &logger);
- image_header.VisitPackedImTables(forward_metadata, target_base, kPointerSize);
+ image_header->VisitPackedImTables(forward_metadata, target_base, kPointerSize);
}
{
TimingLogger::ScopedTiming timing("Fixup conflict tables", &logger);
- image_header.VisitPackedImtConflictTables(forward_metadata, target_base, kPointerSize);
+ image_header->VisitPackedImtConflictTables(forward_metadata, target_base, kPointerSize);
}
// Fix up the intern table.
- const auto& intern_table_section = image_header.GetInternedStringsSection();
+ const auto& intern_table_section = image_header->GetInternedStringsSection();
if (intern_table_section.Size() > 0u) {
TimingLogger::ScopedTiming timing("Fixup intern table", &logger);
ScopedObjectAccess soa(Thread::Current());
@@ -2539,6 +2656,7 @@
}
MaybeRelocateSpaces(spaces, logger);
+ DeduplicateInternedStrings(ArrayRef<const std::unique_ptr<ImageSpace>>(spaces), logger);
boot_image_spaces->swap(spaces);
*extra_reservation = std::move(local_extra_reservation);
return true;
@@ -2680,6 +2798,9 @@
? static_cast<int64_t>(reinterpret_cast32<uint32_t>(spaces.front()->Begin())) -
static_cast<int64_t>(image_begin)
: base_diff64;
+ if (base_diff64 == 0 && current_diff64 == 0) {
+ return;
+ }
uint32_t base_diff = static_cast<uint32_t>(base_diff64);
uint32_t current_diff = static_cast<uint32_t>(current_diff64);
@@ -2719,7 +2840,8 @@
class_roots = ObjPtr<mirror::ObjectArray<mirror::Class>>::DownCast(base_relocate_visitor(
image_roots->GetWithoutChecks<kVerifyNone>(class_roots_index).Ptr()));
if (kExtension) {
- DCHECK(patched_objects->Test(class_roots.Ptr()));
+ // Class roots must have been visited if we relocated the primary boot image.
+ DCHECK(base_diff == 0 || patched_objects->Test(class_roots.Ptr()));
class_class = GetClassRoot<mirror::Class, kWithoutReadBarrier>(class_roots);
method_class = GetClassRoot<mirror::Method, kWithoutReadBarrier>(class_roots);
constructor_class = GetClassRoot<mirror::Constructor, kWithoutReadBarrier>(class_roots);
@@ -2868,7 +2990,6 @@
static_cast<int64_t>(reinterpret_cast32<uint32_t>(first_space_header.GetImageBegin()));
if (!relocate_) {
DCHECK_EQ(base_diff64, 0);
- return;
}
ArrayRef<const std::unique_ptr<ImageSpace>> spaces_ref(spaces);
@@ -2880,6 +3001,56 @@
}
}
+ void DeduplicateInternedStrings(ArrayRef<const std::unique_ptr<ImageSpace>> spaces,
+ TimingLogger* logger) REQUIRES_SHARED(Locks::mutator_lock_) {
+ TimingLogger::ScopedTiming timing("DeduplicateInternedStrings", logger);
+ DCHECK(!spaces.empty());
+ size_t num_spaces = spaces.size();
+ const ImageHeader& primary_header = spaces.front()->GetImageHeader();
+ size_t primary_image_count = primary_header.GetImageSpaceCount();
+ DCHECK_LE(primary_image_count, num_spaces);
+ DCHECK_EQ(primary_image_count, primary_header.GetComponentCount());
+ size_t component_count = primary_image_count;
+ size_t space_pos = primary_image_count;
+ while (space_pos != num_spaces) {
+ const ImageHeader& current_header = spaces[space_pos]->GetImageHeader();
+ size_t image_space_count = current_header.GetImageSpaceCount();
+ DCHECK_LE(image_space_count, num_spaces - space_pos);
+ size_t dependency_component_count = current_header.GetBootImageComponentCount();
+ DCHECK_LE(dependency_component_count, component_count);
+ if (dependency_component_count < component_count) {
+ // There shall be no duplicate strings with the components that this space depends on.
+ // Find the end of the dependencies, i.e. start of non-dependency images.
+ size_t start_component_count = primary_image_count;
+ size_t start_pos = primary_image_count;
+ while (start_component_count != dependency_component_count) {
+ const ImageHeader& dependency_header = spaces[start_pos]->GetImageHeader();
+ DCHECK_LE(dependency_header.GetComponentCount(),
+ dependency_component_count - start_component_count);
+ start_component_count += dependency_header.GetComponentCount();
+ start_pos += dependency_header.GetImageSpaceCount();
+ }
+ // Remove duplicates from all intern tables belonging to the chunk.
+ ArrayRef<const std::unique_ptr<ImageSpace>> old_spaces =
+ spaces.SubArray(/*pos=*/ start_pos, space_pos - start_pos);
+ SafeMap<mirror::String*, mirror::String*> intern_remap;
+ for (size_t i = 0; i != image_space_count; ++i) {
+ ImageSpace* new_space = spaces[space_pos + i].get();
+ Loader::RemoveInternTableDuplicates(old_spaces, new_space, &intern_remap);
+ }
+ // Remap string for all spaces belonging to the chunk.
+ if (!intern_remap.empty()) {
+ for (size_t i = 0; i != image_space_count; ++i) {
+ ImageSpace* new_space = spaces[space_pos + i].get();
+ Loader::RemapInternedStringDuplicates(std::move(intern_remap), new_space);
+ }
+ }
+ }
+ component_count += current_header.GetComponentCount();
+ space_pos += image_space_count;
+ }
+ }
+
std::unique_ptr<ImageSpace> Load(const std::string& image_location,
const std::string& image_filename,
const std::string& profile_file,
@@ -2899,7 +3070,6 @@
image_filename.c_str(),
image_location.c_str(),
profile_file.c_str(),
- /*oat_file=*/ nullptr,
/*allow_direct_mapping=*/ false,
logger,
image_reservation,
@@ -2936,7 +3106,6 @@
// file name.
return Loader::Init(image_filename.c_str(),
image_location.c_str(),
- /*oat_file=*/ nullptr,
logger,
image_reservation,
error_msg);
@@ -3549,10 +3718,23 @@
const OatFile* oat_file,
std::string* error_msg) {
// Note: The oat file has already been validated.
+ const std::vector<ImageSpace*>& boot_image_spaces =
+ Runtime::Current()->GetHeap()->GetBootImageSpaces();
+ return CreateFromAppImage(image,
+ oat_file,
+ ArrayRef<ImageSpace* const>(boot_image_spaces),
+ error_msg);
+}
+
+std::unique_ptr<ImageSpace> ImageSpace::CreateFromAppImage(
+ const char* image,
+ const OatFile* oat_file,
+ ArrayRef<ImageSpace* const> boot_image_spaces,
+ std::string* error_msg) {
return Loader::InitAppImage(image,
image,
oat_file,
- /*image_reservation=*/ nullptr,
+ boot_image_spaces,
error_msg);
}
diff --git a/runtime/gc/space/image_space.h b/runtime/gc/space/image_space.h
index cf23e75..4ddc519 100644
--- a/runtime/gc/space/image_space.h
+++ b/runtime/gc/space/image_space.h
@@ -136,11 +136,18 @@
/*out*/std::vector<std::unique_ptr<ImageSpace>>* boot_image_spaces,
/*out*/MemMap* extra_reservation) REQUIRES_SHARED(Locks::mutator_lock_);
- // Try to open an existing app image space.
+ // Try to open an existing app image space for an oat file,
+ // using the boot image spaces from the current Runtime.
static std::unique_ptr<ImageSpace> CreateFromAppImage(const char* image,
const OatFile* oat_file,
std::string* error_msg)
REQUIRES_SHARED(Locks::mutator_lock_);
+ // Try to open an existing app image space for an the oat file and given boot image spaces.
+ static std::unique_ptr<ImageSpace> CreateFromAppImage(
+ const char* image,
+ const OatFile* oat_file,
+ ArrayRef<ImageSpace* const> boot_image_spaces,
+ std::string* error_msg) REQUIRES_SHARED(Locks::mutator_lock_);
// Checks whether we have a primary boot image on the disk.
static bool IsBootClassPathOnDisk(InstructionSet image_isa);
@@ -317,6 +324,7 @@
class BootImageLoader;
template <typename ReferenceVisitor>
class ClassTableVisitor;
+ class RemapInternedStringsVisitor;
class Loader;
template <typename PatchObjectVisitor>
class PatchArtFieldVisitor;
diff --git a/runtime/gc/space/image_space_test.cc b/runtime/gc/space/image_space_test.cc
index 580b490..b08c680 100644
--- a/runtime/gc/space/image_space_test.cc
+++ b/runtime/gc/space/image_space_test.cc
@@ -16,18 +16,211 @@
#include <gtest/gtest.h>
+#include "android-base/logging.h"
#include "android-base/stringprintf.h"
#include "android-base/strings.h"
#include "base/stl_util.h"
#include "class_linker.h"
#include "dexopt_test.h"
+#include "dex/utf.h"
+#include "intern_table.h"
#include "noop_compiler_callbacks.h"
+#include "oat_file.h"
namespace art {
namespace gc {
namespace space {
+class ImageSpaceTest : public CommonRuntimeTest {
+ protected:
+ void SetUpRuntimeOptions(RuntimeOptions* options) override {
+ // Disable implicit dex2oat invocations when loading image spaces.
+ options->emplace_back("-Xnoimage-dex2oat", nullptr);
+ // Disable relocation.
+ options->emplace_back("-Xnorelocate", nullptr);
+ }
+
+ std::string GetFilenameBase(const std::string& full_path) {
+ size_t slash_pos = full_path.rfind('/');
+ CHECK_NE(std::string::npos, slash_pos);
+ size_t dot_pos = full_path.rfind('.');
+ CHECK_NE(std::string::npos, dot_pos);
+ CHECK_GT(dot_pos, slash_pos + 1u);
+ return full_path.substr(slash_pos + 1u, dot_pos - (slash_pos + 1u));
+ }
+};
+
+TEST_F(ImageSpaceTest, StringDeduplication) {
+ const char* const kBaseNames[] = { "Extension1", "Extension2" };
+
+ ScratchDir scratch;
+ const std::string& scratch_dir = scratch.GetPath();
+ std::string image_dir = scratch_dir + GetInstructionSetString(kRuntimeISA);
+ int mkdir_result = mkdir(image_dir.c_str(), 0700);
+ ASSERT_EQ(0, mkdir_result);
+
+ // Prepare boot class path variables, exclude conscrypt which is not in the primary boot image.
+ std::vector<std::string> bcp = GetLibCoreDexFileNames();
+ std::vector<std::string> bcp_locations = GetLibCoreDexLocations();
+ CHECK_EQ(bcp.size(), bcp_locations.size());
+ ASSERT_NE(std::string::npos, bcp.back().find("conscrypt"));
+ bcp.pop_back();
+ bcp_locations.pop_back();
+ std::string base_bcp_string = android::base::Join(bcp, ':');
+ std::string base_bcp_locations_string = android::base::Join(bcp_locations, ':');
+ std::string base_image_location = GetImageLocation();
+
+ // Compile the two extensions independently.
+ std::vector<std::string> extension_image_locations;
+ for (const char* base_name : kBaseNames) {
+ std::string jar_name = GetTestDexFileName(base_name);
+ ArrayRef<const std::string> dex_files(&jar_name, /*size=*/ 1u);
+ ScratchFile profile_file;
+ GenerateProfile(dex_files, profile_file.GetFile());
+ std::vector<std::string> extra_args = {
+ "--profile-file=" + profile_file.GetFilename(),
+ "--runtime-arg",
+ "-Xbootclasspath:" + base_bcp_string + ':' + jar_name,
+ "--runtime-arg",
+ "-Xbootclasspath-locations:" + base_bcp_locations_string + ':' + jar_name,
+ "--boot-image=" + base_image_location,
+ };
+ std::string prefix = GetFilenameBase(base_image_location);
+ std::string error_msg;
+ bool success = CompileBootImage(extra_args, image_dir + '/' + prefix, dex_files, &error_msg);
+ ASSERT_TRUE(success) << error_msg;
+ bcp.push_back(jar_name);
+ bcp_locations.push_back(jar_name);
+ extension_image_locations.push_back(
+ scratch_dir + prefix + '-' + GetFilenameBase(jar_name) + ".art");
+ }
+
+ // Also compile the second extension as an app with app image.
+ const char* app_base_name = kBaseNames[std::size(kBaseNames) - 1u];
+ std::string app_jar_name = GetTestDexFileName(app_base_name);
+ std::string app_odex_name = scratch_dir + app_base_name + ".odex";
+ std::string app_image_name = scratch_dir + app_base_name + ".art";
+ {
+ ArrayRef<const std::string> dex_files(&app_jar_name, /*size=*/ 1u);
+ ScratchFile profile_file;
+ GenerateProfile(dex_files, profile_file.GetFile());
+ std::vector<std::string> argv;
+ std::string error_msg;
+ bool success = StartDex2OatCommandLine(&argv, &error_msg, /*use_runtime_bcp_and_image=*/ false);
+ ASSERT_TRUE(success) << error_msg;
+ argv.insert(argv.end(), {
+ "--profile-file=" + profile_file.GetFilename(),
+ "--runtime-arg",
+ "-Xbootclasspath:" + base_bcp_string,
+ "--runtime-arg",
+ "-Xbootclasspath-locations:" + base_bcp_locations_string,
+ "--boot-image=" + base_image_location,
+ "--dex-file=" + app_jar_name,
+ "--dex-location=" + app_jar_name,
+ "--oat-file=" + app_odex_name,
+ "--app-image-file=" + app_image_name,
+ "--initialize-app-image-classes=true",
+ });
+ success = RunDex2Oat(argv, &error_msg);
+ ASSERT_TRUE(success) << error_msg;
+ }
+
+ std::string full_image_locations;
+ std::vector<std::unique_ptr<gc::space::ImageSpace>> boot_image_spaces;
+ MemMap extra_reservation;
+ auto load_boot_image = [&]() REQUIRES_SHARED(Locks::mutator_lock_) {
+ boot_image_spaces.clear();
+ extra_reservation = MemMap::Invalid();
+ return ImageSpace::LoadBootImage(bcp,
+ bcp_locations,
+ full_image_locations,
+ kRuntimeISA,
+ ImageSpaceLoadingOrder::kSystemFirst,
+ /*relocate=*/ false,
+ /*executable=*/ true,
+ /*is_zygote=*/ false,
+ /*extra_reservation_size=*/ 0u,
+ &boot_image_spaces,
+ &extra_reservation);
+ };
+
+ const char test_string[] = "SharedBootImageExtensionTestString";
+ size_t test_string_length = std::size(test_string) - 1u; // Equals UTF-16 length.
+ uint32_t hash = ComputeUtf16HashFromModifiedUtf8(test_string, test_string_length);
+ InternTable::Utf8String utf8_test_string(test_string_length, test_string, hash);
+ auto contains_test_string = [utf8_test_string](ImageSpace* space)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ const ImageHeader& image_header = space->GetImageHeader();
+ if (image_header.GetInternedStringsSection().Size() != 0u) {
+ const uint8_t* data = space->Begin() + image_header.GetInternedStringsSection().Offset();
+ size_t read_count;
+ InternTable::UnorderedSet temp_set(data, /*make_copy_of_data=*/ false, &read_count);
+ return temp_set.find(utf8_test_string) != temp_set.end();
+ } else {
+ return false;
+ }
+ };
+
+ // Load extensions and test for the presence of the test string.
+ ScopedObjectAccess soa(Thread::Current());
+ ASSERT_EQ(2u, extension_image_locations.size());
+ full_image_locations = base_image_location +
+ ImageSpace::kComponentSeparator + extension_image_locations[0] +
+ ImageSpace::kComponentSeparator + extension_image_locations[1];
+ bool success = load_boot_image();
+ ASSERT_TRUE(success);
+ ASSERT_EQ(bcp.size(), boot_image_spaces.size());
+ EXPECT_TRUE(contains_test_string(boot_image_spaces[boot_image_spaces.size() - 2u].get()));
+ // The string in the second extension should be replaced and removed from interned string section.
+ EXPECT_FALSE(contains_test_string(boot_image_spaces[boot_image_spaces.size() - 1u].get()));
+
+ // Reload extensions in reverse order and test for the presence of the test string.
+ std::swap(bcp[bcp.size() - 2u], bcp[bcp.size() - 1u]);
+ std::swap(bcp_locations[bcp_locations.size() - 2u], bcp_locations[bcp_locations.size() - 1u]);
+ full_image_locations = base_image_location +
+ ImageSpace::kComponentSeparator + extension_image_locations[1] +
+ ImageSpace::kComponentSeparator + extension_image_locations[0];
+ success = load_boot_image();
+ ASSERT_TRUE(success);
+ ASSERT_EQ(bcp.size(), boot_image_spaces.size());
+ EXPECT_TRUE(contains_test_string(boot_image_spaces[boot_image_spaces.size() - 2u].get()));
+ // The string in the second extension should be replaced and removed from interned string section.
+ EXPECT_FALSE(contains_test_string(boot_image_spaces[boot_image_spaces.size() - 1u].get()));
+
+ // Reload the image without the second extension.
+ bcp.erase(bcp.end() - 2u);
+ bcp_locations.erase(bcp_locations.end() - 2u);
+ full_image_locations =
+ base_image_location + ImageSpace::kComponentSeparator + extension_image_locations[0];
+ success = load_boot_image();
+ ASSERT_TRUE(success);
+ ASSERT_EQ(bcp.size(), boot_image_spaces.size());
+ ASSERT_TRUE(contains_test_string(boot_image_spaces[boot_image_spaces.size() - 1u].get()));
+
+ // Load the app odex file and app image.
+ std::string error_msg;
+ std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/ -1,
+ app_odex_name.c_str(),
+ app_odex_name.c_str(),
+ /*executable=*/ false,
+ /*low_4gb=*/ false,
+ app_jar_name,
+ &error_msg));
+ ASSERT_TRUE(odex_file != nullptr) << error_msg;
+ std::vector<ImageSpace*> non_owning_boot_image_spaces =
+ MakeNonOwningPointerVector(boot_image_spaces);
+ std::unique_ptr<ImageSpace> app_image_space = ImageSpace::CreateFromAppImage(
+ app_image_name.c_str(),
+ odex_file.get(),
+ ArrayRef<ImageSpace* const>(non_owning_boot_image_spaces),
+ &error_msg);
+ ASSERT_TRUE(app_image_space != nullptr) << error_msg;
+
+ // The string in the app image should be replaced and removed from interned string section.
+ EXPECT_FALSE(contains_test_string(app_image_space.get()));
+}
+
TEST_F(DexoptTest, ValidateOatFile) {
std::string dex1 = GetScratchDir() + "/Dex1.jar";
std::string multidex1 = GetScratchDir() + "/MultiDex1.jar";
@@ -234,6 +427,8 @@
class NoAccessAndroidDataTest : public ImageSpaceLoadingTest<false, true, true> {
protected:
+ NoAccessAndroidDataTest() : quiet_(LogSeverity::FATAL) {}
+
void SetUpRuntimeOptions(RuntimeOptions* options) override {
const char* android_data = getenv("ANDROID_DATA");
CHECK(android_data != nullptr);
@@ -265,6 +460,7 @@
}
private:
+ ScopedLogSeverity quiet_;
std::string old_android_data_;
std::string bad_android_data_;
std::string bad_dalvik_cache_;
diff --git a/runtime/image.h b/runtime/image.h
index 896a83b..637bf1c 100644
--- a/runtime/image.h
+++ b/runtime/image.h
@@ -579,8 +579,8 @@
return val & ~3u;
}
-std::ostream& operator<<(std::ostream& os, const ImageHeader::ImageMethod& policy);
-std::ostream& operator<<(std::ostream& os, const ImageHeader::ImageRoot& policy);
+std::ostream& operator<<(std::ostream& os, const ImageHeader::ImageMethod& method);
+std::ostream& operator<<(std::ostream& os, const ImageHeader::ImageRoot& root);
std::ostream& operator<<(std::ostream& os, const ImageHeader::ImageSections& section);
std::ostream& operator<<(std::ostream& os, const ImageSection& section);
std::ostream& operator<<(std::ostream& os, const ImageHeader::StorageMode& mode);
diff --git a/runtime/intern_table-inl.h b/runtime/intern_table-inl.h
index 6fc53e9..687f5ee 100644
--- a/runtime/intern_table-inl.h
+++ b/runtime/intern_table-inl.h
@@ -19,8 +19,9 @@
#include "intern_table.h"
-// Required for ToModifiedUtf8 below.
-#include "mirror/string-inl.h"
+#include "gc/space/image_space.h"
+#include "image.h"
+#include "mirror/string-inl.h" // Required for ToModifiedUtf8 below.
namespace art {
diff --git a/runtime/intern_table.h b/runtime/intern_table.h
index a5301a5..7065015 100644
--- a/runtime/intern_table.h
+++ b/runtime/intern_table.h
@@ -17,9 +17,6 @@
#ifndef ART_RUNTIME_INTERN_TABLE_H_
#define ART_RUNTIME_INTERN_TABLE_H_
-#include <unordered_set>
-
-#include "base/atomic.h"
#include "base/allocator.h"
#include "base/hash_set.h"
#include "base/mutex.h"
diff --git a/test/Android.bp b/test/Android.bp
index 024e55e..db21339 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -950,6 +950,8 @@
":art-gtest-jars-ErroneousA",
":art-gtest-jars-ErroneousB",
":art-gtest-jars-ErroneousInit",
+ ":art-gtest-jars-Extension1",
+ ":art-gtest-jars-Extension2",
":art-gtest-jars-ForClassLoaderA",
":art-gtest-jars-ForClassLoaderB",
":art-gtest-jars-ForClassLoaderC",
@@ -1050,6 +1052,18 @@
}
java_library {
+ name: "art-gtest-jars-Extension1",
+ srcs: ["Extension1/**/*.java"],
+ defaults: ["art-gtest-jars-defaults"],
+}
+
+java_library {
+ name: "art-gtest-jars-Extension2",
+ srcs: ["Extension2/**/*.java"],
+ defaults: ["art-gtest-jars-defaults"],
+}
+
+java_library {
name: "art-gtest-jars-ForClassLoaderA",
srcs: ["ForClassLoaderA/**/*.java"],
defaults: ["art-gtest-jars-defaults"],
diff --git a/test/Extension1/ExtensionClass1.java b/test/Extension1/ExtensionClass1.java
new file mode 100644
index 0000000..b59643a
--- /dev/null
+++ b/test/Extension1/ExtensionClass1.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+class ExtensionClass1 {
+ public static String sharedString = "SharedBootImageExtensionTestString";
+ public static String uniqueString = "UniqueExtension1String";
+}
diff --git a/test/Extension2/ExtensionClass2.java b/test/Extension2/ExtensionClass2.java
new file mode 100644
index 0000000..437341d
--- /dev/null
+++ b/test/Extension2/ExtensionClass2.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+class ExtensionClass2 {
+ public static String sharedString = "SharedBootImageExtensionTestString";
+ public static String uniqueString1 = "UniqueExtension2String1";
+ public static String uniqueString2 = "UniqueExtension2String2";
+}