Loading...
cache_builder/SubCache.cpp dyld-1066.8 dyld-1330
--- dyld/dyld-1066.8/cache_builder/SubCache.cpp
+++ dyld/dyld-1330/cache_builder/SubCache.cpp
@@ -25,8 +25,10 @@
 #include "BuilderConfig.h"
 #include "BuilderOptions.h"
 #include "CacheDylib.h"
+#include "Chunk.h"
 #include "CodeSigningTypes.h"
 #include "DyldSharedCache.h"
+#include "Header.h"
 #include "SubCache.h"
 #include "JSONWriter.h"
 
@@ -36,8 +38,10 @@
 #include <CommonCrypto/CommonDigest.h>
 #include <CommonCrypto/CommonDigestSPI.h>
 
-using dyld3::GradedArchs;
 using dyld3::MachOFile;
+
+using mach_o::Header;
+using mach_o::Platform;
 
 using error::Error;
 
@@ -54,24 +58,59 @@
 // MARK: --- SharedCacheBuilder::Region methods ---
 
 uint32_t Region::initProt() const
+{
+    uint32_t initProt = 0;
+    switch ( this->kind ) {
+        case Region::Kind::text:
+            initProt = VM_PROT_READ | VM_PROT_EXECUTE;
+            break;
+        case Region::Kind::data:
+        case Region::Kind::auth:
+            initProt = VM_PROT_READ | VM_PROT_WRITE;
+            break;
+        case Region::Kind::dataConst:
+        case Region::Kind::authConst:
+        case Region::Kind::tproConst:
+        case Region::Kind::tproAuthConst:
+            initProt = VM_PROT_READ;
+            break;
+        case Region::Kind::readOnly:
+        case Region::Kind::linkedit:
+            initProt = VM_PROT_READ;
+            break;
+        case Region::Kind::dynamicConfig:
+            // HACK: This is not actually mapped, but it is used in vm calculations
+            initProt = VM_PROT_READ;
+            break;
+        case Region::Kind::unmapped:
+        case Region::Kind::codeSignature:
+            // This isn't mapped, so we should never ask for its initProt
+            assert(0);
+            break;
+        case Region::Kind::numKinds:
+            assert(0);
+            break;
+    }
+
+    return initProt;
+}
+
+uint32_t Region::maxProt() const
 {
     uint32_t maxProt = 0;
     switch ( this->kind ) {
         case Region::Kind::text:
             maxProt = VM_PROT_READ | VM_PROT_EXECUTE;
             break;
+        case Region::Kind::tproConst:
         case Region::Kind::data:
+        case Region::Kind::dataConst:
+        case Region::Kind::tproAuthConst:
+        case Region::Kind::auth:
+        case Region::Kind::authConst:
             maxProt = VM_PROT_READ | VM_PROT_WRITE;
             break;
-        case Region::Kind::dataConst:
-            maxProt = VM_PROT_READ;
-            break;
-        case Region::Kind::auth:
-            maxProt = VM_PROT_READ | VM_PROT_WRITE;
-            break;
-        case Region::Kind::authConst:
-            maxProt = VM_PROT_READ;
-            break;
+        case Region::Kind::readOnly:
         case Region::Kind::linkedit:
             maxProt = VM_PROT_READ;
             break;
@@ -92,45 +131,6 @@
     return maxProt;
 }
 
-uint32_t Region::maxProt() const
-{
-    uint32_t maxProt = 0;
-    switch ( this->kind ) {
-        case Region::Kind::text:
-            maxProt = VM_PROT_READ | VM_PROT_EXECUTE;
-            break;
-        case Region::Kind::data:
-            maxProt = VM_PROT_READ | VM_PROT_WRITE;
-            break;
-        case Region::Kind::dataConst:
-            maxProt = VM_PROT_READ | VM_PROT_WRITE;
-            break;
-        case Region::Kind::auth:
-            maxProt = VM_PROT_READ | VM_PROT_WRITE;
-            break;
-        case Region::Kind::authConst:
-            maxProt = VM_PROT_READ | VM_PROT_WRITE;
-            break;
-        case Region::Kind::linkedit:
-            maxProt = VM_PROT_READ;
-            break;
-        case Region::Kind::dynamicConfig:
-            // HACK: This is not actually mapped, but it is used in vm calculations
-            maxProt = VM_PROT_READ;
-            break;
-        case Region::Kind::unmapped:
-        case Region::Kind::codeSignature:
-            // This isn't mapped, so we should never ask for its maxprot
-            assert(0);
-            break;
-        case Region::Kind::numKinds:
-            assert(0);
-            break;
-    }
-
-    return maxProt;
-}
-
 bool Region::canContainAuthPointers() const
 {
     bool result = false;
@@ -139,14 +139,19 @@
             // We should never ask this region if it has auth content
             assert(0);
             break;
+        case Region::Kind::tproConst:
+        case Region::Kind::tproAuthConst:
         case Region::Kind::data:
         case Region::Kind::dataConst:
+            // Note TPRO never contains authenticated fixups, so its name is wrong
+            // but its really there to be placed next to the AUTH regions
             result = false;
             break;
         case Region::Kind::auth:
         case Region::Kind::authConst:
             result = true;
             break;
+        case Region::Kind::readOnly:
         case Region::Kind::linkedit:
         case Region::Kind::dynamicConfig:
             // We should never ask this region if it has auth content
@@ -170,10 +175,13 @@
 {
     switch ( this->kind ) {
         case Region::Kind::text:
+        case Region::Kind::tproConst:
         case Region::Kind::data:
         case Region::Kind::dataConst:
+        case Region::Kind::tproAuthConst:
         case Region::Kind::auth:
         case Region::Kind::authConst:
+        case Region::Kind::readOnly:
         case Region::Kind::linkedit:
             return true;
         case Region::Kind::unmapped:
@@ -190,10 +198,13 @@
 {
     switch ( this->kind ) {
         case Region::Kind::text:
+        case Region::Kind::tproConst:
         case Region::Kind::data:
         case Region::Kind::dataConst:
+        case Region::Kind::tproAuthConst:
         case Region::Kind::auth:
         case Region::Kind::authConst:
+        case Region::Kind::readOnly:
         case Region::Kind::linkedit:
         case Region::Kind::dynamicConfig:
             return true;
@@ -221,10 +232,16 @@
             bool nextIsRW = (next.initProt() & VM_PROT_WRITE) != 0;
             return nextIsRW;
         }
+        case Region::Kind::tproConst:
+        case Region::Kind::tproAuthConst:
         case Region::Kind::data:
         case Region::Kind::auth: {
             // HACK: Remove once we have rdar://96315050
             if ( (this->kind == Region::Kind::auth) && (next.kind == Region::Kind::authConst) )
+                return false;
+
+            // Don't add adding from data to tproConst as we didn't have padding from data to auth before
+            if ( (next.kind == Kind::tproConst) || (next.kind == Kind::tproAuthConst) )
                 return false;
 
             // Add padding if DATA is adjacent to something immutable, eg TEXT/DATA_CONST
@@ -233,12 +250,17 @@
         }
         case Region::Kind::dataConst:
         case Region::Kind::authConst: {
+            // Always add padding from DATA_CONST to TPRO_CONST as TPRO_CONST will actually be dirtied
+            if ( (next.kind == Kind::tproConst) || (next.kind == Kind::tproAuthConst) )
+                return true;
+
             // Don't add padding if *_CONST is next to *_CONST, otherwise add padding
             bool nextInitIsRO = (next.initProt() & VM_PROT_WRITE) == 0;
             bool nextMaxIsRW = (next.maxProt() & VM_PROT_WRITE) != 0;
             bool nextIsDataConst = nextInitIsRO & nextMaxIsRW;
             return !nextIsDataConst;
         }
+        case Region::Kind::readOnly:
         case Region::Kind::linkedit:
         case Region::Kind::dynamicConfig: {
             // Add padding if LINKEDIT is adjacent to something that is mutable,
@@ -321,10 +343,40 @@
         switch ( region.kind ) {
             case cache_builder::Region::Kind::text:
                 break;
+            case cache_builder::Region::Kind::tproConst:
+            case cache_builder::Region::Kind::tproAuthConst:
             case cache_builder::Region::Kind::dataConst:
             case cache_builder::Region::Kind::data:
             case cache_builder::Region::Kind::auth:
             case cache_builder::Region::Kind::authConst:
+                return true;
+            case Region::Kind::readOnly:
+            case cache_builder::Region::Kind::linkedit:
+            case cache_builder::Region::Kind::unmapped:
+            case cache_builder::Region::Kind::dynamicConfig:
+            case cache_builder::Region::Kind::codeSignature:
+            case cache_builder::Region::Kind::numKinds:
+                break;
+        }
+    }
+    return false;
+}
+
+static bool hasReadOnlyRegion(std::span<Region> regions)
+{
+    for ( const Region& region : regions ) {
+        if ( region.chunks.empty() )
+            continue;
+        switch ( region.kind ) {
+            case cache_builder::Region::Kind::text:
+            case cache_builder::Region::Kind::tproConst:
+            case cache_builder::Region::Kind::tproAuthConst:
+            case cache_builder::Region::Kind::dataConst:
+            case cache_builder::Region::Kind::data:
+            case cache_builder::Region::Kind::auth:
+            case cache_builder::Region::Kind::authConst:
+                break;
+            case Region::Kind::readOnly:
                 return true;
             case cache_builder::Region::Kind::linkedit:
             case cache_builder::Region::Kind::unmapped:
@@ -344,10 +396,13 @@
             continue;
         switch ( region.kind ) {
             case cache_builder::Region::Kind::text:
+            case cache_builder::Region::Kind::tproConst:
+            case cache_builder::Region::Kind::tproAuthConst:
             case cache_builder::Region::Kind::dataConst:
             case cache_builder::Region::Kind::data:
             case cache_builder::Region::Kind::auth:
             case cache_builder::Region::Kind::authConst:
+            case Region::Kind::readOnly:
                 break;
             case cache_builder::Region::Kind::linkedit:
                 return true;
@@ -361,43 +416,46 @@
     return false;
 }
 
-void SubCache::setSuffix(dyld3::Platform platform, bool forceDevelopmentSubCacheSuffix,
-                         size_t subCacheIndex)
+void SubCache::setSuffix(Platform platform, bool forceDevelopmentSubCacheSuffix, size_t subCacheIndex)
 {
     assert(this->isSubCache() || this->isStubsCache());
     assert(subCacheIndex > 0);
 
     const char* dataSuffix = forceDevelopmentSubCacheSuffix ? ".development.dylddata" : ".dylddata";
     const char* linkeditSuffix = forceDevelopmentSubCacheSuffix ? ".development.dyldlinkedit" : ".dyldlinkedit";
+    const char* readonlySuffix = forceDevelopmentSubCacheSuffix ? ".development.dyldreadonly" : ".dyldreadonly";
     const char* subCacheSuffix = forceDevelopmentSubCacheSuffix ? ".development" : "";
 
-    if ( platform == dyld3::Platform::macOS ) {
-        // macOS never has a .development suffix
-        this->fileSuffix = "." + dyld3::json::decimal(subCacheIndex);
-    } else if ( platform == dyld3::Platform::driverKit ) {
+    if ( (platform == Platform::macOS) || platform.isSimulator() ) {
+        // macOS/sims never has a .development suffix
+        this->fileSuffix = "." + json::decimal(subCacheIndex);
+    } else if ( platform == Platform::driverKit ) {
         // driverKit never has a .development suffix
-        this->fileSuffix = "." + dyld3::json::decimal(subCacheIndex);
+        this->fileSuffix = "." + json::decimal(subCacheIndex);
     } else if ( this->isStubsDevelopmentCache() ) {
         // Dev stubs always have a suffix
-        this->fileSuffix = "." + dyld3::json::decimal(subCacheIndex) + ".development";
+        this->fileSuffix = "." + json::decimal(subCacheIndex) + ".development";
     } else if ( this->isStubsCustomerCache() ) {
         // Customer stubs never have a suffix
-        this->fileSuffix = "." + dyld3::json::decimal(subCacheIndex);
+        this->fileSuffix = "." + json::decimal(subCacheIndex);
     } else if ( hasDataRegion(this->regions) ) {
         // Data only subcaches have their own suffix
-        this->fileSuffix = "." + dyld3::json::decimal(subCacheIndex) + dataSuffix;
+        this->fileSuffix = "." + json::decimal(subCacheIndex) + dataSuffix;
+    } else if ( hasReadOnlyRegion(this->regions) ) {
+        // read-only only subcaches have their own suffix
+        this->fileSuffix = "." + json::decimal(subCacheIndex) + readonlySuffix;
     } else if ( hasLinkeditRegion(this->regions) ) {
         // Linkedit only subcaches have their own suffix
-        this->fileSuffix = "." + dyld3::json::decimal(subCacheIndex) + linkeditSuffix;
+        this->fileSuffix = "." + json::decimal(subCacheIndex) + linkeditSuffix;
     } else {
-        this->fileSuffix = "." + dyld3::json::decimal(subCacheIndex) + subCacheSuffix;
+        this->fileSuffix = "." + json::decimal(subCacheIndex) + subCacheSuffix;
     }
 }
 
 static std::string getCodeSigningIdentifier(const BuilderOptions& options)
 {
     std::string cacheIdentifier = "com.apple.dyld.cache.";
-    cacheIdentifier += options.archs.name();
+    cacheIdentifier += options.arch.name();
     if ( options.dylibsRemovedFromDisk ) {
         switch ( options.kind ) {
             case CacheKind::development:
@@ -685,6 +743,14 @@
     // Note: cdHash is defined as first 20 bytes of hash
     memcpy(this->cdHash, fullCdHash, 20);
 
+    if ( layout.agile ) {
+        // hash of entire code directory (cdHash) uses same hash as each page
+        uint8_t altfullCdHash[CS_HASH_SIZE_SHA256];
+        CCDigest(kCCDigestSHA256, (const uint8_t*)cd256, layout.cd256Size, altfullCdHash);
+        // Note: cdHash is defined as first 20 bytes of hash
+        memcpy(this->agilecdHash, altfullCdHash, 20);
+    }
+
     // Set the UUID string in the subcache
     uuid_unparse_upper(dyldCacheHeader->uuid, this->uuidString);
 }
@@ -700,6 +766,20 @@
     this->regions[(uint32_t)Region::Kind::text].chunks.push_back(chunk);
 }
 
+void SubCache::addTPROConstChunk(const BuilderConfig& config, Chunk* chunk)
+{
+    // Rosetta can't handle an extra shared cache mapping, so use DATA instead
+    // We use DATA instead of DATA_CONST because we don't want the MemoryManager
+    // and DataConstWriter to both mprotect the same pages.  But the DataConstWriter
+    // will ignore everything in DATA while the MemoryManager will find TPRO there
+    if ( config.layout.discontiguous.has_value() )
+        this->addDataChunk(chunk);
+    else if ( config.layout.hasAuthRegion )
+        this->regions[(uint32_t)Region::Kind::tproAuthConst].chunks.push_back(chunk);
+    else
+        this->regions[(uint32_t)Region::Kind::tproConst].chunks.push_back(chunk);
+}
+
 void SubCache::addDataChunk(Chunk* chunk)
 {
     this->regions[(uint32_t)Region::Kind::data].chunks.push_back(chunk);
@@ -720,6 +800,15 @@
     this->regions[(uint32_t)Region::Kind::authConst].chunks.push_back(chunk);
 }
 
+void SubCache::addReadOnlyChunk(const BuilderConfig& config, Chunk* chunk)
+{
+    // Rosetta can't handle an extra shared cache mapping, so use __TEXT instead
+    if ( config.layout.discontiguous.has_value() )
+        this->addTextChunk(chunk);
+    else
+        this->regions[(uint32_t)Region::Kind::readOnly].chunks.push_back(chunk);
+}
+
 void SubCache::addLinkeditChunk(Chunk* chunk)
 {
     this->regions[(uint32_t)Region::Kind::linkedit].chunks.push_back(chunk);
@@ -733,28 +822,11 @@
 void SubCache::addCodeSignatureChunk(Chunk* chunk)
 {
     this->regions[(uint32_t)Region::Kind::codeSignature].chunks.push_back(chunk);
-}
-
-// HACK: We need to insert the libobjc __TEXT first so that its before all the other OBJC_RO chunks
-void SubCache::addObjCTextChunk(Chunk* chunk)
-{
-    std::vector<Chunk*>& chunks = this->regions[(uint32_t)Region::Kind::text].chunks;
-    chunks.insert(chunks.begin(), chunk);
-}
-
-// ObjC optimizations need to add read-only chunks.  For now these are added to the start
-// of TEXT, so that they are in the same subCache when split by One Cache.  In future we want to
-// move these to LINKEDIT
-void SubCache::addObjCReadOnlyChunk(Chunk* chunk)
-{
-    std::vector<Chunk*>& chunks = this->regions[(uint32_t)Region::Kind::text].chunks;
-    chunks.insert(chunks.begin(), chunk);
 }
 
 // All objc optimizations need to be contiguous, but that means if any need AUTH then all do
 void SubCache::addObjCReadWriteChunk(const BuilderConfig& config, Chunk* chunk)
 {
-    // Add canonical objc protocols
     if ( config.layout.hasAuthRegion ) {
         addAuthChunk(chunk);
     }
@@ -763,18 +835,17 @@
     }
 }
 
-void SubCache::addDylib(CacheDylib& cacheDylib, bool addLinkedit)
+void SubCache::addDylib(const BuilderConfig& config, CacheDylib& cacheDylib)
 {
     for ( DylibSegmentChunk& segmentInfo : cacheDylib.segments ) {
         switch ( segmentInfo.kind ) {
             case Chunk::Kind::dylibText:
-                if ( cacheDylib.installName == "/usr/lib/libobjc.A.dylib" )
-                    this->addObjCTextChunk(&segmentInfo);
-                else
-                    this->addTextChunk(&segmentInfo);
+                this->addTextChunk(&segmentInfo);
+                break;
+            case Chunk::Kind::tproDataConst:
+                this->addTPROConstChunk(config, &segmentInfo);
                 break;
             case Chunk::Kind::dylibData:
-            case Chunk::Kind::dylibDataConstWorkaround:
                 this->addDataChunk(&segmentInfo);
                 break;
             case Chunk::Kind::dylibDataConst:
@@ -782,21 +853,19 @@
                 break;
             case Chunk::Kind::dylibDataDirty:
                 // On arm64e, dataDirty goes in to auth
-                if ( cacheDylib.inputMF->isArch("arm64e") )
+                if ( cacheDylib.inputHdr->isArch("arm64e") )
                     this->addAuthChunk(&segmentInfo);
                 else
                     this->addDataChunk(&segmentInfo);
                 break;
             case Chunk::Kind::dylibAuth:
-            case Chunk::Kind::dylibAuthConstWorkaround:
                 this->addAuthChunk(&segmentInfo);
                 break;
             case Chunk::Kind::dylibAuthConst:
                 this->addAuthConstChunk(&segmentInfo);
                 break;
             case Chunk::Kind::dylibReadOnly:
-                // FIXME: Read-only data should really be in a read-only mapping.
-                this->addTextChunk(&segmentInfo);
+                this->addReadOnlyChunk(config, &segmentInfo);
                 break;
             case Chunk::Kind::dylibLinkedit:
                 // Skip adding here.  We'll do this in addLinkeditFromDylib()
@@ -806,9 +875,6 @@
                 break;
         }
     }
-
-    if ( addLinkedit )
-        this->addLinkeditFromDylib(cacheDylib);
 }
 
 // Linkedit is stored in Chunks in its own array on the dylib.  This adds it to the subCache.
@@ -822,19 +888,102 @@
         this->addLinkeditChunk(&chunk);
 }
 
-void SubCache::addCacheHeaderChunk(const std::span<CacheDylib> cacheDylibs)
+void SubCache::forEachTPRORegionInData(SubCache* mainSubCache, std::span<SubCache*> subCaches,
+                                       void (^callback)(Region& region, const Chunk* firstChunk, const Chunk* lastChunk))
+{
+    // Find all subcaches with TPRO regions, or on x86_64 its those with DATA in them
+    auto runOnSubCache = ^(SubCache* subCache) {
+        for ( Region& region : subCache->regions ) {
+            if ( region.kind != Region::Kind::data )
+                continue;
+
+            const Chunk* firstTPROChunk = nullptr;
+            const Chunk* lastTPROChunk = nullptr;
+            for ( const Chunk* chunk : region.chunks ) {
+                if ( chunk->isTPROChunk() ) {
+                    if ( !firstTPROChunk ) {
+                        firstTPROChunk = chunk;
+                        lastTPROChunk = chunk;
+                    } else {
+                        lastTPROChunk = chunk;
+                    }
+                } else if ( firstTPROChunk ) {
+                    // HACK: If we find an alignment chunk then add it as-if its a TPRO chunk. This is to
+                    // make sure callers can get page-aligned TPRO regions
+                    if ( const AlignChunk* alignChunk = chunk->isAlignChunk() ) {
+                        if ( alignChunk->alignment() >= 4_KB ) {
+                            lastTPROChunk = chunk;
+                            continue;
+                        }
+                    }
+
+                    // Found a non-tpro chunk.  Break here and do the callback outside the chunk loop
+                    // We don't need to continue the loop as we sorted all the TPRO to be adjacent
+                    break;
+                }
+            }
+
+            if ( firstTPROChunk ) {
+                // Ended with a tpro chunk.  Make the callback for it
+                callback(region, firstTPROChunk, lastTPROChunk);
+            }
+        }
+    };
+
+    runOnSubCache(mainSubCache);
+    for ( SubCache* subCache : subCaches )
+        runOnSubCache(subCache);
+}
+
+static void forEachTPRORegion(const BuilderConfig& config, SubCache* mainSubCache,
+                              std::span<SubCache*> subCaches,
+                              void (^callback)(CacheVMAddress firstChunkAddress, CacheVMAddress lastChunkAddress, CacheVMSize lastChunkSize))
+{
+    // Find all subcaches with TPRO regions, or on x86_64 its those with DATA in them
+    if ( config.layout.tproIsInData ) {
+        SubCache::forEachTPRORegionInData(mainSubCache, subCaches, ^(Region& region, const Chunk *firstChunk, const Chunk *lastChunk) {
+            callback(firstChunk->cacheVMAddress, lastChunk->cacheVMAddress, lastChunk->cacheVMSize);
+        });
+        return;
+    }
+
+    auto runOnSubCache = ^(const SubCache* subCache) {
+        for ( const Region& region : subCache->regions ) {
+            if ( (region.kind == Region::Kind::tproConst) || (region.kind == Region::Kind::tproAuthConst) )
+                callback(region.subCacheVMAddress, region.subCacheVMAddress, region.subCacheVMSize);
+        }
+    };
+
+    runOnSubCache(mainSubCache);
+    for ( const SubCache* subCache : subCaches )
+        runOnSubCache(subCache);
+}
+
+static uint32_t numTPRORegions(const BuilderConfig& config, SubCache* mainSubCache,
+                               std::span<SubCache*> subCaches)
+{
+    __block uint32_t count = 0;
+    forEachTPRORegion(config, mainSubCache, subCaches, ^(CacheVMAddress, CacheVMAddress, CacheVMSize) {
+        ++count;
+    });
+    return count;
+}
+
+void SubCache::addCacheHeaderChunk(const BuilderOptions& options, const BuilderConfig& config,
+                                   const std::span<CacheDylib> cacheDylibs)
 {
     // calculate size of header info and where first dylib's mach_header should start
     uint64_t numMappings = this->regions.size();
     uint64_t startOffset = sizeof(dyld_cache_header) + (numMappings * sizeof(dyld_cache_mapping_info));
     startOffset += numMappings * sizeof(dyld_cache_mapping_and_slide_info);
 
-    // Only the main cache has a list of subCaches to write
+    // Only the main cache has a list of subCaches and TPRO mappings to write
     if ( this->isMainCache() ) {
         startOffset += sizeof(dyld_subcache_entry) * this->subCaches.size();
-    }
-
-    if ( this->needsCacheHeaderImageList() ) {
+        startOffset += sizeof(dyld_cache_tpro_mapping_info) * numTPRORegions(config, this, this->subCaches);
+    }
+
+    if ( this->needsCacheHeaderImageList(options) ) {
         startOffset += sizeof(dyld_cache_image_info) * cacheDylibs.size();
         startOffset += sizeof(dyld_cache_image_text_info) * cacheDylibs.size();
         for ( const CacheDylib& cacheDylib : cacheDylibs ) {
@@ -870,6 +1019,12 @@
     // we sort the Chunk's.  For now just add placeholder(s) and we'll update the
     // size in calculateSlideInfoSize()
 
+    // TPRO_CONST
+    if ( !this->regions[(uint32_t)Region::Kind::tproConst].chunks.empty() ) {
+        this->tproConstSlideInfo = std::make_unique<SlideInfoChunk>();
+        addLinkeditChunk(this->tproConstSlideInfo.get());
+    }
+
     // DATA
     if ( !this->regions[(uint32_t)Region::Kind::data].chunks.empty() ) {
         this->dataSlideInfo = std::make_unique<SlideInfoChunk>();
@@ -882,6 +1037,12 @@
         addLinkeditChunk(this->dataConstSlideInfo.get());
     }
 
+    // TPRO_CONST (on arm64e)
+    if ( !this->regions[(uint32_t)Region::Kind::tproAuthConst].chunks.empty() ) {
+        this->tproAuthConstSlideInfo = std::make_unique<SlideInfoChunk>();
+        addLinkeditChunk(this->tproAuthConstSlideInfo.get());
+    }
+
     // AUTH
     if ( !this->regions[(uint32_t)Region::Kind::auth].chunks.empty() ) {
         this->authSlideInfo = std::make_unique<SlideInfoChunk>();
@@ -904,7 +1065,7 @@
     addCodeSignatureChunk(this->codeSignature.get());
 }
 
-void SubCache::addObjCOptsHeaderChunk(ObjCOptimizer& objcOptimizer)
+void SubCache::addObjCOptsHeaderChunk(const BuilderConfig& config, ObjCOptimizer& objcOptimizer)
 {
     this->objcOptsHeader = std::make_unique<ObjCOptsHeaderChunk>();
     this->objcOptsHeader->cacheVMSize       = CacheVMSize(objcOptimizer.optsHeaderByteSize);
@@ -912,10 +1073,10 @@
 
     objcOptimizer.optsHeaderChunk = this->objcOptsHeader.get();
 
-    this->addLinkeditChunk(this->objcOptsHeader.get());
-}
-
-void SubCache::addObjCHeaderInfoReadOnlyChunk(ObjCOptimizer& objcOptimizer)
+    this->addReadOnlyChunk(config, this->objcOptsHeader.get());
+}
+
+void SubCache::addObjCHeaderInfoReadOnlyChunk(const BuilderConfig& config, ObjCOptimizer& objcOptimizer)
 {
     this->objcHeaderInfoRO = std::make_unique<ObjCHeaderInfoReadOnlyChunk>();
     this->objcHeaderInfoRO->cacheVMSize         = CacheVMSize(objcOptimizer.headerInfoReadOnlyByteSize);
@@ -923,10 +1084,21 @@
 
     objcOptimizer.headerInfoReadOnlyChunk = this->objcHeaderInfoRO.get();
 
-    this->addObjCReadOnlyChunk(this->objcHeaderInfoRO.get());
-}
-
-void SubCache::addObjCSelectorStringsChunk(ObjCSelectorOptimizer& objCSelectorOptimizer)
+    this->addReadOnlyChunk(config, this->objcHeaderInfoRO.get());
+}
+
+void SubCache::addObjCImageInfoChunk(const BuilderConfig& config, ObjCOptimizer& objcOptimizer)
+{
+    this->objcImageInfo = std::make_unique<ObjCImageInfoChunk>();
+    this->objcImageInfo->cacheVMSize         = CacheVMSize(objcOptimizer.imageInfoSize);
+    this->objcImageInfo->subCacheFileSize    = CacheFileSize(objcOptimizer.imageInfoSize);
+
+    objcOptimizer.imageInfoChunk = this->objcImageInfo.get();
+
+    this->addReadOnlyChunk(config, this->objcImageInfo.get());
+}
+
+void SubCache::addObjCSelectorStringsChunk(const BuilderConfig& config, ObjCSelectorOptimizer& objCSelectorOptimizer)
 {
     this->objcSelectorStrings = std::make_unique<ObjCStringsChunk>();
     this->objcSelectorStrings->cacheVMSize      = CacheVMSize(objCSelectorOptimizer.selectorStringsTotalByteSize);
@@ -934,10 +1106,10 @@
 
     objCSelectorOptimizer.selectorStringsChunk = this->objcSelectorStrings.get();
 
-    this->addObjCReadOnlyChunk(this->objcSelectorStrings.get());
-}
-
-void SubCache::addObjCSelectorHashTableChunk(ObjCSelectorOptimizer& objCSelectorOptimizer)
+    this->addReadOnlyChunk(config, this->objcSelectorStrings.get());
+}
+
+void SubCache::addObjCSelectorHashTableChunk(const BuilderConfig& config, ObjCSelectorOptimizer& objCSelectorOptimizer)
 {
     this->objcSelectorsHashTable = std::make_unique<ObjCSelectorHashTableChunk>();
     this->objcSelectorsHashTable->cacheVMSize       = CacheVMSize(objCSelectorOptimizer.selectorHashTableTotalByteSize);
@@ -945,10 +1117,10 @@
 
     objCSelectorOptimizer.selectorHashTableChunk = this->objcSelectorsHashTable.get();
 
-    this->addObjCReadOnlyChunk(this->objcSelectorsHashTable.get());
-}
-
-void SubCache::addObjCClassNameStringsChunk(ObjCClassOptimizer& objcClassOptimizer)
+    this->addReadOnlyChunk(config, this->objcSelectorsHashTable.get());
+}
+
+void SubCache::addObjCClassNameStringsChunk(const BuilderConfig& config, ObjCClassOptimizer& objcClassOptimizer)
 {
     this->objcClassNameStrings = std::make_unique<ObjCStringsChunk>();
     this->objcClassNameStrings->cacheVMSize         = CacheVMSize(objcClassOptimizer.nameStringsTotalByteSize);
@@ -956,10 +1128,10 @@
 
     objcClassOptimizer.classNameStringsChunk = this->objcClassNameStrings.get();
 
-    this->addObjCReadOnlyChunk(this->objcClassNameStrings.get());
-}
-
-void SubCache::addObjCClassHashTableChunk(ObjCClassOptimizer& objcClassOptimizer)
+    this->addReadOnlyChunk(config, this->objcClassNameStrings.get());
+}
+
+void SubCache::addObjCClassHashTableChunk(const BuilderConfig& config, ObjCClassOptimizer& objcClassOptimizer)
 {
     this->objcClassesHashTable = std::make_unique<ObjCClassHashTableChunk>();
     this->objcClassesHashTable->cacheVMSize         = CacheVMSize(objcClassOptimizer.classHashTableTotalByteSize);
@@ -967,10 +1139,10 @@
 
     objcClassOptimizer.classHashTableChunk = this->objcClassesHashTable.get();
 
-    this->addObjCReadOnlyChunk(this->objcClassesHashTable.get());
-}
-
-void SubCache::addObjCProtocolNameStringsChunk(ObjCProtocolOptimizer& objcProtocolOptimizer)
+    this->addReadOnlyChunk(config, this->objcClassesHashTable.get());
+}
+
+void SubCache::addObjCProtocolNameStringsChunk(const BuilderConfig& config, ObjCProtocolOptimizer& objcProtocolOptimizer)
 {
     this->objcProtocolNameStrings = std::make_unique<ObjCStringsChunk>();
     this->objcProtocolNameStrings->cacheVMSize      = CacheVMSize(objcProtocolOptimizer.nameStringsTotalByteSize);
@@ -978,10 +1150,10 @@
 
     objcProtocolOptimizer.protocolNameStringsChunk = this->objcProtocolNameStrings.get();
 
-    this->addObjCReadOnlyChunk(this->objcProtocolNameStrings.get());
-}
-
-void SubCache::addObjCProtocolHashTableChunk(ObjCProtocolOptimizer& objcProtocolOptimizer)
+    this->addReadOnlyChunk(config, this->objcProtocolNameStrings.get());
+}
+
+void SubCache::addObjCProtocolHashTableChunk(const BuilderConfig& config, ObjCProtocolOptimizer& objcProtocolOptimizer)
 {
     this->objcProtocolsHashTable = std::make_unique<ObjCProtocolHashTableChunk>();
     this->objcProtocolsHashTable->cacheVMSize       = CacheVMSize(objcProtocolOptimizer.protocolHashTableTotalByteSize);
@@ -989,10 +1161,10 @@
 
     objcProtocolOptimizer.protocolHashTableChunk = this->objcProtocolsHashTable.get();
 
-    this->addObjCReadOnlyChunk(this->objcProtocolsHashTable.get());
-}
-
-void SubCache::addObjCProtocolSwiftDemangledNamesChunk(ObjCProtocolOptimizer& objcProtocolOptimizer)
+    this->addReadOnlyChunk(config, this->objcProtocolsHashTable.get());
+}
+
+void SubCache::addObjCProtocolSwiftDemangledNamesChunk(const BuilderConfig& config, ObjCProtocolOptimizer& objcProtocolOptimizer)
 {
     this->objcSwiftDemangledNameStrings = std::make_unique<ObjCStringsChunk>();
     this->objcSwiftDemangledNameStrings->cacheVMSize        = CacheVMSize(objcProtocolOptimizer.swiftDemangledNameStringsTotalByteSize);
@@ -1000,10 +1172,23 @@
 
     objcProtocolOptimizer.swiftDemangledNameStringsChunk = this->objcSwiftDemangledNameStrings.get();
 
-    this->addObjCReadOnlyChunk(this->objcSwiftDemangledNameStrings.get());
-}
-
-void SubCache::addObjCIMPCachesChunk(ObjCIMPCachesOptimizer& objcIMPCachesOptimizer)
+    this->addReadOnlyChunk(config, this->objcSwiftDemangledNameStrings.get());
+}
+
+void SubCache::addObjCCanonicalProtocolsChunk(const BuilderConfig& config,
+                                              ObjCProtocolOptimizer& objcProtocolOptimizer)
+{
+    this->objcCanonicalProtocols = std::make_unique<ObjCCanonicalProtocolsChunk>();
+    this->objcCanonicalProtocols->cacheVMSize         = CacheVMSize(objcProtocolOptimizer.canonicalProtocolsTotalByteSize);
+    this->objcCanonicalProtocols->subCacheFileSize    = CacheFileSize(objcProtocolOptimizer.canonicalProtocolsTotalByteSize);
+
+    objcProtocolOptimizer.canonicalProtocolsChunk = this->objcCanonicalProtocols.get();
+
+    // Add canonical objc protocols
+    addObjCReadWriteChunk(config, this->objcCanonicalProtocols.get());
+}
+
+void SubCache::addObjCIMPCachesChunk(const BuilderConfig& config, ObjCIMPCachesOptimizer& objcIMPCachesOptimizer)
 {
     this->objcIMPCaches = std::make_unique<ObjCIMPCachesChunk>();
     this->objcIMPCaches->cacheVMSize                        = CacheVMSize(objcIMPCachesOptimizer.impCachesTotalByteSize);
@@ -1011,20 +1196,20 @@
 
     objcIMPCachesOptimizer.impCachesChunk = this->objcIMPCaches.get();
 
-    this->addLinkeditChunk(this->objcIMPCaches.get());
-}
-
-void SubCache::addObjCCanonicalProtocolsChunk(const BuilderConfig& config,
-                                              ObjCProtocolOptimizer& objcProtocolOptimizer)
-{
-    this->objcCanonicalProtocols = std::make_unique<ObjCCanonicalProtocolsChunk>();
-    this->objcCanonicalProtocols->cacheVMSize         = CacheVMSize(objcProtocolOptimizer.canonicalProtocolsTotalByteSize);
-    this->objcCanonicalProtocols->subCacheFileSize    = CacheFileSize(objcProtocolOptimizer.canonicalProtocolsTotalByteSize);
-
-    objcProtocolOptimizer.canonicalProtocolsChunk = this->objcCanonicalProtocols.get();
-
-    // Add canonical objc protocols
-    addObjCReadWriteChunk(config, this->objcCanonicalProtocols.get());
+    this->addReadOnlyChunk(config, this->objcIMPCaches.get());
+}
+
+void SubCache::addObjCCategoriesChunk(const BuilderConfig& config,
+                                     ObjCCategoryOptimizer& objcCategoryOptimizer)
+{
+    this->objcCategories = std::make_unique<ObjCPreAttachedCategoriesChunk>();
+    this->objcCategories->cacheVMSize        = CacheVMSize(objcCategoryOptimizer.categoriesTotalByteSize);
+    this->objcCategories->subCacheFileSize   = CacheFileSize(objcCategoryOptimizer.categoriesTotalByteSize);
+
+    objcCategoryOptimizer.categoriesChunk = this->objcCategories.get();
+
+    // Add objc categories
+    addReadOnlyChunk(config, this->objcCategories.get());
 }
 
 void SubCache::addCacheTrieChunk(DylibTrieOptimizer& dylibTrieOptimizer)
@@ -1040,16 +1225,26 @@
 
 void SubCache::addPatchTableChunk(PatchTableOptimizer& patchTableOptimizer)
 {
-    // We can't compute the size yet.  We need to know how many fixups we have
-    // And yet we have an estimate, so we'll use it
+    // We can't compute the size yet so just make an empty chunk
 
     this->patchTable = std::make_unique<PatchTableChunk>();
-    this->patchTable->cacheVMSize       = CacheVMSize(patchTableOptimizer.patchTableTotalByteSize);
-    this->patchTable->subCacheFileSize  = CacheFileSize(patchTableOptimizer.patchTableTotalByteSize);
+    this->patchTable->cacheVMSize       = CacheVMSize(0ULL);
+    this->patchTable->subCacheFileSize  = CacheFileSize(0ULL);
 
     patchTableOptimizer.patchTableChunk = this->patchTable.get();
 
     this->addLinkeditChunk(this->patchTable.get());
+}
+
+void SubCache::addFunctionVariantsChunk(FunctionVariantsOptimizer& optimizer)
+{
+    this->functionVariants                    = std::make_unique<FunctionVariantsPatchTableChunk>();
+    this->functionVariants->cacheVMSize       = CacheVMSize(optimizer.fvInfoTotalByteSize);
+    this->functionVariants->subCacheFileSize  = CacheFileSize(optimizer.fvInfoTotalByteSize);
+
+    optimizer.chunk = this->functionVariants.get();
+
+    this->addLinkeditChunk(this->functionVariants.get());
 }
 
 void SubCache::addCacheDylibsLoaderChunk(PrebuiltLoaderBuilder& builder)
@@ -1091,7 +1286,21 @@
     this->addLinkeditChunk(this->executablesTrie.get());
 }
 
-void SubCache::addSwiftOptsHeaderChunk(SwiftProtocolConformanceOptimizer& opt)
+void SubCache::addPrewarmingDataChunk(const BuilderConfig& config, PrewarmingOptimizer& opt)
+{
+    if ( opt.prewarmingByteSize == 0 )
+        return;
+
+    this->prewarmingChunk = std::make_unique<PrewarmingChunk>(Chunk::Kind::prewarmingData);
+    this->prewarmingChunk->cacheVMSize      = CacheVMSize(opt.prewarmingByteSize);
+    this->prewarmingChunk->subCacheFileSize = CacheFileSize(opt.prewarmingByteSize);
+
+    opt.prewarmingChunk = this->prewarmingChunk.get();
+
+    this->addReadOnlyChunk(config, this->prewarmingChunk.get());
+}
+
+void SubCache::addSwiftOptsHeaderChunk(const BuilderConfig& config, SwiftOptimizer& opt)
 {
     this->swiftOptsHeader = std::make_unique<SwiftOptsHeaderChunk>();
     this->swiftOptsHeader->cacheVMSize      = CacheVMSize(opt.optsHeaderByteSize);
@@ -1099,10 +1308,10 @@
 
     opt.optsHeaderChunk = this->swiftOptsHeader.get();
 
-    this->addLinkeditChunk(this->swiftOptsHeader.get());
-}
-
-void SubCache::addSwiftTypeHashTableChunk(SwiftProtocolConformanceOptimizer& opt)
+    this->addReadOnlyChunk(config, this->swiftOptsHeader.get());
+}
+
+void SubCache::addSwiftTypeHashTableChunk(const BuilderConfig& config, SwiftOptimizer& opt)
 {
     this->swiftTypeHashTable = std::make_unique<SwiftProtocolConformancesHashTableChunk>();
     this->swiftTypeHashTable->cacheVMSize       = CacheVMSize(opt.typeConformancesHashTableSize);
@@ -1110,10 +1319,10 @@
 
     opt.typeConformancesHashTable = this->swiftTypeHashTable.get();
 
-    this->addLinkeditChunk(this->swiftTypeHashTable.get());
-}
-
-void SubCache::addSwiftMetadataHashTableChunk(SwiftProtocolConformanceOptimizer& opt)
+    this->addReadOnlyChunk(config, this->swiftTypeHashTable.get());
+}
+
+void SubCache::addSwiftMetadataHashTableChunk(const BuilderConfig& config, SwiftOptimizer& opt)
 {
     this->swiftMetadataHashTable = std::make_unique<SwiftProtocolConformancesHashTableChunk>();
     this->swiftMetadataHashTable->cacheVMSize       = CacheVMSize(opt.metadataConformancesHashTableSize);
@@ -1121,10 +1330,10 @@
 
     opt.metadataConformancesHashTable = this->swiftMetadataHashTable.get();
 
-    this->addLinkeditChunk(this->swiftMetadataHashTable.get());
-}
-
-void SubCache::addSwiftForeignHashTableChunk(SwiftProtocolConformanceOptimizer& opt)
+    this->addReadOnlyChunk(config, this->swiftMetadataHashTable.get());
+}
+
+void SubCache::addSwiftForeignHashTableChunk(const BuilderConfig& config, SwiftOptimizer& opt)
 {
     this->swiftForeignTypeHashTable = std::make_unique<SwiftProtocolConformancesHashTableChunk>();
     this->swiftForeignTypeHashTable->cacheVMSize        = CacheVMSize(opt.foreignTypeConformancesHashTableSize);
@@ -1132,7 +1341,19 @@
 
     opt.foreignTypeConformancesHashTable = this->swiftForeignTypeHashTable.get();
 
-    this->addLinkeditChunk(this->swiftForeignTypeHashTable.get());
+    this->addReadOnlyChunk(config, this->swiftForeignTypeHashTable.get());
+}
+
+void SubCache::addSwiftPrespecializedMetadataPointerTableChunks(const BuilderConfig& config, SwiftOptimizer& opt)
+{
+    for ( PointerHashTableOptimizerInfo& tableInfo : opt.prespecializedMetadataHashTables ) {
+        PointerHashTableChunk* chunk = this->pointerHashTables.emplace_back(std::make_unique<PointerHashTableChunk>()).get();
+        chunk->cacheVMSize       = CacheVMSize(tableInfo.size);
+        chunk->subCacheFileSize  = CacheFileSize(tableInfo.size);
+
+        tableInfo.chunk = chunk;
+        this->addReadOnlyChunk(config, chunk);
+    }
 }
 
 void SubCache::addUnmappedSymbols(const BuilderConfig& config, UnmappedSymbolsOptimizer& opt)
