Add support for LZ4 compressed image files

Added dex2oat option --image-format=(store|lz4). Using lz4 means
that the main image section (all data other than header and bitmap)
are stored in a compressed state.

N5 results:
Boot image size: 8067128 -> 2827605
Decompression time 18.93ms
Decompression rate: 426MB/s

Patchoat is not currently supported since it maps the source image
directly. In order to support compressed images we would need to
recompress the output image and then write it back out to a file.
Also there are not many cases where we would want to patch a
compressed image since they are going to be dirty memory when
uncompressed anyways. Might as well just patch as we are loading.

Bug: 22858531

Change-Id: I8c54ccf73408273011161a61bb891736735074d9
diff --git a/build/Android.common_build.mk b/build/Android.common_build.mk
index a93d8a8..43e1457 100644
--- a/build/Android.common_build.mk
+++ b/build/Android.common_build.mk
@@ -206,6 +206,7 @@
 ART_C_INCLUDES := \
   external/gtest/include \
   external/icu/icu4c/source/common \
+  external/lz4/lib \
   external/valgrind/include \
   external/valgrind \
   external/vixl/src \
diff --git a/compiler/Android.mk b/compiler/Android.mk
index 348eabd..bdd9a84 100644
--- a/compiler/Android.mk
+++ b/compiler/Android.mk
@@ -258,9 +258,9 @@
   ifeq ($$(art_ndebug_or_debug),ndebug)
     LOCAL_MODULE := libart-compiler
     ifeq ($$(art_static_or_shared), static)
-      LOCAL_STATIC_LIBRARIES += libart
+      LOCAL_STATIC_LIBRARIES += libart liblz4
     else
-      LOCAL_SHARED_LIBRARIES += libart
+      LOCAL_SHARED_LIBRARIES += libart liblz4
     endif
     ifeq ($$(art_target_or_host),target)
       LOCAL_FDO_SUPPORT := true
@@ -268,9 +268,9 @@
   else # debug
     LOCAL_MODULE := libartd-compiler
     ifeq ($$(art_static_or_shared), static)
-      LOCAL_STATIC_LIBRARIES += libartd
+      LOCAL_STATIC_LIBRARIES += libartd liblz4
     else
-      LOCAL_SHARED_LIBRARIES += libartd
+      LOCAL_SHARED_LIBRARIES += libartd liblz4
     endif
   endif
 
diff --git a/compiler/common_compiler_test.cc b/compiler/common_compiler_test.cc
index e6cc50c..3901e25 100644
--- a/compiler/common_compiler_test.cc
+++ b/compiler/common_compiler_test.cc
@@ -189,21 +189,33 @@
     }
 
     timer_.reset(new CumulativeLogger("Compilation times"));
-    compiler_driver_.reset(new CompilerDriver(compiler_options_.get(),
-                                              verification_results_.get(),
-                                              method_inliner_map_.get(),
-                                              compiler_kind_, instruction_set,
-                                              instruction_set_features_.get(),
-                                              true,
-                                              GetImageClasses(),
-                                              GetCompiledClasses(),
-                                              GetCompiledMethods(),
-                                              2, true, true, "", false, timer_.get(), -1, ""));
+    CreateCompilerDriver(compiler_kind_, instruction_set);
   }
   // We typically don't generate an image in unit tests, disable this optimization by default.
   compiler_driver_->SetSupportBootImageFixup(false);
 }
 
+void CommonCompilerTest::CreateCompilerDriver(Compiler::Kind kind, InstructionSet isa) {
+  compiler_driver_.reset(new CompilerDriver(compiler_options_.get(),
+                                            verification_results_.get(),
+                                            method_inliner_map_.get(),
+                                            kind,
+                                            isa,
+                                            instruction_set_features_.get(),
+                                            true,
+                                            GetImageClasses(),
+                                            GetCompiledClasses(),
+                                            GetCompiledMethods(),
+                                            2,
+                                            true,
+                                            true,
+                                            "",
+                                            false,
+                                            timer_.get(),
+                                            -1,
+                                            ""));
+}
+
 void CommonCompilerTest::SetUpRuntimeOptions(RuntimeOptions* options) {
   CommonRuntimeTest::SetUpRuntimeOptions(options);
 
diff --git a/compiler/common_compiler_test.h b/compiler/common_compiler_test.h
index 1b57b7d..b491946 100644
--- a/compiler/common_compiler_test.h
+++ b/compiler/common_compiler_test.h
@@ -90,6 +90,8 @@
                             const char* method_name, const char* signature)
       SHARED_REQUIRES(Locks::mutator_lock_);
 
