diff options
-rw-r--r-- | compiler/driver/compiler_driver.cc | 19 | ||||
-rw-r--r-- | dex2oat/linker/image_writer.cc | 504 | ||||
-rw-r--r-- | dex2oat/linker/image_writer.h | 135 | ||||
-rw-r--r-- | runtime/class_linker.cc | 153 | ||||
-rw-r--r-- | runtime/image.h | 48 | ||||
-rw-r--r-- | runtime/mirror/object-inl.h | 4 | ||||
-rw-r--r-- | runtime/mirror/object_reference-inl.h | 13 | ||||
-rw-r--r-- | runtime/mirror/object_reference.h | 9 |
8 files changed, 778 insertions, 107 deletions
diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc index df84b25027..c5416d5a3d 100644 --- a/compiler/driver/compiler_driver.cc +++ b/compiler/driver/compiler_driver.cc @@ -2157,10 +2157,9 @@ class InitializeClassVisitor : public CompilationVisitor { // Otherwise it's in app image but superclasses can't be initialized, no need to proceed. old_status = klass->GetStatus(); - bool too_many_encoded_fields = false; - if (!is_boot_image && klass->NumStaticFields() > kMaxEncodedFields) { - too_many_encoded_fields = true; - } + bool too_many_encoded_fields = !is_boot_image && + klass->NumStaticFields() > kMaxEncodedFields; + // If the class was not initialized, we can proceed to see if we can initialize static // fields. Limit the max number of encoded fields. if (!klass->IsInitialized() && @@ -2210,9 +2209,13 @@ class InitializeClassVisitor : public CompilationVisitor { if (success) { runtime->ExitTransactionMode(); DCHECK(!runtime->IsActiveTransaction()); - } - if (!success) { + if (is_boot_image) { + // For boot image, we want to put the updated status in the oat class since we + // can't reject the image anyways. + old_status = klass->GetStatus(); + } + } else { CHECK(soa.Self()->IsExceptionPending()); mirror::Throwable* exception = soa.Self()->GetException(); VLOG(compiler) << "Initialization of " << descriptor << " aborted because of " @@ -2226,10 +2229,6 @@ class InitializeClassVisitor : public CompilationVisitor { soa.Self()->ClearException(); runtime->RollbackAllTransactions(); CHECK_EQ(old_status, klass->GetStatus()) << "Previous class status not restored"; - } else if (is_boot_image) { - // For boot image, we want to put the updated status in the oat class since we can't - // reject the image anyways. - old_status = klass->GetStatus(); } } diff --git a/dex2oat/linker/image_writer.cc b/dex2oat/linker/image_writer.cc index 685174dedd..901f47819f 100644 --- a/dex2oat/linker/image_writer.cc +++ b/dex2oat/linker/image_writer.cc @@ -200,13 +200,17 @@ static void ClearDexFileCookies() REQUIRES_SHARED(Locks::mutator_lock_) { bool ImageWriter::PrepareImageAddressSpace(TimingLogger* timings) { target_ptr_size_ = InstructionSetPointerSize(compiler_options_.GetInstructionSet()); + + Thread* const self = Thread::Current(); + gc::Heap* const heap = Runtime::Current()->GetHeap(); { - ScopedObjectAccess soa(Thread::Current()); + ScopedObjectAccess soa(self); { TimingLogger::ScopedTiming t("PruneNonImageClasses", timings); PruneNonImageClasses(); // Remove junk } + if (compile_app_image_) { TimingLogger::ScopedTiming t("ClearDexFileCookies", timings); // Clear dex file cookies for app images to enable app image determinism. This is required @@ -215,22 +219,108 @@ bool ImageWriter::PrepareImageAddressSpace(TimingLogger* timings) { ClearDexFileCookies(); } } + { TimingLogger::ScopedTiming t("CollectGarbage", timings); heap->CollectGarbage(/* clear_soft_references */ false); // Remove garbage. } if (kIsDebugBuild) { - ScopedObjectAccess soa(Thread::Current()); + ScopedObjectAccess soa(self); CheckNonImageClassesRemoved(); } + // Used to store information that will later be used to calculate image + // offsets to string references in the AppImage. + std::vector<RefInfoPair> string_ref_info; + if (ClassLinker::kAppImageMayContainStrings && compile_app_image_) { + // Count the number of string fields so we can allocate the appropriate + // amount of space in the image section. + TimingLogger::ScopedTiming t("AppImage:CollectStringReferenceInfo", timings); + ScopedObjectAccess soa(self); + + if (kIsDebugBuild) { + VerifyNativeGCRootInvariants(); + CHECK_EQ(image_infos_.size(), 1u); + } + + string_ref_info = CollectStringReferenceInfo(); + image_infos_.back().num_string_references_ = string_ref_info.size(); + } + { TimingLogger::ScopedTiming t("CalculateNewObjectOffsets", timings); - ScopedObjectAccess soa(Thread::Current()); + ScopedObjectAccess soa(self); CalculateNewObjectOffsets(); } + // Obtain class count for debugging purposes + if (kIsDebugBuild && compile_app_image_) { + ScopedObjectAccess soa(self); + + size_t app_image_class_count = 0; + + for (ImageInfo& info : image_infos_) { + info.class_table_->Visit([&](ObjPtr<mirror::Class> klass) + REQUIRES_SHARED(Locks::mutator_lock_) { + if (!IsInBootImage(klass.Ptr())) { + ++app_image_class_count; + } + + // Indicate that we would like to continue visiting classes. + return true; + }); + } + + LOG(INFO) << "Dex2Oat:AppImage:classCount = " << app_image_class_count; + } + + if (ClassLinker::kAppImageMayContainStrings && compile_app_image_) { + // Use the string reference information obtained earlier to calculate image + // offsets. These will later be written to the image by Write/CopyMetadata. + TimingLogger::ScopedTiming t("AppImage:CalculateImageOffsets", timings); + ScopedObjectAccess soa(self); + + size_t managed_string_refs = 0, + native_string_refs = 0; + + /* + * Iterate over the string reference info and calculate image offsets. + * The first element of the pair is the object the reference belongs to + * and the second element is the offset to the field. If the offset has + * a native ref tag 1) the object is a DexCache and 2) the offset needs + * to be calculated using the relocation information for the DexCache's + * strings array. + */ + for (const RefInfoPair& ref_info : string_ref_info) { + uint32_t image_offset; + + if (HasNativeRefTag(ref_info.second)) { + ++native_string_refs; + + // Only DexCaches can contain native references to Java strings. + ObjPtr<mirror::DexCache> dex_cache(ref_info.first->AsDexCache()); + + // No need to set or clear native ref tags. The existing tag will be + // carried forward. + image_offset = native_object_relocations_[dex_cache->GetStrings()].offset + + ref_info.second; + } else { + ++managed_string_refs; + image_offset = GetImageOffset(ref_info.first) + ref_info.second; + } + + string_reference_offsets_.push_back(image_offset); + } + + CHECK_EQ(image_infos_.back().num_string_references_, + string_reference_offsets_.size()); + + LOG(INFO) << "Dex2Oat:AppImage:stringReferences = " << string_reference_offsets_.size(); + LOG(INFO) << "Dex2Oat:AppImage:managedStringReferences = " << managed_string_refs; + LOG(INFO) << "Dex2Oat:AppImage:nativeStringReferences = " << native_string_refs; + } + // This needs to happen after CalculateNewObjectOffsets since it relies on intern_table_bytes_ and // bin size sums being calculated. TimingLogger::ScopedTiming t("AllocMemory", timings); @@ -241,6 +331,252 @@ bool ImageWriter::PrepareImageAddressSpace(TimingLogger* timings) { return true; } +class ImageWriter::CollectStringReferenceVisitor { + public: + explicit CollectStringReferenceVisitor(const ImageWriter& image_writer) + : dex_cache_string_ref_counter_(0), + image_writer_(image_writer) {} + + // Used to prevent repeated null checks in the code that calls the visitor. + ALWAYS_INLINE + void VisitRootIfNonNull(mirror::CompressedReference<mirror::Object>* root) const + REQUIRES_SHARED(Locks::mutator_lock_) { + if (!root->IsNull()) { + VisitRoot(root); + } + } + + /* + * Counts the number of native references to strings reachable through + * DexCache objects for verification later. + */ + ALWAYS_INLINE + void VisitRoot(mirror::CompressedReference<mirror::Object>* root) const + REQUIRES_SHARED(Locks::mutator_lock_) { + ObjPtr<mirror::Object> referred_obj = root->AsMirrorPtr(); + + if (curr_obj_->IsDexCache() && + image_writer_.IsValidAppImageStringReference(referred_obj)) { + ++dex_cache_string_ref_counter_; + } + } + + // Collects info for Java fields that reference Java Strings. + ALWAYS_INLINE + void operator() (ObjPtr<mirror::Object> obj, + MemberOffset member_offset, + bool is_static ATTRIBUTE_UNUSED) const + REQUIRES_SHARED(Locks::mutator_lock_) { + ObjPtr<mirror::Object> referred_obj = + obj->GetFieldObject<mirror::Object, kVerifyNone, kWithoutReadBarrier>( + member_offset); + + if (image_writer_.IsValidAppImageStringReference(referred_obj)) { + string_ref_info_.emplace_back(obj.Ptr(), member_offset.Uint32Value()); + } + } + + ALWAYS_INLINE + void operator() (ObjPtr<mirror::Class> klass ATTRIBUTE_UNUSED, + ObjPtr<mirror::Reference> ref) const + REQUIRES_SHARED(Locks::mutator_lock_) { + operator()(ref, mirror::Reference::ReferentOffset(), /* is_static */ false); + } + + // Used by the wrapper function to obtain a native reference count. + size_t GetDexCacheStringRefCounter() const { + return dex_cache_string_ref_counter_; + } + + // Resets the native reference count. + void ResetDexCacheStringRefCounter() { + dex_cache_string_ref_counter_ = 0; + } + + ObjPtr<mirror::Object> curr_obj_; + mutable std::vector<RefInfoPair> string_ref_info_; + + private: + mutable size_t dex_cache_string_ref_counter_; + const ImageWriter& image_writer_; +}; + +std::vector<ImageWriter::RefInfoPair> ImageWriter::CollectStringReferenceInfo() const + REQUIRES_SHARED(Locks::mutator_lock_) { + gc::Heap* const heap = Runtime::Current()->GetHeap(); + CollectStringReferenceVisitor visitor(*this); + + heap->VisitObjects([this, &visitor](ObjPtr<mirror::Object> object) + REQUIRES_SHARED(Locks::mutator_lock_) { + if (!IsInBootImage(object.Ptr())) { + // Many native GC roots are wrapped in std::atomics. Due to the + // semantics of atomic objects we can't actually visit the addresses of + // the native GC roots. Instead the visiting functions will pass the + // visitor the address of a temporary copy of the native GC root and, if + // it is changed, copy it back into the original location. + // + // This analysis requires the actual address of the native GC root so + // we will only count them in the visitor and then collect them manually + // afterwards. This count will then be used to verify that we collected + // all native GC roots. + visitor.curr_obj_ = object; + if (object->IsDexCache()) { + object->VisitReferences</* kVisitNativeRoots */ true, + kVerifyNone, + kWithoutReadBarrier>(visitor, visitor); + + ObjPtr<mirror::DexCache> dex_cache = object->AsDexCache(); + size_t new_native_ref_counter = 0; + + for (size_t string_index = 0; string_index < dex_cache->NumStrings(); ++string_index) { + mirror::StringDexCachePair dc_pair = dex_cache->GetStrings()[string_index].load(); + mirror::Object* referred_obj = dc_pair.object.AddressWithoutBarrier()->AsMirrorPtr(); + + if (IsValidAppImageStringReference(referred_obj)) { + ++new_native_ref_counter; + + uint32_t string_vector_offset = + (string_index * sizeof(mirror::StringDexCachePair)) + + offsetof(mirror::StringDexCachePair, object); + + visitor.string_ref_info_.emplace_back(object.Ptr(), + SetNativeRefTag(string_vector_offset)); + } + } + + CHECK_EQ(visitor.GetDexCacheStringRefCounter(), new_native_ref_counter); + } else { + object->VisitReferences</* kVisitNativeRoots */ false, + kVerifyNone, + kWithoutReadBarrier>(visitor, visitor); + } + + visitor.ResetDexCacheStringRefCounter(); + } + }); + + return std::move(visitor.string_ref_info_); +} + +class ImageWriter::NativeGCRootInvariantVisitor { + public: + explicit NativeGCRootInvariantVisitor(const ImageWriter& image_writer) : + image_writer_(image_writer) {} + + ALWAYS_INLINE + void VisitRootIfNonNull(mirror::CompressedReference<mirror::Object>* root) const + REQUIRES_SHARED(Locks::mutator_lock_) { + if (!root->IsNull()) { + VisitRoot(root); + } + } + + ALWAYS_INLINE + void VisitRoot(mirror::CompressedReference<mirror::Object>* root) const + REQUIRES_SHARED(Locks::mutator_lock_) { + ObjPtr<mirror::Object> referred_obj = root->AsMirrorPtr(); + + if (curr_obj_->IsClass()) { + class_violation = class_violation || + image_writer_.IsValidAppImageStringReference(referred_obj); + + } else if (curr_obj_->IsClassLoader()) { + class_loader_violation = class_loader_violation || + image_writer_.IsValidAppImageStringReference(referred_obj); + + } else if (!curr_obj_->IsDexCache()) { + LOG(FATAL) << "Dex2Oat:AppImage | " << + "Native reference to String found in unexpected object type."; + } + } + + ALWAYS_INLINE + void operator() (ObjPtr<mirror::Object> obj ATTRIBUTE_UNUSED, + MemberOffset member_offset ATTRIBUTE_UNUSED, + bool is_static ATTRIBUTE_UNUSED) const + REQUIRES_SHARED(Locks::mutator_lock_) {} + + ALWAYS_INLINE + void operator() (ObjPtr<mirror::Class> klass ATTRIBUTE_UNUSED, + ObjPtr<mirror::Reference> ref ATTRIBUTE_UNUSED) const + REQUIRES_SHARED(Locks::mutator_lock_) {} + + // Returns true iff the only reachable native string references are through DexCache objects. + bool InvariantsHold() const { + return !(class_violation || class_loader_violation); + } + + ObjPtr<mirror::Object> curr_obj_; + mutable bool class_violation = false, + class_loader_violation = false; + + private: + const ImageWriter& image_writer_; +}; + +void ImageWriter::VerifyNativeGCRootInvariants() const REQUIRES_SHARED(Locks::mutator_lock_) { + gc::Heap* const heap = Runtime::Current()->GetHeap(); + + NativeGCRootInvariantVisitor visitor(*this); + + heap->VisitObjects([this, &visitor](ObjPtr<mirror::Object> object) + REQUIRES_SHARED(Locks::mutator_lock_) { + visitor.curr_obj_ = object; + + if (!IsInBootImage(object.Ptr())) { + object->VisitReferences</* kVisitNativeRefernces */ true, + kVerifyNone, + kWithoutReadBarrier>(visitor, visitor); + } + }); + + bool error = false; + std::ostringstream error_str; + + /* + * Build the error string + */ + + if (UNLIKELY(visitor.class_violation)) { + error_str << "Class"; + error = true; + } + + if (UNLIKELY(visitor.class_loader_violation)) { + if (error) { + error_str << ", "; + } + + error_str << "ClassLoader"; + } + + CHECK(visitor.InvariantsHold()) << + "Native GC root invariant failure. String refs reachable through the following objects: " << + error_str.str(); +} + +void ImageWriter::CopyMetadata() { + CHECK(compile_app_image_); + CHECK_EQ(image_infos_.size(), 1u); + + const ImageInfo& image_info = image_infos_.back(); + std::vector<ImageSection> image_sections = image_info.CreateImageSections().second; + + uint32_t* sfo_section_base = reinterpret_cast<uint32_t*>( + image_info.image_.Begin() + + image_sections[ImageHeader::kSectionStringReferenceOffsets].Offset()); + + std::copy(string_reference_offsets_.begin(), + string_reference_offsets_.end(), + sfo_section_base); +} + +bool ImageWriter::IsValidAppImageStringReference(ObjPtr<mirror::Object> referred_obj) const { + return referred_obj != nullptr && + !IsInBootImage(referred_obj.Ptr()) && + referred_obj->IsString(); +} + bool ImageWriter::Write(int image_fd, const std::vector<const char*>& image_filenames, const std::vector<const char*>& oat_filenames) { @@ -268,6 +604,10 @@ bool ImageWriter::Write(int image_fd, CopyAndFixupObjects(); } + if (compile_app_image_) { + CopyMetadata(); + } + for (size_t i = 0; i < image_filenames.size(); ++i) { const char* image_filename = image_filenames[i]; ImageInfo& image_info = GetImageInfo(i); @@ -366,8 +706,23 @@ bool ImageWriter::Write(int image_fd, return false; } + if (kIsDebugBuild) { + size_t separately_written_section_size = bitmap_section.Size() + + image_header->GetImageRelocationsSection().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; + + LOG(INFO) << "Dex2Oat:uncompressedImageSize = " << total_uncompressed_size; + if (total_uncompressed_size != total_compressed_size) { + LOG(INFO) << "Dex2Oat:compressedImageSize = " << total_compressed_size; + } + } + CHECK_EQ(relocations_position_in_file + relocations.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; @@ -721,9 +1076,7 @@ ImageWriter::BinSlot ImageWriter::GetImageBinSlot(mirror::Object* object) const bool ImageWriter::AllocMemory() { for (ImageInfo& image_info : image_infos_) { - ImageSection unused_sections[ImageHeader::kSectionCount]; - const size_t length = RoundUp( - image_info.CreateImageSections(unused_sections), kPageSize); + const size_t length = RoundUp(image_info.CreateImageSections().first, kPageSize); std::string error_msg; image_info.image_ = MemMap::MapAnonymous("image writer image", @@ -1779,9 +2132,9 @@ void ImageWriter::CalculateNewObjectOffsets() { } ProcessWorkStack(&work_stack); - // For app images, there may be objects that are only held live by the by the boot image. One + // For app images, there may be objects that are only held live by the boot image. One // example is finalizer references. Forward these objects so that EnsureBinSlotAssignedCallback - // does not fail any checks. TODO: We should probably avoid copying these objects. + // does not fail any checks. if (compile_app_image_) { for (gc::space::ImageSpace* space : heap->GetBootImageSpaces()) { DCHECK(space->IsImageSpace()); @@ -1873,9 +2226,7 @@ void ImageWriter::CalculateNewObjectOffsets() { for (ImageInfo& image_info : image_infos_) { image_info.image_begin_ = global_image_begin_ + image_offset; image_info.image_offset_ = image_offset; - ImageSection unused_sections[ImageHeader::kSectionCount]; - image_info.image_size_ = - RoundUp(image_info.CreateImageSections(unused_sections), kPageSize); + image_info.image_size_ = RoundUp(image_info.CreateImageSections().first, kPageSize); // There should be no gaps until the next image. image_offset += image_info.image_size_; } @@ -1909,58 +2260,94 @@ void ImageWriter::CalculateNewObjectOffsets() { boot_image_live_objects_ = boot_image_live_objects.Get(); } -size_t ImageWriter::ImageInfo::CreateImageSections(ImageSection* out_sections) const { - DCHECK(out_sections != nullptr); +std::pair<size_t, std::vector<ImageSection>> ImageWriter::ImageInfo::CreateImageSections() const { + std::vector<ImageSection> sections(ImageHeader::kSectionCount); + + // Do not round up any sections here that are represented by the bins since it + // will break offsets. + + /* + * Objects section + */ + sections[ImageHeader::kSectionObjects] = + ImageSection(0u, image_end_); + + /* + * Field section + */ + sections[ImageHeader::kSectionArtFields] = + ImageSection(GetBinSlotOffset(Bin::kArtField), GetBinSlotSize(Bin::kArtField)); + + /* + * Method section + */ + sections[ImageHeader::kSectionArtMethods] = + ImageSection(GetBinSlotOffset(Bin::kArtMethodClean), + GetBinSlotSize(Bin::kArtMethodClean) + + GetBinSlotSize(Bin::kArtMethodDirty)); + + /* + * IMT section + */ + sections[ImageHeader::kSectionImTables] = + ImageSection(GetBinSlotOffset(Bin::kImTable), GetBinSlotSize(Bin::kImTable)); + + /* + * Conflict Tables section + */ + sections[ImageHeader::kSectionIMTConflictTables] = + ImageSection(GetBinSlotOffset(Bin::kIMTConflictTable), GetBinSlotSize(Bin::kIMTConflictTable)); + + /* + * Runtime Methods section + */ + sections[ImageHeader::kSectionRuntimeMethods] = + ImageSection(GetBinSlotOffset(Bin::kRuntimeMethod), GetBinSlotSize(Bin::kRuntimeMethod)); + + /* + * DexCache Arrays section. + */ + const ImageSection& dex_cache_arrays_section = + sections[ImageHeader::kSectionDexCacheArrays] = + ImageSection(GetBinSlotOffset(Bin::kDexCacheArray), + GetBinSlotSize(Bin::kDexCacheArray)); + + /* + * Interned Strings section + */ + + // Round up to the alignment the string table expects. See HashSet::WriteToMemory. + size_t cur_pos = RoundUp(dex_cache_arrays_section.End(), sizeof(uint64_t)); - // Do not round up any sections here that are represented by the bins since it will break - // offsets. + const ImageSection& interned_strings_section = + sections[ImageHeader::kSectionInternedStrings] = + ImageSection(cur_pos, intern_table_bytes_); - // Objects section - ImageSection* objects_section = &out_sections[ImageHeader::kSectionObjects]; - *objects_section = ImageSection(0u, image_end_); + /* + * Class Table section + */ - // Add field section. - ImageSection* field_section = &out_sections[ImageHeader::kSectionArtFields]; - *field_section = ImageSection(GetBinSlotOffset(Bin::kArtField), GetBinSlotSize(Bin::kArtField)); + // Obtain the new position and round it up to the appropriate alignment. + cur_pos = RoundUp(interned_strings_section.End(), sizeof(uint64_t)); - // Add method section. - ImageSection* methods_section = &out_sections[ImageHeader::kSectionArtMethods]; - *methods_section = ImageSection( - GetBinSlotOffset(Bin::kArtMethodClean), - GetBinSlotSize(Bin::kArtMethodClean) + GetBinSlotSize(Bin::kArtMethodDirty)); + const ImageSection& class_table_section = + sections[ImageHeader::kSectionClassTable] = + ImageSection(cur_pos, class_table_bytes_); - // IMT section. - ImageSection* imt_section = &out_sections[ImageHeader::kSectionImTables]; - *imt_section = ImageSection(GetBinSlotOffset(Bin::kImTable), GetBinSlotSize(Bin::kImTable)); + /* + * String Field Offsets section + */ - // Conflict tables section. - ImageSection* imt_conflict_tables_section = &out_sections[ImageHeader::kSectionIMTConflictTables]; - *imt_conflict_tables_section = ImageSection(GetBinSlotOffset(Bin::kIMTConflictTable), - GetBinSlotSize(Bin::kIMTConflictTable)); + // Round up to the alignment of the offsets we are going to store. + cur_pos = RoundUp(class_table_section.End(), sizeof(uint32_t)); - // Runtime methods section. - ImageSection* runtime_methods_section = &out_sections[ImageHeader::kSectionRuntimeMethods]; - *runtime_methods_section = ImageSection(GetBinSlotOffset(Bin::kRuntimeMethod), - GetBinSlotSize(Bin::kRuntimeMethod)); + const ImageSection& string_reference_offsets = + sections[ImageHeader::kSectionStringReferenceOffsets] = + ImageSection(cur_pos, sizeof(uint32_t) * num_string_references_); - // Add dex cache arrays section. - ImageSection* dex_cache_arrays_section = &out_sections[ImageHeader::kSectionDexCacheArrays]; - *dex_cache_arrays_section = ImageSection(GetBinSlotOffset(Bin::kDexCacheArray), - GetBinSlotSize(Bin::kDexCacheArray)); - // Round up to the alignment the string table expects. See HashSet::WriteToMemory. - size_t cur_pos = RoundUp(dex_cache_arrays_section->End(), sizeof(uint64_t)); - // Calculate the size of the interned strings. - ImageSection* interned_strings_section = &out_sections[ImageHeader::kSectionInternedStrings]; - *interned_strings_section = ImageSection(cur_pos, intern_table_bytes_); - cur_pos = interned_strings_section->End(); - // Round up to the alignment the class table expects. See HashSet::WriteToMemory. - cur_pos = RoundUp(cur_pos, sizeof(uint64_t)); - // Calculate the size of the class table section. - ImageSection* class_table_section = &out_sections[ImageHeader::kSectionClassTable]; - *class_table_section = ImageSection(cur_pos, class_table_bytes_); - cur_pos = class_table_section->End(); - // Image end goes right before the start of the image bitmap. - return cur_pos; + // Return the number of bytes described by these sections, and the sections + // themselves. + return make_pair(string_reference_offsets.End(), std::move(sections)); } void ImageWriter::CreateHeader(size_t oat_index) { @@ -1970,8 +2357,9 @@ void ImageWriter::CreateHeader(size_t oat_index) { const uint8_t* oat_data_end = image_info.oat_data_begin_ + image_info.oat_size_; // Create the image sections. - ImageSection sections[ImageHeader::kSectionCount]; - const size_t image_end = image_info.CreateImageSections(sections); + auto section_info_pair = image_info.CreateImageSections(); + const size_t image_end = section_info_pair.first; + std::vector<ImageSection>& sections = section_info_pair.second; // Finally bitmap section. const size_t bitmap_bytes = image_info.image_bitmap_->Size(); @@ -2008,7 +2396,7 @@ void ImageWriter::CreateHeader(size_t oat_index) { ImageHeader* header = new (image_info.image_.Begin()) ImageHeader( PointerToLowMemUInt32(image_info.image_begin_), image_end, - sections, + sections.data(), image_info.image_roots_address_, image_info.oat_checksum_, PointerToLowMemUInt32(oat_file_begin), diff --git a/dex2oat/linker/image_writer.h b/dex2oat/linker/image_writer.h index f4557467b6..142f77bbb4 100644 --- a/dex2oat/linker/image_writer.h +++ b/dex2oat/linker/image_writer.h @@ -84,6 +84,22 @@ class ImageWriter final { const std::unordered_map<const DexFile*, size_t>& dex_file_oat_index_map, const HashSet<std::string>* dirty_image_objects); + /* + * Modifies the heap and collects information about objects and code so that + * they can be written to the boot or app image later. + * + * First, unneeded classes are removed from the managed heap. Next, we + * remove cached values and calculate necessary metadata for later in the + * process. Optionally some debugging information is collected and used to + * verify the state of the heap at this point. Next, metadata from earlier + * is used to calculate offsets of references to strings to speed up string + * interning when the image is loaded. Lastly, we allocate enough memory to + * fit all image data minus the bitmap and relocation sections. + * + * This function should only be called when all objects to be included in the + * image have been initialized and all native methods have been generated. In + * addition, no other thread should be modifying the heap. + */ bool PrepareImageAddressSpace(TimingLogger* timings); bool IsImageAddressSpaceReady() const { @@ -270,9 +286,18 @@ class ImageWriter final { ImageInfo(); ImageInfo(ImageInfo&&) = default; - // Create the image sections into the out sections variable, returns the size of the image - // excluding the bitmap. - size_t CreateImageSections(ImageSection* out_sections) const; + /* + * Creates ImageSection objects that describe most of the sections of a + * boot or AppImage. The following sections are not included: + * - ImageHeader::kSectionImageBitmap + * - ImageHeader::kSectionImageRelocations + * + * In addition, the ImageHeader is not covered here. + * + * This function will return the total size of the covered sections as well + * as a vector containing the individual ImageSection objects. + */ + std::pair<size_t, std::vector<ImageSection>> CreateImageSections() const; size_t GetStubOffset(StubType stub_type) const { DCHECK_LT(static_cast<size_t>(stub_type), kNumberOfStubTypes); @@ -364,6 +389,10 @@ class ImageWriter final { // Number of pointer fixup bytes. size_t pointer_fixup_bytes_ = 0; + // Number of offsets to string references that will be written to the + // StringFieldOffsets section. + size_t num_string_references_ = 0; + // Intern table associated with this image for serialization. std::unique_ptr<InternTable> intern_table_; @@ -474,6 +503,21 @@ class ImageWriter final { ImtConflictTable* copy, size_t oat_index) REQUIRES_SHARED(Locks::mutator_lock_); + + /* + * Copies metadata from the heap into a buffer that will be compressed and + * written to the image. + * + * This function copies the string offset metadata from a local vector to an + * offset inside the image_ field of an ImageInfo struct. The offset into the + * memory pointed to by the image_ field is obtained from the ImageSection + * object for the String Offsets section. + * + * All data for the image, besides the object bitmap and the relocation data, + * will also be copied into the memory region pointed to by image_. + */ + void CopyMetadata(); + template <bool kCheckNotNull = true> void RecordImageRelocation(const void* dest, size_t oat_index, bool app_to_boot_image = false); void FixupClass(mirror::Class* orig, mirror::Class* copy, size_t oat_index) @@ -554,6 +598,60 @@ class ImageWriter final { std::unordered_set<mirror::Object*>* visited) REQUIRES_SHARED(Locks::mutator_lock_); + /* + * A pair containing the information necessary to calculate the position of a + * managed object's field or native reference inside an AppImage. + * + * The first element of this pair is a raw mirror::Object pointer because its + * usage will cross a suspend point and ObjPtr would produce a false positive. + * + * The second element is an offset either into the object or into the string + * array of a DexCache object. + * + * TODO (chriswailes): Add a note indicating the source line where we ensure + * that no moving garbage collection will occur. + */ + typedef std::pair<mirror::Object*, uint32_t> RefInfoPair; + + /* + * Collects the info necessary for calculating image offsets to string field + * later. + * + * This function is used when constructing AppImages. Because AppImages + * contain strings that must be interned we need to visit references to these + * strings when the AppImage is loaded and either insert them into the + * runtime intern table or replace the existing reference with a reference + * to the interned strings. + * + * To speed up the interning of strings when the AppImage is loaded we include + * a list of offsets to string references in the AppImage. These are then + * iterated over at load time and fixed up. + * + * To record the offsets we first have to count the number of string + * references that will be included in the AppImage. This allows use to both + * allocate enough memory for soring the offsets and correctly calculate the + * offsets of various objects into the image. Once the image offset + * calculations are done for Java objects the reference object/offset pairs + * are translated to image offsets. The CopyMetadata function then copies + * these offsets into the image. + * + * A vector containing pairs of object pointers and offsets. The offsets are + * tagged to indicate if the offset is for a field of a mirror object or a + * native reference. If the offset is tagged as a native reference it must + * have come from a DexCache's string array. + */ + std::vector<RefInfoPair> CollectStringReferenceInfo() const + REQUIRES_SHARED(Locks::mutator_lock_); + + /* + * Ensures that assumptions about native GC roots and AppImages hold. + * + * This function verifies the following condition(s): + * - Native references to Java strings are only reachable through DexCache + * objects + */ + void VerifyNativeGCRootInvariants() const REQUIRES_SHARED(Locks::mutator_lock_); + bool IsMultiImage() const { return image_infos_.size() > 1; } @@ -628,6 +726,18 @@ class ImageWriter final { void CopyAndFixupPointer(void* object, MemberOffset offset, void* value, size_t oat_index) REQUIRES_SHARED(Locks::mutator_lock_); + /* + * Tests an object to see if it will be contained in an AppImage. + * + * An object reference is considered to be a AppImage String reference iff: + * - It isn't null + * - The referred-object isn't in the boot image + * - The referred-object is a Java String + */ + ALWAYS_INLINE + bool IsValidAppImageStringReference(ObjPtr<mirror::Object> referred_obj) const + REQUIRES_SHARED(Locks::mutator_lock_); + const CompilerOptions& compiler_options_; // Beginning target image address for the first image. @@ -680,6 +790,9 @@ class ImageWriter final { // Boot image live objects, null for app image. mirror::ObjectArray<mirror::Object>* boot_image_live_objects_; + // Offsets into the image that indicate where string references are recorded. + std::vector<uint32_t> string_reference_offsets_; + // Which mode the image is stored as, see image.h const ImageHeader::StorageMode image_storage_mode_; @@ -700,9 +813,23 @@ class ImageWriter final { class NativeLocationVisitor; class PruneClassesVisitor; class PruneClassLoaderClassesVisitor; + class PruneObjectReferenceVisitor; class RegisterBootClassPathClassesVisitor; class VisitReferencesVisitor; - class PruneObjectReferenceVisitor; + + /* + * A visitor class for extracting object/offset pairs. + * + * This visitor walks the fields of an object and extracts object/offset pairs + * that are later translated to image offsets. This visitor is only + * responsible for extracting info for Java references. Native references to + * Java strings are handled in the wrapper function + * CollectStringReferenceInfo(). + */ + class CollectStringReferenceVisitor; + + // A visitor used by the VerifyNativeGCRootInvariants() function. + class NativeGCRootInvariantVisitor; DISALLOW_COPY_AND_ASSIGN(ImageWriter); }; diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 9030017b84..b7472250ad 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -104,6 +104,8 @@ #include "mirror/object-inl.h" #include "mirror/object-refvisitor-inl.h" #include "mirror/object_array-inl.h" +#include "mirror/object_reference.h" +#include "mirror/object_reference-inl.h" #include "mirror/proxy.h" #include "mirror/reference-inl.h" #include "mirror/stack_trace_element.h" @@ -1171,18 +1173,31 @@ class VerifyDeclaringClassVisitor : public ArtMethodVisitor { gc::accounting::HeapBitmap* const live_bitmap_; }; -class FixupInternVisitor { +/* + * A class used to ensure that all strings in an AppImage have been properly + * interned. + */ +class VerifyStringInterningVisitor { public: - ALWAYS_INLINE ObjPtr<mirror::Object> TryInsertIntern(mirror::Object* obj) const + explicit VerifyStringInterningVisitor(const gc::space::ImageSpace& space) : + uninterned_string_found_(false), + space_(space), + intern_table_(*Runtime::Current()->GetInternTable()) {} + + ALWAYS_INLINE + void TestObject(ObjPtr<mirror::Object> referred_obj) const REQUIRES_SHARED(Locks::mutator_lock_) { - if (obj != nullptr && obj->IsString()) { - const auto intern = Runtime::Current()->GetInternTable()->InternStrong(obj->AsString()); - return intern; + if (referred_obj != nullptr && + space_.HasAddress(referred_obj.Ptr()) && + referred_obj->IsString()) { + ObjPtr<mirror::String> referred_str = referred_obj->AsString(); + uninterned_string_found_ = uninterned_string_found_ || + (intern_table_.LookupStrong(Thread::Current(), referred_str) != referred_str); } - return obj; } - ALWAYS_INLINE void VisitRootIfNonNull( + ALWAYS_INLINE + void VisitRootIfNonNull( mirror::CompressedReference<mirror::Object>* root) const REQUIRES_SHARED(Locks::mutator_lock_) { if (!root->IsNull()) { @@ -1190,44 +1205,77 @@ class FixupInternVisitor { } } - ALWAYS_INLINE void VisitRoot(mirror::CompressedReference<mirror::Object>* root) const + ALWAYS_INLINE + void VisitRoot(mirror::CompressedReference<mirror::Object>* root) const REQUIRES_SHARED(Locks::mutator_lock_) { - root->Assign(TryInsertIntern(root->AsMirrorPtr())); + TestObject(root->AsMirrorPtr()); } // Visit Class Fields - ALWAYS_INLINE void operator()(ObjPtr<mirror::Object> obj, - MemberOffset offset, - bool is_static ATTRIBUTE_UNUSED) const + ALWAYS_INLINE + void operator()(ObjPtr<mirror::Object> obj, + MemberOffset offset, + bool is_static ATTRIBUTE_UNUSED) const REQUIRES_SHARED(Locks::mutator_lock_) { // There could be overlap between ranges, we must avoid visiting the same reference twice. // Avoid the class field since we already fixed it up in FixupClassVisitor. if (offset.Uint32Value() != mirror::Object::ClassOffset().Uint32Value()) { // Updating images, don't do a read barrier. - // Only string fields are fixed, don't do a verify. - mirror::Object* ref = obj->GetFieldObject<mirror::Object, kVerifyNone, kWithoutReadBarrier>( - offset); - obj->SetFieldObject<false, false>(offset, TryInsertIntern(ref)); + ObjPtr<mirror::Object> referred_obj = + obj->GetFieldObject<mirror::Object, kVerifyNone, kWithoutReadBarrier>(offset); + + TestObject(referred_obj); } } void operator()(ObjPtr<mirror::Class> klass ATTRIBUTE_UNUSED, ObjPtr<mirror::Reference> ref) const REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_) { - this->operator()(ref, mirror::Reference::ReferentOffset(), false); + operator()(ref, mirror::Reference::ReferentOffset(), false); } - void operator()(mirror::Object* obj) const - REQUIRES_SHARED(Locks::mutator_lock_) { - if (obj->IsDexCache()) { - obj->VisitReferences<true, kVerifyNone, kWithoutReadBarrier>(*this, *this); - } else { - // Don't visit native roots for non-dex-cache - obj->VisitReferences<false, kVerifyNone, kWithoutReadBarrier>(*this, *this); - } - } + mutable bool uninterned_string_found_; + const gc::space::ImageSpace& space_; + InternTable& intern_table_; }; +/* + * This function verifies that string references in the AppImage have been + * properly interned. To be considered properly interned a reference must + * point to the same version of the string that the intern table does. + */ +bool VerifyStringInterning(gc::space::ImageSpace& space) REQUIRES_SHARED(Locks::mutator_lock_) { + const gc::accounting::ContinuousSpaceBitmap* bitmap = space.GetMarkBitmap(); + const ImageHeader& image_header = space.GetImageHeader(); + const uint8_t* target_base = space.GetMemMap()->Begin(); + const ImageSection& objects_section = image_header.GetObjectsSection(); + uintptr_t objects_begin = reinterpret_cast<uintptr_t>(target_base + objects_section.Offset()); + uintptr_t objects_end = reinterpret_cast<uintptr_t>(target_base + objects_section.End()); + + VerifyStringInterningVisitor visitor(space); + bitmap->VisitMarkedRange(objects_begin, + objects_end, + [&space, &visitor](mirror::Object* obj) + REQUIRES_SHARED(Locks::mutator_lock_) { + if (space.HasAddress(obj)) { + if (obj->IsDexCache()) { + obj->VisitReferences</* kVisitNativeRoots */ true, + kVerifyNone, + kWithoutReadBarrier>(visitor, visitor); + } else { + // Don't visit native roots for non-dex-cache as they can't contain + // native references to strings. This is verified during compilation + // by ImageWriter::VerifyNativeGCRootInvariants. + obj->VisitReferences</* kVisitNativeRoots */ false, + kVerifyNone, + kWithoutReadBarrier>(visitor, visitor); + } + } + }); + + return !visitor.uninterned_string_found_; +} + // new_class_set is the set of classes that were read from the class table section in the image. // If there was no class table section, it is null. // Note: using a class here to avoid having to make ClassLinker internals public. @@ -1268,6 +1316,7 @@ void AppImageClassLoadersAndDexCachesHelper::Update( CHECK(!class_linker->FindDexCacheDataLocked(*dex_file).IsValid()); class_linker->RegisterDexFileLocked(*dex_file, dex_cache, class_loader.Get()); } + if (kIsDebugBuild) { CHECK(new_class_set != nullptr); mirror::TypeDexCacheType* const types = dex_cache->GetResolvedTypes(); @@ -1275,17 +1324,20 @@ void AppImageClassLoadersAndDexCachesHelper::Update( for (size_t j = 0; j != num_types; ++j) { // The image space is not yet added to the heap, avoid read barriers. ObjPtr<mirror::Class> klass = types[j].load(std::memory_order_relaxed).object.Read(); + if (space->HasAddress(klass.Ptr())) { DCHECK(!klass->IsErroneous()) << klass->GetStatus(); auto it = new_class_set->find(ClassTable::TableSlot(klass)); DCHECK(it != new_class_set->end()); DCHECK_EQ(it->Read(), klass); ObjPtr<mirror::Class> super_class = klass->GetSuperClass(); + if (super_class != nullptr && !heap->ObjectIsInBootImageSpace(super_class)) { auto it2 = new_class_set->find(ClassTable::TableSlot(super_class)); DCHECK(it2 != new_class_set->end()); DCHECK_EQ(it2->Read(), super_class); } + for (ArtMethod& m : klass->GetDirectMethods(kRuntimePointerSize)) { const void* code = m.GetEntryPointFromQuickCompiledCode(); const void* oat_code = m.IsInvokable() ? class_linker->GetQuickOatCodeFor(&m) : code; @@ -1296,6 +1348,7 @@ void AppImageClassLoadersAndDexCachesHelper::Update( DCHECK_EQ(code, oat_code) << m.PrettyMethod(); } } + for (ArtMethod& m : klass->GetVirtualMethods(kRuntimePointerSize)) { const void* code = m.GetEntryPointFromQuickCompiledCode(); const void* oat_code = m.IsInvokable() ? class_linker->GetQuickOatCodeFor(&m) : code; @@ -1311,20 +1364,54 @@ void AppImageClassLoadersAndDexCachesHelper::Update( } } } + if (ClassLinker::kAppImageMayContainStrings) { - // Fixup all the literal strings happens at app images which are supposed to be interned. + // Iterate over the string reference offsets stored in the image and intern + // the strings they point to. + ScopedTrace timing("AppImage:InternString"); const auto& image_header = space->GetImageHeader(); - const auto bitmap = space->GetMarkBitmap(); // bitmap of objects const uint8_t* target_base = space->GetMemMap()->Begin(); - const ImageSection& objects_section = image_header.GetObjectsSection(); + const ImageSection& sro_section = image_header.GetImageStringReferenceOffsetsSection(); + + size_t num_string_offsets = sro_section.Size() / sizeof(uint32_t); + + if (kIsDebugBuild) { + LOG(INFO) + << "ClassLinker:AppImage:InternStrings:imageStringReferenceOffsetCount = " + << num_string_offsets; + } - uintptr_t objects_begin = reinterpret_cast<uintptr_t>(target_base + objects_section.Offset()); - uintptr_t objects_end = reinterpret_cast<uintptr_t>(target_base + objects_section.End()); + DCHECK(Runtime::Current()->GetInternTable() != nullptr); - FixupInternVisitor fixup_intern_visitor; - bitmap->VisitMarkedRange(objects_begin, objects_end, fixup_intern_visitor); + InternTable& intern_table = *Runtime::Current()->GetInternTable(); + const uint32_t* sro_base = + reinterpret_cast<const uint32_t*>(target_base + sro_section.Offset()); + + for (size_t offset_index = 0; offset_index < num_string_offsets; ++offset_index) { + if (HasNativeRefTag(sro_base[offset_index])) { + void* raw_field_addr = space->Begin() + ClearNativeRefTag(sro_base[offset_index]); + mirror::CompressedReference<mirror::Object>* objref_addr = + reinterpret_cast<mirror::CompressedReference<mirror::Object>*>(raw_field_addr); + mirror::String* referred_string = objref_addr->AsMirrorPtr()->AsString(); + DCHECK(referred_string != nullptr); + + objref_addr->Assign(intern_table.InternStrong(referred_string)); + + } else { + void* raw_field_addr = space->Begin() + sro_base[offset_index]; + mirror::HeapReference<mirror::Object>* objref_addr = + reinterpret_cast<mirror::HeapReference<mirror::Object>*>(raw_field_addr); + mirror::String* referred_string = objref_addr->AsMirrorPtr()->AsString(); + DCHECK(referred_string != nullptr); + + objref_addr->Assign<false>(intern_table.InternStrong(referred_string)); + } + } + + DCHECK(VerifyStringInterning(*space)); } + if (kVerifyArtMethodDeclaringClasses) { ScopedTrace timing("AppImage:VerifyDeclaringClasses"); ReaderMutexLock rmu(self, *Locks::heap_bitmap_lock_); diff --git a/runtime/image.h b/runtime/image.h index 6acb64b18e..73c1b8f5cd 100644 --- a/runtime/image.h +++ b/runtime/image.h @@ -219,6 +219,16 @@ class PACKED(4) ImageHeader { kBootImageLiveObjects = kSpecialRoots, // Array of boot image objects that must be kept live. }; + /* + * This describes the number and ordering of sections inside of Boot + * and App Images. It is very important that changes to this struct + * are reflected in the compiler and loader. + * + * See: + * - ImageWriter::ImageInfo::CreateImageSections() + * - ImageWriter::Write() + * - ImageWriter::AllocMemory() + */ enum ImageSections { kSectionObjects, kSectionArtFields, @@ -229,6 +239,7 @@ class PACKED(4) ImageHeader { kSectionDexCacheArrays, kSectionInternedStrings, kSectionClassTable, + kSectionStringReferenceOffsets, kSectionImageBitmap, kSectionImageRelocations, kSectionCount, // Number of elements in enum. @@ -291,6 +302,10 @@ class PACKED(4) ImageHeader { return GetImageSection(kSectionImageRelocations); } + const ImageSection& GetImageStringReferenceOffsetsSection() const { + return GetImageSection(kSectionStringReferenceOffsets); + } + template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier> ObjPtr<mirror::Object> GetImageRoot(ImageRoot image_root) const REQUIRES_SHARED(Locks::mutator_lock_); @@ -451,6 +466,39 @@ class PACKED(4) ImageHeader { friend class linker::ImageWriter; }; +/* + * Tags the last bit. Used by AppImage logic to differentiate between managed + * and native references. + */ +template<typename T> +T SetNativeRefTag(T val) { + static_assert(std::is_integral<T>::value, "Expected integral type."); + + return val | 1u; +} + +/* + * Retrieves the value of the last bit. Used by AppImage logic to + * differentiate between managed and native references. + */ +template<typename T> +bool HasNativeRefTag(T val) { + static_assert(std::is_integral<T>::value, "Expected integral type."); + + return (val & 1u) == 1u; +} + +/* + * Sets the last bit of the value to 0. Used by AppImage logic to + * differentiate between managed and native references. + */ +template<typename T> +T ClearNativeRefTag(T val) { + static_assert(std::is_integral<T>::value, "Expected integral type."); + + return val & ~1u; +} + 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::ImageSections& section); diff --git a/runtime/mirror/object-inl.h b/runtime/mirror/object-inl.h index 99a0d92f21..fbe002a9f0 100644 --- a/runtime/mirror/object-inl.h +++ b/runtime/mirror/object-inl.h @@ -588,6 +588,10 @@ inline bool Object::CasFieldStrongSequentiallyConsistent64(MemberOffset field_of return atomic_addr->CompareAndSetStrongSequentiallyConsistent(old_value, new_value); } +/* + * Returns a pointer to an object representing what the field points to, not an + * object representing the field. + */ template<class T, VerifyObjectFlags kVerifyFlags, ReadBarrierOption kReadBarrierOption, diff --git a/runtime/mirror/object_reference-inl.h b/runtime/mirror/object_reference-inl.h index 295b460a01..780d6621ae 100644 --- a/runtime/mirror/object_reference-inl.h +++ b/runtime/mirror/object_reference-inl.h @@ -24,17 +24,26 @@ namespace art { namespace mirror { -template<bool kPoisonReferences, class MirrorType> +template <bool kPoisonReferences, class MirrorType> +ALWAYS_INLINE void ObjectReference<kPoisonReferences, MirrorType>::Assign(ObjPtr<MirrorType> ptr) { Assign(ptr.Ptr()); } -template<class MirrorType> +template <class MirrorType> +ALWAYS_INLINE bool HeapReference<MirrorType>::CasWeakRelaxed(MirrorType* expected_ptr, MirrorType* new_ptr) { return reference_.CompareAndSetWeakRelaxed(Compression::Compress(expected_ptr), Compression::Compress(new_ptr)); } +template <typename MirrorType> +template <bool kIsVolatile> +ALWAYS_INLINE +void HeapReference<MirrorType>::Assign(ObjPtr<MirrorType> ptr) { + Assign<kIsVolatile>(ptr.Ptr()); +} + } // namespace mirror } // namespace art diff --git a/runtime/mirror/object_reference.h b/runtime/mirror/object_reference.h index 77154e2cda..d6a39aae5a 100644 --- a/runtime/mirror/object_reference.h +++ b/runtime/mirror/object_reference.h @@ -60,6 +60,15 @@ class MANAGED ObjectReference { using Compression = PtrCompression<kPoisonReferences, MirrorType>; public: + /* + * Returns a pointer to the mirror of the managed object this reference is for. + * + * This does NOT return the current object (which isn't derived from, and + * therefor cannot be a mirror::Object) as a mirror pointer. Instead, this + * returns a pointer to the mirror of the managed object this refers to. + * + * TODO (chriswailes): Rename to GetPtr(). + */ MirrorType* AsMirrorPtr() const { return Compression::Decompress(reference_); } |