@@ -1232,6 +1453,26 @@
             case Region::Kind::text:
                 flags    = this->isStubsCache() ? DYLD_CACHE_MAPPING_TEXT_STUBS : 0;
                 break;
+            case Region::Kind::tproConst:
+                flags    = DYLD_CACHE_MAPPING_CONST_DATA | DYLD_CACHE_MAPPING_CONST_TPRO_DATA;
+
+                // Get the slide info
+                if ( this->tproConstSlideInfo ) {
+                    slideInfoFileOffset = this->tproConstSlideInfo->subCacheFileOffset;
+                    slideInfoFileSize   = this->tproConstSlideInfo->usedFileSize;
+                }
+                break;
+            case Region::Kind::tproAuthConst:
+                // Note, we don't set AUTH here.  TPRO_CONST doesn't actually contain authenticated fixups, ie, is just data
+                // but we still give it its own Region so that we can place it adjacent to dirty data
+                flags    = DYLD_CACHE_MAPPING_CONST_DATA | DYLD_CACHE_MAPPING_CONST_TPRO_DATA;
+
+                // Get the slide info
+                if ( this->tproAuthConstSlideInfo ) {
+                    slideInfoFileOffset = this->tproAuthConstSlideInfo->subCacheFileOffset;
+                    slideInfoFileSize   = this->tproAuthConstSlideInfo->usedFileSize;
+                }
+                break;
             case Region::Kind::data:
                 flags    = 0;
 