+  void CreateCompilerDriver(Compiler::Kind kind, InstructionSet isa);
+
   void ReserveImageSpace();
 
   void UnreserveImageSpace();
diff --git a/compiler/image_test.cc b/compiler/image_test.cc
index cda6240..b596ba6 100644
--- a/compiler/image_test.cc
+++ b/compiler/image_test.cc
@@ -43,10 +43,17 @@
     ReserveImageSpace();
     CommonCompilerTest::SetUp();
   }
+  void TestWriteRead(ImageHeader::StorageMode storage_mode);
 };
 
-TEST_F(ImageTest, WriteRead) {
-  TEST_DISABLED_FOR_NON_PIC_COMPILING_WITH_OPTIMIZING();
+void ImageTest::TestWriteRead(ImageHeader::StorageMode storage_mode) {
+  // TODO: Test does not currently work with optimizing.
+  CreateCompilerDriver(Compiler::kQuick, kRuntimeISA);
+  ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+  // Enable write for dex2dex.
+  for (const DexFile* dex_file : class_linker->GetBootClassPath()) {
+    dex_file->EnableWrite();
+  }
   // Create a generic location tmp file, to be the base of the .art and .oat temporary files.
   ScratchFile location;
   ScratchFile image_location(location, ".art");
@@ -68,17 +75,14 @@
   std::unique_ptr<ImageWriter> writer(new ImageWriter(*compiler_driver_,
                                                       requested_image_base,
                                                       /*compile_pic*/false,
-                                                      /*compile_app_image*/false));
+                                                      /*compile_app_image*/false,
+                                                      storage_mode));
   // TODO: compile_pic should be a test argument.
   {
     {
       jobject class_loader = nullptr;
-      ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
       TimingLogger timings("ImageTest::WriteRead", false, false);
       TimingLogger::ScopedTiming t("CompileAll", &timings);
-      for (const DexFile* dex_file : class_linker->GetBootClassPath()) {
-        dex_file->EnableWrite();
-      }
       compiler_driver_->SetDexFilesForOatFile(class_linker->GetBootClassPath());
       compiler_driver_->CompileAll(class_loader, class_linker->GetBootClassPath(), &timings);
 
@@ -209,7 +213,13 @@
 
   gc::space::ImageSpace* image_space = heap->GetBootImageSpace();
   ASSERT_TRUE(image_space != nullptr);
-  ASSERT_LE(image_space->Size(), image_file_size);
+  if (storage_mode == ImageHeader::kStorageModeUncompressed) {
+    // Uncompressed, image should be smaller than file.
+    ASSERT_LE(image_space->Size(), image_file_size);
+  } else {
+    // Compressed, file should be smaller than image.
+    ASSERT_LE(image_file_size, image_space->Size());
+  }
 
   image_space->VerifyImageAllocations();
   uint8_t* image_begin = image_space->Begin();
@@ -237,6 +247,14 @@
   CHECK_EQ(0, rmdir_result);
 }
 
+TEST_F(ImageTest, WriteReadUncompressed) {
+  TestWriteRead(ImageHeader::kStorageModeUncompressed);
+}
+
+TEST_F(ImageTest, WriteReadLZ4) {
+  TestWriteRead(ImageHeader::kStorageModeLZ4);
+}
+
 TEST_F(ImageTest, ImageHeaderIsValid) {
     uint32_t image_begin = ART_BASE_ADDRESS;
     uint32_t image_size_ = 16 * KB;
@@ -257,7 +275,9 @@
                              oat_data_end,
                              oat_file_end,
                              sizeof(void*),
-                             /*compile_pic*/false);
+                             /*compile_pic*/false,
+                             ImageHeader::kDefaultStorageMode,
+                             /*data_size*/0u);
     ASSERT_TRUE(image_header.IsValid());
 
     char* magic = const_cast<char*>(image_header.GetMagic());
diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc
index bf1fcdd..f9f0eb8 100644
--- a/compiler/image_writer.cc
+++ b/compiler/image_writer.cc
@@ -17,6 +17,7 @@
 #include "image_writer.h"
 
 #include <sys/stat.h>
+#include <lz4.h>
 
 #include <memory>
 #include <numeric>
@@ -225,27 +226,72 @@
     return EXIT_FAILURE;
   }
 
-  // Write out the image + fields + methods.
+  std::unique_ptr<char[]> compressed_data;
+  // Image data size excludes the bitmap and the header.
   ImageHeader* const image_header = reinterpret_cast<ImageHeader*>(image_->Begin());
-  const auto write_count = image_header->GetImageSize();
-  if (!image_file->WriteFully(image_->Begin(), write_count)) {
-    PLOG(ERROR) << "Failed to write image file " << image_filename;
+  const size_t image_data_size = image_header->GetImageSize() - sizeof(ImageHeader);
+  char* image_data = reinterpret_cast<char*>(image_->Begin()) + sizeof(ImageHeader);
+  size_t data_size;
+  const char* image_data_to_write;
+
+  CHECK_EQ(image_header->storage_mode_, image_storage_mode_);
+  switch (image_storage_mode_) {
+    case ImageHeader::kStorageModeLZ4: {
+      size_t compressed_max_size = LZ4_compressBound(image_data_size);
+      compressed_data.reset(new char[compressed_max_size]);
+      data_size = LZ4_compress(
+          reinterpret_cast<char*>(image_->Begin()) + sizeof(ImageHeader),
+          &compressed_data[0],
+          image_data_size);
+      image_data_to_write = &compressed_data[0];
+      VLOG(compiler) << "Compressed from " << image_data_size << " to " << data_size;
+      break;
+    }
+    case ImageHeader::kStorageModeUncompressed: {
+      data_size = image_data_size;
+      image_data_to_write = image_data;
+      break;
+    }
+    default: {
+      LOG(FATAL) << "Unsupported";
+      UNREACHABLE();
+    }
+  }
+
+  // Write header first, as uncompressed.
+  image_header->data_size_ = data_size;
+  if (!image_file->WriteFully(image_->Begin(), sizeof(ImageHeader))) {
+    PLOG(ERROR) << "Failed to write image file header " << image_filename;
     image_file->Erase();
     return false;
   }
 
-  // Write out the image bitmap at the page aligned start of the image end.
+  // Write out the image + fields + methods.
+  const bool is_compressed = compressed_data != nullptr;
+  if (!image_file->WriteFully(image_data_to_write, data_size)) {
+    PLOG(ERROR) << "Failed to write image file data " << image_filename;
+    image_file->Erase();
+    return false;
+  }
+
+  // Write out the image bitmap at the page aligned start of the image end, also uncompressed for
+  // convenience.
   const ImageSection& bitmap_section = image_header->GetImageSection(
       ImageHeader::kSectionImageBitmap);
-  CHECK_ALIGNED(bitmap_section.Offset(), kPageSize);
+  // Align up since data size may be unaligned if the image is compressed.
+  size_t bitmap_position_in_file = RoundUp(sizeof(ImageHeader) + data_size, kPageSize);
+  if (!is_compressed) {
+    CHECK_EQ(bitmap_position_in_file, bitmap_section.Offset());
+  }
   if (!image_file->Write(reinterpret_cast<char*>(image_bitmap_->Begin()),
-                         bitmap_section.Size(), bitmap_section.Offset())) {
+                         bitmap_section.Size(),
+                         bitmap_position_in_file)) {
     PLOG(ERROR) << "Failed to write image file " << image_filename;
     image_file->Erase();
     return false;
   }
-
-  CHECK_EQ(bitmap_section.End(), static_cast<size_t>(image_file->GetLength()));
+  CHECK_EQ(bitmap_position_in_file + bitmap_section.Size(),
+           static_cast<size_t>(image_file->GetLength()));
   if (image_file->FlushCloseOrErase() != 0) {
     PLOG(ERROR) << "Failed to flush and close image file " << image_filename;
     return false;
@@ -1247,7 +1293,8 @@
   }
   CHECK_EQ(AlignUp(image_begin_ + image_end, kPageSize), oat_file_begin) <<
       "Oat file should be right after the image.";
