Loading...
cache_builder/NewSharedCacheBuilder.cpp dyld-1162 dyld-1066.8
--- dyld/dyld-1162/cache_builder/NewSharedCacheBuilder.cpp
+++ dyld/dyld-1066.8/cache_builder/NewSharedCacheBuilder.cpp
@@ -36,7 +36,6 @@
 #include "ObjCVisitor.h"
 #include "Trie.hpp"
 #include "JustInTimeLoader.h"
-#include "OptimizerObjC.h"
 #include "OptimizerSwift.h"
 #include "PrebuiltLoader.h"
 #include "DyldProcessConfig.h"
@@ -62,7 +61,6 @@
 using dyld4::ProcessConfig;
 using dyld4::RuntimeState;
 using dyld4::SyscallDelegate;
-using dyld4::RuntimeLocks;
 
 using lsl::EphemeralAllocator;
 
@@ -112,7 +110,7 @@
 }
 
 void SharedCacheBuilder::addFile(const void* buffer, size_t bufferSize, std::string_view path,
-                                 uint64_t inode, uint64_t modTime, bool forceNotCacheEligible)
+                                 uint64_t inode, uint64_t modTime)
 {
     Diagnostics diag;
     const bool  isOSBinary = false;
@@ -120,11 +118,10 @@
                                                           this->options.platform, isOSBinary,
                                                           this->options.archs) ) {
         InputFile inputFile;
-        inputFile.mf                    = mf;
-        inputFile.inode                 = inode;
-        inputFile.mtime                 = modTime;
-        inputFile.path                  = path;
-        inputFile.forceNotCacheEligible = forceNotCacheEligible;
+        inputFile.mf        = mf;
+        inputFile.inode     = inode;
+        inputFile.mtime     = modTime;
+        inputFile.path      = path;
         allInputFiles.push_back(std::move(inputFile));
         return;
     }
@@ -136,11 +133,10 @@
                                                               dyld3::Platform::iOSMac, isOSBinary,
                                                               this->options.archs) ) {
             InputFile inputFile;
-            inputFile.mf                    = mf;
-            inputFile.inode                 = inode;
-            inputFile.mtime                 = modTime;
-            inputFile.path                  = path;
-            inputFile.forceNotCacheEligible = forceNotCacheEligible;
+            inputFile.mf        = mf;
+            inputFile.inode     = inode;
+            inputFile.mtime     = modTime;
+            inputFile.path      = path;
             allInputFiles.push_back(std::move(inputFile));
             return;
         }
@@ -201,10 +197,8 @@
     this->findCanonicalObjCProtocolNames();
     this->findObjCClasses();
     this->findObjCProtocols();
-    this->findObjCCategories();
     this->estimateObjCHashTableSizes();
     this->calculateObjCCanonicalProtocolsSize();
-    this->calculateObjCCategoriesSize();
 
     // Note, swift hash tables depends on findObjCClasses()
     this->estimateSwiftHashTableSizes();
@@ -244,8 +238,8 @@
     return Error();
 }
 
-// This is phase 4 of the build() process. It takes the inputs and Optimizers
-// from the previous phases, and emits them to the cache buffers
+// This is phase 4 of the build() process.  It takes the inputs and Optimizers
+// from the previous phases, and creates the SubCache objects
 // Inputs:  subCaches, various Optimizers
 // Outputs: emitted objc strings in the subCache buffers
 Error SharedCacheBuilder::preDylibEmitChunks()
@@ -256,7 +250,6 @@
     this->setupSplitSegAdjustors();
     this->adjustObjCClasses();
     this->adjustObjCProtocols();
-    this->adjustObjCCategories();
 
     // Note this could be after dylib passes, but having the strings emitted now makes
     // it easier to debug the ObjC dylib passes
@@ -268,8 +261,8 @@
     return Error();
 }
 
-// This is phase 5 of the build() process.
-// It runs the passes on each of the cache Dylibs
+// This is phase 4 of the build() process.
+// It runs the passes on each of the cacheDylib's
 // Inputs:  subCaches, various Optimizers
 // Outputs: emitted objc strings in the subCache buffers
 Error SharedCacheBuilder::runDylibPasses()
@@ -341,13 +334,12 @@
     return err;
 }
 
-// This is phase 6 of the build() process. It takes the Optimizers
+// This is phase 5 of the build() process.  It takes the Optimizers
 // from the previous phases, and emits them to the cache buffers
 // Inputs:  subCaches, various Optimizers
-// Outputs: emitted optimizations in the subCache buffers
+// Outputs: emitted optimiations in the subCache buffers
 Error SharedCacheBuilder::postDylibEmitChunks()
 {
-
     this->optimizeTLVs();
 
     if ( Error error = this->emitUniquedGOTs(); error.hasError() )
@@ -357,25 +349,7 @@
     if ( Error error = this->emitCanonicalObjCProtocols(); error.hasError() )
         return error;
 
-    this->emitCacheDylibsTrie();
-    if ( Error error = this->emitPatchTable(); error.hasError() )
-        return error;
-
-    // Note, this must be after we emit the patch table
-    if ( Error error = this->emitCacheDylibsPrebuiltLoaders(); error.hasError() )
-        return error;
-
     this->emitObjCHashTables();
-
-    bool preAttachedCategories = true;
-    if ( preAttachedCategories ) {
-        // Note this has to be after anyone walking the objc metadata format
-        if ( Error error = this->emitPreAttachedObjCCategories(); error.hasError() )
-            return error;
-    }
-
-    // Note, this must be after emitCacheDylibsPrebuiltLoaders() as it needs the offset to the SectionLocations*
-    // in the PrebuiltLoader*
     this->emitObjCHeaderInfo();
     if ( Error error = this->computeObjCClassLayout(); error.hasError() )
         return error;
@@ -389,6 +363,14 @@
     if ( Error error = this->emitSwiftHashTables(); error.hasError() )
         return error;
 
+    this->emitCacheDylibsTrie();
+    if ( Error error = this->emitPatchTable(); error.hasError() )
+        return error;
+
+    // Note, this must be after we emit the patch table
+    if ( Error error = this->emitCacheDylibsPrebuiltLoaders(); error.hasError() )
+        return error;
+
     // Note, this has to be after we've emitted the objc hash tables and the objc header infos
     if ( Error error = this->emitExecutablePrebuiltLoaders(); error.hasError() )
         return error;
@@ -403,8 +385,8 @@
     return Error();
 }
 
-// This is phase 7 of the build() process. It does any final work
-// to emit the sub caches
+// This is phase 6 of the build() process.  it does any final work to emit
+// the sub caches
 // Inputs: everything else
 // Outputs: final emitted data in the sub caches
 Error SharedCacheBuilder::finalize()
@@ -493,14 +475,8 @@
 
     for ( InputFile& inputFile : this->allInputFiles ) {
         if ( inputFile.mf->isDylib() || inputFile.mf->isDyld() ) {
-            auto failureHandler = ^(const char* format, ...) __attribute__((format(printf, 1, 2))) {
-                char*   output_string;
-                va_list list;
-                va_start(list, format);
-                vasprintf(&output_string, format, list);
-                va_end(list);
-                inputFile.setError(Error("%s", (const char*)output_string));
-                free(output_string);
+            auto failureHandler = ^(const char* reason) {
+                inputFile.setError(Error("%s", reason));
             };
 
             std::string_view installName = inputFile.mf->installName();
@@ -525,7 +501,7 @@
                 }
             }
 
-            if ( !inputFile.forceNotCacheEligible && inputFile.mf->canBePlacedInDyldCache(dylibPath.data(), failureHandler) ) {
+            if ( inputFile.mf->canBePlacedInDyldCache(dylibPath.data(), failureHandler) ) {
                 CacheDylib cacheDylib(inputFile);
                 this->cacheDylibs.push_back(std::move(cacheDylib));
             }
@@ -957,16 +933,14 @@
 
 void SharedCacheBuilder::estimateIMPCaches()
 {
-    // Only LP64 is supported by the runtime
     if ( !this->config.layout.is64 )
         return;
 
     if ( this->config.layout.cacheSize.rawValue() > 0x100000000 )
         return;
 
-    // Only arm64* are is supported by the runtime
-    std::string_view archName = this->options.archs.name();
-    if ( archName != "arm64e" && archName != "arm64")
+    // Only iOS for now
+    if ( this->options.platform != dyld3::Platform::iOS )
         return;
 
     // Skip everything if the JSON file is empty
@@ -988,27 +962,6 @@
             // diag.warning("libobjc's magical IMP caches shared cache offsets list section missing (metadata not optimized)");
             return;
         }
-
-        // Also find the _objc_opt_preopt_caches_version symbol, which has the IMP caches version
-        __block Diagnostics diag;
-        cacheDylib.inputMF->withFileLayout(diag, ^(const mach_o::Layout &layout) {
-            mach_o::Layout::FoundSymbol foundInfo;
-            if ( !layout.findExportedSymbol(diag, "_objc_opt_preopt_caches_version", false, foundInfo) )
-                return;
-
-            // We only support header offsets in this dylib, as we are looking for self binds
-            // which are likely only to classes
-            if ( foundInfo.kind != mach_o::Layout::FoundSymbol::Kind::headerOffset )
-                return;
-
-            uint64_t vmAddr = layout.textUnslidVMAddr() + foundInfo.value;
-
-            __block objc_visitor::Visitor objcVisitor = makeInputDylibObjCVisitor(cacheDylib);
-            metadata_visitor::ResolvedValue value = objcVisitor.getValueFor(VMAddress(vmAddr));
-            this->objcIMPCachesOptimizer.libobjcImpCachesVersion = *(int*)value.value();
-        });
-        if ( diag.hasError() )
-            return;
     }
 
     // Find all the objc dylibs, classes, categories
@@ -1419,8 +1372,6 @@
         this->objcOptimizer.headerInfoReadWriteByteSize += (uint32_t)this->objcOptimizer.objcDylibs.size() * sizeof(ObjCOptimizer::header_info_rw_32_t);
     }
 