@@ -1267,6 +1508,9 @@
                     slideInfoFileOffset = this->authConstSlideInfo->subCacheFileOffset;
                     slideInfoFileSize   = this->authConstSlideInfo->usedFileSize;
                 }
+                break;
+            case Region::Kind::readOnly:
+                flags    = DYLD_CACHE_READ_ONLY_DATA;
                 break;
             case Region::Kind::linkedit:
                 flags    = 0;
@@ -1305,15 +1549,16 @@
 }
 
 void SubCache::writeCacheHeader(const BuilderOptions& options, const BuilderConfig& config,
-                                const std::span<CacheDylib> cacheDylibs)
+                                const std::span<CacheDylib> cacheDylibs,
+                                uint32_t osVersion, uint32_t altPlatform, uint32_t altOsVersion)
 {
     Chunk& cacheHeaderChunk = *this->cacheHeader.get();
     dyld_cache_header* dyldCacheHeader = (dyld_cache_header*)cacheHeaderChunk.subCacheBuffer;
 
     // "dyld_v1" + spaces + archName(), with enough spaces to pad to 15 bytes
     std::string magic = "dyld_v1";
-    magic.append(15 - magic.length() - strlen(options.archs.name()), ' ');
-    magic.append(options.archs.name());
+    magic.append(15 - magic.length() - strlen(options.arch.name()), ' ');
+    magic.append(options.arch.name());
     assert(magic.length() == 15);
 
     // Num of mappings depends on cache layout.
@@ -1359,12 +1604,14 @@
     dyldCacheHeader->progClosuresSize              = 0; // no longer used
     dyldCacheHeader->progClosuresTrieAddr          = 0; // no longer used
     dyldCacheHeader->progClosuresTrieSize          = 0; // no longer used
-    dyldCacheHeader->platform                      = (uint8_t)options.platform;
+    dyldCacheHeader->platform                      = options.platform.value();
     dyldCacheHeader->formatVersion                 = 0; //dyld3::closure::kFormatVersion;
     dyldCacheHeader->dylibsExpectedOnDisk          = !options.dylibsRemovedFromDisk;
-    dyldCacheHeader->simulator                     = options.isSimultor();
+    dyldCacheHeader->simulator                     = options.isSimulator();
     dyldCacheHeader->locallyBuiltCache             = options.isLocallyBuiltCache;
     dyldCacheHeader->builtFromChainedFixups        = false; // no longer used
+    dyldCacheHeader->newFormatTLVs                 = true;
+    dyldCacheHeader->padding                       = 0;
     dyldCacheHeader->sharedRegionStart             = this->subCacheVMAddress.rawValue();
     dyldCacheHeader->sharedRegionSize              = 0;
     dyldCacheHeader->maxSlide                      = 0; // overwritten later in build if the cache supports ASLR
@@ -1382,9 +1629,9 @@
     dyldCacheHeader->programsPBLSetPoolSize        = 0; // set later only on the main cache file
     dyldCacheHeader->programTrieAddr               = 0; // set later only on the main cache file
     dyldCacheHeader->programTrieSize               = 0; // set later only on the main cache file
-    dyldCacheHeader->osVersion                     = 0; // set later only on the main cache file
-    dyldCacheHeader->altPlatform                   = 0; // set later only on the main cache file
-    dyldCacheHeader->altOsVersion                  = 0; // set later only on the main cache file
+    dyldCacheHeader->osVersion                     = osVersion;
+    dyldCacheHeader->altPlatform                   = altPlatform;
+    dyldCacheHeader->altOsVersion                  = altOsVersion;
     dyldCacheHeader->swiftOptsOffset               = 0; // set later only on the main cache file
     dyldCacheHeader->swiftOptsSize                 = 0; // set later only on the main cache file
     dyldCacheHeader->subCacheArrayOffset           = 0;
@@ -1401,25 +1648,30 @@
     dyldCacheHeader->cacheAtlasSize                = 0; // set later only on the main cache file
     dyldCacheHeader->dynamicDataOffset             = 0; // set later only on the main cache file
     dyldCacheHeader->dynamicDataMaxSize            = 0; // set later only on the main cache file
+    dyldCacheHeader->tproMappingsOffset            = 0; // set later only on the main cache file
+    dyldCacheHeader->tproMappingsCount             = 0; // set later only on the main cache file
+    dyldCacheHeader->prewarmingDataOffset          = 0; // set later only on the main cache file
+    dyldCacheHeader->prewarmingDataSize            = 0; // set later only on the main cache file
 
     // Fill in old mappings
     // And new mappings which also have slide info
     this->writeCacheHeaderMappings();
 
-    this->addCacheHeaderImageInfo(options, cacheDylibs);
+    this->addCacheHeaderImageInfo(options, config, cacheDylibs);
 }
 
 void SubCache::addMainCacheHeaderInfo(const BuilderOptions& options, const BuilderConfig& config,
                                       const std::span<CacheDylib> cacheDylibs,
                                       CacheVMSize totalVMSize, uint64_t maxSlide,
-                                      uint32_t osVersion, uint32_t altPlatform, uint32_t altOsVersion,
                                       CacheVMAddress dyldInCacheUnslidAddr,
                                       CacheVMAddress dyldInCacheEntryUnslidAddr,
                                       const DylibTrieOptimizer& dylibTrieOptimizer,
                                       const ObjCOptimizer& objcOptimizer,
-                                      const SwiftProtocolConformanceOptimizer& swiftProtocolConformanceOpt,
+                                      const SwiftOptimizer& swiftOpt,
                                       const PatchTableOptimizer& patchTableOptimizer,
-                                      const PrebuiltLoaderBuilder& prebuiltLoaderBuilder)
+                                      const FunctionVariantsOptimizer& functionVariantOptimizer,
+                                      const PrebuiltLoaderBuilder& prebuiltLoaderBuilder,
+                                      const PrewarmingOptimizer& prewarmingOptimizer)
 {
     const CacheVMAddress cacheBaseAddress = config.layout.cacheBaseAddress;
 
@@ -1432,15 +1684,15 @@
     dyldCacheHeader->dylibsTrieAddr = dylibTrieOptimizer.dylibsTrieChunk->cacheVMAddress.rawValue();
     dyldCacheHeader->dylibsTrieSize = dylibTrieOptimizer.dylibsTrieChunk->subCacheFileSize.rawValue();
 
-    if ( !objcOptimizer.objcDylibs.empty() ) {
+    // Disable objc optimizations from EK shared cache
+    bool emitObjcOpts = !options.platform.isExclaveKit();
+    if ( !objcOptimizer.objcDylibs.empty() && emitObjcOpts ) {
         dyldCacheHeader->objcOptsOffset = (objcOptimizer.optsHeaderChunk->cacheVMAddress - cacheBaseAddress).rawValue();
         dyldCacheHeader->objcOptsSize   = objcOptimizer.optsHeaderChunk->subCacheFileSize.rawValue();
-    }
-
-    if ( !objcOptimizer.objcDylibs.empty() ) {
-        const auto& opt = swiftProtocolConformanceOpt;
+
+        const auto& opt = swiftOpt;
         dyldCacheHeader->swiftOptsOffset = (opt.optsHeaderChunk->cacheVMAddress - cacheBaseAddress).rawValue();
-        dyldCacheHeader->objcOptsSize    = opt.optsHeaderChunk->subCacheFileSize.rawValue();
+        dyldCacheHeader->swiftOptsSize   = opt.optsHeaderChunk->subCacheFileSize.rawValue();
     }
 
     dyldCacheHeader->patchInfoAddr = patchTableOptimizer.patchTableChunk->cacheVMAddress.rawValue();
@@ -1455,16 +1707,15 @@
     dyldCacheHeader->dyldInCacheMH      = dyldInCacheUnslidAddr.rawValue();
     dyldCacheHeader->dyldInCacheEntry   = dyldInCacheEntryUnslidAddr.rawValue();
 
-    dyldCacheHeader->osVersion      = osVersion;
-    dyldCacheHeader->altPlatform    = altPlatform;
-    dyldCacheHeader->altOsVersion   = altOsVersion;
-
     // record max slide now that final size is established
     dyldCacheHeader->maxSlide           = maxSlide;
 
     // TODO: Build the atlas
     dyldCacheHeader->cacheAtlasOffset              = 0; // set later only on the main cache file
     dyldCacheHeader->cacheAtlasSize                = 0; // set later only on the main cache file
+
+    dyldCacheHeader->functionVariantInfoAddr = functionVariantOptimizer.chunk->cacheVMAddress.rawValue();
+    dyldCacheHeader->functionVariantInfoSize = functionVariantOptimizer.fvInfoTotalByteSize;
 
     // The main cache has offsets to all the caches
     if ( !this->subCaches.empty() ) {
@@ -1485,6 +1736,30 @@
         dyldCacheHeader->dynamicDataOffset  = (dynamicConfig->cacheVMAddress - cacheBaseAddress).rawValue();
         dyldCacheHeader->dynamicDataMaxSize = dynamicConfig->cacheVMSize.rawValue();
     }
+
+    // Find all the TPRO regions
+    if ( dyldCacheHeader->tproMappingsCount != 0 ) {
+        assert(cacheHeaderChunk.subCacheFileOffset.rawValue() == 0);
+        auto* mappings = (dyld_cache_tpro_mapping_info*)((uint8_t*)dyldCacheHeader + dyldCacheHeader->tproMappingsOffset);
+        __block uint32_t index = 0;
+        forEachTPRORegion(config, this, this->subCaches, ^(CacheVMAddress firstChunkAddress, CacheVMAddress lastChunkAddress, CacheVMSize lastChunkSize) {
+            assert(index < dyldCacheHeader->tproMappingsCount);
+
+            CacheVMAddress vmAddr = firstChunkAddress;
+            CacheVMSize vmSize = (lastChunkAddress - firstChunkAddress) + lastChunkSize;
+
+            dyld_cache_tpro_mapping_info* mapping = & mappings[index];
+            mapping->unslidAddress = vmAddr.rawValue();
+            mapping->size = vmSize.rawValue();
+
+            ++index;
+        });
+    }
+
+    if ( prewarmingOptimizer.prewarmingChunk != nullptr ) {
+        dyldCacheHeader->prewarmingDataOffset = (prewarmingOptimizer.prewarmingChunk->cacheVMAddress - cacheBaseAddress).rawValue();
+        dyldCacheHeader->prewarmingDataSize   = prewarmingOptimizer.prewarmingChunk->subCacheFileSize.rawValue();
+    }
 }
 
 void SubCache::addSymbolsCacheHeaderInfo(const UnmappedSymbolsOptimizer& optimizer)
@@ -1508,9 +1783,10 @@
 }
 
 void SubCache::addCacheHeaderImageInfo(const BuilderOptions& options,
+                                       const BuilderConfig& config,
                                        const std::span<CacheDylib> cacheDylibs)
 {
-    if ( !this->needsCacheHeaderImageList() )
+    if ( !this->needsCacheHeaderImageList(options) )
         return;
 
     Chunk&             cacheHeaderChunk   = *this->cacheHeader.get();
@@ -1525,13 +1801,18 @@
     dyldCacheHeader->subCacheArrayOffset = (uint32_t)(dyldCacheHeader->imagesTextOffset + sizeof(dyld_cache_image_text_info) * cacheDylibs.size());
     dyldCacheHeader->subCacheArrayCount  = (uint32_t)this->subCaches.size();
 
+    // Always set the TPRO offset, but the count will only be set in the main cache
+    dyldCacheHeader->tproMappingsOffset = (uint32_t)(dyldCacheHeader->subCacheArrayOffset + sizeof(dyld_subcache_entry) * dyldCacheHeader->subCacheArrayCount);
+    if ( this->isMainCache() )
+        dyldCacheHeader->tproMappingsCount = numTPRORegions(config, this, this->subCaches);
+
     // calculate start of text image array and trailing string pool
     auto*    textImages   = (dyld_cache_image_text_info*)((uint8_t*)dyldCacheHeader + dyldCacheHeader->imagesTextOffset);
-    uint32_t stringOffset = (uint32_t)(dyldCacheHeader->subCacheArrayOffset + sizeof(dyld_subcache_entry) * dyldCacheHeader->subCacheArrayCount);
+    uint32_t stringOffset = (uint32_t)(dyldCacheHeader->tproMappingsOffset + sizeof(dyld_cache_tpro_mapping_info) * dyldCacheHeader->tproMappingsCount);
 
     // write text image array and image names pool at same time
     for ( const CacheDylib& cacheDylib : cacheDylibs ) {
-        cacheDylib.inputMF->getUuid(textImages->uuid);
+        cacheDylib.inputHdr->getUuid(textImages->uuid);
         textImages->loadAddress     = cacheDylib.cacheLoadAddress.rawValue();
         textImages->textSegmentSize = (uint32_t)cacheDylib.segments.front().cacheVMSize.rawValue();
         textImages->pathOffset      = stringOffset;
@@ -1639,16 +1920,17 @@
     return this->kind == Kind::stubsCustomer;
 }
 
-bool SubCache::needsCacheHeaderImageList() const
+bool SubCache::needsCacheHeaderImageList(const BuilderOptions& options) const
 {
     // Symbols and stubs files don't need an image list
-    // We'd like to not add the image list to subcaches, only the main cache, but Rosetta needs
-    // the image list on subCaches.
     switch ( this->kind ) {
         case Kind::mainDevelopment:
         case Kind::mainCustomer:
+            return true;
         case Kind::subUniversal:
-            return true;
+            // We'd like to not add the image list to subcaches, only the main cache, but Rosetta needs
+            // the image list on subCaches.
+            return options.arch.sameCpu(mach_o::Architecture::x86_64);
         case Kind::stubsDevelopment:
         case Kind::stubsCustomer:
         case Kind::symbols:
@@ -1771,6 +2053,9 @@
     __block MachOFile::ChainedFixupPointerOnDisk* lastFixup = nullptr;
     __block uint64_t lastPageIndex = ~0ULL;
     for ( Chunk* chunk : region.chunks ) {
+        if ( chunk->isAlignChunk() )
+            continue;
+
         SlidChunk* slidChunk = chunk->isSlidChunk();
 
         slidChunk->tracker.forEachFixup(^(void *loc, bool& stop) {
@@ -1947,6 +2232,110 @@
     return Error();
 }
 
+Error SubCache::computeSlideInfoV5(const BuilderConfig&           config,
+                                   cache_builder::SlideInfoChunk* slideChunk,
+                                   Region&                        region)
+{
+    __block Diagnostics diag;
+
+    bool canContainAuthPointers = region.canContainAuthPointers();
+
+    assert((region.subCacheVMSize.rawValue() % config.slideInfo.slideInfoPageSize) == 0);
+    dyld_cache_slide_info5* info    = (dyld_cache_slide_info5*)slideChunk->subCacheBuffer;
+    info->version                   = 5;
+    info->page_size                 = config.slideInfo.slideInfoPageSize;
+    info->page_starts_count         = (uint32_t)region.subCacheVMSize.rawValue() / config.slideInfo.slideInfoPageSize;
+    info->value_add                 = config.layout.cacheBaseAddress.rawValue();
+
+    assert((sizeof(dyld_cache_slide_info5) + (info->page_starts_count * sizeof(uint16_t))) <= slideChunk->cacheVMSize.rawValue());
+
+    std::fill(&info->page_starts[0], &info->page_starts[info->page_starts_count], DYLD_CACHE_SLIDE_V5_PAGE_ATTR_NO_REBASE);
+
+    // Walk each fixup in each segment.  Every time we cross a page, add a page start
+    __block dyld_cache_slide_pointer5* lastFixup     = nullptr;
+    __block uint64_t                   lastPageIndex = ~0ULL;
+    for ( Chunk* chunk : region.chunks ) {
+        SlidChunk* slidChunk = chunk->isSlidChunk();
+
+        slidChunk->tracker.forEachFixup(^(void *loc, bool& stop) {
+            // V5 fixups must be 8-byte aligned
+            assert(((uint64_t)loc % 8) == 0);
+
+            VMOffset       vmOffsetInSegment((uint64_t)loc - (uint64_t)slidChunk->subCacheBuffer);
+            CacheVMAddress fixupVMAddr = slidChunk->cacheVMAddress + vmOffsetInSegment;
+            uint64_t       pageIndex   = (fixupVMAddr - region.subCacheVMAddress).rawValue() / info->page_size;
+
+            // If we are on a new page, then start a new chain
+            if ( pageIndex != lastPageIndex ) {
+                uint64_t vmOffsetInPage      = fixupVMAddr.rawValue() % info->page_size;
+                info->page_starts[pageIndex] = vmOffsetInPage;
+            }
+            else {
+                // Patch the previous fixup on this page to point to this one
+                lastFixup->auth.next = ((uint64_t)loc - (uint64_t)lastFixup) / 8;
+            }
+
+            dyld_cache_slide_pointer5* fixupLocation = (dyld_cache_slide_pointer5*)loc;
+
+            // Convert this fixup from the chained format in the cache builder, to the version we want in the cache file
+            CacheVMAddress vmAddr = Fixup::Cache64::getCacheVMAddressFromLocation(config.layout.cacheBaseAddress,
+                                                                                  loc);
+
+            uint8_t high8 = Fixup::Cache64::getHigh8(loc);
+            VMOffset cacheVMOffset = vmAddr - config.layout.cacheBaseAddress;
+
+            uint16_t    authDiversity  = 0;
+            bool        authIsAddr     = false;
+            uint8_t     authKey        = 0;
+            if ( Fixup::Cache64::hasAuthData(loc, authDiversity, authIsAddr, authKey) ) {
+                // Authenticated value
+                assert(high8 == 0);
+                assert(canContainAuthPointers);
+                assert((authKey == 0) || (authKey == 2)); // IA (0) or DA (2)
+
+                fixupLocation->auth.runtimeOffset       = cacheVMOffset.rawValue();
+                fixupLocation->auth.diversity           = authDiversity;
+                fixupLocation->auth.addrDiv             = authIsAddr ? 1 : 0;
+                fixupLocation->auth.keyIsData           = (authKey == 2) ? 1 : 0;
+                fixupLocation->auth.next                = 0;
+                fixupLocation->auth.auth                = 1;
+
+                if ( fixupLocation->auth.runtimeOffset != cacheVMOffset.rawValue() ) {
+                    diag.error("cache offset 0x%llx exceeds v5 format", cacheVMOffset.rawValue());
+                    stop = true;
+                    return;
+                }
+            } else {
+                // Unauthenticated value
+                fixupLocation->regular.runtimeOffset    = cacheVMOffset.rawValue();
+                fixupLocation->regular.high8            = high8;
+                fixupLocation->regular.next             = 0;
+                fixupLocation->regular.unused           = 0;
+                fixupLocation->regular.auth             = 0;
+
+                if ( fixupLocation->regular.runtimeOffset != cacheVMOffset.rawValue() ) {
+                    diag.error("cache offset 0x%llx exceeds v5 format", cacheVMOffset.rawValue());
+                    stop = true;
+                    return;
+                }
+            }
+
+            lastFixup     = fixupLocation;
+            lastPageIndex = pageIndex;
+        });
+        if ( diag.hasError() )
+            break;
+    }
+
+    if ( diag.hasError() )
+        return Error("could not build slide info because: %s", diag.errorMessageCStr());
+
+    // V5 doesn't deduplicate content like V1, so the used size is the original size too
+    slideChunk->usedFileSize = slideChunk->subCacheFileSize;
+
+    return Error();
+}
+
 Error SubCache::computeSlideInfoForRegion(const BuilderConfig&           config,
                                           cache_builder::SlideInfoChunk* slideChunk,
                                           Region&                        region)
@@ -1958,6 +2347,8 @@
             return computeSlideInfoV2(config, slideChunk, region);
         case cache_builder::SlideInfo::SlideInfoFormat::v3:
             return computeSlideInfoV3(config, slideChunk, region);
+        case cache_builder::SlideInfo::SlideInfoFormat::v5:
+            return computeSlideInfoV5(config, slideChunk, region);
     }
 
     return Error();
@@ -1968,8 +2359,10 @@
     Diagnostics diag;
 
     for ( Chunk* chunk : region.chunks ) {
+        if ( chunk->isAlignChunk() )
+            continue;
+
         SlidChunk* slidChunk = chunk->isSlidChunk();
-
         slidChunk->tracker.forEachFixup(^(void *loc, bool& stop) {
             if ( config.layout.is64 ) {
                 CacheVMAddress vmAddr = Fixup::Cache64::getCacheVMAddressFromLocation(config.layout.cacheBaseAddress, loc);
@@ -1996,6 +2389,12 @@
             case Region::Kind::text:
                 // No slide info for text
                 break;
+            case Region::Kind::tproConst:
+                if ( config.slideInfo.slideInfoFormat.has_value() )
+                    err = computeSlideInfoForRegion(config, this->tproConstSlideInfo.get(), region);
+                else
+                    err = convertChainsToVMAddresses(config, region);
+                break;
             case Region::Kind::data:
                 if ( config.slideInfo.slideInfoFormat.has_value() )
                     err = computeSlideInfoForRegion(config, this->dataSlideInfo.get(), region);
@@ -2008,6 +2407,12 @@
                 else
                     err = convertChainsToVMAddresses(config, region);
                 break;
+            case Region::Kind::tproAuthConst:
+                if ( config.slideInfo.slideInfoFormat.has_value() )
+                    err = computeSlideInfoForRegion(config, this->tproAuthConstSlideInfo.get(), region);
+                else
+                    err = convertChainsToVMAddresses(config, region);
+                break;
             case Region::Kind::auth:
                 if ( config.slideInfo.slideInfoFormat.has_value() )
                     err = computeSlideInfoForRegion(config, this->authSlideInfo.get(), region);
@@ -2020,8 +2425,9 @@
                 else
                     err = convertChainsToVMAddresses(config, region);
                 break;
+            case Region::Kind::readOnly:
             case Region::Kind::linkedit:
-                // No slide info for text
+                // No slide info for linkedit
                 break;
             case Region::Kind::unmapped:
             case Region::Kind::dynamicConfig: