Loading...
cache_builder/SectionCoalescer.cpp /dev/null dyld-1330
--- /dev/null
+++ dyld/dyld-1330/cache_builder/SectionCoalescer.cpp
@@ -0,0 +1,477 @@
+/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*-
+ *
+ * Copyright (c) 2014 Apple Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#include "SectionCoalescer.h"
+#include "SubCache.h"
+
+//
+// MARK: --- CoalescedSection methods ---
+//
+
+// TODO
+
+//
+// MARK: --- CoalescedStringsSection methods ---
+//
+
+// TODO
+
+//
+// MARK: --- CoalescedGOTSection methods ---
+//
+
+void CoalescedGOTSection::addClientDylibSection(OptimizedGOTSection* section)
+{
+    this->dylibSections.push_back(section);
+}
+
+std::pair<uint32_t, bool> CoalescedGOTSection::addOptimizedOffset(uint32_t pointerSize,
+                                                                  CoalescedGOTSection::GOTKey key)
+{
+    GOTMap& gotMap = key.isFunctionVariant ? fvTargetsToOffsets : gotTargetsToOffsets;
+    uint32_t cacheSectionOffset = (uint32_t)(gotMap.size() * pointerSize);
+    auto itAndInserted = gotMap.insert({ key, cacheSectionOffset });
+    if ( itAndInserted.second ) {
+        // We inserted the element, so its offset is already valid.  Nothing else to do
+        return { cacheSectionOffset, false };
+    } else {
+        // Debugging only.  If we didn't include the GOT then we saved that many bytes
+        this->savedSpace += pointerSize;
+        cacheSectionOffset = itAndInserted.first->second;
+
+        return { cacheSectionOffset, false };
+    }
+}
+
+void CoalescedGOTSection::addFunctionVariantInfo(CoalescedGOTSection::GOTKey key,
+                                                 CoalescedGOTSection::FunctionVariantInfo info)
+{
+    // store function-variant index in other map
+    this->functionVariantIndexes[key] = info;
+}
+
+uint64_t CoalescedGOTSection::numSourceGOTs() const
+{
+    uint64_t totalSourceGOTs = 0;
+    for ( const OptimizedSection* section : dylibSections ) {
+        totalSourceGOTs += section->numOptimizedEntries();
+    }
+    return totalSourceGOTs;
+}
+
+uint64_t CoalescedGOTSection::numCacheGOTs() const
+{
+    return this->gotTargetsToOffsets.size() + fvTargetsToOffsets.size();
+}
+
+bool CoalescedGOTSection::empty() const
+{
+    return this->gotTargetsToOffsets.empty() && fvTargetsToOffsets.empty();
+}
+
+uint64_t CoalescedGOTSection::gotVMSize(uint32_t pointerSize) const
+{
+    return this->gotTargetsToOffsets.size() * pointerSize;
+}
+
+uint64_t CoalescedGOTSection::fvVMSize(uint32_t pointerSize) const
+{
+    return this->fvTargetsToOffsets.size() * pointerSize;
+}
+
+void CoalescedGOTSection::sort(uint32_t pointerSize, std::string_view sectionName,
+                               std::span<OptimizedGOTSection*> dylibSections,
+                               bool functionVariants,
+                               CoalescedGOTSection::GOTMap& gotMap)
+{
+    // Sort the coalesced GOTs based on the target install name.  We find GOTs in the order we parse
+    // the fixups in the dylibs, but we want the final cache to keep all GOTs for the same target near
+    // each other
+    typedef CoalescedGOTSection::GOTKey Key;
+    std::vector<Key> sortedKeys;
+    sortedKeys.reserve(gotMap.size());
+    for ( const auto& keyAndValue : gotMap )
+        sortedKeys.push_back(keyAndValue.first);
+
+    std::sort(sortedKeys.begin(), sortedKeys.end(), [](const Key& a, const Key& b) {
+        // sort all function-variants together at end
+        if ( a.isFunctionVariant != b.isFunctionVariant )
+            return b.isFunctionVariant;
+        // sort first by impl dylib name
+        if ( a.targetDylibName != b.targetDylibName )
+            return (a.targetDylibName < b.targetDylibName);
+        // if install names are the same, sort by symbol name
+        return a.targetSymbolName < b.targetSymbolName;
+    });
+
+    // Rewrite entries from their original offset to the new offset
+    std::unordered_map<uint32_t, uint32_t> oldToNewOffsetMap;
+    for ( uint32_t i = 0; i != sortedKeys.size(); ++i ) {
+        const Key& key = sortedKeys[i];
+        auto it = gotMap.find(key);
+        assert(it != gotMap.end());
+
+        uint32_t newCacheSectionOffset = i * pointerSize;
+
+        // Record the offset mapping for updating the dylibs
+        oldToNewOffsetMap[it->second] = newCacheSectionOffset;
+
+        const bool log = false;
+        if ( log ) {
+            printf("%s[%d]: %s\n", sectionName.data(), newCacheSectionOffset, key.targetSymbolName.data());
+        }
+
+        it->second = newCacheSectionOffset;
+    }
+
+    // Also rewrite entries in each dylib
+    for ( OptimizedSection* section : dylibSections )
+        section->reassignOffsets(oldToNewOffsetMap, functionVariants);
+}
+
+void CoalescedGOTSection::finalize(uint32_t pointerSize, std::string_view sectionName,
+                                   const cache_builder::BuilderConfig& config,
+                                   cache_builder::SubCache& subCache, cache_builder::Region& region)
+{
+    CoalescedGOTSection::sort(pointerSize, sectionName, this->dylibSections, false, this->gotTargetsToOffsets);
+    CoalescedGOTSection::sort(pointerSize, sectionName, this->dylibSections, true, this->fvTargetsToOffsets);
+
+    if ( !this->gotTargetsToOffsets.empty() ) {
+        this->gotChunk = std::make_unique<cache_builder::UniquedGOTsChunk>();
+        this->gotChunk->cacheVMSize       = CacheVMSize(this->gotVMSize(pointerSize));
+        this->gotChunk->subCacheFileSize  = CacheFileSize(this->gotVMSize(pointerSize));
+
+        region.chunks.push_back(this->gotChunk.get());
+    }
+
+    if ( !this->fvTargetsToOffsets.empty() ) {
+        this->fvChunk = std::make_unique<cache_builder::UniquedGOTsChunk>();
+        this->fvChunk->cacheVMSize       = CacheVMSize(this->fvVMSize(pointerSize));
+        this->fvChunk->subCacheFileSize  = CacheFileSize(this->fvVMSize(pointerSize));
+
+        // The function variants go in TPRO
+        subCache.addTPROConstChunk(config, this->fvChunk.get());
+    }
+}
+
+void CoalescedGOTSection::forEachFunctionVariant(void (^callback)(const CoalescedGOTSection::FunctionVariantInfo& tv, uint64_t gotVMAddr,
+                                                                  dyld3::MachOFile::PointerMetaData pmd)) const
+{
+    for (const auto& fv : this->functionVariantIndexes) {
+        uint32_t offsetInGOTSection = this->fvTargetsToOffsets.at(fv.first);
+        uint64_t cacheVMAddr = this->fvChunk->cacheVMAddress.rawValue() + offsetInGOTSection;
+        callback(fv.second, cacheVMAddr, fv.first.pmd);
+    }
+}
+
+void* CoalescedGOTSection::gotLocation(CacheVMAddress gotVMAddr)
+{
+    if ( gotChunk ) {
+        if ( (gotVMAddr >= gotChunk->cacheVMAddress) && (gotVMAddr < (gotChunk->cacheVMAddress + gotChunk->cacheVMSize))) {
+            VMOffset cacheSectionVMOffset = gotVMAddr - gotChunk->cacheVMAddress;
+            return gotChunk->subCacheBuffer + cacheSectionVMOffset.rawValue();
+        }
+    }
+
+#if 0
+    // Enable this if we ever need to write out the function variant GOTs
+    if ( fvChunk ) {
+        if ( (gotVMAddr >= fvChunk->cacheVMAddress) && (gotVMAddr < (fvChunk->cacheVMAddress + fvChunk->cacheVMSize))) {
+            VMOffset cacheSectionVMOffset = gotVMAddr - fvChunk->cacheVMAddress;
+            return fvChunk->subCacheBuffer + cacheSectionVMOffset.rawValue();
+        }
+    }
+#endif
+
+    assert(0 && "unreachable");
+}
+
+bool CoalescedGOTSection::shouldEmitGOT(CacheVMAddress gotVMAddr) const
+{
+    if ( gotChunk ) {
+        if ( (gotVMAddr >= gotChunk->cacheVMAddress) && (gotVMAddr < (gotChunk->cacheVMAddress + gotChunk->cacheVMSize))) {
+            return true;
+        }
+    }
+
+    if ( fvChunk ) {
+        if ( (gotVMAddr >= fvChunk->cacheVMAddress) && (gotVMAddr < (fvChunk->cacheVMAddress + fvChunk->cacheVMSize))) {
+            return false;
+        }
+    }
+
+    assert(0 && "unreachable");
+}
+
+void CoalescedGOTSection::trackFixup(void* loc)
+{
+    uint64_t rawLoc = (uint64_t)loc;
+    if ( gotChunk ) {
+        if ( (rawLoc >= (uint64_t)gotChunk->subCacheBuffer) && (rawLoc < ((uint64_t)gotChunk->subCacheBuffer + gotChunk->cacheVMSize.rawValue()))) {
+            gotChunk->tracker.add(loc);
+            return;
+        }
+    }
+
+#if 0
+    // Enable this if we ever need to write out the function variant GOTs
+    if ( fvChunk ) {
+        if ( (rawLoc >= (uint64_t)fvChunk->subCacheBuffer) && (rawLoc < ((uint64_t)fvChunk->subCacheBuffer + fvChunk->cacheVMSize.rawValue()))) {
+            fvChunk->tracker.add(loc);
+            return;
+        }
+    }
+#endif
+
+    assert(0 && "unreachable");
+}
+
+//
+// MARK: --- DylibSectionCoalescer methods ---
+//
+
+// Returns true if the section was removed from the source dylib after being optimized
+bool DylibSectionCoalescer::sectionWasRemoved(std::string_view segmentName,
+                                              std::string_view sectionName) const
+{
+    if ( const OptimizedSection* section = this->getSection(segmentName, sectionName) ) {
+        return section->sectionWasRemoved();
+    }
+
+    return false;
+}
+
+// Returns true if the section was optimized.  It may or may not have been removed too, see sectionWasRemoved().
+bool DylibSectionCoalescer::sectionWasOptimized(std::string_view segmentName,
+                                                std::string_view sectionName) const
+{
+    if ( const OptimizedSection* section = this->getSection(segmentName, sectionName) )
+        return section->sectionWasOptimized();
+
+    return false;
+}
+
+OptimizedSection* DylibSectionCoalescer::getSection(std::string_view segmentName,
+                                                    std::string_view sectionName)
+{
+    if (segmentName.size() > 16)
+        segmentName = segmentName.substr(0, 16);
+    if (sectionName.size() > 16)
+        sectionName = sectionName.substr(0, 16);
+
+    if ( segmentName == "__TEXT" ) {
+        if ( sectionName == "__objc_classname" )
+            return &this->objcClassNames;
+        if ( sectionName == "__objc_methname" )
+            return &this->objcMethNames;
+        if ( sectionName == "__objc_methtype" )
+            return &this->objcMethTypes;
+    } else if ( segmentName == "__DATA_CONST" ) {
+        if ( sectionName == "__got" )
+            return &this->gots;
+    } else if ( segmentName == "__AUTH_CONST" ) {
+        if ( sectionName == "__auth_got" )
+            return &this->auth_gots;
+        if ( sectionName == "__auth_ptr" )
+            return &this->auth_ptrs;
+    }
+
+    return nullptr;
+}
+
+const OptimizedSection* DylibSectionCoalescer::getSection(std::string_view segmentName,
+                                                          std::string_view sectionName) const
+{
+    return ((DylibSectionCoalescer*)this)->getSection(segmentName, sectionName);
+}
+
+void DylibSectionCoalescer::forEachCacheGOTChunk(void (^callback)(const cache_builder::Chunk* cacheGOTChunk)) const
+{
+    gots.forEachCacheGOTChunk(callback);
+    auth_gots.forEachCacheGOTChunk(callback);
+    auth_ptrs.forEachCacheGOTChunk(callback);
+}
+
+//
+// MARK: --- OptimizedSection methods ---
+//
+
+bool OptimizedSection::sectionWasRemoved() const
+{
+    // Some sections, eg, GOTs, are optimized but not removed
+    if ( !sectionWillBeRemoved )
+        return false;
+
+    return !offsetMap.empty();
+}
+
+bool OptimizedSection::sectionWasOptimized() const
+{
+    return !offsetMap.empty();
+}
+
+void OptimizedSection::addUnoptimizedOffset(uint32_t sourceSectionOffset)
+{
+    this->unoptimizedOffsets.insert(sourceSectionOffset);
+}
+
+void OptimizedSection::setSourceSectionInfo(const mach_o::Header::SectionInfo& info)
+{
+    assert(!this->sourceSectionInfo.has_value());
+    this->sourceSectionInfo = info;
+}
+
+void OptimizedSection::reassignOffsets(const std::unordered_map<uint32_t, uint32_t>& oldToNewOffsetMap,
+                                       bool functionVariants)
+{
+    for ( auto& keyAndCacheOffset : offsetMap ) {
+        if ( keyAndCacheOffset.second.isFunctionVariant != functionVariants )
+            continue;
+        auto it = oldToNewOffsetMap.find(keyAndCacheOffset.second.cacheSectionOffset);
+        assert(it != oldToNewOffsetMap.end());
+        keyAndCacheOffset.second.cacheSectionOffset = it->second;
+    }
+}
+
+uint64_t OptimizedSection::numOptimizedEntries() const
+{
+    return this->offsetMap.size();
+}
+
+//
+// MARK: --- OptimizedStringsSection methods ---
+//
+
+void OptimizedStringSection::setSubCacheSection(CoalescedStringsSection* section)
+{
+    assert(this->subCacheSection == nullptr);
+    this->subCacheSection = section;
+}
+
+std::optional<uint64_t> OptimizedStringSection::cacheVMAddress(uint32_t originalDylibSectionOffset) const
+{
+    auto offsetIt = offsetMap.find(originalDylibSectionOffset);
+    if ( sectionWillBeRemoved ) {
+        // If the section was removed then we have to find an entry for every atom in there
+        assert(offsetIt != offsetMap.end());
+    } else {
+        // Not all GOTs are optimized, but we should find the element somewhere
+        assert((offsetIt != offsetMap.end()) || unoptimizedOffsets.count(originalDylibSectionOffset));
+    }
+
+    if ( offsetIt == offsetMap.end() ) {
+        // To was not fully optimized/coalesced so we have no element
+        return std::nullopt;
+    } else {
+        assert(!offsetIt->second.isFunctionVariant);
+        uint64_t baseVMAddr = subCacheSection->cacheChunk->cacheVMAddress.rawValue();
+        return baseVMAddr + offsetIt->second.cacheSectionOffset;
+    }
+}
+
+//
+// MARK: --- OptimizedGOTSection methods ---
+//
+
+void OptimizedGOTSection::setSubCacheSection(CoalescedGOTSection* section)
+{
+    assert(this->subCacheSection == nullptr);
+    this->subCacheSection = section;
+
+    this->subCacheSection->addClientDylibSection(this);
+}
+
+std::optional<uint64_t> OptimizedGOTSection::cacheVMAddress(uint32_t originalDylibSectionOffset) const
+{
+    auto offsetIt = offsetMap.find(originalDylibSectionOffset);
+    if ( sectionWillBeRemoved ) {
+        // If the section was removed then we have to find an entry for every atom in there
+        assert(offsetIt != offsetMap.end());
+    } else {
+        // Not all GOTs are optimized, but we should find the element somewhere
+        assert((offsetIt != offsetMap.end()) || unoptimizedOffsets.count(originalDylibSectionOffset));
+    }
+
+    if ( offsetIt == offsetMap.end() ) {
+        // To was not fully optimized/coalesced so we have no element
+        return std::nullopt;
+    } else {
+        uint64_t baseVMAddr = 0;
+        if ( offsetIt->second.isFunctionVariant )
+            baseVMAddr = subCacheSection->fvChunk->cacheVMAddress.rawValue();
+        else
+            baseVMAddr = subCacheSection->gotChunk->cacheVMAddress.rawValue();
+        return baseVMAddr + offsetIt->second.cacheSectionOffset;
+    }
+}
+
+bool OptimizedGOTSection::addOptimizedOffset(uint32_t sourceSectionOffset, uint32_t pointerSize,
+                                             CoalescedGOTSection::GOTKey key)
+{
+    auto cacheSectionOffsetAndAdded = subCacheSection->addOptimizedOffset(pointerSize, key);
+
+    // Now keep track of this offset in our source dylib as pointing to this offset
+    offsetMap[sourceSectionOffset] = { cacheSectionOffsetAndAdded.first, key.isFunctionVariant };
+
+    return cacheSectionOffsetAndAdded.second;
+}
+
+void OptimizedGOTSection::addFunctionVariantInfo(CoalescedGOTSection::GOTKey key,
+                                                 CoalescedGOTSection::FunctionVariantInfo info)
+{
+    // store function-variant index in other map
+    subCacheSection->addFunctionVariantInfo(key, info);
+}
+
+OptimizedGOTSection::CoalescedGOTsMap OptimizedGOTSection::getCoalescedGOTsMap() const
+{
+    if ( this->offsetMap.empty() )
+        return { };
+
+    assert(this->sourceSectionInfo.has_value());
+    InputDylibVMAddress dylibGOTBaseVMAddr(this->sourceSectionInfo->address);
+
+    OptimizedGOTSection::CoalescedGOTsMap coalescedGOTs;
+    for ( const auto& dylibOffsetAndCacheOffset : this->offsetMap ) {
+        VMOffset dylibSectionOffset((uint64_t)dylibOffsetAndCacheOffset.first);
+        VMOffset cacheSectionOffset((uint64_t)dylibOffsetAndCacheOffset.second.cacheSectionOffset);
+        if ( dylibOffsetAndCacheOffset.second.isFunctionVariant )
+            coalescedGOTs[dylibGOTBaseVMAddr + dylibSectionOffset] = { this->subCacheSection->fvChunk.get(), cacheSectionOffset };
+        else
+            coalescedGOTs[dylibGOTBaseVMAddr + dylibSectionOffset] = { this->subCacheSection->gotChunk.get(), cacheSectionOffset };
+    }
+    return coalescedGOTs;
+}
+
+void OptimizedGOTSection::forEachCacheGOTChunk(void (^callback)(const cache_builder::Chunk* cacheGOTChunk)) const
+{
+    if ( this->subCacheSection == nullptr )
+        return;
+
+    if ( this->subCacheSection->gotChunk != nullptr )
+        callback(this->subCacheSection->gotChunk.get());
+
+    if ( this->subCacheSection->fvChunk != nullptr )
+        callback(this->subCacheSection->fvChunk.get());
+}