diff options
-rw-r--r-- | compiler/driver/compiler_options.cc | 1 | ||||
-rw-r--r-- | compiler/driver/compiler_options.h | 11 | ||||
-rw-r--r-- | compiler/driver/compiler_options_map-inl.h | 7 | ||||
-rw-r--r-- | compiler/driver/compiler_options_map.def | 1 | ||||
-rw-r--r-- | dex2oat/dex2oat.cc | 2 | ||||
-rw-r--r-- | dex2oat/linker/image_test.cc | 19 | ||||
-rw-r--r-- | dex2oat/linker/image_test.h | 14 | ||||
-rw-r--r-- | dex2oat/linker/image_write_read_test.cc | 14 | ||||
-rw-r--r-- | dex2oat/linker/image_writer.cc | 126 | ||||
-rw-r--r-- | oatdump/oatdump.cc | 2 | ||||
-rw-r--r-- | runtime/gc/collector/immune_spaces_test.cc | 4 | ||||
-rw-r--r-- | runtime/gc/space/image_space.cc | 49 | ||||
-rw-r--r-- | runtime/image.cc | 43 | ||||
-rw-r--r-- | runtime/image.h | 79 |
14 files changed, 268 insertions, 104 deletions
diff --git a/compiler/driver/compiler_options.cc b/compiler/driver/compiler_options.cc index bef5be1c0d..b28c7e0edb 100644 --- a/compiler/driver/compiler_options.cc +++ b/compiler/driver/compiler_options.cc @@ -71,6 +71,7 @@ CompilerOptions::CompilerOptions() count_hotness_in_compiled_code_(false), resolve_startup_const_strings_(false), check_profiled_methods_(ProfileMethodsCheck::kNone), + max_image_block_size_(std::numeric_limits<uint32_t>::max()), register_allocation_strategy_(RegisterAllocator::kRegisterAllocatorDefault), passes_to_run_(nullptr) { } diff --git a/compiler/driver/compiler_options.h b/compiler/driver/compiler_options.h index f0970a9e0d..17a779c965 100644 --- a/compiler/driver/compiler_options.h +++ b/compiler/driver/compiler_options.h @@ -335,6 +335,14 @@ class CompilerOptions final { return check_profiled_methods_; } + uint32_t MaxImageBlockSize() const { + return max_image_block_size_; + } + + void SetMaxImageBlockSize(uint32_t size) { + max_image_block_size_ = size; + } + private: bool ParseDumpInitFailures(const std::string& option, std::string* error_msg); void ParseDumpCfgPasses(const StringPiece& option, UsageFn Usage); @@ -424,6 +432,9 @@ class CompilerOptions final { // up compiled and are not punted. ProfileMethodsCheck check_profiled_methods_; + // Maximum solid block size in the generated image. + uint32_t max_image_block_size_; + RegisterAllocator::Strategy register_allocation_strategy_; // If not null, specifies optimization passes which will be run instead of defaults. diff --git a/compiler/driver/compiler_options_map-inl.h b/compiler/driver/compiler_options_map-inl.h index c7334a72e7..7e2a64b52b 100644 --- a/compiler/driver/compiler_options_map-inl.h +++ b/compiler/driver/compiler_options_map-inl.h @@ -84,6 +84,7 @@ inline bool ReadCompilerOptions(Base& map, CompilerOptions* options, std::string if (map.Exists(Base::CheckProfiledMethods)) { options->check_profiled_methods_ = *map.Get(Base::CheckProfiledMethods); } + map.AssignIfExists(Base::MaxImageBlockSize, &options->max_image_block_size_); if (map.Exists(Base::DumpTimings)) { options->dump_timings_ = true; @@ -201,7 +202,11 @@ inline void AddCompilerOptionsArgumentParserOptions(Builder& b) { .Define("--verbose-methods=_") .template WithType<ParseStringList<','>>() - .IntoKey(Map::VerboseMethods); + .IntoKey(Map::VerboseMethods) + + .Define("--max-image-block-size=_") + .template WithType<unsigned int>() + .IntoKey(Map::MaxImageBlockSize); } #pragma GCC diagnostic pop diff --git a/compiler/driver/compiler_options_map.def b/compiler/driver/compiler_options_map.def index c2fac5e017..0a9c873988 100644 --- a/compiler/driver/compiler_options_map.def +++ b/compiler/driver/compiler_options_map.def @@ -65,5 +65,6 @@ COMPILER_OPTIONS_KEY (ProfileMethodsCheck, CheckProfiledMethods) COMPILER_OPTIONS_KEY (Unit, DumpTimings) COMPILER_OPTIONS_KEY (Unit, DumpPassTimings) COMPILER_OPTIONS_KEY (Unit, DumpStats) +COMPILER_OPTIONS_KEY (unsigned int, MaxImageBlockSize) #undef COMPILER_OPTIONS_KEY diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index 8f56f3e82d..edd61897a9 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -482,6 +482,8 @@ NO_RETURN static void Usage(const char* fmt, ...) { UsageError(" --resolve-startup-const-strings=true|false: If true, the compiler eagerly"); UsageError(" resolves strings referenced from const-string of startup methods."); UsageError(""); + UsageError(" --max-image-block-size=<size>: Maximum solid block size for compressed images."); + UsageError(""); UsageError(" Example: --compilation-reason=install"); UsageError(""); std::cerr << "See log for usage error information\n"; diff --git a/dex2oat/linker/image_test.cc b/dex2oat/linker/image_test.cc index 64b98cdb37..ebd829bc98 100644 --- a/dex2oat/linker/image_test.cc +++ b/dex2oat/linker/image_test.cc @@ -32,7 +32,11 @@ TEST_F(ImageTest, TestImageLayout) { // Compile multi-image with ImageLayoutA being the last image. { CompilationHelper helper; - Compile(ImageHeader::kStorageModeUncompressed, helper, "ImageLayoutA", {"LMyClass;"}); + Compile(ImageHeader::kStorageModeUncompressed, + /*max_image_block_size=*/std::numeric_limits<uint32_t>::max(), + helper, + "ImageLayoutA", + {"LMyClass;"}); image_sizes = helper.GetImageObjectSectionSizes(); } TearDown(); @@ -41,7 +45,11 @@ TEST_F(ImageTest, TestImageLayout) { // Compile multi-image with ImageLayoutB being the last image. { CompilationHelper helper; - Compile(ImageHeader::kStorageModeUncompressed, helper, "ImageLayoutB", {"LMyClass;"}); + Compile(ImageHeader::kStorageModeUncompressed, + /*max_image_block_size=*/std::numeric_limits<uint32_t>::max(), + helper, + "ImageLayoutB", + {"LMyClass;"}); image_sizes_extra = helper.GetImageObjectSectionSizes(); } // Make sure that the new stuff in the clinit in ImageLayoutB is in the last image and not in the @@ -76,9 +84,8 @@ TEST_F(ImageTest, ImageHeaderIsValid) { oat_file_end, /*boot_image_begin=*/ 0u, /*boot_image_size=*/ 0u, - sizeof(void*), - ImageHeader::kDefaultStorageMode, - /*data_size=*/ 0u); + sizeof(void*)); + ASSERT_TRUE(image_header.IsValid()); ASSERT_TRUE(!image_header.IsAppImage()); @@ -97,6 +104,7 @@ TEST_F(ImageTest, ImageHeaderIsValid) { TEST_F(ImageTest, TestDefaultMethods) { CompilationHelper helper; Compile(ImageHeader::kStorageModeUncompressed, + /*max_image_block_size=*/std::numeric_limits<uint32_t>::max(), helper, "DefaultMethods", {"LIface;", "LImpl;", "LIterableBase;"}); @@ -156,6 +164,7 @@ TEST_F(ImageTest, TestDefaultMethods) { TEST_F(ImageTest, TestSoftVerificationFailureDuringClassInitialization) { CompilationHelper helper; Compile(ImageHeader::kStorageModeUncompressed, + /*max_image_block_size=*/std::numeric_limits<uint32_t>::max(), helper, "VerifySoftFailDuringClinit", /*image_classes=*/ {"LClassToInitialize;"}, diff --git a/dex2oat/linker/image_test.h b/dex2oat/linker/image_test.h index cde657380a..844a72803e 100644 --- a/dex2oat/linker/image_test.h +++ b/dex2oat/linker/image_test.h @@ -77,9 +77,10 @@ class ImageTest : public CommonCompilerTest { CommonCompilerTest::SetUp(); } - void TestWriteRead(ImageHeader::StorageMode storage_mode); + void TestWriteRead(ImageHeader::StorageMode storage_mode, uint32_t max_image_block_size); void Compile(ImageHeader::StorageMode storage_mode, + uint32_t max_image_block_size, /*out*/ CompilationHelper& out_helper, const std::string& extra_dex = "", const std::initializer_list<std::string>& image_classes = {}, @@ -362,6 +363,7 @@ inline void ImageTest::DoCompile(ImageHeader::StorageMode storage_mode, inline void ImageTest::Compile( ImageHeader::StorageMode storage_mode, + uint32_t max_image_block_size, CompilationHelper& helper, const std::string& extra_dex, const std::initializer_list<std::string>& image_classes, @@ -376,6 +378,7 @@ inline void ImageTest::Compile( CreateCompilerDriver(); // Set inline filter values. compiler_options_->SetInlineMaxCodeUnits(CompilerOptions::kDefaultInlineMaxCodeUnits); + compiler_options_->SetMaxImageBlockSize(max_image_block_size); image_classes_.clear(); if (!extra_dex.empty()) { helper.extra_dex_files = OpenTestDexFiles(extra_dex.c_str()); @@ -399,9 +402,10 @@ inline void ImageTest::Compile( } } -inline void ImageTest::TestWriteRead(ImageHeader::StorageMode storage_mode) { +inline void ImageTest::TestWriteRead(ImageHeader::StorageMode storage_mode, + uint32_t max_image_block_size) { CompilationHelper helper; - Compile(storage_mode, /*out*/ helper); + Compile(storage_mode, max_image_block_size, /*out*/ helper); std::vector<uint64_t> image_file_sizes; for (ScratchFile& image_file : helper.image_files) { std::unique_ptr<File> file(OS::OpenFileForReading(image_file.GetFilename().c_str())); @@ -476,6 +480,10 @@ inline void ImageTest::TestWriteRead(ImageHeader::StorageMode storage_mode) { } else if (image_file_size > 16 * KB) { // Compressed, file should be smaller than image. Not really valid for small images. ASSERT_LE(image_file_size, image_space->GetImageHeader().GetImageSize()); + // TODO: Actually validate the blocks, this is hard since the blocks are not copied over for + // compressed images. Add kPageSize since image_size is rounded up to this. + ASSERT_GT(image_space->GetImageHeader().GetBlockCount() * max_image_block_size, + image_space->GetImageHeader().GetImageSize() - kPageSize); } image_space->VerifyImageAllocations(); diff --git a/dex2oat/linker/image_write_read_test.cc b/dex2oat/linker/image_write_read_test.cc index 30996b562b..5ddbd0944d 100644 --- a/dex2oat/linker/image_write_read_test.cc +++ b/dex2oat/linker/image_write_read_test.cc @@ -20,15 +20,23 @@ namespace art { namespace linker { TEST_F(ImageTest, WriteReadUncompressed) { - TestWriteRead(ImageHeader::kStorageModeUncompressed); + TestWriteRead(ImageHeader::kStorageModeUncompressed, + /*max_image_block_size=*/std::numeric_limits<uint32_t>::max()); } TEST_F(ImageTest, WriteReadLZ4) { - TestWriteRead(ImageHeader::kStorageModeLZ4); + TestWriteRead(ImageHeader::kStorageModeLZ4, + /*max_image_block_size=*/std::numeric_limits<uint32_t>::max()); } TEST_F(ImageTest, WriteReadLZ4HC) { - TestWriteRead(ImageHeader::kStorageModeLZ4HC); + TestWriteRead(ImageHeader::kStorageModeLZ4HC, + /*max_image_block_size=*/std::numeric_limits<uint32_t>::max()); +} + + +TEST_F(ImageTest, WriteReadLZ4HCKBBlock) { + TestWriteRead(ImageHeader::kStorageModeLZ4HC, /*max_image_block_size=*/KB); } } // namespace linker diff --git a/dex2oat/linker/image_writer.cc b/dex2oat/linker/image_writer.cc index a7c7fbf895..231ac04f8c 100644 --- a/dex2oat/linker/image_writer.cc +++ b/dex2oat/linker/image_writer.cc @@ -745,32 +745,93 @@ bool ImageWriter::Write(int image_fd, // Image data size excludes the bitmap and the header. ImageHeader* const image_header = reinterpret_cast<ImageHeader*>(image_info.image_.Begin()); - ArrayRef<const uint8_t> raw_image_data(image_info.image_.Begin() + sizeof(ImageHeader), - image_header->GetImageSize() - sizeof(ImageHeader)); - - CHECK_EQ(image_header->storage_mode_, image_storage_mode_); - std::vector<uint8_t> compressed_data; - ArrayRef<const uint8_t> image_data = - MaybeCompressData(raw_image_data, image_storage_mode_, &compressed_data); - image_header->data_size_ = image_data.size(); // Fill in the data size. - - // Write out the image + fields + methods. - if (!image_file->PwriteFully(image_data.data(), image_data.size(), sizeof(ImageHeader))) { - PLOG(ERROR) << "Failed to write image file data " << image_filename; - return false; + + // Block sources (from the image). + const bool is_compressed = image_storage_mode_ != ImageHeader::kStorageModeUncompressed; + std::vector<std::pair<uint32_t, uint32_t>> block_sources; + std::vector<ImageHeader::Block> blocks; + + // Add a set of solid blocks such that no block is larger than the maximum size. A solid block + // is a block that must be decompressed all at once. + auto add_blocks = [&](uint32_t offset, uint32_t size) { + while (size != 0u) { + const uint32_t cur_size = std::min(size, compiler_options_.MaxImageBlockSize()); + block_sources.emplace_back(offset, cur_size); + offset += cur_size; + size -= cur_size; + } + }; + + add_blocks(sizeof(ImageHeader), image_header->GetImageSize() - sizeof(ImageHeader)); + + // Checksum of compressed image data and header. + uint32_t image_checksum = adler32(0L, Z_NULL, 0); + image_checksum = adler32(image_checksum, + reinterpret_cast<const uint8_t*>(image_header), + sizeof(ImageHeader)); + // Copy and compress blocks. + size_t out_offset = sizeof(ImageHeader); + for (const std::pair<uint32_t, uint32_t> block : block_sources) { + ArrayRef<const uint8_t> raw_image_data(image_info.image_.Begin() + block.first, + block.second); + std::vector<uint8_t> compressed_data; + ArrayRef<const uint8_t> image_data = + MaybeCompressData(raw_image_data, image_storage_mode_, &compressed_data); + + if (!is_compressed) { + // For uncompressed, preserve alignment since the image will be directly mapped. + out_offset = block.first; + } + + // Fill in the compressed location of the block. + blocks.emplace_back(ImageHeader::Block( + image_storage_mode_, + /*data_offset=*/ out_offset, + /*data_size=*/ image_data.size(), + /*image_offset=*/ block.first, + /*image_size=*/ block.second)); + + // Write out the image + fields + methods. + if (!image_file->PwriteFully(image_data.data(), image_data.size(), out_offset)) { + PLOG(ERROR) << "Failed to write image file data " << image_filename; + image_file->Erase(); + return false; + } + out_offset += image_data.size(); + image_checksum = adler32(image_checksum, image_data.data(), image_data.size()); + } + + // Write the block metadata directly after the image sections. + // Note: This is not part of the mapped image and is not preserved after decompressing, it's + // only used for image loading. For this reason, only write it out for compressed images. + if (is_compressed) { + // Align up since the compressed data is not necessarily aligned. + out_offset = RoundUp(out_offset, alignof(ImageHeader::Block)); + CHECK(!blocks.empty()); + const size_t blocks_bytes = blocks.size() * sizeof(blocks[0]); + if (!image_file->PwriteFully(&blocks[0], blocks_bytes, out_offset)) { + PLOG(ERROR) << "Failed to write image blocks " << image_filename; + image_file->Erase(); + return false; + } + image_header->blocks_offset_ = out_offset; + image_header->blocks_count_ = blocks.size(); + out_offset += blocks_bytes; } - // Write out the image bitmap at the page aligned start of the image end, also uncompressed for - // convenience. - const ImageSection& bitmap_section = image_header->GetImageBitmapSection(); + // Data size includes everything except the bitmap. + image_header->data_size_ = out_offset - sizeof(ImageHeader); + + // Update and write the bitmap section. Note that the bitmap section is relative to the + // possibly compressed image. + ImageSection& bitmap_section = image_header->GetImageSection(ImageHeader::kSectionImageBitmap); // Align up since data size may be unaligned if the image is compressed. - size_t bitmap_position_in_file = RoundUp(sizeof(ImageHeader) + image_data.size(), kPageSize); - if (image_storage_mode_ == ImageHeader::kDefaultStorageMode) { - CHECK_EQ(bitmap_position_in_file, bitmap_section.Offset()); - } + out_offset = RoundUp(out_offset, kPageSize); + bitmap_section = ImageSection(out_offset, bitmap_section.Size()); + if (!image_file->PwriteFully(image_info.image_bitmap_->Begin(), bitmap_section.Size(), - bitmap_position_in_file)) { + bitmap_section.Offset())) { PLOG(ERROR) << "Failed to write image file bitmap " << image_filename; return false; } @@ -781,22 +842,17 @@ bool ImageWriter::Write(int image_fd, return false; } - // Calculate the image checksum. - uint32_t image_checksum = adler32(0L, Z_NULL, 0); - image_checksum = adler32(image_checksum, - reinterpret_cast<const uint8_t*>(image_header), - sizeof(ImageHeader)); - image_checksum = adler32(image_checksum, image_data.data(), image_data.size()); + // Calculate the image checksum of the remaining data. image_checksum = adler32(image_checksum, reinterpret_cast<const uint8_t*>(image_info.image_bitmap_->Begin()), bitmap_section.Size()); image_header->SetImageChecksum(image_checksum); if (VLOG_IS_ON(compiler)) { - size_t separately_written_section_size = bitmap_section.Size() + sizeof(ImageHeader); - - size_t total_uncompressed_size = raw_image_data.size() + separately_written_section_size, - total_compressed_size = image_data.size() + separately_written_section_size; + const size_t separately_written_section_size = bitmap_section.Size(); + const size_t total_uncompressed_size = image_info.image_size_ + + separately_written_section_size; + const size_t total_compressed_size = out_offset + separately_written_section_size; VLOG(compiler) << "Dex2Oat:uncompressedImageSize = " << total_uncompressed_size; if (total_uncompressed_size != total_compressed_size) { @@ -804,8 +860,8 @@ bool ImageWriter::Write(int image_fd, } } - CHECK_EQ(bitmap_position_in_file + bitmap_section.Size(), - static_cast<size_t>(image_file->GetLength())); + CHECK_EQ(bitmap_section.End(), static_cast<size_t>(image_file->GetLength())) + << "Bitmap should be at the end of the file"; // Write header last in case the compiler gets killed in the middle of image writing. // We do not want to have a corrupted image with a valid header. @@ -2574,9 +2630,7 @@ void ImageWriter::CreateHeader(size_t oat_index) { PointerToLowMemUInt32(oat_file_end), boot_image_begin, boot_oat_end - boot_image_begin, - static_cast<uint32_t>(target_ptr_size_), - image_storage_mode_, - /*data_size*/0u); + static_cast<uint32_t>(target_ptr_size_)); } ArtMethod* ImageWriter::GetImageMethodAddress(ArtMethod* method) { diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc index 51f60084a1..1c74a923e6 100644 --- a/oatdump/oatdump.cc +++ b/oatdump/oatdump.cc @@ -1936,7 +1936,7 @@ class ImageDumper { stats_.file_bytes = file->GetLength(); // If the image is compressed, adjust to decompressed size. size_t uncompressed_size = image_header_.GetImageSize() - sizeof(ImageHeader); - if (image_header_.GetStorageMode() == ImageHeader::kStorageModeUncompressed) { + if (image_header_.HasCompressedBlock()) { DCHECK_EQ(uncompressed_size, data_size) << "Sizes should match for uncompressed image"; } stats_.file_bytes += uncompressed_size - data_size; diff --git a/runtime/gc/collector/immune_spaces_test.cc b/runtime/gc/collector/immune_spaces_test.cc index 9f98f6c7f0..c29b79c0d4 100644 --- a/runtime/gc/collector/immune_spaces_test.cc +++ b/runtime/gc/collector/immune_spaces_test.cc @@ -124,9 +124,7 @@ class ImmuneSpacesTest : public CommonRuntimeTest { /*oat_file_end=*/ PointerToLowMemUInt32(oat_map.Begin() + oat_size), /*boot_image_begin=*/ 0u, /*boot_image_size=*/ 0u, - /*pointer_size=*/ sizeof(void*), - ImageHeader::kStorageModeUncompressed, - /*data_size=*/ 0u); + /*pointer_size=*/ sizeof(void*)); return new DummyImageSpace(std::move(image_map), std::move(live_bitmap), std::move(oat_file), diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc index 085799c492..c772bdab18 100644 --- a/runtime/gc/space/image_space.cc +++ b/runtime/gc/space/image_space.cc @@ -16,7 +16,6 @@ #include "image_space.h" -#include <lz4.h> #include <sys/statvfs.h> #include <sys/types.h> #include <unistd.h> @@ -466,9 +465,10 @@ class ImageSpace::Loader { // 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()) { - *error_msg = StringPrintf("Image file truncated: %" PRIu64 " vs. %" PRIu64 ".", - 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())); return nullptr; } @@ -588,8 +588,9 @@ class ImageSpace::Loader { /*inout*/MemMap* image_reservation, /*out*/std::string* error_msg) { TimingLogger::ScopedTiming timing("MapImageFile", logger); - const ImageHeader::StorageMode storage_mode = image_header.GetStorageMode(); - if (storage_mode == ImageHeader::kStorageModeUncompressed) { + std::string temp_error_msg; + const bool is_compressed = image_header.HasCompressedBlock(); + if (!is_compressed) { uint8_t* address = (image_reservation != nullptr) ? image_reservation->Begin() : nullptr; return MemMap::MapFileAtAddress(address, image_header.GetImageSize(), @@ -604,15 +605,6 @@ class ImageSpace::Loader { error_msg); } - if (storage_mode != ImageHeader::kStorageModeLZ4 && - storage_mode != ImageHeader::kStorageModeLZ4HC) { - if (error_msg != nullptr) { - *error_msg = StringPrintf("Invalid storage mode in image header %d", - static_cast<int>(storage_mode)); - } - return MemMap::Invalid(); - } - // Reserve output and decompress into it. MemMap map = MemMap::MapAnonymous(image_location, image_header.GetImageSize(), @@ -622,7 +614,6 @@ class ImageSpace::Loader { error_msg); if (map.IsValid()) { const size_t stored_size = image_header.GetDataSize(); - const size_t decompress_offset = sizeof(ImageHeader); // Skip the header. MemMap temp_map = MemMap::MapFile(sizeof(ImageHeader) + stored_size, PROT_READ, MAP_PRIVATE, @@ -637,27 +628,20 @@ class ImageSpace::Loader { } memcpy(map.Begin(), &image_header, sizeof(ImageHeader)); const uint64_t start = NanoTime(); - // LZ4HC and LZ4 have same internal format, both use LZ4_decompress. - TimingLogger::ScopedTiming timing2("LZ4 decompress image", logger); - const size_t decompressed_size = LZ4_decompress_safe( - reinterpret_cast<char*>(temp_map.Begin()) + sizeof(ImageHeader), - reinterpret_cast<char*>(map.Begin()) + decompress_offset, - stored_size, - map.Size() - decompress_offset); + for (const ImageHeader::Block& block : image_header.GetBlocks(temp_map.Begin())) { + TimingLogger::ScopedTiming timing2("LZ4 decompress image", logger); + if (!block.Decompress(/*out_ptr=*/map.Begin(), /*in_ptr=*/temp_map.Begin(), error_msg)) { + if (error_msg != nullptr) { + *error_msg = "Failed to decompress image block " + *error_msg; + } + return MemMap::Invalid(); + } + } const uint64_t time = NanoTime() - start; // Add one 1 ns to prevent possible divide by 0. VLOG(image) << "Decompressing image took " << PrettyDuration(time) << " (" << PrettySize(static_cast<uint64_t>(map.Size()) * MsToNs(1000) / (time + 1)) << "/s)"; - if (decompressed_size + sizeof(ImageHeader) != image_header.GetImageSize()) { - if (error_msg != nullptr) { - *error_msg = StringPrintf( - "Decompressed size does not match expected image size %zu vs %zu", - decompressed_size + sizeof(ImageHeader), - image_header.GetImageSize()); - } - return MemMap::Invalid(); - } } return map; @@ -766,6 +750,7 @@ class ImageSpace::Loader { ALWAYS_INLINE void operator()(ObjPtr<mirror::Object> obj, MemberOffset offset, + bool is_static ATTRIBUTE_UNUSED) const NO_THREAD_SAFETY_ANALYSIS { // There could be overlap between ranges, we must avoid visiting the same reference twice. diff --git a/runtime/image.cc b/runtime/image.cc index f50c39c3d5..ae3d8e34e6 100644 --- a/runtime/image.cc +++ b/runtime/image.cc @@ -16,6 +16,9 @@ #include "image.h" +#include <lz4.h> +#include <sstream> + #include "base/bit_utils.h" #include "base/length_prefixed_array.h" #include "base/utils.h" @@ -26,7 +29,7 @@ namespace art { const uint8_t ImageHeader::kImageMagic[] = { 'a', 'r', 't', '\n' }; -const uint8_t ImageHeader::kImageVersion[] = { '0', '7', '0', '\0' }; // Store ImtIndex. +const uint8_t ImageHeader::kImageVersion[] = { '0', '7', '1', '\0' }; // Add image blocks. ImageHeader::ImageHeader(uint32_t image_begin, uint32_t image_size, @@ -39,9 +42,7 @@ ImageHeader::ImageHeader(uint32_t image_begin, uint32_t oat_file_end, uint32_t boot_image_begin, uint32_t boot_image_size, - uint32_t pointer_size, - StorageMode storage_mode, - size_t data_size) + uint32_t pointer_size) : image_begin_(image_begin), image_size_(image_size), image_checksum_(0u), @@ -53,9 +54,7 @@ ImageHeader::ImageHeader(uint32_t image_begin, boot_image_begin_(boot_image_begin), boot_image_size_(boot_image_size), image_roots_(image_roots), - pointer_size_(pointer_size), - storage_mode_(storage_mode), - data_size_(data_size) { + pointer_size_(pointer_size) { CHECK_EQ(image_begin, RoundUp(image_begin, kPageSize)); CHECK_EQ(oat_file_begin, RoundUp(oat_file_begin, kPageSize)); CHECK_EQ(oat_data_begin, RoundUp(oat_data_begin, kPageSize)); @@ -144,4 +143,34 @@ PointerSize ImageHeader::GetPointerSize() const { return ConvertToPointerSize(pointer_size_); } +bool ImageHeader::Block::Decompress(uint8_t* out_ptr, + const uint8_t* in_ptr, + std::string* error_msg) const { + switch (storage_mode_) { + case kStorageModeUncompressed: { + CHECK_EQ(image_size_, data_size_); + memcpy(out_ptr + image_offset_, in_ptr + data_offset_, data_size_); + break; + } + case kStorageModeLZ4: + case kStorageModeLZ4HC: { + // LZ4HC and LZ4 have same internal format, both use LZ4_decompress. + const size_t decompressed_size = LZ4_decompress_safe( + reinterpret_cast<const char*>(in_ptr) + data_offset_, + reinterpret_cast<char*>(out_ptr) + image_offset_, + data_size_, + image_size_); + CHECK_EQ(decompressed_size, image_size_); + break; + } + default: { + if (error_msg != nullptr) { + *error_msg = (std::ostringstream() << "Invalid image format " << storage_mode_).str(); + } + return false; + } + } + return true; +} + } // namespace art diff --git a/runtime/image.h b/runtime/image.h index f33b9b2a2e..76fb3b70c9 100644 --- a/runtime/image.h +++ b/runtime/image.h @@ -21,6 +21,7 @@ #include "base/enums.h" #include "base/globals.h" +#include "base/iteration_range.h" #include "mirror/object.h" namespace art { @@ -82,8 +83,10 @@ class PACKED(4) ImageSection { uint32_t size_; }; -// header of image files written by ImageWriter, read and validated by Space. -class PACKED(4) ImageHeader { +// Header of image files written by ImageWriter, read and validated by Space. +// Packed to object alignment since the first object follows directly after the header. +static_assert(kObjectAlignment == 8, "Alignment check"); +class PACKED(8) ImageHeader { public: enum StorageMode : uint32_t { kStorageModeUncompressed, @@ -93,8 +96,40 @@ class PACKED(4) ImageHeader { }; static constexpr StorageMode kDefaultStorageMode = kStorageModeUncompressed; - ImageHeader() {} + // Solid block of the image. May be compressed or uncompressed. + class PACKED(4) Block final { + public: + Block(StorageMode storage_mode, + uint32_t data_offset, + uint32_t data_size, + uint32_t image_offset, + uint32_t image_size) + : storage_mode_(storage_mode), + data_offset_(data_offset), + data_size_(data_size), + image_offset_(image_offset), + image_size_(image_size) {} + + bool Decompress(uint8_t* out_ptr, const uint8_t* in_ptr, std::string* error_msg) const; + + StorageMode GetStorageMode() const { + return storage_mode_; + } + + private: + // Storage method for the image, the image may be compressed. + StorageMode storage_mode_ = kDefaultStorageMode; + + // Compressed offset and size. + uint32_t data_offset_ = 0u; + uint32_t data_size_ = 0u; + + // Image offset and size (decompressed or mapped location). + uint32_t image_offset_ = 0u; + uint32_t image_size_ = 0u; + }; + ImageHeader() {} ImageHeader(uint32_t image_begin, uint32_t image_size, ImageSection* sections, @@ -106,9 +141,7 @@ class PACKED(4) ImageHeader { uint32_t oat_file_end, uint32_t boot_image_begin, uint32_t boot_image_size, - uint32_t pointer_size, - StorageMode storage_mode, - size_t data_size); + uint32_t pointer_size); bool IsValid() const; const char* GetMagic() const; @@ -231,6 +264,11 @@ class PACKED(4) ImageHeader { ArtMethod* GetImageMethod(ImageMethod index) const; + ImageSection& GetImageSection(ImageSections index) { + DCHECK_LT(static_cast<size_t>(index), kSectionCount); + return sections_[index]; + } + const ImageSection& GetImageSection(ImageSections index) const { DCHECK_LT(static_cast<size_t>(index), kSectionCount); return sections_[index]; @@ -304,10 +342,6 @@ class PACKED(4) ImageHeader { return boot_image_size_; } - StorageMode GetStorageMode() const { - return storage_mode_; - } - uint64_t GetDataSize() const { return data_size_; } @@ -345,6 +379,24 @@ class PACKED(4) ImageHeader { uint8_t* base, PointerSize pointer_size) const; + IterationRange<const Block*> GetBlocks() const { + return GetBlocks(GetImageBegin()); + } + + IterationRange<const Block*> GetBlocks(const uint8_t* image_begin) const { + const Block* begin = reinterpret_cast<const Block*>(image_begin + blocks_offset_); + return {begin, begin + blocks_count_}; + } + + // Return true if the image has any compressed blocks. + bool HasCompressedBlock() const { + return blocks_count_ != 0u; + } + + uint32_t GetBlockCount() const { + return blocks_count_; + } + private: static const uint8_t kImageMagic[4]; static const uint8_t kImageVersion[4]; @@ -404,13 +456,14 @@ class PACKED(4) ImageHeader { // Image methods, may be inside of the boot image for app images. uint64_t image_methods_[kImageMethodsCount]; - // Storage method for the image, the image may be compressed. - StorageMode storage_mode_ = kDefaultStorageMode; - // Data size for the image data excluding the bitmap and the header. For compressed images, this // is the compressed size in the file. uint32_t data_size_ = 0u; + // Image blocks, only used for compressed images. + uint32_t blocks_offset_ = 0u; + uint32_t blocks_count_ = 0u; + friend class linker::ImageWriter; }; |