-    this->objcOptimizer.imageInfoSize = this->objcOptimizer.objcDylibs.size() * sizeof(objc::objc_image_info);
-
     if ( this->config.log.printStats ) {
         stats.add("  objc: found %lld objc dylibs\n", (uint64_t)this->objcOptimizer.objcDylibs.size());
     }
@@ -1800,162 +1751,6 @@
     }
 }
 
-// Walk all the dylibs and build a map of ObjC categories
-void SharedCacheBuilder::findObjCCategories()
-{
-    if ( this->objcOptimizer.objcDylibs.empty() )
-        return;
-
-    Stats        stats(this->config);
-    Timer::Scope timedScope(this->config, "findObjCCategories time");
-
-    // Reserve space for 15k categories, as we have 10k as of writing
-    const uint32_t numCategoriesToReserve = 1 << 14;
-    this->objcCategoryOptimizer.categories.reserve(numCategoriesToReserve);
-    size_t objcIndex = 0;
-    for (size_t cacheIndex = 0; cacheIndex < this->cacheDylibs.size(); cacheIndex++) {
-        CacheDylib& cacheDylib = this->cacheDylibs[cacheIndex];
-        if ( !cacheDylib.inputMF->hasObjC() )
-            continue;
-
-        // Skip dylibs with opcode fixups, as the Category visitor operates on chained fixups to find classes
-        if ( cacheDylib.inputMF->hasOpcodeFixups() ) {
-            this->objcCategoryOptimizer.excludedDylibs.insert(objcIndex);
-            objcIndex++;
-            continue;
-        }
-        struct BindTarget {
-            std::string_view        symbolName;
-            std::optional<uint32_t> targetDylibIndex;
-            bool                    isWeakImport     = false;
-        };
-        __block std::vector<BindTarget> bindTargets;
-        __block Diagnostics diag;
-        cacheDylib.inputMF->withFileLayout(diag, ^(const mach_o::Layout& layout) {
-            mach_o::Fixups fixups(layout);
-
-            fixups.forEachBindTarget(diag, false, 0, ^(const mach_o::Fixups::BindTargetInfo& info, bool& stop) {
-                if ( info.libOrdinal == BIND_SPECIAL_DYLIB_SELF ) {
-                    bindTargets.push_back({ info.symbolName, cacheDylib.cacheIndex, info.weakImport });
-                } else if ( info.libOrdinal < 0 ) {
-                    // A special ordinal such as weak.  Just put in a placeholder for now
-                    bindTargets.push_back({ info.symbolName, std::nullopt, info.weakImport });
-                } else {
-                    assert(info.libOrdinal <= (int)cacheDylib.dependents.size());
-                    const CacheDylib *targetDylib = cacheDylib.dependents[info.libOrdinal-1].dylib;
-                    assert(info.weakImport || (targetDylib != nullptr));
-                    std::optional<uint32_t> targetDylibIndex;
-                    if ( targetDylib != nullptr )
-                        targetDylibIndex = targetDylib->cacheIndex;
-                    bindTargets.push_back({ info.symbolName, targetDylibIndex, info.weakImport });
-                }
-
-                if ( diag.hasError() )
-                    stop = true;
-            }, ^(const mach_o::Fixups::BindTargetInfo& info, bool& stop) {
-                // This shouldn't happen with chained fixups
-                assert(0);
-            });
-        });
-        diag.assertNoError();
-
-        __block objc_visitor::Visitor objCVisitor = makeInputDylibObjCVisitor(cacheDylib);
-
-        __block bool categoriesHaveClassProperties = false;
-        objCVisitor.withImageInfo(^(const uint32_t version, const uint32_t flags) {
-            const uint64_t hasCategoryClassPropertiesFlag = (1 << 6);
-            categoriesHaveClassProperties = (flags & hasCategoryClassPropertiesFlag);
-        });
-
-        objCVisitor.forEachCategory(^(const objc_visitor::Category &objcCategory, bool &stopCategory) {
-
-            __block ObjCCategoryOptimizer::Category objCCategoryInfo(objcCategory.getName(objCVisitor));
-            objCCategoryInfo.dylibObjcIndex = objcIndex;
-            objCCategoryInfo.vmAddress = objcCategory.getVMAddress();
-
-            objcCategory.withClass(objCVisitor, ^(const dyld3::MachOFile::ChainedFixupPointerOnDisk *fixup, uint16_t pointerFormat) {
-                assert(fixup->raw64 != 0);
-
-                uint64_t runtimeOffset = 0;
-                if ( fixup->isRebase(pointerFormat, objCVisitor.getOnDiskDylibChainedPointerBaseAddress().rawValue(), runtimeOffset) ) {
-                    // Rebase to a class in this image.
-                    objCCategoryInfo.classDylibIndex = cacheDylib.cacheIndex;
-                    objCCategoryInfo.classVMAddress = VMAddress(runtimeOffset);
-                } else {
-                    uint32_t bindOrdinal = 0;
-                    int64_t bindAddend = 0;
-                    if ( fixup->isBind(pointerFormat, bindOrdinal, bindAddend) ) {
-                        BindTarget& bindTarget = bindTargets[bindOrdinal];
-                        FoundSymbol foundSymbol = findTargetClass(diag, this->cacheDylibs,
-                                                                  bindTarget.symbolName,
-                                                                  bindTarget.targetDylibIndex);
-                        if ( foundSymbol.foundInDylib == nullptr ) {
-                            // Ignore category if class is missing. Usually due to a weak-link
-                            if ( !bindTarget.isWeakImport ) {
-                                this->warning("Class %s could not be found for category %s in %s.", bindTarget.symbolName.data(), objCCategoryInfo.name.data(), cacheDylib.installName.data());
-                            }
-                            return;
-                        }
-                        objCCategoryInfo.classVMAddress = VMAddress(foundSymbol.offsetInDylib.rawValue());
-                        objCCategoryInfo.classDylibIndex = foundSymbol.foundInDylib->cacheIndex;
-                    } else {
-                        assert(0);
-                    }
-                }
-            });
-
-            if ( !objCCategoryInfo.classVMAddress.has_value() )
-                return;
-
-            // instance methods
-            {
-                objc_visitor::MethodList objcMethodList = objcCategory.getInstanceMethods(objCVisitor);
-                uint32_t numMethods = objcMethodList.numMethods();
-                if ( numMethods > 0 )
-                    objCCategoryInfo.iMethodListVMAddress = objcMethodList.getVMAddress().value();
-            }
-            
-            // class methods
-            {
-                objc_visitor::MethodList objcMethodList = objcCategory.getClassMethods(objCVisitor);
-                uint32_t numMethods = objcMethodList.numMethods();
-                if ( numMethods > 0 )
-                    objCCategoryInfo.cMethodListVMAddress = objcMethodList.getVMAddress().value();
-            }
-
-            // protocols
-            {
-                objc_visitor::ProtocolList protocols = objcCategory.getProtocols(objCVisitor);
-                uint64_t numProtocols = protocols.numProtocols(objCVisitor);
-                if ( numProtocols > 0 )
-                    objCCategoryInfo.protocolListVMAddress = protocols.getVMAddress().value();
-            }
-
-            // instance properties
-            {
-                objc_visitor::PropertyList objcPropertyList = objcCategory.getInstanceProperties(objCVisitor);
-                uint64_t numProperties = objcPropertyList.numProperties();
-                if ( numProperties > 0 )
-                    objCCategoryInfo.iPropertyListVMAddress = objcPropertyList.getVMAddress().value();
-            }
-
-            // class properties
-            if ( categoriesHaveClassProperties ) {
-                objc_visitor::PropertyList objcPropertyList = objcCategory.getClassProperties(objCVisitor);
-                uint64_t numProperties = objcPropertyList.numProperties();
-                if ( numProperties > 0 )
-                    objCCategoryInfo.cPropertyListVMAddress = objcPropertyList.getVMAddress().value();
-            }
-            this->objcCategoryOptimizer.categories.push_back(std::move(objCCategoryInfo));
-        });
-        objcIndex++;
-    }
-
-    if ( this->config.log.printStats ) {
-        stats.add("  objc: found %lld categories\n", (uint64_t)this->objcCategoryOptimizer.categories.size());
-    }
-}
-
 static uint32_t hashTableSize(uint32_t maxElements, uint32_t perElementData)
 {
     uint32_t elementsWithPadding = maxElements * 11 / 10; // if close to power of 2, perfect hash may fail, so don't get within 10% of that
@@ -2012,71 +1807,6 @@
 
     if ( this->config.log.printStats ) {
         stats.add("  objc: canonical protocols size: %lld\n", (uint64_t)this->objcProtocolOptimizer.canonicalProtocolsTotalByteSize);
-    }
-}
-
-void SharedCacheBuilder::calculateObjCCategoriesSize()
-{
-    if ( this->objcOptimizer.objcDylibs.empty() )
-        return;
-
-    Stats        stats(this->config);
-    Timer::Scope timedScope(this->config, "calculateObjCCanonicalCategoriesSize time");
-
-    uint64_t sizeAndCountSize = sizeof(struct ListOfListsEntry);
-    uint64_t listEntrySize = sizeof(struct ListOfListsEntry);
-
-    // Add an empty method list that all lists of lists can use if the need it
-    // This is used for classes when they don't have method lists, but we want them to
-    // FIXME: Get the size from somewhere.  Its really the { uint32_t entsize; uint32_t count }
-    this->objcCategoryOptimizer.categoriesTotalByteSize += 8;
-
-    // This will allocate one listEntrySize for each category list.
-    // We might end up using less if the category does not extend a specific list.
-    // This will also allocate one listEntrySize and one sizeAndCountSize
-    // for every list in the original class.
-    // We might end up using less due to multiple categories extending the same class.
-    for ( auto& categoryInfo : this->objcCategoryOptimizer.categories ) {
-
-        if ( categoryInfo.iMethodListVMAddress.has_value()) {
-            // instance methods
-            this->objcCategoryOptimizer.categoriesTotalByteSize += sizeAndCountSize;
-            this->objcCategoryOptimizer.categoriesTotalByteSize += listEntrySize;
-            // original instance methods
-            this->objcCategoryOptimizer.categoriesTotalByteSize += listEntrySize;
-        }
-        if ( categoryInfo.cMethodListVMAddress.has_value()) {
-            // class methods
-            this->objcCategoryOptimizer.categoriesTotalByteSize += sizeAndCountSize;
-            this->objcCategoryOptimizer.categoriesTotalByteSize += listEntrySize;
-            // original class methods
-            this->objcCategoryOptimizer.categoriesTotalByteSize += listEntrySize;
-        }
-        if ( categoryInfo.protocolListVMAddress.has_value()) {
-            // protocols
-            this->objcCategoryOptimizer.categoriesTotalByteSize += sizeAndCountSize;
-            this->objcCategoryOptimizer.categoriesTotalByteSize += listEntrySize;
-            // original protocols
-            this->objcCategoryOptimizer.categoriesTotalByteSize += listEntrySize;
-        }
-        if ( categoryInfo.iPropertyListVMAddress.has_value()) {
-            // instance properties
-            this->objcCategoryOptimizer.categoriesTotalByteSize += sizeAndCountSize;
-            this->objcCategoryOptimizer.categoriesTotalByteSize += listEntrySize;
-            // original instance properties
-            this->objcCategoryOptimizer.categoriesTotalByteSize += listEntrySize;
-        }
-        if ( categoryInfo.cPropertyListVMAddress.has_value()) {
-            // instance properties
-            this->objcCategoryOptimizer.categoriesTotalByteSize += sizeAndCountSize;
-            this->objcCategoryOptimizer.categoriesTotalByteSize += listEntrySize;
-            // original class properties
-            this->objcCategoryOptimizer.categoriesTotalByteSize += listEntrySize;
-        }
-    }
-
-    if ( this->config.log.printStats ) {
-        stats.add("  objc: categories size: %lld\n", (uint64_t)this->objcCategoryOptimizer.categoriesTotalByteSize);
     }
 }
 