-  // Create the header.
+  // Create the header, leave 0 for data size since we will fill this in as we are writing the
+  // image.
   new (image_->Begin()) ImageHeader(PointerToLowMemUInt32(image_begin_),
                                                           image_end,
                                                           sections,
@@ -1258,7 +1305,9 @@
                                                           PointerToLowMemUInt32(oat_data_end),
                                                           PointerToLowMemUInt32(oat_file_end),
                                                           target_ptr_size_,
-                                                          compile_pic_);
+                                                          compile_pic_,
+                                                          image_storage_mode_,
+                                                          /*data_size*/0u);
 }
 
 ArtMethod* ImageWriter::GetImageMethodAddress(ArtMethod* method) {
diff --git a/compiler/image_writer.h b/compiler/image_writer.h
index 386838f..c20d836 100644
--- a/compiler/image_writer.h
+++ b/compiler/image_writer.h
@@ -30,6 +30,7 @@
 #include "base/macros.h"
 #include "driver/compiler_driver.h"
 #include "gc/space/space.h"
+#include "image.h"
 #include "length_prefixed_array.h"
 #include "lock_word.h"
 #include "mem_map.h"
@@ -54,7 +55,8 @@
   ImageWriter(const CompilerDriver& compiler_driver,
               uintptr_t image_begin,
               bool compile_pic,
-              bool compile_app_image)
+              bool compile_app_image,
+              ImageHeader::StorageMode image_storage_mode)
       : compiler_driver_(compiler_driver),
         image_begin_(reinterpret_cast<uint8_t*>(image_begin)),
         image_end_(0),
@@ -73,7 +75,8 @@
         image_method_array_(ImageHeader::kImageMethodsCount),
         dirty_methods_(0u),
         clean_methods_(0u),
-        class_table_bytes_(0u) {
+        class_table_bytes_(0u),
+        image_storage_mode_(image_storage_mode) {
     CHECK_NE(image_begin, 0U);
     std::fill_n(image_methods_, arraysize(image_methods_), nullptr);
     std::fill_n(oat_address_offsets_, arraysize(oat_address_offsets_), 0);
@@ -460,6 +463,9 @@
   // Number of image class table bytes.
   size_t class_table_bytes_;
 
+  // Which mode the image is stored as, see image.h
+  const ImageHeader::StorageMode image_storage_mode_;
+
   friend class ContainsBootClassLoaderNonImageClassVisitor;
   friend class FixupClassVisitor;
   friend class FixupRootVisitor;
diff --git a/dex2oat/Android.mk b/dex2oat/Android.mk
index e252765..c7dd0e5 100644
--- a/dex2oat/Android.mk
+++ b/dex2oat/Android.mk
@@ -56,18 +56,18 @@
 
 # We always build dex2oat and dependencies, even if the host build is otherwise disabled, since they are used to cross compile for the target.
 ifeq ($(ART_BUILD_HOST_NDEBUG),true)
-  $(eval $(call build-art-executable,dex2oat,$(DEX2OAT_SRC_FILES),libcutils libart-compiler libsigchain libziparchive-host,art/compiler,host,ndebug,$(dex2oat_host_arch)))
+  $(eval $(call build-art-executable,dex2oat,$(DEX2OAT_SRC_FILES),libcutils libart-compiler libsigchain libziparchive-host liblz4,art/compiler,host,ndebug,$(dex2oat_host_arch)))
   ifeq ($(ART_BUILD_HOST_STATIC),true)
     $(eval $(call build-art-executable,dex2oat,$(DEX2OAT_SRC_FILES),libart libart-compiler libart libziparchive-host libnativehelper libnativebridge libsigchain_dummy libvixl liblog libz \
-        libbacktrace libLLVMObject libLLVMBitReader libLLVMMC libLLVMMCParser libLLVMCore libLLVMSupport libcutils libunwindbacktrace libutils libbase,art/compiler,host,ndebug,$(dex2oat_host_arch),static))
+        libbacktrace libLLVMObject libLLVMBitReader libLLVMMC libLLVMMCParser libLLVMCore libLLVMSupport libcutils libunwindbacktrace libutils libbase liblz4,art/compiler,host,ndebug,$(dex2oat_host_arch),static))
   endif
 endif
 
 ifeq ($(ART_BUILD_HOST_DEBUG),true)
-  $(eval $(call build-art-executable,dex2oat,$(DEX2OAT_SRC_FILES),libcutils libartd-compiler libsigchain libziparchive-host,art/compiler,host,debug,$(dex2oat_host_arch)))
+  $(eval $(call build-art-executable,dex2oat,$(DEX2OAT_SRC_FILES),libcutils libartd-compiler libsigchain libziparchive-host liblz4,art/compiler,host,debug,$(dex2oat_host_arch)))
   ifeq ($(ART_BUILD_HOST_STATIC),true)
     $(eval $(call build-art-executable,dex2oat,$(DEX2OAT_SRC_FILES),libartd libartd-compiler libartd libziparchive-host libnativehelper libnativebridge libsigchain_dummy libvixld liblog libz \
-        libbacktrace libLLVMObject libLLVMBitReader libLLVMMC libLLVMMCParser libLLVMCore libLLVMSupport libcutils libunwindbacktrace libutils libbase,art/compiler,host,debug,$(dex2oat_host_arch),static))
+        libbacktrace libLLVMObject libLLVMBitReader libLLVMMC libLLVMMCParser libLLVMCore libLLVMSupport libcutils libunwindbacktrace libutils libbase liblz4,art/compiler,host,debug,$(dex2oat_host_arch),static))
   endif
 endif
 
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 2aa4085..87d5a78 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -208,6 +208,11 @@
   UsageError("  --image=<file.art>: specifies the output image filename.");
   UsageError("      Example: --image=/system/framework/boot.art");
   UsageError("");
+  UsageError("  --image-format=(uncompressed|lz4):");
+  UsageError("      Which format to store the image.");
+  UsageError("      Example: --image-format=lz4");
+  UsageError("      Default: uncompressed");
+  UsageError("");
   UsageError("  --image-classes=<classname-file>: specifies classes to include in an image.");
   UsageError("      Example: --image=frameworks/base/preloaded-classes");
   UsageError("");
@@ -490,6 +495,7 @@
       image_base_(0U),
       image_classes_zip_filename_(nullptr),
       image_classes_filename_(nullptr),
+      image_storage_mode_(ImageHeader::kStorageModeUncompressed),
       compiled_classes_zip_filename_(nullptr),
       compiled_classes_filename_(nullptr),
       compiled_methods_zip_filename_(nullptr),
@@ -621,6 +627,19 @@
     }
   }
 
+  void ParseImageFormat(const StringPiece& option) {
+    const StringPiece substr("--image-format=");
+    DCHECK(option.starts_with(substr));
+    const StringPiece format_str = option.substr(substr.length());
+    if (format_str == "lz4") {
+      image_storage_mode_ = ImageHeader::kStorageModeLZ4;
+    } else if (format_str == "uncompressed") {
+      image_storage_mode_ = ImageHeader::kStorageModeUncompressed;
+    } else {
+      Usage("Unknown image format: %s", format_str.data());
+    }
+  }
+
   void ProcessOptions(ParserOptions* parser_options) {
     boot_image_ = !image_filename_.empty();
     app_image_ = app_image_fd_ != -1 || !app_image_file_name_.empty();
@@ -877,6 +896,8 @@
         image_classes_filename_ = option.substr(strlen("--image-classes=")).data();
       } else if (option.starts_with("--image-classes-zip=")) {
         image_classes_zip_filename_ = option.substr(strlen("--image-classes-zip=")).data();
+      } else if (option.starts_with("--image-format=")) {
+        ParseImageFormat(option);
       } else if (option.starts_with("--compiled-classes=")) {
         compiled_classes_filename_ = option.substr(strlen("--compiled-classes=")).data();
       } else if (option.starts_with("--compiled-classes-zip=")) {
@@ -1643,7 +1664,8 @@
     image_writer_.reset(new ImageWriter(*driver_,
                                         image_base,
                                         compiler_options_->GetCompilePic(),
-                                        IsAppImage()));
+                                        IsAppImage(),
+                                        image_storage_mode_));
   }
 
   // Let the ImageWriter write the image file. If we do not compile PIC, also fix up the oat file.
@@ -1818,6 +1840,7 @@
   uintptr_t image_base_;
   const char* image_classes_zip_filename_;
   const char* image_classes_filename_;
+  ImageHeader::StorageMode image_storage_mode_;
   const char* compiled_classes_zip_filename_;
   const char* compiled_classes_filename_;
   const char* compiled_methods_zip_filename_;
diff --git a/patchoat/patchoat.cc b/patchoat/patchoat.cc
index 723bb17..46ab34b 100644
--- a/patchoat/patchoat.cc
+++ b/patchoat/patchoat.cc
@@ -153,6 +153,12 @@
     return false;
   }
 
+  if (image_header.GetStorageMode() != ImageHeader::kStorageModeUncompressed) {
+    LOG(ERROR) << "Patchoat is not supported with compressed image files "
+               << input_image->GetPath();
+    return false;
+  }
+
   /*bool is_image_pic = */IsImagePic(image_header, input_image->GetPath());
   // Nothing special to do right now since the image always needs to get patched.
   // Perhaps in some far-off future we may have images with relative addresses that are true-PIC.
diff --git a/runtime/Android.mk b/runtime/Android.mk
index 74cc899..36c81fb 100644
--- a/runtime/Android.mk
+++ b/runtime/Android.mk
@@ -491,9 +491,9 @@
   LOCAL_C_INCLUDES += art
 
   ifeq ($$(art_static_or_shared),static)
-    LOCAL_STATIC_LIBRARIES := libnativehelper libnativebridge libsigchain_dummy libbacktrace
+    LOCAL_STATIC_LIBRARIES := libnativehelper libnativebridge libsigchain_dummy libbacktrace liblz4
   else
-    LOCAL_SHARED_LIBRARIES := libnativehelper libnativebridge libsigchain libbacktrace
+    LOCAL_SHARED_LIBRARIES := libnativehelper libnativebridge libsigchain libbacktrace liblz4
   endif
 
   ifeq ($$(art_target_or_host),target)
diff --git a/runtime/gc/collector/immune_spaces_test.cc b/runtime/gc/collector/immune_spaces_test.cc
index f741117..4884e66 100644
--- a/runtime/gc/collector/immune_spaces_test.cc
+++ b/runtime/gc/collector/immune_spaces_test.cc
@@ -113,7 +113,9 @@
         /*oat_data_end*/PointerToLowMemUInt32(map->End() + oat_size),
         /*oat_file_end*/PointerToLowMemUInt32(map->End() + oat_size),
         /*pointer_size*/sizeof(void*),
-        /*compile_pic*/false);
+        /*compile_pic*/false,
+        ImageHeader::kStorageModeUncompressed,
+        /*storage_size*/0u);
     return new DummyImageSpace(map.release(), live_bitmap.release());
   }
 };
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index e2b2431..8f67c21 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -17,12 +17,12 @@
 #include "image_space.h"
 
 #include <dirent.h>
+#include <lz4.h>
+#include <random>
 #include <sys/statvfs.h>
 #include <sys/types.h>
 #include <unistd.h>
 
-#include <random>
-
 #include "art_method.h"
 #include "base/macros.h"
 #include "base/stl_util.h"
@@ -677,11 +677,12 @@
     *error_msg = StringPrintf("Invalid image header in '%s'", image_filename);
     return nullptr;
   }
-  // Check that the file is large enough.
-  uint64_t image_file_size = static_cast<uint64_t>(file->GetLength());
-  if (image_header.GetImageSize() > image_file_size) {
-    *error_msg = StringPrintf("Image file too small for image heap: %" PRIu64 " vs. %zu.",
-                              image_file_size, image_header.GetImageSize());
+  // 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,
+                              image_header.GetDataSize());
     return nullptr;
   }
 
@@ -697,7 +698,11 @@
   }
 
   const auto& bitmap_section = image_header.GetImageSection(ImageHeader::kSectionImageBitmap);
-  auto end_of_bitmap = static_cast<size_t>(bitmap_section.End());
+  // 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(image_header) + 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(
         "Image file size does not equal end of bitmap: size=%" PRIu64 " vs. %zu.", image_file_size,
@@ -706,16 +711,60 @@
   }
 
   // Note: The image header is part of the image due to mmap page alignment required of offset.
-  std::unique_ptr<MemMap> map(MemMap::MapFileAtAddress(image_header.GetImageBegin(),
-                                                       image_header.GetImageSize(),
-                                                       PROT_READ | PROT_WRITE,
+  std::unique_ptr<MemMap> map;
+  if (image_header.GetStorageMode() == ImageHeader::kStorageModeUncompressed) {
+    map.reset(MemMap::MapFileAtAddress(image_header.GetImageBegin(),
+                                       image_header.GetImageSize(),
+                                       PROT_READ | PROT_WRITE,
+                                       MAP_PRIVATE,
+                                       file->Fd(),
+                                       0,
+                                       /*low_4gb*/false,
+                                       /*reuse*/false,
+                                       image_filename,
+                                       error_msg));
+  } else {
+    // Reserve output and decompress into it.
+    map.reset(MemMap::MapAnonymous(image_location,
+                                   image_header.GetImageBegin(),
+                                   image_header.GetImageSize(),
+                                   PROT_READ | PROT_WRITE,
+                                   /*low_4gb*/false,
+                                   /*reuse*/false,
+                                   error_msg));
+    if (map != nullptr) {
+      const size_t stored_size = image_header.GetDataSize();
+      const size_t write_offset = sizeof(image_header);  // Skip the header.
+      std::unique_ptr<MemMap> temp_map(MemMap::MapFile(sizeof(ImageHeader) + stored_size,
+                                                       PROT_READ,
                                                        MAP_PRIVATE,
                                                        file->Fd(),
-                                                       0,
+                                                       /*offset*/0,
                                                        /*low_4gb*/false,
-                                                       /*reuse*/false,
                                                        image_filename,
                                                        error_msg));
+      if (temp_map == nullptr) {
+        DCHECK(!error_msg->empty());
+        return nullptr;
+      }
+      memcpy(map->Begin(), &image_header, sizeof(image_header));
+      const uint64_t start = NanoTime();
+      const size_t decompressed_size = LZ4_decompress_safe(
+          reinterpret_cast<char*>(temp_map->Begin()) + sizeof(ImageHeader),
+          reinterpret_cast<char*>(map->Begin()) + write_offset,
+          stored_size,
+          map->Size());
+      // TODO: VLOG(image)
+      VLOG(class_linker) << "Decompressing image took " << PrettyDuration(NanoTime() - start);
+      if (decompressed_size + sizeof(ImageHeader) != image_header.GetImageSize()) {
+        *error_msg = StringPrintf("Decompressed size does not match expected image size %zu vs %zu",
+                                  decompressed_size + sizeof(ImageHeader),
+                                  image_header.GetImageSize());
+        return nullptr;
+      }
+    }
+  }
+
   if (map == nullptr) {
     DCHECK(!error_msg->empty());
     return nullptr;
@@ -723,16 +772,16 @@
   CHECK_EQ(image_header.GetImageBegin(), map->Begin());
   DCHECK_EQ(0, memcmp(&image_header, map->Begin(), sizeof(ImageHeader)));
 
-  std::unique_ptr<MemMap> image_map(MemMap::MapFileAtAddress(nullptr,
-                                                             bitmap_section.Size(),
-                                                             PROT_READ, MAP_PRIVATE,
-                                                             file->Fd(),
-                                                             bitmap_section.Offset(),
-                                                             /*low_4gb*/false,
-                                                             /*reuse*/false,
-                                                             image_filename,
-                                                             error_msg));
-  if (image_map.get() == nullptr) {
+  std::unique_ptr<MemMap> image_bitmap_map(MemMap::MapFileAtAddress(nullptr,
+                                                                    bitmap_section.Size(),
+                                                                    PROT_READ, MAP_PRIVATE,
+                                                                    file->Fd(),
+                                                                    image_bitmap_offset,
+                                                                    /*low_4gb*/false,
+                                                                    /*reuse*/false,
+                                                                    image_filename,
+                                                                    error_msg));
+  if (image_bitmap_map == nullptr) {
     *error_msg = StringPrintf("Failed to map image bitmap: %s", error_msg->c_str());
     return nullptr;
   }
@@ -741,9 +790,11 @@
                                        bitmap_index));
   std::unique_ptr<accounting::ContinuousSpaceBitmap> bitmap(
       accounting::ContinuousSpaceBitmap::CreateFromMemMap(
-          bitmap_name, image_map.release(), reinterpret_cast<uint8_t*>(map->Begin()),
+          bitmap_name,
+          image_bitmap_map.release(),
+          reinterpret_cast<uint8_t*>(map->Begin()),
           accounting::ContinuousSpaceBitmap::ComputeHeapSize(bitmap_section.Size())));
-  if (bitmap.get() == nullptr) {
+  if (bitmap == nullptr) {
     *error_msg = StringPrintf("Could not create bitmap '%s'", bitmap_name.c_str());
     return nullptr;
   }
diff --git a/runtime/image.cc b/runtime/image.cc
index 2eac3fb..7d2ef75 100644
--- a/runtime/image.cc
+++ b/runtime/image.cc
@@ -24,7 +24,7 @@
 namespace art {
 
 const uint8_t ImageHeader::kImageMagic[] = { 'a', 'r', 't', '\n' };
-const uint8_t ImageHeader::kImageVersion[] = { '0', '2', '3', '\0' };
+const uint8_t ImageHeader::kImageVersion[] = { '0', '2', '4', '\0' };
 
 ImageHeader::ImageHeader(uint32_t image_begin,
                          uint32_t image_size,
@@ -36,7 +36,9 @@
                          uint32_t oat_data_end,
                          uint32_t oat_file_end,
                          uint32_t pointer_size,
-                         bool compile_pic)
+                         bool compile_pic,
+                         StorageMode storage_mode,
+                         size_t data_size)
   : image_begin_(image_begin),
     image_size_(image_size),
     oat_checksum_(oat_checksum),
@@ -47,7 +49,9 @@
     patch_delta_(0),
     image_roots_(image_roots),
     pointer_size_(pointer_size),
-    compile_pic_(compile_pic) {
+    compile_pic_(compile_pic),
+    storage_mode_(storage_mode),
+    data_size_(data_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));
diff --git a/runtime/image.h b/runtime/image.h
index a16f3c9..3032beb 100644
--- a/runtime/image.h
+++ b/runtime/image.h
@@ -78,10 +78,27 @@
 // header of image files written by ImageWriter, read and validated by Space.
 class PACKED(4) ImageHeader {
  public:
+  enum StorageMode : uint32_t {
+    kStorageModeUncompressed,
+    kStorageModeLZ4,
+    kStorageModeCount,  // Number of elements in enum.
+  };
+  static constexpr StorageMode kDefaultStorageMode = kStorageModeUncompressed;
+
   ImageHeader()
-      : image_begin_(0U), image_size_(0U), oat_checksum_(0U), oat_file_begin_(0U),
-        oat_data_begin_(0U), oat_data_end_(0U), oat_file_end_(0U), patch_delta_(0),
-        image_roots_(0U), pointer_size_(0U), compile_pic_(0) {}
+      : image_begin_(0U),
+        image_size_(0U),
+        oat_checksum_(0U),
+        oat_file_begin_(0U),
+        oat_data_begin_(0U),
+        oat_data_end_(0U),
+        oat_file_end_(0U),
+        patch_delta_(0),
+        image_roots_(0U),
+        pointer_size_(0U),
+        compile_pic_(0),
+        storage_mode_(kDefaultStorageMode),
+        data_size_(0) {}
 
   ImageHeader(uint32_t image_begin,
               uint32_t image_size,
@@ -93,7 +110,9 @@
               uint32_t oat_data_end,
               uint32_t oat_file_end,
               uint32_t pointer_size,
-              bool compile_pic);
+              bool compile_pic,
+              StorageMode storage_mode,
+              size_t data_size);
 
   bool IsValid() const;
   const char* GetMagic() const;
@@ -194,6 +213,14 @@
     return compile_pic_ != 0;
   }
 
+  StorageMode GetStorageMode() const {
+    return storage_mode_;
+  }
+
+  uint64_t GetDataSize() const {
+    return data_size_;
+  }
+
  private:
   static const uint8_t kImageMagic[4];
   static const uint8_t kImageVersion[4];
@@ -241,6 +268,13 @@
   // Image methods.
   uint64_t image_methods_[kImageMethodsCount];
 
+  // Storage method for the image, the image may be compressed.
+  StorageMode storage_mode_;
+
+  // 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_;
+
   friend class ImageWriter;
 };
 
@@ -248,6 +282,7 @@
 std::ostream& operator<<(std::ostream& os, const ImageHeader::ImageRoot& policy);
 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);
 
 }  // namespace art