@@ -2318,7 +2048,7 @@
             size += cacheDylib.inputFile->path.size() + 1;
             size = alignTo(size, alignof(dyld4::Loader::LoaderRef));
             size += sizeof(dyld4::Loader::LoaderRef) * cacheDylib.dependents.size();
-            size += sizeof(Loader::DependentDylibAttributes) * cacheDylib.dependents.size();
+            size += sizeof(Loader::DependentKind) * cacheDylib.dependents.size();
             size += sizeof(Loader::FileValidationInfo);
             size += sizeof(Loader::Region) * cacheDylib.segments.size();
 
@@ -2433,7 +2163,16 @@
 void SharedCacheBuilder::computeSubCaches()
 {
     Timer::Scope timedScope(this->config, "computeSubCaches time");
-    computeLargeSubCache();
+
+    // We have 3 different kinds of caches.
+    // - regular: put everything in a single file
+    // - large: A file is (TEXT, DATA, LINKEDIT), and we might have > 1 file
+    // - split: A file is TEXT/DATA/LINKEDIT, and we've have 1 or more TEXT, and exactly 1 DATA and LINKEDIT
+    if ( config.layout.large.has_value() ) {
+        computeLargeSubCache();
+    } else {
+        computeRegularSubCache();
+    }
 }
 
 // ObjC/Swift optimizations produce arrays, hash tables, string sections, etc.
@@ -2446,17 +2185,11 @@
     // Add canonical objc protocols
     subCache.addObjCCanonicalProtocolsChunk(this->config, this->objcProtocolOptimizer);
 
-    // Add objc categories
-    subCache.addObjCCategoriesChunk(this->config, this->objcCategoryOptimizer);
-
     // Add objc opts header
     subCache.addObjCOptsHeaderChunk(this->objcOptimizer);
 
     // Add objc header info RO
     subCache.addObjCHeaderInfoReadOnlyChunk(this->objcOptimizer);
-
-    // Add objc image info
-    subCache.addObjCImageInfoChunk(this->objcOptimizer);
 
     // Add selector strings and hash table. These need to be adjacent as the table has offsets in
     // to the string section
@@ -2523,6 +2256,30 @@
 
     // Finalize the SubCache, by removing any unused regions
     subCache.removeEmptyRegions();
+}
+
+void SharedCacheBuilder::computeRegularSubCache()
+{
+    // Put everything into a single file.
+    SubCache subCache = SubCache::makeMainCache(this->options, true);
+
+    // Add all the objc tables.  This must be done before we add libobjc's __TEXT
+    this->addObjCOptimizationsToSubCache(subCache);
+
+    for ( CacheDylib& cacheDylib : this->cacheDylibs ) {
+        bool addLinkedit = true;
+        subCache.addDylib(cacheDylib, addLinkedit);
+    }
+
+    // Add all the global optimizations
+    this->addGlobalOptimizationsToSubCache(subCache);
+
+    // Reserve space in the last sub cache for dynamic config data
+    subCache.addDynamicConfigChunk();
+
+    this->addFinalChunksToSubCache(subCache);
+
+    this->subCaches.push_back(std::move(subCache));
 }
 
 // Add stubs Chunk's for every stubs section in the given text subCache
@@ -2742,7 +2499,7 @@
 
         // If we exceed the current limit, then the current subCache is complete and we need
         // to start a new one
-        if ( (subCacheTextSize + textSize) > this->config.layout.subCacheTextLimit ) {
+        if ( (subCacheTextSize + textSize) > this->config.layout.large->subCacheTextLimit ) {
             // Create a new subCache
             otherCaches.push_back(SubCache::makeSubCache(this->options));
             currentSubCache = &otherCaches.back();
@@ -2755,10 +2512,7 @@
 
         // The subCache with libobjc gets the header info sections
         // Add all the objc tables.  This must be done before we add libobjc's __TEXT
-        std::string_view libObjcInstallName = "/usr/lib/libobjc.A.dylib";
-        if ( dyld3::MachOFile::isExclaveKitPlatform(this->options.platform) )
-            libObjcInstallName = "/System/ExclaveKit/usr/lib/libobjc.A.dylib";
-        if ( cacheDylib.installName == libObjcInstallName )
+        if ( cacheDylib.installName == "/usr/lib/libobjc.A.dylib" )
             this->addObjCOptimizationsToSubCache(*currentSubCache);
 
         // We'll add LINKEDIT at the end.  As the shared region is <= 4GB in size, we can fit
@@ -3609,7 +3363,7 @@
 Error SharedCacheBuilder::calculateUniqueGOTs()
 {
     // Skip this optimiation on simulator until we've qualified it there
-    if ( this->options.isSimulator() )
+    if ( this->options.isSimultor() )
         return Error();
 
     Stats        stats(this->config);
@@ -3888,9 +3642,6 @@
                 break;
             case cache_builder::SlideInfo::SlideInfoFormat::v3:
                 slideInfoSize += sizeof(dyld_cache_slide_info3);
-                break;
-            case cache_builder::SlideInfo::SlideInfoFormat::v5:
-                slideInfoSize += sizeof(dyld_cache_slide_info5);
                 break;
         }
         slideInfoSize += pagesToSlide * builderConfig.slideInfo.slideInfoBytesPerDataPage;
@@ -3933,7 +3684,7 @@
 
 void SharedCacheBuilder::printSubCaches() const
 {
-    const bool printSegments = this->config.log.printDebug;
+    const bool printSegments = false;
 
     if ( !this->config.log.printStats )
         return;
@@ -4111,6 +3862,106 @@
         assert(this->totalVMSize == totalCustomerCacheSize);
     }
 
+    if ( this->totalVMSize > this->config.layout.cacheSize ) {
+        return Error("Cache overflow (0x%llx > 0x%llx)",
+                     this->totalVMSize.rawValue(),
+                     this->config.layout.cacheSize.rawValue());
+    }
+
+    return Error();
+}
+
+// This is the x86_64 sim layout, where each of TEXT/DATA/LINKEDIT has its own fixed address
+Error SharedCacheBuilder::computeSubCacheDiscontiguousSimVMLayout()
+{
+    // Add padding between each region, and set the Region VMAddr's
+    CacheVMAddress maxVMAddress = this->config.layout.cacheBaseAddress;
+    assert(this->subCaches.size() == 1);
+    SubCache& subCache = this->subCaches.front();
+    subCache.subCacheVMAddress = this->config.layout.cacheBaseAddress;
+
+    bool seenText = false;
+    bool seenData = false;
+    bool seenLinkedit = false;
+    bool seenDynamicConfig = false;
+    CacheVMAddress lastDataEnd;
+    CacheVMAddress linkEditEnd;
+    for ( Region& region : subCache.regions ) {
+        switch ( region.kind ) {
+            case Region::Kind::text:
+                assert(!seenText);
+                seenText = true;
+                region.subCacheVMAddress = this->config.layout.discontiguous->simTextBaseAddress;
+
+                // Check for overflow
+                if ( region.subCacheVMSize > this->config.layout.discontiguous->simTextSize ) {
+                    return Error("Overflow in text (0x%llx > 0x%llx)",
+                                 region.subCacheVMSize.rawValue(),
+                                 this->config.layout.discontiguous->simTextSize.rawValue());
+                }
+                break;
+            case Region::Kind::dataConst:
+            case Region::Kind::data:
+            case Region::Kind::auth:
+            case Region::Kind::authConst:
+                if ( seenData ) {
+                    // This data follows from the previous one
+                    region.subCacheVMAddress = lastDataEnd;
+                } else {
+                    seenData = true;
+                    region.subCacheVMAddress = this->config.layout.discontiguous->simDataBaseAddress;
+                }
+                lastDataEnd = region.subCacheVMAddress + region.subCacheVMSize;
+                break;
+            case Region::Kind::linkedit:
+                assert(!seenLinkedit);
+                seenLinkedit = true;
+                region.subCacheVMAddress = this->config.layout.discontiguous->simLinkeditBaseAddress;
+
+                // Check for overflow
+                if ( region.subCacheVMSize > this->config.layout.discontiguous->simLinkeditSize ) {
+                    return Error("Overflow in linkedit (0x%llx > 0x%llx)",
+                                 region.subCacheVMSize.rawValue(),
+                                 this->config.layout.discontiguous->simLinkeditSize.rawValue());
+                }
+                linkEditEnd = region.subCacheVMAddress + region.subCacheVMSize;
+                break;
+            case Region::Kind::dynamicConfig:
+                assert(!seenDynamicConfig);
+                seenDynamicConfig = true;
+                // Grab space right after the linkedit
+                region.subCacheVMAddress = linkEditEnd;
+                // Check for overflow
+                if ( region.subCacheVMSize > this->config.layout.discontiguous->simLinkeditSize ) {
+                    return Error("Overflow in dynamicConfig (0x%llx > 0x%llx)",
+                                 region.subCacheVMSize.rawValue(),
+                                 this->config.layout.discontiguous->simLinkeditSize.rawValue());
+                }
+                break;
+            case Region::Kind::unmapped:
+            case Region::Kind::codeSignature:
+                break;
+            case Region::Kind::numKinds:
+                assert(0);
+                break;
+        }
+
+        if ( seenData ) {
+            // Check for overflow
+            CacheVMSize dataSize(lastDataEnd.rawValue() - this->config.layout.discontiguous->simDataBaseAddress.rawValue());
+            if ( dataSize > this->config.layout.discontiguous->simDataSize ) {
+                return Error("Overflow in data (0x%llx > 0x%llx)",
+                             dataSize.rawValue(),
+                             this->config.layout.discontiguous->simDataSize.rawValue());
+            }
+        }
+
+        if ( region.needsSharedCacheReserveAddressSpace() )
+            maxVMAddress = region.subCacheVMAddress + region.subCacheVMSize;
+    }
+
+    this->totalVMSize = CacheVMSize((maxVMAddress - this->config.layout.cacheBaseAddress).rawValue());
+
     return Error();
 }
 
@@ -4200,105 +4051,14 @@
     }
 
     this->totalVMSize = CacheVMSize((vmAddress - this->config.layout.cacheBaseAddress).rawValue());
+    
+    if ( this->totalVMSize > this->config.layout.cacheSize ) {
+        return Error("Cache overflow (0x%llx > 0x%llx)",
+                     this->totalVMSize.rawValue(),
+                     this->config.layout.cacheSize.rawValue());
+    }
 
     return Error();
-}
-
-void SharedCacheBuilder::evictLeafDylibs(CacheVMSize reductionTarget)
-{
-    // build a reverse map of all dylib dependencies
-    std::unordered_map<std::string_view, std::unordered_set<std::string_view>> references;
-    // Ensure we have an entry (even if it is empty)
-    for ( const CacheDylib& cacheDylib : cacheDylibs )
-        references[cacheDylib.installName] = { };
-    
-    for ( const CacheDylib& cacheDylib : cacheDylibs ) {
-        for ( const CacheDylib::DependentDylib& depDylib : cacheDylib.dependents ) {
-            // Skip missing weak links
-            if ( depDylib.dylib == nullptr )
-                continue;
-            references[depDylib.dylib->installName].insert(cacheDylib.installName);
-        }
-    }
-
-    struct DylibAndSize
-    {
-        CacheDylib* dylib;
-        CacheVMSize size;
-    };
-
-    // Find the sizes of all the dylibs
-    std::vector<DylibAndSize> dylibsToSort;
-    for ( CacheDylib& cacheDylib : cacheDylibs ) {
-        CacheVMSize segsSize = CacheVMSize(0ULL);
-        for ( const DylibSegmentChunk& segment : cacheDylib.segments ) {
-            if ( segment.segmentName == "__LINKEDIT" )
-                continue;
-
-            segsSize += segment.cacheVMSize;
-        }
-        dylibsToSort.push_back({ &cacheDylib, segsSize });
-    }
-
-    // Build an ordered list of what to remove. At each step we do following
-    // 1) Find all dylibs that nothing else depends on
-    // 2a) If any of those dylibs are not in the order select the largest one of them
-    // 2b) If all the leaf dylibs are in the order file select the last dylib that appears last in the order file
-    // 3) Remove all entries to the removed file from the reverse dependency map
-    // 4) Go back to one and repeat until there are no more evictable dylibs
-    // This results in us always choosing the locally optimal selection, and then taking into account how that impacts
-    // the dependency graph for subsequent selections
-
-    std::vector<DylibAndSize> sortedDylibs;
-    bool candidateFound = true;
-    while ( candidateFound ) {
-        candidateFound = false;
-        DylibAndSize candidate;
-        uint64_t candidateOrder = 0;
-        for( const auto& dylib : dylibsToSort ) {
-            const auto& dylibRefs = references.at(dylib.dylib->installName);
-            if ( !dylibRefs.empty())
-                continue;
-
-            const auto& j = options.dylibOrdering.find(std::string(dylib.dylib->installName));
-            uint64_t order = 0;
-            if ( j != options.dylibOrdering.end() ) {
-                order = j->second;
-            } else {
-                // Not in the order file, set order sot it goes to the front of the list
-                order = UINT64_MAX;
-            }
-            if ( order > candidateOrder || (order == UINT64_MAX && candidate.size < dylib.size) ) {
-                // The new file is either a lower priority in the order file
-                // or the same priority as the candidate but larger
-                candidate = dylib;
-                candidateOrder = order;
-                candidateFound = true;
-            }
-        }
-        if (candidateFound) {
-            sortedDylibs.push_back(candidate);
-            references.erase(candidate.dylib->installName);
-            for (auto& dependent : references) {
-                (void)dependent.second.erase(candidate.dylib->installName);
-            }
-            auto j = std::find_if(dylibsToSort.begin(), dylibsToSort.end(),
-                                  [&candidate](const DylibAndSize& dylib) {
-                return candidate.dylib->installName == dylib.dylib->installName;
-            });
-            if ( j != dylibsToSort.end() ) {
-                dylibsToSort.erase(j);
-            }
-        }
-    }
-
-     // build set of dylibs that if removed will allow cache to build
-    for ( DylibAndSize& dylib : sortedDylibs ) {
-        this->evictedDylibs.push_back(dylib.dylib->inputFile->path);
-        if ( dylib.size > reductionTarget )
-            break;
-        reductionTarget -= dylib.size;
-    }
 }
 
 // In file layout, we need each Region to start page-aligned.  Within a Region, we can pack pages
@@ -4377,15 +4137,13 @@
         if ( Error error = computeSubCacheContiguousVMLayout(); error.hasError() )
             return error;
     } else {
-        if ( Error error = computeSubCacheDiscontiguousVMLayout(); error.hasError() )
-            return error;
-    }
-    
-    if ( this->totalVMSize > this->config.layout.cacheSize ) {
-        evictLeafDylibs(this->totalVMSize - this->config.layout.cacheSize);
-        return Error("Cache overflow (0x%llx > 0x%llx)",
-                     this->totalVMSize.rawValue(),
-                     this->config.layout.cacheSize.rawValue());
+        if ( this->options.isSimultor() ) {
+            if ( Error error = computeSubCacheDiscontiguousSimVMLayout(); error.hasError() )
+                return error;
+        } else {
+            if ( Error error = computeSubCacheDiscontiguousVMLayout(); error.hasError() )
+                return error;
+        }
     }
 
     // Update Section VMAddr's now that we know where all the Region's are in memory
@@ -4402,12 +4160,18 @@
         }
     }
 
+    if ( this->totalVMSize > this->config.layout.cacheSize ) {
+        return Error("Cache overflow (0x%llx > 0x%llx)",
+                     this->totalVMSize.rawValue(),
+                     this->config.layout.cacheSize.rawValue());
+    }
+
     return Error();
 }
 
 Error SharedCacheBuilder::allocateSubCacheBuffers()
 {
-    const bool log = this->options.debug;
+    const bool log = false;
 
     Timer::Scope timedScope(this->config, "allocateSubCacheBuffers time");
 
@@ -4723,76 +4487,6 @@
     }
 }
 
-void SharedCacheBuilder::adjustObjCCategories()
-{
-    if ( this->objcOptimizer.objcDylibs.empty() )
-        return;
-
-    Timer::Scope timedScope(this->config, "adjustObjCCategories time");
-
-    // Categories were stored as input dylib VMAddr's.  Convert to cache dylib VMAddr's
-    for ( auto& categoryInfo : this->objcCategoryOptimizer.categories ) {
-        CacheDylib* cacheDylib =  this->objcOptimizer.objcDylibs[categoryInfo.dylibObjcIndex.value()];
-
-        // category address
-        if ( categoryInfo.vmAddress.has_value() ) {
-            uint64_t inputAddr = categoryInfo.vmAddress.value().rawValue();
-            InputDylibVMAddress inputVMAddr(inputAddr);
-            CacheVMAddress cacheVMAddr = cacheDylib->adjustor->adjustVMAddr(inputVMAddr);
-            categoryInfo.vmAddress = VMAddress(cacheVMAddr.rawValue());
-        }
-
-        // class address
-        if ( categoryInfo.classVMAddress.has_value() ) {
-            CacheDylib& classDylib = this->cacheDylibs[categoryInfo.classDylibIndex.value()];
-            uint64_t inputAddr = categoryInfo.classVMAddress.value().rawValue();
-            InputDylibVMAddress inputVMAddr(inputAddr);
-            CacheVMAddress cacheVMAddr = classDylib.adjustor->adjustVMAddr(inputVMAddr);
-            categoryInfo.classVMAddress = VMAddress(cacheVMAddr.rawValue());
-        }
-
-        // instance methods
-        if ( categoryInfo.iMethodListVMAddress.has_value() ) {
-            uint64_t inputAddr = categoryInfo.iMethodListVMAddress.value().rawValue();
-            InputDylibVMAddress inputVMAddr(inputAddr);
-            CacheVMAddress cacheVMAddr = cacheDylib->adjustor->adjustVMAddr(inputVMAddr);
-            categoryInfo.iMethodListVMAddress = VMAddress(cacheVMAddr.rawValue());
-        }
-
-        // class methods
-        if ( categoryInfo.cMethodListVMAddress.has_value() ) {
-            uint64_t inputAddr = categoryInfo.cMethodListVMAddress.value().rawValue();
-            InputDylibVMAddress inputVMAddr(inputAddr);
-            CacheVMAddress cacheVMAddr = cacheDylib->adjustor->adjustVMAddr(inputVMAddr);
-            categoryInfo.cMethodListVMAddress = VMAddress(cacheVMAddr.rawValue());
-        }
-
-        // protocols
-        if ( categoryInfo.protocolListVMAddress.has_value() ) {
-            uint64_t inputAddr = categoryInfo.protocolListVMAddress.value().rawValue();
-            InputDylibVMAddress inputVMAddr(inputAddr);
-            CacheVMAddress cacheVMAddr = cacheDylib->adjustor->adjustVMAddr(inputVMAddr);
-            categoryInfo.protocolListVMAddress = VMAddress(cacheVMAddr.rawValue());
-        }
-
-        // instance properties
-        if ( categoryInfo.iPropertyListVMAddress.has_value() ) {
-            uint64_t inputAddr = categoryInfo.iPropertyListVMAddress.value().rawValue();
-            InputDylibVMAddress inputVMAddr(inputAddr);
-            CacheVMAddress cacheVMAddr = cacheDylib->adjustor->adjustVMAddr(inputVMAddr);
-            categoryInfo.iPropertyListVMAddress = VMAddress(cacheVMAddr.rawValue());
-        }
-
-        // class properties
-        if ( categoryInfo.cPropertyListVMAddress.has_value() ) {
-            uint64_t inputAddr = categoryInfo.cPropertyListVMAddress.value().rawValue();
-            InputDylibVMAddress inputVMAddr(inputAddr);
-            CacheVMAddress cacheVMAddr = cacheDylib->adjustor->adjustVMAddr(inputVMAddr);
-            categoryInfo.cPropertyListVMAddress = VMAddress(cacheVMAddr.rawValue());
-        }
-    }
-}
-
 Error SharedCacheBuilder::emitPatchTable()
 {
     Stats        stats(this->config);
@@ -4801,7 +4495,7 @@
     // Skip this optimization on simulator until we've qualified it there
     __block PatchTableBuilder::PatchableClassesSet      patchableObjCClasses;
     __block PatchTableBuilder::PatchableSingletonsSet   patchableCFObj2;
-    if ( !this->options.isSimulator() ) {
+    if ( !this->options.isSimultor() ) {
         for ( const CacheDylib& cacheDylib : this->cacheDylibs ) {
             __block objc_visitor::Visitor objcVisitor = makeInputDylibObjCVisitor(cacheDylib);
             objcVisitor.forEachClassAndMetaClass(^(const objc_visitor::Class& objcClass, bool& stopClass) {
@@ -4847,21 +4541,13 @@
 // dyld4 needs a fake "main.exe" to set up the state.
 // On macOS this *has* to come from an actual executable, as choosing a zippered
 // dylib may incorrectly lead to setting up the ProcessConfig as iOSMac.
-// Simulators and ExclaveKit don't have executables yet so choose a dylib there
+// Simulators don't have executables yet so choose a dylib there
 static const MachOFile* getFakeMainExecutable(const BuilderOptions& options,
                                               std::span<CacheDylib> cacheDylibs,
                                               std::span<InputFile*> executableFiles)
 {
-    if ( options.isSimulator() ) {
+    if ( options.isSimultor() ) {
         std::string_view installName = "/usr/lib/libSystem.B.dylib";
-        for ( const CacheDylib& cacheDylib : cacheDylibs ) {
-            if ( cacheDylib.installName == installName ) {
-                assert(cacheDylib.cacheMF != nullptr);
-                return cacheDylib.cacheMF;
-            }
-        }
-    } else if (options.isExclaveKit() ) {
-        std::string_view installName = "/System/ExclaveKit/usr/lib/libSystem.dylib";
         for ( const CacheDylib& cacheDylib : cacheDylibs ) {
             if ( cacheDylib.installName == installName ) {
                 assert(cacheDylib.cacheMF != nullptr);
@@ -5175,8 +4861,7 @@
     SyscallDelegate    osDelegate;
     EphemeralAllocator alloc;
     ProcessConfig      processConfig(&kernArgs, osDelegate, alloc);
-    RuntimeLocks       locks;
-    RuntimeState       state(processConfig, locks, alloc);
+    RuntimeState       state(processConfig, alloc);
 
     // FIXME: This is terrible and needs to be a real reset method
     processConfig.dyldCache.cacheBuilderDylibs = &processConfigDylibs;
@@ -5232,7 +4917,7 @@
                                VMAddress& protocolClassVMAddr, MachOFile::PointerMetaData& protocolClassPMD)
 {
     for ( CacheDylib* cacheDylib : objcDylibs ) {
-        if ( cacheDylib->installName.ends_with("/usr/lib/libobjc.A.dylib" )) {
+        if ( cacheDylib->installName == "/usr/lib/libobjc.A.dylib" ) {
             __block InputDylibVMAddress inputOptPtrsVMAddress;
             __block uint64_t            sectionSize = 0;
             __block bool                found       = false;
@@ -5266,7 +4951,7 @@
 
             CacheVMAddress cacheOptPtrsVMAddr = cacheDylib->adjustor->adjustVMAddr(inputOptPtrsVMAddress);
 
-            objc_visitor::Visitor objcVisitor = cacheDylib->makeCacheObjCVisitor(config, nullptr, nullptr, nullptr);
+            objc_visitor::Visitor objcVisitor = cacheDylib->makeCacheObjCVisitor(config, nullptr, nullptr);
 
             metadata_visitor::ResolvedValue protocolClassValue = objcVisitor.getValueFor(VMAddress(cacheOptPtrsVMAddr.rawValue()));
             protocolClassVMAddr                           = objcVisitor.resolveRebase(protocolClassValue).vmAddress();
@@ -5394,8 +5079,7 @@
         //osDelegate._dyldCache           = dyldCache;
         EphemeralAllocator alloc;
         ProcessConfig      processConfig(&kernArgs, osDelegate, alloc);
-        RuntimeLocks       locks;
-        RuntimeState       state(processConfig, locks, alloc);
+        RuntimeState       state(processConfig, alloc);
         RuntimeState*      statePtr = &state;
         Diagnostics        launchDiag;
 
@@ -5739,9 +5423,9 @@
     Diagnostics diag;
 
     // Find the subCache with the hash tables
-    cache_builder::ObjCSelectorHashTableChunk* selectorsHashTable  = nullptr;
-    cache_builder::ObjCClassHashTableChunk*    classesHashTable    = nullptr;
-    cache_builder::ObjCProtocolHashTableChunk* protocolsHashTable  = nullptr;
+    cache_builder::ObjCSelectorHashTableChunk* selectorsHashTable = nullptr;
+    cache_builder::ObjCClassHashTableChunk*    classesHashTable   = nullptr;
+    cache_builder::ObjCProtocolHashTableChunk* protocolsHashTable = nullptr;
     for ( SubCache& subCache : this->subCaches ) {
         if ( subCache.objcSelectorsHashTable ) {
             assert(selectorsHashTable == nullptr);
@@ -5818,32 +5502,15 @@
 
     Timer::Scope timedScope(this->config, "emitObjCHeaderInfo time");
 
-    // We need the prebuilt loaders from the cache dylibs as they contain SectionLocations.
-    auto* cachedDylibsLoaderSet = this->prebuiltLoaderBuilder.cachedDylibsLoaderSet;
-    assert(cachedDylibsLoaderSet != nullptr);
-
     // Emit header info RO
     auto* readOnlyList    = (ObjCOptimizer::header_info_ro_list_t*)this->objcOptimizer.headerInfoReadOnlyChunk->subCacheBuffer;
     readOnlyList->count   = (uint32_t)this->objcOptimizer.objcDylibs.size();
     readOnlyList->entsize = this->config.layout.is64 ? sizeof(ObjCOptimizer::header_info_ro_64_t) : sizeof(ObjCOptimizer::header_info_ro_32_t);
 
-    // We're also going to populate the objc image info array with the image infos we have here
-    auto* imageInfoArray   = (objc::objc_image_info*)this->objcOptimizer.imageInfoChunk->subCacheBuffer;
-    CacheVMAddress cacheImageInfoBaseAddress = this->objcOptimizer.imageInfoChunk->cacheVMAddress;
-    assert(this->objcOptimizer.imageInfoChunk->subCacheFileSize.rawValue() == (readOnlyList->count * sizeof(objc::objc_image_info)));
-
     for ( uint32_t i = 0; i != readOnlyList->count; ++i ) {
         CacheDylib& cacheDylib = *this->objcOptimizer.objcDylibs[i];
 
-        const uint64_t dyldCategoriesOptimizedFlag = (1 << 0);
-        const uint64_t optimizedByDyldFlag         = (1 << 3);
-
-        // We want the headerinfo_ro_t to point to the imageInfo in the contiguous buffer
-        // not the imageinfo in the original dylib
-        // Note: We only have standard (8-byte) image infos in the array right now.  We'll ignore
-        // the array for any elements which use a different sized image info.
-        __block CacheVMAddress cacheImageInfoAddress = cacheImageInfoBaseAddress + VMOffset((uint64_t)i * sizeof(objc::objc_image_info));
-
+        __block CacheVMAddress  cacheImageInfoAddress;
         __block uint8_t*        cacheImageInfoBuffer = nullptr;
         cacheDylib.forEachCacheSection(^(std::string_view segmentName, std::string_view sectionName,
                                          uint8_t* sectionBuffer, CacheVMAddress sectionVMAddr,
@@ -5853,11 +5520,7 @@
             if ( sectionName != "__objc_imageinfo" )
                 return;
 
-            if ( sectionVMSize.rawValue() != sizeof(objc::objc_image_info) ) {
-                // Skip the optimized array and use this element directly
-                cacheImageInfoAddress = sectionVMAddr;
-            }
-
+            cacheImageInfoAddress = sectionVMAddr;
             cacheImageInfoBuffer = sectionBuffer;
             stop = true;
         });
@@ -5866,12 +5529,6 @@
 
         void*          arrayElement     = &readOnlyList->arrayBase[0] + (i * readOnlyList->entsize);
         CacheVMAddress machHeaderVMAddr = cacheDylib.cacheLoadAddress;
-
-        // Get the PrebuiltLoader* for this cache dylib
-        const PrebuiltLoader* ldr = cachedDylibsLoaderSet->atIndex(cacheDylib.cacheIndex);
-        assert(ldr->path() == cacheDylib.installName);
-
-        CacheVMAddress ldrVMAddr = getVMAddressInSection(*this->prebuiltLoaderBuilder.cacheDylibsLoaderChunk, ldr);
 
         if ( this->config.layout.is64 ) {
             ObjCOptimizer::header_info_ro_64_t* element = (ObjCOptimizer::header_info_ro_64_t*)arrayElement;
@@ -5889,13 +5546,6 @@
             element->info_offset           = infoOffset;
             // Check for truncation
             assert(element->info_offset == infoOffset);
-
-            // metadata_offset
-            CacheVMAddress metadataOffsetVMAddr = getVMAddressInSection(*this->objcOptimizer.headerInfoReadOnlyChunk, &element->metadata_offset);
-            int64_t metadataOffset         = ldrVMAddr.rawValue() - metadataOffsetVMAddr.rawValue();
-            element->metadata_offset       = metadataOffset;
-            // Check for truncation
-            assert(element->metadata_offset == metadataOffset);
         }
         else {
             ObjCOptimizer::header_info_ro_32_t* element = (ObjCOptimizer::header_info_ro_32_t*)arrayElement;
@@ -5913,25 +5563,15 @@
             element->info_offset           = (int32_t)infoOffset;
             // Check for truncation
             assert(element->info_offset == infoOffset);
-
-            // metadata_offset
-            CacheVMAddress metadataOffsetVMAddr = getVMAddressInSection(*this->objcOptimizer.headerInfoReadOnlyChunk, &element->metadata_offset);
-            int64_t       metadataOffset   = ldrVMAddr.rawValue() - metadataOffsetVMAddr.rawValue();
-            element->metadata_offset       = (int32_t)metadataOffset;
-            // Check for truncation
-            assert(element->metadata_offset == metadataOffset);
         }
 
         // Set the dylib to be optimized, which lets it use this header info
-        objc::objc_image_info* info = (objc::objc_image_info*)cacheImageInfoBuffer;
-        info->flags |= optimizedByDyldFlag;
-        if ( this->objcCategoryOptimizer.preAttachedDylibs.contains(i) ) {
-            if ( !this->objcCategoryOptimizer.excludedDylibs.contains(i) )
-                info->flags |= dyldCategoriesOptimizedFlag;
-        }
-
-        // Also copy in to the contiguous space
-        memcpy(&imageInfoArray[i], info, sizeof(objc::objc_image_info));
+        struct objc_image_info {
+            int32_t version;
+            uint32_t flags;
+        };
+        objc_image_info* info = (objc_image_info*)cacheImageInfoBuffer;
+        info->flags = info->flags | (1 << 3);
     }
 
     // Emit header info RW
@@ -6457,8 +6097,7 @@
 
     for ( CacheDylib* cacheDylib : this->objcOptimizer.objcDylibs ) {
         objcVisitors.push_back(cacheDylib->makeCacheObjCVisitor(config, nullptr,
-                                                                this->objcProtocolOptimizer.canonicalProtocolsChunk,
-                                                                this->objcCategoryOptimizer.categoriesChunk));
+                                                                this->objcProtocolOptimizer.canonicalProtocolsChunk));
     }
 
     // The offset in the protocol buffer for the next protocol to emit
@@ -6624,7 +6263,7 @@
     objcVisitors.reserve(this->cacheDylibs.size());
 
     for ( CacheDylib& cacheDylib : this->cacheDylibs ) {
-        objcVisitors.push_back(cacheDylib.makeCacheObjCVisitor(config, nullptr, nullptr, nullptr));
+        objcVisitors.push_back(cacheDylib.makeCacheObjCVisitor(config, nullptr, nullptr));
     }
 
     // Check for missing superclasses, but only error on customer/universal caches
@@ -6788,396 +6427,6 @@
             objcClass.setInstanceStart(*classInfo->objcVisitor, objcClass.getInstanceStart(*classInfo->objcVisitor) + diff);
             objcClass.setInstanceSize(*classInfo->objcVisitor, objcClass.getInstanceSize(*classInfo->objcVisitor) + diff);
         }
-    }
-
-    return Error();
-}
-
-Error SharedCacheBuilder::emitPreAttachedObjCCategories()
-{
-    
-    if ( this->objcOptimizer.objcDylibs.empty() )
-        return Error();
-
-    Timer::Scope timedScope(this->config, "dylib emitPreAttachedObjCCategories time");
-
-    ObjCPreAttachedCategoriesChunk* categoriesChunk = this->objcCategoryOptimizer.categoriesChunk;
-
-    // Build reverse map of categories for fast lookup during optimization
-    __block std::unordered_multimap<size_t, uint64_t> dylibsToClasses;
-    __block std::unordered_multimap<uint64_t, const ObjCCategoryOptimizer::Category*> classMap;
-    __block std::unordered_multimap<uint64_t, const ObjCCategoryOptimizer::Category*> metaClassMap;
-    auto& categories = this->objcCategoryOptimizer.categories;
-    for (size_t i = 0; i < categories.size(); i++) {
-        dylibsToClasses.insert({ categories[i].classDylibIndex.value(), categories[i].classVMAddress.value().rawValue() });
-        classMap.insert({ categories[i].classVMAddress.value().rawValue(), &categories[i] });
-    }
-
-    __block dyld3::MachOFile::PointerMetaData methodListPMD;
-    if ( this->config.layout.hasAuthRegion ) {
-        methodListPMD.diversity         = 0xC310;
-        methodListPMD.high8             = 0;
-        methodListPMD.authenticated     = 1;
-        methodListPMD.key               = ptrauth_key_asda;
-        methodListPMD.usesAddrDiversity = 1;
-    }
-
-    __block uint64_t chunkOffset = 0;
-
-    // The very first thing we want to do is make an empty class method list
-    // All classes which have no method lists will have this added as the last entry
-    // in their list
-    assert(categoriesChunk->subCacheFileSize.rawValue() >= 8);
-    void* emptyMethodListLocation = categoriesChunk->subCacheBuffer;
-    CacheVMAddress emptyMethodListVMAddr = categoriesChunk->cacheVMAddress;
-    chunkOffset += objc_visitor::MethodList::makeEmptyMethodList(emptyMethodListLocation);
-
-    auto visitCategoryMetaClass = ^(objc_visitor::Visitor &objCVisitor,
-                                size_t classCacheIndex, size_t classObjcIndex,
-                                objc_visitor::Class &objcClass,
-                                std::span<DylibSegmentChunk> cacheDylibSegments) {
-        auto metaClassRange = metaClassMap.equal_range(objcClass.getVMAddress().rawValue());
-        if ( metaClassRange.first == metaClassRange.second )
-            return;
-
-        //fprintf(stderr, "meta class %s %s 0x%llx\n", this->cacheDylibs[classCacheIndex].installName.data(), objcClass.getName(objCVisitor), objcClass.getVMAddress().rawValue());
-        // class method lists
-        {
-            uint8_t* listBuffer = categoriesChunk->subCacheBuffer + chunkOffset;
-            uint64_t firstEntryOffset = chunkOffset;
-            ListOfListsEntry* listHeader = (ListOfListsEntry*)listBuffer;
-            ListOfListsEntry* listEntry = listHeader + 1;
-
-            bool createdNewList = false;
-            for (auto classEntry = metaClassRange.first; classEntry != metaClassRange.second; classEntry++) {
-                const ObjCCategoryOptimizer::Category* category = classEntry->second;
-                if ( !category->cMethodListVMAddress.has_value() )
-                    continue;
-
-                //fprintf(stderr, "  meta cat %s 0x%llx %s (%llu) => %zu\n", category->name.data(), category->vmAddress.value().rawValue(), this->objcOptimizer.objcDylibs[category->dylibObjcIndex.value()]->installName.data(), category->dylibObjcIndex.value(), classObjcIndex);
-                if ( !createdNewList ) {
-                    createdNewList = true;
-                    // New list with count and entry size
-                    listHeader->entsize = sizeof(ListOfListsEntry);
-                    listHeader->count = 0;
-                    chunkOffset += sizeof(ListOfListsEntry);
-                }
-
-                listEntry->imageIndex = category->dylibObjcIndex.value();
-                int64_t destVMAddr = (int64_t)category->cMethodListVMAddress.value().rawValue();
-                listEntry->offset = destVMAddr - getVMAddressInSection(*categoriesChunk, listEntry).rawValue();
-                listEntry++;
-                listHeader->count++;
-                chunkOffset += sizeof(ListOfListsEntry);
-            }
-
-            if ( createdNewList ) {
-                objc_visitor::MethodList methodList = objcClass.getBaseMethods(objCVisitor);
-                // add original method list
-                if ( methodList.numMethods() > 0 ) {
-                    listEntry->imageIndex = classObjcIndex;
-                    int64_t destVMAddr = (int64_t)methodList.getVMAddress().value().rawValue();
-                    listEntry->offset = destVMAddr - getVMAddressInSection(*categoriesChunk, listEntry).rawValue();
-                } else {
-                    // add an empty list entry if no original was found
-                    listEntry->imageIndex = classObjcIndex;
-                    int64_t destVMAddr = (int64_t)emptyMethodListVMAddr.rawValue();
-                    listEntry->offset = destVMAddr - getVMAddressInSection(*categoriesChunk, listEntry).rawValue();
-                }
-                listEntry++;
-                listHeader->count++;
-                chunkOffset += sizeof(ListOfListsEntry);
-                //fprintf(stderr, "Total cMethod list lists: %d\n", listHeader->count);
-                // store new base methods pointer to update
-                CacheVMAddress newBaseMethods = categoriesChunk->cacheVMAddress + VMOffset(firstEntryOffset);
-                metadata_visitor::ResolvedValue field = objcClass.setBaseMethodsVMAddr(objCVisitor,
-                                                                                       VMAddress(newBaseMethods.rawValue() | 1),
-                                                                                       methodListPMD);
-                cacheDylibSegments[field.segmentIndex()].tracker.add(field.value());
-            }
-        }
-        // class property lists
-        {
-            uint8_t* listBuffer = categoriesChunk->subCacheBuffer + chunkOffset;
-            uint64_t firstEntryOffset = chunkOffset;
-            ListOfListsEntry* listHeader = (ListOfListsEntry*)listBuffer;
-            ListOfListsEntry* listEntry = listHeader + 1;
-
-            bool createdNewList = false;
-            for (auto classEntry = metaClassRange.first; classEntry != metaClassRange.second; classEntry++) {
-                const ObjCCategoryOptimizer::Category* category = classEntry->second;
-                if ( !category->cPropertyListVMAddress.has_value() )
-                    continue;
-
-                //fprintf(stderr, "  meta cat %s 0x%llx %s (%llu) => %zu\n", category->name.data(), category->vmAddress.value().rawValue(), this->objcOptimizer.objcDylibs[category->dylibObjcIndex.value()]->installName.data(), category->dylibObjcIndex.value(), classObjcIndex);
-                if ( !createdNewList ) {
-                    createdNewList = true;
-                    // New list with count and entry size
-                    listHeader->entsize = sizeof(ListOfListsEntry);
-                    listHeader->count = 0;
-                    chunkOffset += sizeof(ListOfListsEntry);
-                }
-
-                listEntry->imageIndex = category->dylibObjcIndex.value();
-                int64_t destVMAddr = (int64_t)category->cPropertyListVMAddress.value().rawValue();
-                listEntry->offset = destVMAddr - getVMAddressInSection(*categoriesChunk, listEntry).rawValue();
-                listEntry++;
-                listHeader->count++;
-                chunkOffset += sizeof(ListOfListsEntry);
-            }
-
-            if ( createdNewList ) {
-                objc_visitor::PropertyList propertyList = objcClass.getBaseProperties(objCVisitor);
-                // add original method list
-                if ( propertyList.numProperties() > 0 ) {
-                    listEntry->imageIndex = classObjcIndex;
-                    int64_t destVMAddr = (int64_t)propertyList.getVMAddress().value().rawValue();
-                    listEntry->offset = destVMAddr - getVMAddressInSection(*categoriesChunk, listEntry).rawValue();
-                } else {
-                    // add an empty list entry if no original was found
-                    // Zeroing the entry will make the offset point to itself
-                    // That will then be interpreted as a ListOfListsEntry of count 0
-                    // This also means that the image at index 0 needs to always be libobjc.A.dylib
-                    listEntry->imageIndex = 0;
-                    listEntry->offset = 0;
-                }
-                listEntry++;
-                listHeader->count++;
-                chunkOffset += sizeof(ListOfListsEntry);
-
-                //fprintf(stderr, "Total cProperty list lists: %d\n", listHeader->count);
-                // store new base properties pointer to update
-                CacheVMAddress newBaseProperties = categoriesChunk->cacheVMAddress + VMOffset(firstEntryOffset);
-                metadata_visitor::ResolvedValue field = objcClass.setBasePropertiesVMAddr(objCVisitor,
-                                                                                          VMAddress(newBaseProperties.rawValue() | 1));
-                cacheDylibSegments[field.segmentIndex()].tracker.add(field.value());
-            }
-        }
-        return;
-    };
-    auto visitCategoryClass = ^(objc_visitor::Visitor &objCVisitor,
-                                size_t classCacheIndex, size_t classObjcIndex,
-                                objc_visitor::Class &objcClass,
-                                std::span<DylibSegmentChunk> cacheDylibSegments) {
-        auto classRange = classMap.equal_range(objcClass.getVMAddress().rawValue());
-        if ( classRange.first == classRange.second )
-            return;
-
-        //fprintf(stderr, "class %s %s 0x%llx\n", this->cacheDylibs[classCacheIndex].installName.data(), objcClass.getName(objCVisitor), objcClass.getVMAddress().rawValue());
-        // instance method lists
-        {
-            uint8_t* listBuffer = categoriesChunk->subCacheBuffer + chunkOffset;
-            uint64_t firstEntryOffset = chunkOffset;
-            ListOfListsEntry* listHeader = (ListOfListsEntry*)listBuffer;
-            ListOfListsEntry* listEntry = listHeader + 1;
-
-            bool createdNewList = false;
-            for (auto classEntry = classRange.first; classEntry != classRange.second; classEntry++) {
-                const ObjCCategoryOptimizer::Category* category = classEntry->second;
-                if ( !category->iMethodListVMAddress.has_value() )
-                    continue;
-
-                //fprintf(stderr, "  cat %s 0x%llx %s (%llu) => %zu\n", category->name.data(), category->vmAddress.value().rawValue(), this->objcOptimizer.objcDylibs[category->dylibObjcIndex.value()]->installName.data(), category->dylibObjcIndex.value(), classObjcIndex);
-                if ( !createdNewList ) {
-                    createdNewList = true;
-                    // New list with count and entry size
-                    listHeader->entsize = sizeof(ListOfListsEntry);
-                    listHeader->count = 0;
-                    chunkOffset += sizeof(ListOfListsEntry);
-                }
-
-                listEntry->imageIndex = category->dylibObjcIndex.value();
-                int64_t destVMAddr = (int64_t)category->iMethodListVMAddress.value().rawValue();
-                listEntry->offset = destVMAddr - getVMAddressInSection(*categoriesChunk, listEntry).rawValue();
-                listEntry++;
-                listHeader->count++;
-                chunkOffset += sizeof(ListOfListsEntry);
-            }
-
-            if ( createdNewList ) {
-                objc_visitor::MethodList methodList = objcClass.getBaseMethods(objCVisitor);
-                // add original method list
-                if ( methodList.numMethods() > 0 ) {
-                    listEntry->imageIndex = classObjcIndex;
-                    int64_t destVMAddr = (int64_t)methodList.getVMAddress().value().rawValue();
-                    listEntry->offset = destVMAddr - getVMAddressInSection(*categoriesChunk, listEntry).rawValue();
-                } else {
-                    // add an empty list entry if no original was found
-                    listEntry->imageIndex = classObjcIndex;
-                    int64_t destVMAddr = (int64_t)emptyMethodListVMAddr.rawValue();
-                    listEntry->offset = destVMAddr - getVMAddressInSection(*categoriesChunk, listEntry).rawValue();
-                }
-                listEntry++;
-                listHeader->count++;
-                chunkOffset += sizeof(ListOfListsEntry);
-                //fprintf(stderr, "Total iMethod list lists: %d\n", listHeader->count);
-                // store new base methods pointer to update
-                CacheVMAddress newBaseMethods = categoriesChunk->cacheVMAddress + VMOffset(firstEntryOffset);
-                metadata_visitor::ResolvedValue field = objcClass.setBaseMethodsVMAddr(objCVisitor,
-                                                                                       VMAddress(newBaseMethods.rawValue() | 1),
-                                                                                       methodListPMD);
-                cacheDylibSegments[field.segmentIndex()].tracker.add(field.value());
-            }
-        }
-
-        // protocol lists
-        {
-            uint8_t* listBuffer = categoriesChunk->subCacheBuffer + chunkOffset;
-            uint64_t firstEntryOffset = chunkOffset;
-            ListOfListsEntry* listHeader = (ListOfListsEntry*)listBuffer;
-            ListOfListsEntry* listEntry = listHeader + 1;
-
-            bool createdNewList = false;
-            for (auto classEntry = classRange.first; classEntry != classRange.second; classEntry++) {
-                const ObjCCategoryOptimizer::Category* category = classEntry->second;
-                if ( !category->protocolListVMAddress.has_value() )
-                    continue;
-
-                //fprintf(stderr, "  cat %s 0x%llx %s (%llu) => %zu\n", category->name.data(), category->vmAddress.value().rawValue(), this->objcOptimizer.objcDylibs[category->dylibObjcIndex.value()]->installName.data(), category->dylibObjcIndex.value(), classObjcIndex);
-                if ( !createdNewList ) {
-                    createdNewList = true;
-                    // New list with count and entry size
-                    listHeader->entsize = sizeof(ListOfListsEntry);
-                    listHeader->count = 0;
-                    chunkOffset += sizeof(ListOfListsEntry);
-                }
-
-                listEntry->imageIndex = category->dylibObjcIndex.value();
-                int64_t destVMAddr = (int64_t)category->protocolListVMAddress.value().rawValue();
-                listEntry->offset = destVMAddr - getVMAddressInSection(*categoriesChunk, listEntry).rawValue();
-                listEntry++;
-                listHeader->count++;
-                chunkOffset += sizeof(ListOfListsEntry);
-            }
-
-            if ( createdNewList ) {
-                objc_visitor::ProtocolList protocolList = objcClass.getBaseProtocols(objCVisitor);
-                // add original protocol list
-                if ( protocolList.numProtocols(objCVisitor) > 0 ) {
-                    listEntry->imageIndex = classObjcIndex;
-                    int64_t destVMAddr = (int64_t)protocolList.getVMAddress().value().rawValue();
-                    listEntry->offset = destVMAddr - getVMAddressInSection(*categoriesChunk, listEntry).rawValue();
-                } else {
-                    // add an empty list entry if no original was found
-                    // Zeroing the entry will make the offset point to itself
-                    // That will then be interpreted as a ListOfListsEntry of count 0
-                    // This also means that the image at index 0 needs to always be libobjc.A.dylib
-                    listEntry->imageIndex = 0;
-                    listEntry->offset = 0;
-                }
-                listEntry++;
-                listHeader->count++;
-                chunkOffset += sizeof(ListOfListsEntry);
-
-                //fprintf(stderr, "Total Protocol list lists: %d\n", listHeader->count);
-                // store new base protocols pointer to update
-                CacheVMAddress newBaseProtocols = categoriesChunk->cacheVMAddress + VMOffset(firstEntryOffset);
-                metadata_visitor::ResolvedValue field = objcClass.setBaseProtocolsVMAddr(objCVisitor,
-                                                                                         VMAddress(newBaseProtocols.rawValue() | 1));
-                cacheDylibSegments[field.segmentIndex()].tracker.add(field.value());
-            }
-        }
-
-        // instance properties
-        {
-            uint8_t* listBuffer = categoriesChunk->subCacheBuffer + chunkOffset;
-            uint64_t firstEntryOffset = chunkOffset;
-            ListOfListsEntry* listHeader = (ListOfListsEntry*)listBuffer;
-            ListOfListsEntry* listEntry = listHeader + 1;
-
-            bool createdNewList = false;
-            for (auto classEntry = classRange.first; classEntry != classRange.second; classEntry++) {
-                const ObjCCategoryOptimizer::Category* category = classEntry->second;
-                if ( !category->iPropertyListVMAddress.has_value() )
-                    continue;
-
-                //fprintf(stderr, "  cat %s 0x%llx %s (%llu) => %zu\n", category->name.data(), category->vmAddress.value().rawValue(), this->objcOptimizer.objcDylibs[category->dylibObjcIndex.value()]->installName.data(), category->dylibObjcIndex.value(), classObjcIndex);
-                if ( !createdNewList ) {
-                    createdNewList = true;
-                    // New list with count and entry size
-                    listHeader->entsize = sizeof(ListOfListsEntry);
-                    listHeader->count = 0;
-                    chunkOffset += sizeof(ListOfListsEntry);
-                }
-
-                listEntry->imageIndex = category->dylibObjcIndex.value();
-                int64_t destVMAddr = (int64_t)category->iPropertyListVMAddress.value().rawValue();
-                listEntry->offset = destVMAddr - getVMAddressInSection(*categoriesChunk, listEntry).rawValue();
-                listEntry++;
-                listHeader->count++;
-                chunkOffset += sizeof(ListOfListsEntry);
-            }
-
-            if ( createdNewList ) {
-                objc_visitor::PropertyList propertyList = objcClass.getBaseProperties(objCVisitor);
-                // add original property list
-                if ( propertyList.numProperties() > 0 ) {
-                    listEntry->imageIndex = classObjcIndex;
-                    int64_t destVMAddr = (int64_t)propertyList.getVMAddress().value().rawValue();
-                    listEntry->offset = destVMAddr - getVMAddressInSection(*categoriesChunk, listEntry).rawValue();
-                } else {
-                    // add an empty list entry if no original was found
-                    // Zeroing the entry will make the offset point to itself
-                    // That will then be interpreted as a ListOfListsEntry of count 0
-                    // This also means that the image at index 0 needs to always be libobjc.A.dylib
-                    listEntry->imageIndex = 0;
-                    listEntry->offset = 0;
-                }
-                listEntry++;
-                listHeader->count++;
-                chunkOffset += sizeof(ListOfListsEntry);
-                //fprintf(stderr, "Total iProperty list lists: %d\n", listHeader->count);
-                // store new base properties pointer to update
-                CacheVMAddress newBaseProperties = categoriesChunk->cacheVMAddress + VMOffset(firstEntryOffset);
-                metadata_visitor::ResolvedValue field = objcClass.setBasePropertiesVMAddr(objCVisitor,
-                                                                                          VMAddress(newBaseProperties.rawValue() | 1));
-                cacheDylibSegments[field.segmentIndex()].tracker.add(field.value());
-            }
-        }
-
-        // class method lists and class properties
-        {
-            // check if we need to update the meta class
-            bool isPatchableClass;
-            VMAddress metaClassVMAddr = objcClass.getISA(objCVisitor, isPatchableClass).vmAddress();
-
-            bool visitMetaClassMap = false;
-            for (auto classEntry = classRange.first; classEntry != classRange.second; classEntry++) {
-                const ObjCCategoryOptimizer::Category* category = classEntry->second;
-                if ( category->cPropertyListVMAddress.has_value() || category->cMethodListVMAddress.has_value() ) {
-                    metaClassMap.insert({ metaClassVMAddr.rawValue(), category });
-                    visitMetaClassMap = true;
-                }
-            }
-            if ( visitMetaClassMap ) {
-                metadata_visitor::ResolvedValue metaClassValue = objCVisitor.getValueFor(metaClassVMAddr);
-                objc_visitor::Class objcMetaClass(metaClassValue, /*isMetaClass*/ true, /*isPatchable*/ false);
-                visitCategoryMetaClass(objCVisitor, classCacheIndex, classObjcIndex, objcMetaClass, cacheDylibSegments);
-            }
-        }
-    };
-
-    size_t objcIndex = 0;
-    for (size_t cacheIndex = 0; cacheIndex < this->cacheDylibs.size(); cacheIndex++) {
-        CacheDylib& cacheDylib = this->cacheDylibs[cacheIndex];
-        if ( !cacheDylib.inputMF->hasObjC() )
-            continue;
-
-        this->objcCategoryOptimizer.preAttachedDylibs.insert(objcIndex);
-        __block objc_visitor::Visitor objCVisitor = cacheDylib.makeCacheObjCVisitor(config, nullptr, nullptr, categoriesChunk);
-
-        // dylibsToClasses can contain multiple entries with the same pair of key/values
-        std::set<uint64_t> visitedClasses;
-        auto dylibToClassRange = dylibsToClasses.equal_range(cacheIndex);
-        for (auto dylibToClass = dylibToClassRange.first; dylibToClass != dylibToClassRange.second; dylibToClass++) {
-            if ( visitedClasses.contains(dylibToClass->second) )
-                continue;
-            visitedClasses.insert(dylibToClass->second);
-            metadata_visitor::ResolvedValue classValue = objCVisitor.getValueFor(VMAddress(dylibToClass->second));
-            objc_visitor::Class objcClass(classValue, /*isMetaClass*/ false, /*isPatchable*/ false);
-            visitCategoryClass(objCVisitor, cacheIndex, objcIndex, objcClass, cacheDylib.segments);
-        }
-        objcIndex++;
     }
 
     return Error();
@@ -7212,7 +6461,7 @@
 
     Diagnostics diag;
     auto objcClassOpt = (objc::ClassHashTable*)this->objcClassOptimizer.classHashTableChunk->subCacheBuffer;
-    buildSwiftHashTables(this->config, diag, this->objcOptimizer.objcDylibs,
+    buildSwiftHashTables(this->config, diag, this->cacheDylibs,
                          extraRegions, objcClassOpt,
                          this->objcOptimizer.headerInfoReadOnlyChunk->subCacheBuffer,
                          this->objcOptimizer.headerInfoReadWriteChunk->subCacheBuffer,
@@ -7239,7 +6488,7 @@
     Timer::Scope timedScope(this->config, "computeSlideInfo time");
 
     if ( !this->config.slideInfo.slideInfoFormat.has_value() ) {
-        assert(this->options.isSimulator());
+        assert(this->options.isSimultor());
     }
 
     Error err = parallel::forEach(this->subCaches, ^(size_t index, SubCache& subCache) {
@@ -7334,10 +6583,6 @@
     if ( sizeUpToTextEnd <= twoGB )
         maxSlide = CacheVMSize(twoGB - sizeUpToTextEnd);
 
-    if ( this->config.layout.cacheMaxSlide.has_value() ) {
-        maxSlide = std::min(maxSlide, CacheVMSize(this->config.layout.cacheMaxSlide.value()));
-    }
-
     return maxSlide.rawValue();
 }
 
@@ -7351,9 +6596,7 @@
         Diagnostics diag;
         cacheDylib.addObjcSegments(diag, aggregateTimer,
                                    this->objcOptimizer.headerInfoReadOnlyChunk,
-                                   this->objcOptimizer.imageInfoChunk,
                                    this->objcProtocolOptimizer.protocolHashTableChunk,
-                                   this->objcCategoryOptimizer.categoriesChunk,
                                    this->objcOptimizer.headerInfoReadWriteChunk,
                                    this->objcProtocolOptimizer.canonicalProtocolsChunk);
     }
@@ -7485,11 +6728,6 @@
     for (int i = 0; i < 20; ++i)
         snprintf(&buff[2*i], sizeof(buff), "%2.2x", hash[i]);
     return buff;
-}
-
-std::span<const std::string_view> SharedCacheBuilder::getEvictedDylibs() const
-{
-    return this->evictedDylibs;
 }
 
 void SharedCacheBuilder::getResults(std::vector<CacheBuffer>& results) const