Loading...
dyld/PrebuiltObjC.cpp dyld-1340 dyld-960
--- dyld/dyld-1340/dyld/PrebuiltObjC.cpp
+++ dyld/dyld-960/dyld/PrebuiltObjC.cpp
@@ -21,128 +21,292 @@
  * @APPLE_LICENSE_HEADER_END@
  */
 
-#include <TargetConditionals.h>
-
-#if !TARGET_OS_EXCLAVEKIT
-
 #include <assert.h>
 #include <unistd.h>
 #include <sys/types.h>
 
-#include "Defines.h"
-#include "Header.h"
 #include "Loader.h"
 #include "PrebuiltLoader.h"
 #include "JustInTimeLoader.h"
-#include "MachOFile.h"
+#include "MachOAnalyzer.h"
 #include "BumpAllocator.h"
 #include "DyldProcessConfig.h"
 #include "DyldRuntimeState.h"
 #include "OptimizerObjC.h"
-#include "ObjCVisitor.h"
 #include "PerfectHash.h"
 #include "PrebuiltObjC.h"
 #include "objc-shared-cache.h"
 
-using mach_o::Header;
-
-#if SUPPORT_PREBUILTLOADERS || BUILDING_UNIT_TESTS || BUILDING_CACHE_BUILDER_UNIT_TESTS
+
+using dyld3::MachOAnalyzer;
 using dyld3::OverflowSafeArray;
 typedef dyld4::PrebuiltObjC::ObjCOptimizerImage ObjCOptimizerImage;
 
-// This holds all the maps we are going to serialize.
-namespace prebuilt_objc
-{
-
-void forEachSelectorStringEntry(const void* selMap, void (^handler)(const PrebuiltLoader::BindTargetRef& target))
-{
-    // The on-disk map is really an ObjCSelectorMapOnDisk
-    const ObjCSelectorMapOnDisk map(selMap);
-    map.forEachEntry(^(const ObjCSelectorMapOnDisk::NodeT& node) {
-        handler(node.first.stringTarget);
+namespace dyld4 {
+
+////////////////////////////  ObjCStringTable ////////////////////////////////////////
+
+uint32_t ObjCStringTable::hash(const char* key, size_t keylen) const
+{
+    uint64_t val   = objc::lookup8((uint8_t*)key, keylen, salt);
+    uint32_t index = (uint32_t)((shift == 64) ? 0 : (val >> shift)) ^ scramble[tab[val & mask]];
+    return index;
+}
+
+const char* ObjCStringTable::getString(const char* selName, RuntimeState& state) const
+{
+    std::optional<PrebuiltLoader::BindTargetRef> target = getPotentialTarget(selName);
+    if ( !target.has_value() )
+        return nullptr;
+
+    const PrebuiltLoader::BindTargetRef& nameTarget = *target;
+    const PrebuiltLoader::BindTargetRef  sentinel   = getSentinel();
+
+    if ( memcmp(&nameTarget, &sentinel, sizeof(PrebuiltLoader::BindTargetRef)) == 0 )
+        return nullptr;
+
+    const char* stringValue = (const char*)target->value(state);
+    if ( !strcmp(selName, stringValue) )
+        return stringValue;
+    return nullptr;
+}
+
+size_t ObjCStringTable::size(const objc::PerfectHash& phash)
+{
+    // Round tab[] to at least 8 in length to ensure the BindTarget's after are aligned
+    uint32_t roundedTabSize        = std::max(phash.mask + 1, 8U);
+    uint32_t roundedCheckBytesSize = std::max(phash.capacity, 8U);
+    size_t   tableSize             = 0;
+    tableSize += sizeof(ObjCStringTable);
+    tableSize += roundedTabSize * sizeof(uint8_t);
+    tableSize += roundedCheckBytesSize * sizeof(uint8_t);
+    tableSize += phash.capacity * sizeof(PrebuiltLoader::BindTargetRef);
+    return (size_t)align(tableSize, 3);
+}
+
+void ObjCStringTable::write(const objc::PerfectHash& phash, const Array<std::pair<const char*, PrebuiltLoader::BindTarget>>& strings)
+{
+    // Set header
+    capacity              = phash.capacity;
+    occupied              = phash.occupied;
+    shift                 = phash.shift;
+    mask                  = phash.mask;
+    roundedTabSize        = std::max(phash.mask + 1, 8U);
+    roundedCheckBytesSize = std::max(phash.capacity, 8U);
+    salt                  = phash.salt;
+
+    // Set hash data
+    for ( uint32_t i = 0; i < 256; i++ ) {
+        scramble[i] = phash.scramble[i];
+    }
+    for ( uint32_t i = 0; i < phash.mask + 1; i++ ) {
+        tab[i] = phash.tab[i];
+    }
+
+    dyld3::Array<PrebuiltLoader::BindTargetRef> targetsArray    = targets();
+    dyld3::Array<uint8_t>                       checkBytesArray = checkBytes();
+
+    const PrebuiltLoader::BindTargetRef sentinel = getSentinel();
+
+    // Set offsets to the sentinel
+    for ( uint32_t i = 0; i < phash.capacity; i++ ) {
+        targetsArray[i] = sentinel;
+    }
+    // Set checkbytes to 0
+    for ( uint32_t i = 0; i < phash.capacity; i++ ) {
+        checkBytesArray[i] = 0;
+    }
+
+    // Set real string offsets and checkbytes
+    for ( const auto& s : strings ) {
+        assert(memcmp(&s.second, &sentinel, sizeof(PrebuiltLoader::BindTargetRef)) != 0);
+        uint32_t h         = hash(s.first);
+        targetsArray[h]    = s.second;
+        checkBytesArray[h] = checkbyte(s.first);
+    }
+}
+
+////////////////////////////  ObjCSelectorOpt ////////////////////////////////////////
+const char* ObjCSelectorOpt::getStringAtIndex(uint32_t index, RuntimeState& state) const
+{
+    if ( index >= capacity )
+        return nullptr;
+
+    PrebuiltLoader::BindTargetRef       target   = targets()[index];
+    const PrebuiltLoader::BindTargetRef sentinel = getSentinel();
+    if ( memcmp(&target, &sentinel, sizeof(PrebuiltLoader::BindTargetRef)) == 0 )
+        return nullptr;
+
+    const char* stringValue = (const char*)target.value(state);
+    return stringValue;
+}
+
+void ObjCSelectorOpt::forEachString(void (^callback)(const PrebuiltLoader::BindTargetRef& target)) const
+{
+    const PrebuiltLoader::BindTargetRef sentinel = getSentinel();
+
+    dyld3::Array<PrebuiltLoader::BindTargetRef> stringTargets = targets();
+    for ( const PrebuiltLoader::BindTargetRef& target : stringTargets ) {
+        if ( memcmp(&target, &sentinel, sizeof(PrebuiltLoader::BindTargetRef)) == 0 )
+            continue;
+        callback(target);
+    }
+}
+
+////////////////////////////  ObjCClassOpt ////////////////////////////////////////
+
+// Returns true if the class was found and the callback said to stop
+bool ObjCClassOpt::forEachClass(const char* className, RuntimeState& state,
+                                void (^callback)(void* classPtr, bool isLoaded, bool* stop)) const
+{
+    uint32_t index = getIndex(className);
+    if ( index == ObjCStringTable::indexNotFound )
+        return false;
+
+    const PrebuiltLoader::BindTargetRef sentinel = getSentinel();
+
+    const PrebuiltLoader::BindTargetRef& nameTarget = targets()[index];
+    if ( memcmp(&nameTarget, &sentinel, sizeof(PrebuiltLoader::BindTargetRef)) == 0 )
+        return false;
+
+    const char* nameStringValue = (const char*)nameTarget.value(state);
+    if ( strcmp(className, nameStringValue) != 0 )
+        return false;
+
+    // The name matched so now call the handler on all the classes for this name
+    const Array<PrebuiltLoader::BindTargetRef> classes    = classTargets();
+    const Array<PrebuiltLoader::BindTargetRef> duplicates = duplicateTargets();
+
+    const PrebuiltLoader::BindTargetRef& classTarget = classes[index];
+    if ( !classTarget.isAbsolute() ) {
+        // A regular target points to the single class implementation
+        // This class has a single implementation
+        void* classImpl = (void*)classTarget.value(state);
+        bool  stop      = false;
+        callback(classImpl, true, &stop);
+        return stop;
+    }
+    else {
+        // This class has mulitple implementations.
+        // The absolute value of the class target is the index in to the duplicates table
+        // The first entry we point to is the count of duplicates for this class
+        size_t                              duplicateStartIndex  = (size_t)classTarget.value(state);
+        const PrebuiltLoader::BindTargetRef duplicateCountTarget = duplicates[duplicateStartIndex];
+        ++duplicateStartIndex;
+        assert(duplicateCountTarget.isAbsolute());
+        uint64_t duplicateCount = duplicateCountTarget.value(state);
+
+        for ( size_t dupeIndex = 0; dupeIndex != duplicateCount; ++dupeIndex ) {
+            const PrebuiltLoader::BindTargetRef& duplicateTarget = duplicates[duplicateStartIndex + dupeIndex];
+
+            void* classImpl = (void*)duplicateTarget.value(state);
+            bool  stop      = false;
+            callback(classImpl, true, &stop);
+            if ( stop )
+                return true;
+        }
+    }
+    return false;
+}
+
+void ObjCClassOpt::forEachClass(RuntimeState& state,
+                                void (^callback)(const PrebuiltLoader::BindTargetRef&        nameTarget,
+                                                 const Array<PrebuiltLoader::BindTargetRef>& implTargets)) const
+{
+
+    const PrebuiltLoader::BindTargetRef sentinel = getSentinel();
+
+    Array<PrebuiltLoader::BindTargetRef> stringTargets = targets();
+    Array<PrebuiltLoader::BindTargetRef> classes       = classTargets();
+    Array<PrebuiltLoader::BindTargetRef> duplicates    = duplicateTargets();
+    for ( unsigned i = 0; i != capacity; ++i ) {
+        const PrebuiltLoader::BindTargetRef& nameTarget = stringTargets[i];
+        if ( memcmp(&nameTarget, &sentinel, sizeof(PrebuiltLoader::BindTargetRef)) == 0 )
+            continue;
+
+        // Walk each class for this key
+        PrebuiltLoader::BindTargetRef classTarget = classes[i];
+        if ( !classTarget.isAbsolute() ) {
+            // A regular target points to the single class implementation
+            // This class has a single implementation
+            const Array<PrebuiltLoader::BindTargetRef> implTarget(&classTarget, 1, 1);
+            callback(nameTarget, implTarget);
+        }
+        else {
+            // This class has mulitple implementations.
+            // The absolute value of the class target is the index in to the duplicates table
+            // The first entry we point to is the count of duplicates for this class
+            uintptr_t                           duplicateStartIndex  = (uintptr_t)classTarget.value(state);
+            const PrebuiltLoader::BindTargetRef duplicateCountTarget = duplicates[duplicateStartIndex];
+            ++duplicateStartIndex;
+            assert(duplicateCountTarget.isAbsolute());
+            uintptr_t duplicateCount = (uintptr_t)duplicateCountTarget.value(state);
+
+            callback(nameTarget, duplicates.subArray(duplicateStartIndex, duplicateCount));
+        }
+    }
+}
+
+size_t ObjCClassOpt::size(const objc::PerfectHash& phash, uint32_t numClassesWithDuplicates,
+                          uint32_t totalDuplicates)
+{
+    size_t tableSize = 0;
+    tableSize += ObjCStringTable::size(phash);
+    tableSize += phash.capacity * sizeof(PrebuiltLoader::BindTargetRef);                               // classTargets
+    tableSize += sizeof(uint32_t);                                                                     // duplicateCount
+    tableSize += (numClassesWithDuplicates + totalDuplicates) * sizeof(PrebuiltLoader::BindTargetRef); // duplicateTargets
+    return (size_t)align(tableSize, 3);
+}
+
+void ObjCClassOpt::write(const objc::PerfectHash& phash, const Array<std::pair<const char*, PrebuiltLoader::BindTarget>>& strings,
+                         const dyld3::CStringMultiMapTo<PrebuiltLoader::BindTarget>& classes,
+                         uint32_t numClassesWithDuplicates, uint32_t totalDuplicates)
+{
+    ObjCStringTable::write(phash, strings);
+    duplicateCount() = numClassesWithDuplicates + totalDuplicates;
+
+    __block dyld3::Array<PrebuiltLoader::BindTargetRef> classTargets     = this->classTargets();
+    __block dyld3::Array<PrebuiltLoader::BindTargetRef> duplicateTargets = this->duplicateTargets();
+
+    const PrebuiltLoader::BindTargetRef sentinel = getSentinel();
+
+    // Set class offsets to 0
+    for ( uint32_t i = 0; i < capacity; i++ ) {
+        classTargets[i] = sentinel;
+    }
+
+    // Empty the duplicate targets array so that we can push elements in to it.  It already has the correct capacity
+    duplicateTargets.resize(0);
+
+    classes.forEachEntry(^(const char* const& key, const PrebuiltLoader::BindTarget** values, uint64_t valuesCount) {
+        uint32_t keyIndex = getIndex(key);
+        assert(keyIndex != indexNotFound);
+        assert(memcmp(&classTargets[keyIndex], &sentinel, sizeof(PrebuiltLoader::BindTargetRef)) == 0);
+
+        if ( valuesCount == 1 ) {
+            // Only one entry so write it in to the class offsets directly
+            const PrebuiltLoader::BindTarget& classTarget = *(values[0]);
+            classTargets[keyIndex]                        = PrebuiltLoader::BindTargetRef(classTarget);
+            return;
+        }
+
+        // We have more than one value.  We add a placeholder to the class offsets which tells us the head
+        // of the linked list of classes in the duplicates array
+
+        PrebuiltLoader::BindTargetRef classTargetPlaceholder = PrebuiltLoader::BindTargetRef::makeAbsolute(duplicateTargets.count());
+        classTargets[keyIndex]                               = classTargetPlaceholder;
+
+        // The first value we push in to the duplicates array for this class is the count
+        // of how many duplicates for this class we have
+        duplicateTargets.push_back(PrebuiltLoader::BindTargetRef::makeAbsolute(valuesCount));
+        for ( uint64_t i = 0; i != valuesCount; ++i ) {
+            PrebuiltLoader::BindTarget classTarget = *(values[i]);
+            duplicateTargets.push_back(PrebuiltLoader::BindTargetRef(classTarget));
+        }
     });
-}
-
-#if SUPPORT_VM_LAYOUT
-
-const char* findSelector(dyld4::RuntimeState* state, const ObjCSelectorMapOnDisk& map,
-                         const char* selectorName)
-{
-    auto it = map.find((void*)state, selectorName);
-    if ( it == map.end() )
-        return nullptr;
-
-    return (const char*)it->first.stringTarget.value(*state);
-}
-
-void forEachClass(dyld4::RuntimeState* state, const ObjCClassMapOnDisk& classMap, const char* className,
-                  void (^handler)(const dyld3::Array<const PrebuiltLoader::BindTargetRef*>& values))
-{
-    classMap.forEachEntry(state, className, ^(const dyld3::Array<const ObjCObjectOnDiskLocation*>& values) {
-        if ( values.empty() )
-            return;
-
-        STACK_ALLOC_ARRAY(const PrebuiltLoader::BindTargetRef*, newValues, values.count());
-        for ( const ObjCObjectOnDiskLocation* value : values )
-            newValues.push_back(&value->objectLocation);
-
-        handler(newValues);
-    });
-}
-
-void forEachProtocol(dyld4::RuntimeState* state, const ObjCProtocolMapOnDisk& protocolMap, const char* protocolName,
-                     void (^handler)(const dyld3::Array<const PrebuiltLoader::BindTargetRef*>& values))
-{
-    protocolMap.forEachEntry(state, protocolName, ^(const dyld3::Array<const ObjCObjectOnDiskLocation*>& values) {
-        if ( values.empty() )
-            return;
-
-        STACK_ALLOC_ARRAY(const PrebuiltLoader::BindTargetRef*, newValues, values.count());
-        for ( const ObjCObjectOnDiskLocation* value : values )
-            newValues.push_back(&value->objectLocation);
-
-        handler(newValues);
-    });
-}
-
-#endif // SUPPORT_VM_LAYOUT
-
-void forEachClass(const void* classMap,
-                  void (^handler)(const PrebuiltLoader::BindTargetRef& nameTarget,
-                                  const dyld3::Array<const PrebuiltLoader::BindTargetRef*>& values))
-{
-    // The on-disk map is really an ObjCClassMapOnDisk
-    const ObjCClassMapOnDisk map(classMap);
-    map.forEachEntry(^(const ObjCStringKeyOnDisk& key, const dyld3::Array<const ObjCObjectOnDiskLocation*>& values) {
-        STACK_ALLOC_ARRAY(const PrebuiltLoader::BindTargetRef*, newValues, values.count());
-        for ( const ObjCObjectOnDiskLocation* value : values )
-            newValues.push_back(&value->objectLocation);
-        handler(key.stringTarget, newValues);
-    });
-}
-
-void forEachProtocol(const void* protocolMap,
-                     void (^handler)(const PrebuiltLoader::BindTargetRef& nameTarget,
-                                     const dyld3::Array<const PrebuiltLoader::BindTargetRef*>& values))
-{
-    // The on-disk map is really an ObjCProtocolMapOnDisk
-    const ObjCProtocolMapOnDisk map(protocolMap);
-    map.forEachEntry(^(const ObjCStringKeyOnDisk& key, const dyld3::Array<const ObjCObjectOnDiskLocation*>& values) {
-        STACK_ALLOC_ARRAY(const PrebuiltLoader::BindTargetRef*, newValues, values.count());
-        for ( const ObjCObjectOnDiskLocation* value : values )
-            newValues.push_back(&value->objectLocation);
-        handler(key.stringTarget, newValues);
-    });
-}
-
-uint64_t hashStringKey(const std::string_view& str)
-{
-    return murmurHash(str.data(), (int)str.size(), 0);
-}
-
-} // namespace prebuilt_objc
-
-namespace dyld4 {
+
+    assert(duplicateTargets.count() == duplicateCount());
+}
 
 //////////////////////// ObjCOptimizerImage /////////////////////////////////
 
@@ -156,7 +320,7 @@
 #if BUILDING_CACHE_BUILDER || BUILDING_CLOSURE_UTIL
 void ObjCOptimizerImage::calculateMissingWeakImports(RuntimeState& state)
 {
-    const mach_o::MachOFileRef& mf = jitLoader->mf(state);
+    const dyld3::MachOAnalyzer* ma = (const dyld3::MachOAnalyzer*)jitLoader->loadAddress(state);
 
     // build targets table
     STACK_ALLOC_OVERFLOW_SAFE_ARRAY(bool, bindTargetsAreWeakImports, 512);
@@ -185,102 +349,79 @@
         return;
 
     if ( foundMissingWeakImport ) {
-        jitLoader->withLayout(diag, state, ^(const mach_o::Layout& layout) {
-            mach_o::Fixups fixups(layout);
-
-            if ( mf->hasChainedFixups() ) {
-                // walk all chains
-                auto handler = ^(dyld3::MachOFile::ChainedFixupPointerOnDisk *fixupLocation,
-                                 InputDylibVMAddress fixupVMAddr, uint16_t pointerFormat,
-                                 bool &stopChain) {
+        if ( ma->hasChainedFixups() ) {
+            // walk all chains
+            ma->withChainStarts(diag, ma->chainStartsOffset(), ^(const dyld_chained_starts_in_image* startsInfo) {
+                ma->forEachFixupInAllChains(diag, startsInfo, false, ^(MachOLoaded::ChainedFixupPointerOnDisk* fixupLoc, const dyld_chained_starts_in_segment* segInfo, bool& fixupsStop) {
+                    uint64_t fixupOffset = (uint8_t*)fixupLoc - (uint8_t*)ma;
                     uint32_t bindOrdinal;
                     int64_t  addend;
-                    if ( fixupLocation->isBind(pointerFormat, bindOrdinal, addend) ) {
+                    if ( fixupLoc->isBind(segInfo->pointer_format, bindOrdinal, addend) ) {
                         if ( bindOrdinal < bindTargetsAreWeakImports.count() ) {
                             if ( bindTargetsAreWeakImports[bindOrdinal] )
-                                missingWeakImports.insert(fixupVMAddr);
+                                missingWeakImportOffsets[fixupOffset] = true;
                         }
                         else {
-                            diag.error("out of range bind ordinal %d (max %llu)", bindOrdinal, bindTargetsAreWeakImports.count());
-                            stopChain = true;
+                            diag.error("out of range bind ordinal %d (max %lu)", bindOrdinal, bindTargetsAreWeakImports.count());
+                            fixupsStop = true;
                         }
                     }
-                };
-
-                fixups.withChainStarts(diag, ^(const dyld_chained_starts_in_image* startsInfo) {
-                    fixups.forEachFixupChainSegment(diag, startsInfo, ^(const dyld_chained_starts_in_segment *segInfo,
-                                                                        uint32_t segIndex, bool &stopSegment) {
-                        InputDylibVMAddress segmentVMAddr(layout.segments[segIndex].vmAddr);
-                        auto adaptor = ^(dyld3::MachOFile::ChainedFixupPointerOnDisk *fixupLocation,
-                                         uint64_t fixupSegmentOffset,
-                                         bool &stopChain) {
-                            InputDylibVMAddress fixupVMAddr = segmentVMAddr + VMOffset(fixupSegmentOffset);
-                            handler(fixupLocation, fixupVMAddr, segInfo->pointer_format, stopChain);
-                        };
-                        fixups.forEachFixupInSegmentChains(diag, segInfo, segIndex, true, adaptor);
-                    });
                 });
-                if ( diag.hasError() )
-                    return;
-            } else if ( mf->hasOpcodeFixups() ) {
-                // process all bind opcodes
-                fixups.forEachBindLocation_Opcodes(diag, ^(uint64_t runtimeOffset, uint32_t segmentIndex,
-                                                           unsigned targetIndex, bool& fixupsStop) {
-                    if ( targetIndex < bindTargetsAreWeakImports.count() ) {
-                        if ( bindTargetsAreWeakImports[targetIndex] ) {
-                            InputDylibVMAddress fixupVMAddr(layout.textUnslidVMAddr() + runtimeOffset);
-                            missingWeakImports.insert(fixupVMAddr);
-                        }
-                    }
-                    else {
-                        diag.error("out of range bind ordinal %d (max %llu)", targetIndex, bindTargetsAreWeakImports.count());
-                        fixupsStop = true;
-                    }
-                }, ^(uint64_t runtimeOffset, uint32_t segmentIndex,
-                     unsigned overrideBindTargetIndex, bool& fixupsStop) {
-                    if ( overrideBindTargetIndex < overrideBindTargetsAreWeakImports.count() ) {
-                        if ( overrideBindTargetsAreWeakImports[overrideBindTargetIndex] ) {
-                            InputDylibVMAddress fixupVMAddr(layout.textUnslidVMAddr() + runtimeOffset);
-                            missingWeakImports.insert(fixupVMAddr);
-                        }
-                    }
-                    else {
-                        diag.error("out of range bind ordinal %d (max %llu)", overrideBindTargetIndex, overrideBindTargetsAreWeakImports.count());
-                        fixupsStop = true;
-                    }
-                });
-                if ( diag.hasError() )
-                    return;
-            }
-            else {
-                // process external relocations
-                fixups.forEachBindLocation_Relocations(diag, ^(uint64_t runtimeOffset, unsigned targetIndex, bool& fixupsStop) {
-                    if ( targetIndex < bindTargetsAreWeakImports.count() ) {
-                        if ( bindTargetsAreWeakImports[targetIndex] ) {
-                            InputDylibVMAddress fixupVMAddr(layout.textUnslidVMAddr() + runtimeOffset);
-                            missingWeakImports.insert(fixupVMAddr);
-                        }
-                    }
-                    else {
-                        diag.error("out of range bind ordinal %d (max %llu)", targetIndex, bindTargetsAreWeakImports.count());
-                        fixupsStop = true;
-                    }
-                });
-                if ( diag.hasError() )
-                    return;
-            }
-        });
+            });
+            if ( diag.hasError() )
+                return;
+        }
+        else if ( ma->hasOpcodeFixups() ) {
+            // process all bind opcodes
+            ma->forEachBindLocation_Opcodes(diag, ^(uint64_t runtimeOffset, unsigned targetIndex, bool& fixupsStop) {
+                if ( targetIndex < bindTargetsAreWeakImports.count() ) {
+                    if ( bindTargetsAreWeakImports[targetIndex] )
+                        missingWeakImportOffsets[runtimeOffset] = true;
+                }
+                else {
+                    diag.error("out of range bind ordinal %d (max %lu)", targetIndex, bindTargetsAreWeakImports.count());
+                    fixupsStop = true;
+                }
+            }, ^(uint64_t runtimeOffset, unsigned overrideBindTargetIndex, bool& fixupsStop) {
+                if ( overrideBindTargetIndex < overrideBindTargetsAreWeakImports.count() ) {
+                    if ( overrideBindTargetsAreWeakImports[overrideBindTargetIndex] )
+                        missingWeakImportOffsets[runtimeOffset] = true;
+                }
+                else {
+                    diag.error("out of range bind ordinal %d (max %lu)", overrideBindTargetIndex, overrideBindTargetsAreWeakImports.count());
+                    fixupsStop = true;
+                }
+            });
+            if ( diag.hasError() )
+                return;
+        }
+        else {
+            // process external relocations
+            ma->forEachBindLocation_Relocations(diag, ^(uint64_t runtimeOffset, unsigned targetIndex, bool& fixupsStop) {
+                if ( targetIndex < bindTargetsAreWeakImports.count() ) {
+                    if ( bindTargetsAreWeakImports[targetIndex] )
+                        missingWeakImportOffsets[runtimeOffset] = true;
+                }
+                else {
+                    diag.error("out of range bind ordinal %d (max %lu)", targetIndex, bindTargetsAreWeakImports.count());
+                    fixupsStop = true;
+                }
+            });
+            if ( diag.hasError() )
+                return;
+        }
     }
 }
 #endif // (BUILDING_CACHE_BUILDER || BUILDING_CLOSURE_UTIL)
 
-bool ObjCOptimizerImage::isNull(InputDylibVMAddress vmAddr, const void* address) const
+bool ObjCOptimizerImage::isNull(uint64_t vmAddr, const dyld3::MachOAnalyzer* ma, intptr_t slide) const
 {
 #if BUILDING_CACHE_BUILDER || BUILDING_CLOSURE_UTIL
-    return (missingWeakImports.find(vmAddr) != missingWeakImports.end());
+    uint64_t runtimeOffset = vmAddr - loadAddress;
+    return (missingWeakImportOffsets.find(runtimeOffset) != missingWeakImportOffsets.end());
 #elif BUILDING_DYLD
     // In dyld, we are live, so we can just check if we point to a null value
-    uintptr_t* pointer = (uintptr_t*)address;
+    uintptr_t* pointer = (uintptr_t*)(vmAddr + slide);
     return (*pointer == 0);
 #else
     // FIXME: Have we been slide or not in the non-dyld case?
@@ -290,17 +431,16 @@
 }
 
 void ObjCOptimizerImage::visitReferenceToObjCSelector(const objc::SelectorHashTable* objcSelOpt,
-                                                      PrebuiltObjC::SelectorMapTy& appSelectorMap,
-                                                      VMOffset selectorReferenceRuntimeOffset, VMOffset selectorStringRuntimeOffset,
+                                                      const PrebuiltObjC::SelectorMapTy& appSelectorMap,
+                                                      uint64_t selectorReferenceRuntimeOffset, uint64_t selectorStringRuntimeOffset,
                                                       const char* selectorString)
 {
 
     // fprintf(stderr, "selector: %p -> %p %s\n", (void*)selectorReferenceRuntimeOffset, (void*)selectorStringRuntimeOffset, selectorString);
-    if ( const char* sharedCacheSelector = objcSelOpt->get(selectorString) ) {
+    if ( std::optional<uint32_t> cacheSelectorIndex = objcSelOpt->tryGetIndex(selectorString) ) {
         // We got the selector from the cache so add a fixup to point there.
-        // We use an absolute bind here, to reference the offset from the shared cache selector table base
-        uint64_t sharedCacheOffset = (uint64_t)sharedCacheSelector - (uint64_t)objcSelOpt;
-        PrebuiltLoader::BindTargetRef bindTarget = PrebuiltLoader::BindTargetRef::makeAbsolute(sharedCacheOffset);
+        // We use an absolute bind here, to reference the index in to the shared cache table
+        PrebuiltLoader::BindTargetRef bindTarget = PrebuiltLoader::BindTargetRef::makeAbsolute(*cacheSelectorIndex);
 
         //printf("Overriding fixup at 0x%08llX to cache offset 0x%08llX\n", selectorUseImageOffset, (uint64_t)objcSelOpt->getEntryForIndex(cacheSelectorIndex) - (uint64_t)state.config.dyldCache());
         selectorFixups.push_back(bindTarget);
@@ -308,25 +448,23 @@
     }
 
     // See if this selector is already in the app map from a previous image
-    prebuilt_objc::ObjCStringKey selectorMapKey { selectorString };
-    auto appSelectorIt = appSelectorMap.find(selectorMapKey);
+    auto appSelectorIt = appSelectorMap.find(selectorString);
     if ( appSelectorIt != appSelectorMap.end() ) {
         // This selector was found in a previous image, so use it here.
 
         //printf("Overriding fixup at 0x%08llX to other image\n", selectorUseImageOffset);
-        selectorFixups.push_back(PrebuiltLoader::BindTargetRef(appSelectorIt->second.nameLocation));
+        selectorFixups.push_back(PrebuiltLoader::BindTargetRef(appSelectorIt->second));
         return;
     }
 
     // See if this selector is already in the map for this image
-    prebuilt_objc::ObjCSelectorLocation selectorMapValue = { Loader::BindTarget() };
-    auto itAndInserted = selectorMap.insert({ selectorMapKey, selectorMapValue });
+    auto itAndInserted = selectorMap.insert({ selectorString, Loader::BindTarget() });
     if ( itAndInserted.second ) {
         // We added the selector so its pointing in to our own image.
         Loader::BindTarget target;
         target.loader               = jitLoader;
-        target.runtimeOffset        = selectorStringRuntimeOffset.rawValue();
-        itAndInserted.first->second.nameLocation = target;
+        target.runtimeOffset        = selectorStringRuntimeOffset;
+        itAndInserted.first->second = target;
 
         // We'll add a fixup anyway as we want a sel ref fixup for every entry in the sel refs section
 
@@ -337,7 +475,7 @@
 
     // This selector was found elsewhere in our image.  As we want a fixup for every selref, we'll
     // add one here too
-    Loader::BindTarget& target = itAndInserted.first->second.nameLocation;
+    Loader::BindTarget& target = itAndInserted.first->second;
 
     //printf("Overriding fixup at 0x%08llX to '%s' offset 0x%08llX\n", selectorUseImageOffset, findLoadedImage(target.image.imageNum).path(), target.image.offset);
     selectorFixups.push_back(PrebuiltLoader::BindTargetRef(target));
@@ -345,10 +483,10 @@
 
 // Check if the given class is in an image loaded in the shared cache.
 // If so, add the class to the duplicate map
-static void checkForDuplicateClass(const VMAddress dyldCacheBaseAddress,
+static void checkForDuplicateClass(const void* dyldCacheBase,
                                    const char* className, const objc::ClassHashTable* objcClassOpt,
-                                   PrebuiltObjC::SharedCacheImagesMapTy& sharedCacheImagesMap,
-                                   PrebuiltObjC::DuplicateClassesMapTy& duplicateSharedCacheClasses,
+                                   const PrebuiltObjC::SharedCacheImagesMapTy& sharedCacheImagesMap,
+                                   const PrebuiltObjC::DuplicateClassesMapTy& duplicateSharedCacheClasses,
                                    ObjCOptimizerImage& image)
 {
     objcClassOpt->forEachClass(className,
@@ -360,10 +498,10 @@
             // We have a duplicate class, so check if we've already got it in our map.
             if ( duplicateSharedCacheClasses.find(className) == duplicateSharedCacheClasses.end() ) {
                 // We haven't seen this one yet, so record it in the map for this image
-                VMAddress cacheDylibUnslidVMAddr = cacheIt->second.first;
-                VMAddress          classVMAddr = dyldCacheBaseAddress + VMOffset(classCacheOffset);
-                VMOffset           classDylibVMOffset = classVMAddr - cacheDylibUnslidVMAddr;
-                Loader::BindTarget classTarget   = { ldr, classDylibVMOffset.rawValue() };
+                const dyld3::MachOLoaded* sharedCacheMH = cacheIt->second.first;
+                uint64_t           classPointer = (uint64_t)dyldCacheBase + classCacheOffset;
+                uint64_t           classVMOffset = classPointer - (uint64_t)sharedCacheMH;
+                Loader::BindTarget classTarget   = { ldr, classVMOffset };
                 image.duplicateSharedCacheClassMap.insert({ className, classTarget });
             }
 
@@ -372,27 +510,26 @@
     });
 }
 
-void ObjCOptimizerImage::visitClass(const VMAddress dyldCacheBaseAddress,
+void ObjCOptimizerImage::visitClass(const void* dyldCacheBase,
                                     const objc::ClassHashTable* objcClassOpt,
-                                    SharedCacheImagesMapTy& sharedCacheImagesMap,
-                                    DuplicateClassesMapTy& duplicateSharedCacheClasses,
-                                    InputDylibVMAddress classVMAddr, InputDylibVMAddress classNameVMAddr, const char* className)
+                                    const SharedCacheImagesMapTy& sharedCacheImagesMap,
+                                    const DuplicateClassesMapTy& duplicateSharedCacheClasses,
+                                    uint64_t classVMAddr, uint64_t classNameVMAddr, const char* className)
 {
 
     // If the class also exists in a shared cache image which is loaded, then objc
     // would have found that one, regardless of load order.
     // In that case, we still add this class to the map, but also track which shared cache class it is a duplicate of
-    checkForDuplicateClass(dyldCacheBaseAddress, className, objcClassOpt, sharedCacheImagesMap,
-                           duplicateSharedCacheClasses, *this);
-
-    VMOffset classNameVMOffset   = classNameVMAddr - loadAddress;
-    VMOffset classObjectVMOffset = classVMAddr - loadAddress;
+    checkForDuplicateClass(dyldCacheBase, className, objcClassOpt, sharedCacheImagesMap, duplicateSharedCacheClasses, *this);
+
+    uint64_t classNameVMOffset   = classNameVMAddr - loadAddress;
+    uint64_t classObjectVMOffset = classVMAddr - loadAddress;
     classLocations.push_back({ className, classNameVMOffset, classObjectVMOffset });
 }
 
 static bool protocolIsInSharedCache(const char* protocolName,
                                     const objc::ProtocolHashTable* objcProtocolOpt,
-                                    PrebuiltObjC::SharedCacheImagesMapTy& sharedCacheImagesMap)
+                                    const PrebuiltObjC::SharedCacheImagesMapTy& sharedCacheImagesMap)
 {
     __block bool foundProtocol = false;
     objcProtocolOpt->forEachProtocol(protocolName,
@@ -407,9 +544,8 @@
 }
 
 void ObjCOptimizerImage::visitProtocol(const objc::ProtocolHashTable* objcProtocolOpt,
-                                       SharedCacheImagesMapTy& sharedCacheImagesMap,
-                                       InputDylibVMAddress protocolVMAddr, InputDylibVMAddress protocolNameVMAddr,
-                                       const char* protocolName)
+                                       const SharedCacheImagesMapTy& sharedCacheImagesMap,
+                                       uint64_t protocolVMAddr, uint64_t protocolNameVMAddr, const char* protocolName)
 {
 
     uint32_t protocolIndex = (uint32_t)protocolISAFixups.count();
@@ -420,8 +556,8 @@
     if ( protocolIsInSharedCache(protocolName, objcProtocolOpt, sharedCacheImagesMap) )
         return;
 
-    VMOffset protocolNameVMOffset   = protocolNameVMAddr - loadAddress;
-    VMOffset protocolObjectVMOffset = protocolVMAddr - loadAddress;
+    uint64_t protocolNameVMOffset   = protocolNameVMAddr - loadAddress;
+    uint64_t protocolObjectVMOffset = protocolVMAddr - loadAddress;
     protocolLocations.push_back({ protocolName, protocolNameVMOffset, protocolObjectVMOffset });
 
     // Record which index this protocol uses in protocolISAFixups.  Later we can change its entry if we
@@ -433,7 +569,6 @@
 
 // HACK!: dyld3 used to know if each image in a closure has been rebased or not when it was building the closure
 // Now we try to make good guesses based on whether its the shared cache or not, and which binary is executing this code
-#if 0
 static bool hasBeenRebased(const Loader* ldr)
 {
 #if BUILDING_DYLD
@@ -447,55 +582,33 @@
     return false;
 #endif
 }
-#endif
-
-static objc_visitor::Visitor makeObjCVisitor(Diagnostics& diag, RuntimeState& state,
-                                             const Loader* ldr)
-{
-
-#if POINTERS_ARE_UNSLID
-    const dyld3::MachOAnalyzer* dylibMA = ldr->analyzer(state);
-
-    const DyldSharedCache* dyldCache = (const DyldSharedCache*)state.config.dyldCache.addr;
-    uint64_t sharedCacheRelativeSelectorBaseVMAddress = dyldCache->sharedCacheRelativeSelectorBaseVMAddress();
-    objc_visitor::Visitor objcVisitor(dyldCache, dylibMA, VMAddress(sharedCacheRelativeSelectorBaseVMAddress));
-    return objcVisitor;
-#elif SUPPORT_VM_LAYOUT
-    const dyld3::MachOAnalyzer* dylibMA = ldr->analyzer(state);
-
-    objc_visitor::Visitor objcVisitor(dylibMA);
-    return objcVisitor;
-#else
-    const dyld3::MachOFile* dylibMF = ldr->mf(state);
-    return dylibMF->makeObjCVisitor(diag);
-#endif
-}
 
 static void optimizeObjCSelectors(RuntimeState& state,
                                   const objc::SelectorHashTable* objcSelOpt,
-                                  PrebuiltObjC::SelectorMapTy& appSelectorMap,
+                                  const PrebuiltObjC::SelectorMapTy& appSelectorMap,
                                   ObjCOptimizerImage&                image)
 {
 
-    const Header*   hdr         = (const Header*)image.jitLoader->mf(state);
-    uint32_t        pointerSize = hdr->pointerSize();
+    const dyld3::MachOAnalyzer*                 ma              = (const dyld3::MachOAnalyzer*)image.jitLoader->loadAddress(state);
+    uint32_t                                    pointerSize     = ma->pointerSize();
+    const dyld3::MachOAnalyzer::VMAddrConverter vmAddrConverter = ma->makeVMAddrConverter(hasBeenRebased(image.jitLoader));
 
     // The legacy (objc1) codebase uses a bunch of sections we don't want to reason about.  If we see them just give up.
     __block bool foundBadSection = false;
-    hdr->forEachSection(^(const Header::SectionInfo& sectInfo, bool& stop) {
-        if ( sectInfo.segmentName != "__OBJC" )
-            return;
-        if ( sectInfo.sectionName == "__module_info" ) {
+    ma->forEachSection(^(const MachOAnalyzer::SectionInfo& sectInfo, bool malformedSectionRange, bool& stop) {
+        if ( strcmp(sectInfo.segInfo.segName, "__OBJC") != 0 )
+            return;
+        if ( strcmp(sectInfo.sectName, "__module_info") == 0 ) {
             foundBadSection = true;
             stop            = true;
             return;
         }
-        if ( sectInfo.sectionName == "__protocol" ) {
+        if ( strcmp(sectInfo.sectName, "__protocol") == 0 ) {
             foundBadSection = true;
             stop            = true;
             return;
         }
-        if ( sectInfo.sectionName == "__message_refs" ) {
+        if ( strcmp(sectInfo.sectName, "__message_refs") == 0 ) {
             foundBadSection = true;
             stop            = true;
             return;
@@ -510,45 +623,34 @@
     // Note this isn't actually supported in libobjc any more.  Its logic for deciding whether to support it is if this is true:
     // #if (defined(__x86_64__) && (TARGET_OS_OSX || TARGET_OS_SIMULATOR))
     // So to keep it simple, lets only do this walk if we are x86_64
-    if ( hdr->isArch("x86_64") || hdr->isArch("x86_64h") ) {
-        if ( hdr->hasObjCMessageReferences() ) {
+    if ( ma->isArch("x86_64") || ma->isArch("x86_64h") ) {
+        if ( ma->hasObjCMessageReferences() ) {
             image.diag.error("Cannot handle message refs");
             return;
         }
     }
 
-    // FIXME: Don't make a duplicate one of these if we can pass one in instead
-    __block objc_visitor::Visitor objcVisitor = makeObjCVisitor(image.diag, state, image.jitLoader);
-    if ( image.diag.hasError() )
-        return;
-
-    // We only record selector references for __objc_selrefs and pointer based method lists.
-    // If we find a relative method list pointing outside of __objc_selrefs then we give up for now
+    // We only record selector references for __objc_selrefs and pointer based method lists.  If we find a relative method list pointing
+    // outside of __objc_selrefs then we give up for now
     uint64_t selRefsStartRuntimeOffset = image.binaryInfo.selRefsRuntimeOffset;
     uint64_t selRefsEndRuntimeOffset   = selRefsStartRuntimeOffset + (pointerSize * image.binaryInfo.selRefsCount);
-    auto     visitRelativeMethod = ^(const objc_visitor::Method& method, bool& stop) {
-        VMAddress selectorRefVMAddress = method.getNameSelRefVMAddr(objcVisitor);
-        VMOffset selectorReferenceRuntimeOffset = selectorRefVMAddress - VMAddress(image.loadAddress.rawValue());
-        if ( (selectorReferenceRuntimeOffset.rawValue() < selRefsStartRuntimeOffset)
-            || (selectorReferenceRuntimeOffset.rawValue() >= selRefsEndRuntimeOffset) ) {
+    auto     visitMethod               = ^(uint64_t methodVMAddr, const dyld3::MachOAnalyzer::ObjCMethod& method, bool& stop) {
+        uint64_t selectorReferenceRuntimeOffset = method.nameLocationVMAddr - image.loadAddress;
+        if ( (selectorReferenceRuntimeOffset < selRefsStartRuntimeOffset) || (selectorReferenceRuntimeOffset >= selRefsEndRuntimeOffset) ) {
             image.diag.error("Cannot handle relative method list pointing outside of __objc_selrefs");
             stop = true;
         }
     };
 
-    auto visitMethodList = ^(const objc_visitor::MethodList& methodList,
-                             bool& hasPointerBasedMethodList, bool &stop) {
-        if ( methodList.numMethods() == 0 )
-            return;
-
-        if ( methodList.usesRelativeOffsets() ) {
+    auto visitMethodList = ^(uint64_t methodListVMAddr, bool& hasPointerBasedMethodList, bool& hasRelativeMethodList) {
+        if ( methodListVMAddr == 0 )
+            return;
+        uint64_t methodListRuntimeOffset = methodListVMAddr - image.loadAddress;
+        if ( ma->objcMethodListIsRelative(methodListRuntimeOffset) ) {
             // Check relative method lists
-            uint32_t numMethods = methodList.numMethods();
-            for ( uint32_t i = 0; i != numMethods; ++i ) {
-                const objc_visitor::Method& method = methodList.getMethod(objcVisitor, i);
-                visitRelativeMethod(method, stop);
-            }
-        } else {
+            ma->forEachObjCMethod(methodListVMAddr, vmAddrConverter, 0, visitMethod);
+        }
+        else {
             // Record if we found a pointer based method list.  This lets us skip walking method lists later if
             // they are all relative method lists
             hasPointerBasedMethodList = true;
@@ -557,76 +659,94 @@
 
     if ( image.binaryInfo.classListCount != 0 ) {
         __block bool hasPointerBasedMethodList = false;
-        objcVisitor.forEachClassAndMetaClass(^(const objc_visitor::Class &objcClass, bool &stopClass) {
-            objc_visitor::MethodList methodList = objcClass.getBaseMethods(objcVisitor);
-            visitMethodList(methodList, hasPointerBasedMethodList, stopClass);
-        });
+        __block bool hasRelativeMethodList     = false;
+        auto         visitClass                = ^(uint64_t classVMAddr, uint64_t classSuperclassVMAddr, uint64_t classDataVMAddr,
+                            const dyld3::MachOAnalyzer::ObjCClassInfo& objcClass, bool isMetaClass, bool& stop) {
+            visitMethodList(objcClass.baseMethodsVMAddr(pointerSize), hasPointerBasedMethodList, hasRelativeMethodList);
+            if ( image.diag.hasError() )
+                stop = true;
+        };
+        ma->forEachObjCClass(image.binaryInfo.classListRuntimeOffset, image.binaryInfo.classListCount, vmAddrConverter, visitClass);
+        if ( image.diag.hasError() )
+            return;
+
         image.binaryInfo.hasClassMethodListsToUnique     = hasPointerBasedMethodList;
         image.binaryInfo.hasClassMethodListsToSetUniqued = hasPointerBasedMethodList;
     }
 
     if ( image.binaryInfo.categoryCount != 0 ) {
         __block bool hasPointerBasedMethodList = false;
-        objcVisitor.forEachCategory(^(const objc_visitor::Category& objcCategory, bool &stopCategory) {
-            objc_visitor::MethodList instanceMethodList = objcCategory.getInstanceMethods(objcVisitor);
-            objc_visitor::MethodList classMethodList    = objcCategory.getClassMethods(objcVisitor);
-
-            visitMethodList(instanceMethodList, hasPointerBasedMethodList, stopCategory);
-            if ( stopCategory )
+        __block bool hasRelativeMethodList     = false;
+        auto         visitCategory             = ^(uint64_t categoryVMAddr, const dyld3::MachOAnalyzer::ObjCCategory& objcCategory, bool& stop) {
+            visitMethodList(objcCategory.instanceMethodsVMAddr, hasPointerBasedMethodList, hasRelativeMethodList);
+            if ( image.diag.hasError() ) {
+                stop = true;
                 return;
-
-            visitMethodList(classMethodList, hasPointerBasedMethodList, stopCategory);
-        });
+            }
+            visitMethodList(objcCategory.classMethodsVMAddr, hasPointerBasedMethodList, hasRelativeMethodList);
+            if ( image.diag.hasError() )
+                stop = true;
+        };
+        ma->forEachObjCCategory(image.binaryInfo.categoryListRuntimeOffset, image.binaryInfo.categoryCount, vmAddrConverter, visitCategory);
+        if ( image.diag.hasError() )
+            return;
+
         image.binaryInfo.hasCategoryMethodListsToUnique     = hasPointerBasedMethodList;
         image.binaryInfo.hasCategoryMethodListsToSetUniqued = hasPointerBasedMethodList;
     }
 
     if ( image.binaryInfo.protocolListCount != 0 ) {
         __block bool hasPointerBasedMethodList = false;
-        objcVisitor.forEachProtocol(^(const objc_visitor::Protocol& objcProtocol, bool& stopProtocol) {
-            objc_visitor::MethodList instanceMethodList         = objcProtocol.getInstanceMethods(objcVisitor);
-            objc_visitor::MethodList classMethodList            = objcProtocol.getClassMethods(objcVisitor);
-            objc_visitor::MethodList optionalInstanceMethodList = objcProtocol.getOptionalInstanceMethods(objcVisitor);
-            objc_visitor::MethodList optionalClassMethodList    = objcProtocol.getOptionalClassMethods(objcVisitor);
-
-            visitMethodList(instanceMethodList, hasPointerBasedMethodList, stopProtocol);
-            if ( stopProtocol )
+        __block bool hasRelativeMethodList     = false;
+        auto         visitProtocol             = ^(uint64_t protocolVMAddr, const dyld3::MachOAnalyzer::ObjCProtocol& objCProtocol, bool& stop) {
+            visitMethodList(objCProtocol.instanceMethodsVMAddr, hasPointerBasedMethodList, hasRelativeMethodList);
+            if ( image.diag.hasError() ) {
+                stop = true;
                 return;
-
-            visitMethodList(classMethodList, hasPointerBasedMethodList, stopProtocol);
-            if ( stopProtocol )
+            }
+            visitMethodList(objCProtocol.classMethodsVMAddr, hasPointerBasedMethodList, hasRelativeMethodList);
+            if ( image.diag.hasError() ) {
+                stop = true;
                 return;
-
-            visitMethodList(optionalInstanceMethodList, hasPointerBasedMethodList, stopProtocol);
-            if ( stopProtocol )
+            }
+            visitMethodList(objCProtocol.optionalInstanceMethodsVMAddr, hasPointerBasedMethodList, hasRelativeMethodList);
+            if ( image.diag.hasError() ) {
+                stop = true;
                 return;
-
-            visitMethodList(optionalClassMethodList, hasPointerBasedMethodList, stopProtocol);
-        });
+            }
+            visitMethodList(objCProtocol.optionalClassMethodsVMAddr, hasPointerBasedMethodList, hasRelativeMethodList);
+            if ( image.diag.hasError() )
+                stop = true;
+        };
+        ma->forEachObjCProtocol(image.binaryInfo.protocolListRuntimeOffset, image.binaryInfo.protocolListCount, vmAddrConverter, visitProtocol);
+        if ( image.diag.hasError() )
+            return;
+
         image.binaryInfo.hasProtocolMethodListsToUnique     = hasPointerBasedMethodList;
         image.binaryInfo.hasProtocolMethodListsToSetUniqued = hasPointerBasedMethodList;
     }
 
-    auto visitSelRef = ^(uint64_t selectorReferenceRuntimeOffset, uint64_t selectorStringRuntimeOffset,
-                         const char* selectorString) {
-        // Note we don't check if the string is printable.  We already checked earlier that this image doesn't have
-        // Fairplay or protected segments, which would prevent seeing the strings.
-        image.visitReferenceToObjCSelector(objcSelOpt, appSelectorMap,
-                                           VMOffset(selectorReferenceRuntimeOffset),
-                                           VMOffset(selectorStringRuntimeOffset), selectorString);
-    };
-
-    PrebuiltObjC::forEachSelectorReferenceToUnique(state, image.jitLoader, image.loadAddress.rawValue(), image.binaryInfo, visitSelRef);
+    PrebuiltObjC::forEachSelectorReferenceToUnique(state, ma, image.loadAddress, image.binaryInfo, vmAddrConverter,
+                                                   ^(uint64_t selectorReferenceRuntimeOffset, uint64_t selectorStringRuntimeOffset) {
+                                                       // Note we don't check if the string is printable.  We already checked earlier that this image doesn't have
+                                                       // Fairplay or protected segments, which would prevent seeing the strings.
+                                                       const char* selectorString = (const char*)ma + selectorStringRuntimeOffset;
+                                                       image.visitReferenceToObjCSelector(objcSelOpt, appSelectorMap, selectorReferenceRuntimeOffset, selectorStringRuntimeOffset, selectorString);
+                                                   });
 }
 
 static void optimizeObjCClasses(RuntimeState& state,
                                 const objc::ClassHashTable* objcClassOpt,
-                                PrebuiltObjC::SharedCacheImagesMapTy& sharedCacheImagesMap,
-                                PrebuiltObjC::DuplicateClassesMapTy& duplicateSharedCacheClasses,
+                                const PrebuiltObjC::SharedCacheImagesMapTy& sharedCacheImagesMap,
+                                const PrebuiltObjC::DuplicateClassesMapTy& duplicateSharedCacheClasses,
                                 ObjCOptimizerImage& image)
 {
     if ( image.binaryInfo.classListCount == 0 )
         return;
+
+    const dyld3::MachOAnalyzer*                 ma              = (const dyld3::MachOAnalyzer*)image.jitLoader->loadAddress(state);
+    const intptr_t                              slide           = ma->getSlide();
+    const dyld3::MachOAnalyzer::VMAddrConverter vmAddrConverter = ma->makeVMAddrConverter(hasBeenRebased(image.jitLoader));
 
 #if BUILDING_CACHE_BUILDER || BUILDING_CLOSURE_UTIL
     image.calculateMissingWeakImports(state);
@@ -634,22 +754,22 @@
         return;
 #endif
 
-    // FIXME: Don't make a duplicate one of these if we can pass one in instead
-    __block objc_visitor::Visitor objcVisitor = makeObjCVisitor(image.diag, state, image.jitLoader);
-    if ( image.diag.hasError() )
-        return;
-
-    VMAddress dyldCacheBaseAddress(state.config.dyldCache.unslidLoadAddress);
-
-    // Note we skip metaclasses
-    objcVisitor.forEachClass(^(const objc_visitor::Class& objcClass, bool &stopClass) {
+    dyld3::MachOAnalyzer::ClassCallback visitClass = ^(uint64_t classVMAddr,
+                                                       uint64_t classSuperclassVMAddr, uint64_t classDataVMAddr,
+                                                       const MachOAnalyzer::ObjCClassInfo& objcClass, bool isMetaClass,
+                                                       bool& stop) {
+        if ( isMetaClass )
+            return;
+
         // Make sure the superclass pointer is not nil.  Unless we are a root class as those don't have a superclass
-        if ( !objcClass.isRootClass(objcVisitor) ) {
-            metadata_visitor::ResolvedValue classSuperclassField = objcClass.getSuperclassField(objcVisitor);
-            InputDylibVMAddress superclassFieldVMAddr(classSuperclassField.vmAddress().rawValue());
-            if ( image.isNull(superclassFieldVMAddr, classSuperclassField.value()) ) {
-                const char* className = objcClass.getName(objcVisitor);
-                image.diag.error("Missing weak superclass of class %s in %s", className, image.jitLoader->path(state));
+        if ( image.isNull(classSuperclassVMAddr, ma, slide) ) {
+            const uint32_t RO_ROOT = (1 << 1);
+            if ( (objcClass.flags(image.pointerSize) & RO_ROOT) == 0 ) {
+                uint64_t classNameVMAddr = objcClass.nameVMAddr(image.pointerSize);
+                const char* className = (const char*)(classNameVMAddr + slide);
+                char dupPath[PATH_MAX];
+                Diagnostics::quotePath(image.jitLoader->path(), dupPath);
+                image.diag.error("Missing weak superclass of class %s in '%s'", className, dupPath);
                 return;
             }
         }
@@ -657,186 +777,142 @@
         // Does this class need to be fixed up for stable Swift ABI.
         // Note the order matches the objc runtime in that we always do this fix before checking for dupes,
         // but after excluding classes with missing weak superclasses.
-        if ( objcClass.isUnfixedBackwardDeployingStableSwift(objcVisitor) ) {
+        if ( objcClass.isUnfixedBackwardDeployingStableSwift() ) {
             // Class really is stable Swift, pretending to be pre-stable.
             image.binaryInfo.hasClassStableSwiftFixups = true;
         }
 
-        VMAddress classVMAddr = objcClass.getVMAddress();
-        VMAddress classNameVMAddr = objcClass.getNameVMAddr(objcVisitor);
+        uint64_t classNameVMAddr = objcClass.nameVMAddr(image.pointerSize);
         // Note we don't check if the string is printable.  We already checked earlier that this image doesn't have
         // Fairplay or protected segments, which would prevent seeing the strings.
-        const char* className = objcClass.getName(objcVisitor);
-        image.visitClass(dyldCacheBaseAddress, objcClassOpt, sharedCacheImagesMap, duplicateSharedCacheClasses,
-                         InputDylibVMAddress(classVMAddr.rawValue()), InputDylibVMAddress(classNameVMAddr.rawValue()),
-                         className);
-    });
+        const char* className = (const char*)(classNameVMAddr + slide);
+
+        image.visitClass(state.config.dyldCache.addr, objcClassOpt, sharedCacheImagesMap, duplicateSharedCacheClasses, classVMAddr, classNameVMAddr, className);
+    };
+
+    ma->forEachObjCClass(image.binaryInfo.classListRuntimeOffset, image.binaryInfo.classListCount, vmAddrConverter, visitClass);
 }
 
 static void optimizeObjCProtocols(RuntimeState& state,
                                   const objc::ProtocolHashTable* objcProtocolOpt,
-                                  PrebuiltObjC::SharedCacheImagesMapTy& sharedCacheImagesMap,
+                                  const PrebuiltObjC::SharedCacheImagesMapTy& sharedCacheImagesMap,
                                   ObjCOptimizerImage& image)
 {
     if ( image.binaryInfo.protocolListCount == 0 )
         return;
 
+    const dyld3::MachOAnalyzer*                 ma              = (const dyld3::MachOAnalyzer*)image.jitLoader->loadAddress(state);
+    const intptr_t                              slide           = ma->getSlide();
+    const dyld3::MachOAnalyzer::VMAddrConverter vmAddrConverter = ma->makeVMAddrConverter(hasBeenRebased(image.jitLoader));
+
     image.protocolISAFixups.reserve(image.binaryInfo.protocolListCount);
 
-    // FIXME: Don't make a duplicate one of these if we can pass one in instead
-    __block objc_visitor::Visitor objcVisitor = makeObjCVisitor(image.diag, state, image.jitLoader);
-    if ( image.diag.hasError() )
-        return;
-
-    objcVisitor.forEachProtocol(^(const objc_visitor::Protocol& objcProtocol, bool& stopProtocol) {
-        std::optional<VMAddress> isaVMAddr = objcProtocol.getISAVMAddr(objcVisitor);
-        if ( isaVMAddr.has_value() ) {
+    dyld3::MachOAnalyzer::ProtocolCallback visitProtocol = ^(uint64_t                                  protocolVMAddr,
+                                                             const dyld3::MachOAnalyzer::ObjCProtocol& objCProtocol,
+                                                             bool&                                     stop) {
+        if ( objCProtocol.isaVMAddr != 0 ) {
             // We can't optimize this protocol if it has an ISA as we want to override it
             image.diag.error("Protocol ISA must be null");
-            stopProtocol = true;
-            return;
-        }
-
-        VMAddress protocolVMAddr = objcProtocol.getVMAddress();
-        VMAddress protocolNameVMAddr = objcProtocol.getNameVMAddr(objcVisitor);
+            stop = true;
+            return;
+        }
+
+        uint64_t protocolNameVMAddr = objCProtocol.nameVMAddr;
         // Note we don't check if the string is printable.  We already checked earlier that this image doesn't have
         // Fairplay or protected segments, which would prevent seeing the strings.
-        const char* protocolName = objcProtocol.getName(objcVisitor);
-
-        image.visitProtocol(objcProtocolOpt, sharedCacheImagesMap, InputDylibVMAddress(protocolVMAddr.rawValue()),
-                            InputDylibVMAddress(protocolNameVMAddr.rawValue()), protocolName);
-    });
-}
-
-static void optimizeObjCProtocolReferences(RuntimeState& state,
-                                           const objc::ProtocolHashTable* objcProtocolOpt,
-                                           PrebuiltObjC::SharedCacheImagesMapTy& sharedCacheImagesMap,
-                                           PrebuiltObjC::ProtocolMapTy& protocolMap,
-                                           ObjCOptimizerImage& image)
-{
-    if ( image.binaryInfo.protocolRefsCount == 0 )
-        return;
-
-    image.protocolFixups.reserve(image.binaryInfo.protocolRefsCount);
-
-    // FIXME: Don't make a duplicate one of these if we can pass one in instead
-    __block objc_visitor::Visitor objcVisitor = makeObjCVisitor(image.diag, state, image.jitLoader);
-    if ( image.diag.hasError() )
-        return;
-
-    objcVisitor.forEachProtocolReference(^(metadata_visitor::ResolvedValue& protocolRefValue) {
-        if ( image.diag.hasError() )
-            return;
-
-        // Follow the protocol reference to get to the actual protocol
-        metadata_visitor::ResolvedValue protocolValue = objcVisitor.resolveRebase(protocolRefValue);
-        objc_visitor::Protocol objcProtocol(protocolValue);
-
-        const char* protocolName = objcProtocol.getName(objcVisitor);
-
-        // Check if this protocol is in the map in the shared cache.  If so use that one
-        __block std::optional<uint64_t> protocolCacheOffset;
-        objcProtocolOpt->forEachProtocol(protocolName,
-                                         ^(uint64_t classCacheOffset, uint16_t dylibObjCIndex, bool& stopObjects) {
-            // Check if this image is loaded
-            if ( auto cacheIt = sharedCacheImagesMap.find(dylibObjCIndex); cacheIt != sharedCacheImagesMap.end() ) {
-                protocolCacheOffset = classCacheOffset;
-                stopObjects = true;
-            }
-        });
-        if ( protocolCacheOffset.has_value() ) {
-            // We use an absolute bind to point in to the shared cache protocols
-            PrebuiltLoader::BindTargetRef bindTarget = PrebuiltLoader::BindTargetRef::makeAbsolute(protocolCacheOffset.value());
-            image.protocolFixups.push_back(bindTarget);
-            return;
-        }
-
-        // Not using the shared cache, so we should find the protocol in the map in the closure
-        prebuilt_objc::ObjCStringKey key = { protocolName };
-        auto nameIt = protocolMap.find(key);
-        if ( nameIt == protocolMap.end() ) {
-            // FIXME: What do we do here?  The protocols are wrong?  Skip this image for now.
-            image.diag.error("Could not find protocol '%s'", protocolName);
-            return;
-        }
-        const prebuilt_objc::ObjCObjectLocation& protocolLocation = nameIt->value;
-        image.protocolFixups.push_back(protocolLocation.objectLocation);
-    });
-}
-
+        const char* protocolName = (const char*)(protocolNameVMAddr + slide);
+
+        image.visitProtocol(objcProtocolOpt, sharedCacheImagesMap, protocolVMAddr, protocolNameVMAddr, protocolName);
+    };
+
+    ma->forEachObjCProtocol(image.binaryInfo.protocolListRuntimeOffset, image.binaryInfo.protocolListCount, vmAddrConverter, visitProtocol);
+}
 
 static void
-generateClassOrProtocolHashTable(PrebuiltObjC::ObjCStructKind objcKind,
-                                 Array<ObjCOptimizerImage>& objcImages,
-                                 const PrebuiltObjC::DuplicateClassesMapTy& duplicateSharedCacheClassMap,
-                                 PrebuiltObjC::ObjectMapTy& objectMap, bool& hasDuplicates)
-{
+writeClassOrProtocolHashTable(RuntimeState& state, bool classes,
+                              Array<ObjCOptimizerImage>& objcImages,
+                              OverflowSafeArray<uint8_t>& hashTable,
+                              const PrebuiltObjC::DuplicateClassesMapTy& duplicateSharedCacheClassMap)
+{
+
+    dyld3::CStringMultiMapTo<PrebuiltLoader::BindTarget> seenObjectsMap;
+    dyld3::CStringMapTo<PrebuiltLoader::BindTarget>      objectNameMap;
+    OverflowSafeArray<const char*>                       objectNames;
+
     // Note we walk the images backwards as we want them in load order to match the order they are registered with objc
-    for ( uint64_t imageIndex = 0, reverseIndex = (objcImages.count() - 1); imageIndex != objcImages.count(); ++imageIndex, --reverseIndex ) {
+    for ( size_t imageIndex = 0, reverseIndex = (objcImages.count() - 1); imageIndex != objcImages.count(); ++imageIndex, --reverseIndex ) {
         if ( objcImages[reverseIndex].diag.hasError() )
             continue;
         ObjCOptimizerImage& image = objcImages[reverseIndex];
 
-        if ( objcKind == PrebuiltObjC::ObjCStructKind::classes ) {
-            for ( const ObjCOptimizerImage::ObjCObject& classLocation : image.classLocations ) {
-                //uint64_t nameVMAddr     = ma->preferredLoadAddress() + classImage.offsetOfClassNames + classNameTarget.classNameImageOffset;
-                //printf("%s: 0x%08llx = '%s'\n", li.path(), nameVMAddr, className);
-
-                // Also track the name
-                PrebuiltLoader::BindTarget nameTarget  = { image.jitLoader, classLocation.nameRuntimeOffset.rawValue() };
-                PrebuiltLoader::BindTarget valueTarget = { image.jitLoader, classLocation.valueRuntimeOffset.rawValue() };
-                prebuilt_objc::ObjCStringKey key = { classLocation.name };
-                prebuilt_objc::ObjCObjectLocation value = {
-                    nameTarget,
-                    valueTarget
-                };
-                bool alreadyHaveNodeWithKey = false;
-                auto objectIt = objectMap.insert({ key, value }, alreadyHaveNodeWithKey);
-                if ( !alreadyHaveNodeWithKey ) {
-                    // Check if we have a duplicate.  If we do, it will be on the last image which had a duplicate class name,
-                    // but as we walk images backwards, we'll see this before all other images with duplicates.
-                    // Note we only check for duplicates when we know we just inserted the object name in to the map, as this
-                    // ensure's that we only insert each duplicate once
-                    auto duplicateClassIt = duplicateSharedCacheClassMap.find(classLocation.name);
-                    if ( duplicateClassIt != duplicateSharedCacheClassMap.end() ) {
-                        // This is gross.  Change this entry to the duplicate, and add a new one
-                        objectIt->value = { nameTarget, duplicateClassIt->second };
-
-                        bool unusedAlreadyHaveNodeWithKey;
-                        objectMap.insert({ key, value }, unusedAlreadyHaveNodeWithKey);
-                        hasDuplicates = true;
-                    }
-                } else {
-                    // We didn't add the node, so we have duplicates
-                    hasDuplicates = true;
-                }
-            }
-        }
-
-        if ( objcKind == PrebuiltObjC::ObjCStructKind::protocols ) {
-            for ( const ObjCOptimizerImage::ObjCObject& protocolLocation : image.protocolLocations ) {
-                // Also track the name
-                PrebuiltLoader::BindTarget nameTarget  = { image.jitLoader, protocolLocation.nameRuntimeOffset.rawValue() };
-                PrebuiltLoader::BindTarget valueTarget = { image.jitLoader, protocolLocation.valueRuntimeOffset.rawValue() };
-                prebuilt_objc::ObjCStringKey key = { protocolLocation.name };
-                prebuilt_objc::ObjCObjectLocation value = {
-                    nameTarget,
-                    valueTarget
-                };
-                bool alreadyHaveNodeWithKey = false;
-                objectMap.insert({ key, value }, alreadyHaveNodeWithKey);
-                if ( !alreadyHaveNodeWithKey ) {
-                    // We are processing protocols, and this is the first one we've seen, so track its ISA to be fixed up
-                    auto protocolIndexIt = image.protocolIndexMap.find(protocolLocation.valueRuntimeOffset);
+        const OverflowSafeArray<ObjCOptimizerImage::ObjCObject>& objectLocations = classes ? image.classLocations : image.protocolLocations;
+
+        for ( const ObjCOptimizerImage::ObjCObject& objectLocation : objectLocations ) {
+            //uint64_t nameVMAddr     = ma->preferredLoadAddress() + classImage.offsetOfClassNames + classNameTarget.classNameImageOffset;
+            //printf("%s: 0x%08llx = '%s'\n", li.path(), nameVMAddr, className);
+
+            // Also track the name
+            PrebuiltLoader::BindTarget nameTarget    = { image.jitLoader, objectLocation.nameRuntimeOffset };
+            auto                       itAndInserted = objectNameMap.insert({ objectLocation.name, nameTarget });
+            if ( itAndInserted.second ) {
+                // We inserted the class name so we need to add it to the strings for the closure hash table
+                objectNames.push_back(objectLocation.name);
+
+                // If we are processing protocols, and this is the first one we've seen, then track its ISA to be fixed up
+                if ( !classes ) {
+                    auto protocolIndexIt = image.protocolIndexMap.find(objectLocation.valueRuntimeOffset);
                     assert(protocolIndexIt != image.protocolIndexMap.end());
                     image.protocolISAFixups[protocolIndexIt->second] = true;
                 }
+
+                // Check if we have a duplicate.  If we do, it will be on the last image which had a duplicate class name,
+                // but as we walk images backwards, we'll see this before all other images with duplicates.
+                // Note we only check for duplicates when we know we just inserted the object name in to the map, as this
+                // ensure's that we only insert each duplicate once
+                if ( classes ) {
+                    auto duplicateClassIt = duplicateSharedCacheClassMap.find(objectLocation.name);
+                    if ( duplicateClassIt != duplicateSharedCacheClassMap.end() ) {
+                        seenObjectsMap.insert({ objectLocation.name, duplicateClassIt->second });
+                    }
+                }
             }
-        }
+
+            PrebuiltLoader::BindTarget valueTarget = { image.jitLoader, objectLocation.valueRuntimeOffset };
+            seenObjectsMap.insert({ objectLocation.name, valueTarget });
+        }
+    }
+
+    __block uint32_t numClassesWithDuplicates = 0;
+    __block uint32_t totalDuplicates          = 0;
+    seenObjectsMap.forEachEntry(^(const char* const& key, const PrebuiltLoader::BindTarget** values,
+                                  uint64_t valuesCount) {
+        if ( valuesCount != 1 ) {
+            ++numClassesWithDuplicates;
+            totalDuplicates += valuesCount;
+        }
+    });
+
+    // If we have closure class names, we need to make a hash table for them.
+    if ( !objectNames.empty() ) {
+        objc::PerfectHash phash;
+        objc::PerfectHash::make_perfect(objectNames, phash);
+        size_t size = ObjCClassOpt::size(phash, numClassesWithDuplicates, totalDuplicates);
+        hashTable.resize(size);
+        //printf("Class table size: %lld\n", size);
+        ObjCClassOpt* resultHashTable = (ObjCClassOpt*)hashTable.begin();
+        resultHashTable->write(phash, objectNameMap.array(), seenObjectsMap,
+                               numClassesWithDuplicates, totalDuplicates);
     }
 }
 
 //////////////////////// PrebuiltObjC /////////////////////////////////
+
+PrebuiltObjC::~PrebuiltObjC()
+{
+    for ( ObjCOptimizerImage& objcImage : objcImages ) {
+        objcImage.~ObjCOptimizerImage();
+    }
+}
 
 void PrebuiltObjC::commitImage(const ObjCOptimizerImage& image)
 {
@@ -851,75 +927,29 @@
     // Selector results
     // Note we don't need to add the selector binds here.  Its easier just to process them later from each image
     for ( const auto& stringAndTarget : image.selectorMap ) {
-        this->selectorMap[stringAndTarget.first] = stringAndTarget.second;
-    }
-}
-
-uint32_t PrebuiltObjC::serializeSelectorMap(dyld4::BumpAllocator& alloc) const
-{
-    // The key on the new map is the name bind target
-    typedef prebuilt_objc::ObjCSelectorMapOnDisk::KeyType (^KeyFuncTy)(const SelectorMapTy::KeyType&, const SelectorMapTy::ValueType&);
-    KeyFuncTy convertKey = ^(const SelectorMapTy::KeyType& key, const SelectorMapTy::ValueType& value) {
-        return (prebuilt_objc::ObjCSelectorMapOnDisk::KeyType){ PrebuiltLoader::BindTargetRef(value.nameLocation) };
-    };
-
-    // The value on the new map is unused
-    typedef prebuilt_objc::ObjCSelectorMapOnDisk::ValueType (^ValueFuncTy)(const SelectorMapTy::KeyType&, const SelectorMapTy::ValueType&);
-    ValueFuncTy convertValue = ^(const SelectorMapTy::KeyType& key, const SelectorMapTy::ValueType& value) {
-        return (prebuilt_objc::ObjCSelectorMapOnDisk::ValueType)0;
-    };
-
-    uint32_t offset = (uint32_t)alloc.size();
-    this->selectorMap.serialize(alloc, convertKey, convertValue);
-    return offset;
-}
-
-uint32_t PrebuiltObjC::serializeClassMap(dyld4::BumpAllocator& alloc) const
-{
-    // The key on the new map is the name bind taret
-    typedef prebuilt_objc::ObjCClassMapOnDisk::KeyType (^KeyFuncTy)(const ClassMapTy::KeyType&, const ClassMapTy::ValueType&);
-    KeyFuncTy convertKey = ^(const ClassMapTy::KeyType& key, const ClassMapTy::ValueType& value) {
-        return (prebuilt_objc::ObjCClassMapOnDisk::KeyType){ PrebuiltLoader::BindTargetRef(value.nameLocation) };
-    };
-
-    // The value on the new map is just the class impl
-    typedef prebuilt_objc::ObjCClassMapOnDisk::ValueType (^ValueFuncTy)(const ClassMapTy::KeyType&, const ClassMapTy::ValueType&);
-    ValueFuncTy convertValue = ^(const ClassMapTy::KeyType& key, const ClassMapTy::ValueType& value) {
-        return (prebuilt_objc::ObjCClassMapOnDisk::ValueType){ PrebuiltLoader::BindTargetRef(value.objectLocation) };
-    };
-
-    uint32_t offset = (uint32_t)alloc.size();
-    this->classMap.serialize(alloc, convertKey, convertValue);
-    return offset;
-}
-
-uint32_t PrebuiltObjC::serializeProtocolMap(dyld4::BumpAllocator& alloc) const
-{
-    // The key on the new map is the name bind taret
-    typedef prebuilt_objc::ObjCProtocolMapOnDisk::KeyType (^KeyFuncTy)(const ProtocolMapTy::KeyType&, const ProtocolMapTy::ValueType&);
-    KeyFuncTy convertKey = ^(const ProtocolMapTy::KeyType& key, const ProtocolMapTy::ValueType& value) {
-        return (prebuilt_objc::ObjCProtocolMapOnDisk::KeyType){ PrebuiltLoader::BindTargetRef(value.nameLocation) };
-    };
-
-    // The value on the new map is just the protocol impl
-    typedef prebuilt_objc::ObjCProtocolMapOnDisk::ValueType (^ValueFuncTy)(const ProtocolMapTy::KeyType&, const ProtocolMapTy::ValueType&);
-    ValueFuncTy convertValue = ^(const ProtocolMapTy::KeyType& key, const ProtocolMapTy::ValueType& value) {
-        return (prebuilt_objc::ObjCProtocolMapOnDisk::ValueType){ PrebuiltLoader::BindTargetRef(value.objectLocation) };
-    };
-
-    uint32_t offset = (uint32_t)alloc.size();
-    this->protocolMap.serialize(alloc, convertKey, convertValue);
-    return offset;
-}
-
-void PrebuiltObjC::generateHashTables()
-{
-    generateClassOrProtocolHashTable(PrebuiltObjC::ObjCStructKind::classes, objcImages, 
-                                     duplicateSharedCacheClassMap, classMap, this->hasClassDuplicates);
-
-    bool unusedHasProtocolDuplicates = false;
-    generateClassOrProtocolHashTable(PrebuiltObjC::ObjCStructKind::protocols, objcImages, 
-                                     duplicateSharedCacheClassMap, protocolMap, unusedHasProtocolDuplicates);
+        closureSelectorMap[stringAndTarget.first] = stringAndTarget.second;
+        closureSelectorStrings.push_back(stringAndTarget.first);
+    }
+}
+
+void PrebuiltObjC::generateHashTables(RuntimeState& state)
+{
+    // Write out the class table
+    writeClassOrProtocolHashTable(state, true, objcImages, classesHashTable, duplicateSharedCacheClassMap);
+
+    // Write out the protocol table
+    writeClassOrProtocolHashTable(state, false, objcImages, protocolsHashTable, duplicateSharedCacheClassMap);
+
+    // If we have closure selectors, we need to make a hash table for them.
+    if ( !closureSelectorStrings.empty() ) {
+        objc::PerfectHash phash;
+        objc::PerfectHash::make_perfect(closureSelectorStrings, phash);
+        size_t size = ObjCStringTable::size(phash);
+        selectorsHashTable.resize(size);
+        //printf("Selector table size: %lld\n", size);
+        selectorStringTable = (ObjCStringTable*)selectorsHashTable.begin();
+        selectorStringTable->write(phash, closureSelectorMap.array());
+    }
 }
 
 void PrebuiltObjC::generatePerImageFixups(RuntimeState& state, uint32_t pointerSize)
@@ -967,333 +997,149 @@
                 fixups.selectorReferenceFixups.push_back(target);
             }
         }
-
-        // Protocol references.
-        // These are a BindTargetRef for every protocol reference to fixup
-        if ( !image.protocolFixups.empty() ) {
-            fixups.protocolReferenceFixups.reserve(image.protocolFixups.count());
-            for ( const PrebuiltLoader::BindTargetRef& target : image.protocolFixups ) {
-                fixups.protocolReferenceFixups.push_back(target);
-            }
-        }
-    }
-}
-
-__attribute__((noinline))
-static void forEachSelectorReferenceToUnique(objc_visitor::Visitor& objcVisitor,
-                                             uint64_t                loadAddress,
-                                             const ObjCBinaryInfo&   binaryInfo,
-                                             void (^callback)(uint64_t selectorReferenceRuntimeOffset,
-                                                              uint64_t selectorStringRuntimeOffset,
-                                                              const char* selectorString))
-{
+    }
+}
+
+// Visits each selector reference once, in order.  Note the order this visits selector references has to
+// match for serializing/deserializing the PrebuiltLoader.
+void PrebuiltObjC::forEachSelectorReferenceToUnique(RuntimeState&                                state,
+                                                    const dyld3::MachOAnalyzer*                  ma,
+                                                    uint64_t                                     loadAddress,
+                                                    const ObjCBinaryInfo&                        binaryInfo,
+                                                    const dyld3::MachOAnalyzer::VMAddrConverter& vmAddrConverter,
+                                                    void (^callback)(uint64_t selectorReferenceRuntimeOffset, uint64_t selectorStringRuntimeOffset))
+
+{
+    uint32_t pointerSize = ma->pointerSize();
     if ( binaryInfo.selRefsCount != 0 ) {
-        objcVisitor.forEachSelectorReference(^(VMAddress selRefVMAddr, VMAddress selRefTargetVMAddr,
-                                               const char* selectorString) {
-            VMOffset selectorReferenceRuntimeOffset = selRefVMAddr - VMAddress(loadAddress);
-            VMOffset selectorStringRuntimeOffset    = selRefTargetVMAddr - VMAddress(loadAddress);
-            callback(selectorReferenceRuntimeOffset.rawValue(), selectorStringRuntimeOffset.rawValue(),
-                     selectorString);
-        });
-    }
-}
-
-__attribute__((noinline))
-static void forEachClassSelectorReferenceToUnique(objc_visitor::Visitor& objcVisitor,
-                                                  uint64_t                loadAddress,
-                                                  const ObjCBinaryInfo&   binaryInfo,
-                                                  void (^callback)(uint64_t selectorReferenceRuntimeOffset,
-                                                                   uint64_t selectorStringRuntimeOffset,
-                                                                   const char* selectorString))
-{
+        ma->forEachObjCSelectorReference(binaryInfo.selRefsRuntimeOffset, binaryInfo.selRefsCount, vmAddrConverter,
+                                         ^(uint64_t selRefVMAddr, uint64_t selRefTargetVMAddr, bool& stop) {
+                                             uint64_t selectorReferenceRuntimeOffset = selRefVMAddr - loadAddress;
+                                             uint64_t selectorStringRuntimeOffset    = selRefTargetVMAddr - loadAddress;
+                                             callback(selectorReferenceRuntimeOffset, selectorStringRuntimeOffset);
+                                         });
+    }
 
     // We only make the callback for method list selrefs which are not already covered by the __objc_selrefs section.
     // For pointer based method lists, this is all sel ref pointers.
     // For relative method lists, we should always point to the __objc_selrefs section.  This was checked earlier, so
     // we skip this callback on relative method lists as we know here they must point to the (already uniqied) __objc_selrefs.
-    auto visitPointerBasedMethod = ^(const objc_visitor::Method& method) {
-        VMAddress nameVMAddr = method.getNameVMAddr(objcVisitor);
-        VMAddress nameLocationVMAddr = method.getNameField(objcVisitor).vmAddress();
-        const char* selectorString = method.getName(objcVisitor);
-
-        VMOffset selectorStringRuntimeOffset    = nameVMAddr - VMAddress(loadAddress);
-        VMOffset selectorReferenceRuntimeOffset = nameLocationVMAddr - VMAddress(loadAddress);
-        callback(selectorReferenceRuntimeOffset.rawValue(), selectorStringRuntimeOffset.rawValue(), selectorString);
+    auto visitMethod = ^(uint64_t methodVMAddr, const dyld3::MachOAnalyzer::ObjCMethod& method, bool& stop) {
+        uint64_t selectorReferenceRuntimeOffset = method.nameLocationVMAddr - loadAddress;
+        uint64_t selectorStringRuntimeOffset    = method.nameVMAddr - loadAddress;
+        callback(selectorReferenceRuntimeOffset, selectorStringRuntimeOffset);
     };
 
-    auto visitMethodList = ^(const objc_visitor::MethodList& methodList) {
-        if ( methodList.numMethods() == 0 )
-            return;
-        if ( methodList.usesRelativeOffsets() )
-            return;
-
-        // Check pointer based method lists
-        uint32_t numMethods = methodList.numMethods();
-        for ( uint32_t i = 0; i != numMethods; ++i ) {
-            const objc_visitor::Method& method = methodList.getMethod(objcVisitor, i);
-            visitPointerBasedMethod(method);
-        }
+    auto visitMethodList = ^(uint64_t methodListVMAddr) {
+        if ( methodListVMAddr == 0 )
+            return;
+        uint64_t methodListRuntimeOffset = methodListVMAddr - loadAddress;
+        if ( ma->objcMethodListIsRelative(methodListRuntimeOffset) )
+            return;
+        ma->forEachObjCMethod(methodListVMAddr, vmAddrConverter, 0, visitMethod);
     };
 
     if ( binaryInfo.hasClassMethodListsToUnique && (binaryInfo.classListCount != 0) ) {
-        // FIXME: Use binaryInfo.classListRuntimeOffset and binaryInfo.classListCount
-        objcVisitor.forEachClassAndMetaClass(^(const objc_visitor::Class &objcClass, bool &stopClass) {
-            objc_visitor::MethodList methodList = objcClass.getBaseMethods(objcVisitor);
-            visitMethodList(methodList);
-        });
-    }
-}
-
-__attribute__((noinline))
-static void forEachCategorySelectorReferenceToUnique(objc_visitor::Visitor& objcVisitor,
-                                                     uint64_t                loadAddress,
-                                                     const ObjCBinaryInfo&   binaryInfo,
-                                                     void (^callback)(uint64_t selectorReferenceRuntimeOffset,
-                                                                      uint64_t selectorStringRuntimeOffset,
-                                                                      const char* selectorString))
-{
-    // We only make the callback for method list selrefs which are not already covered by the __objc_selrefs section.
-    // For pointer based method lists, this is all sel ref pointers.
-    // For relative method lists, we should always point to the __objc_selrefs section.  This was checked earlier, so
-    // we skip this callback on relative method lists as we know here they must point to the (already uniqied) __objc_selrefs.
-    auto visitPointerBasedMethod = ^(const objc_visitor::Method& method) {
-        VMAddress nameVMAddr = method.getNameVMAddr(objcVisitor);
-        VMAddress nameLocationVMAddr = method.getNameField(objcVisitor).vmAddress();
-        const char* selectorString = method.getName(objcVisitor);
-
-        VMOffset selectorStringRuntimeOffset    = nameVMAddr - VMAddress(loadAddress);
-        VMOffset selectorReferenceRuntimeOffset = nameLocationVMAddr - VMAddress(loadAddress);
-        callback(selectorReferenceRuntimeOffset.rawValue(), selectorStringRuntimeOffset.rawValue(), selectorString);
-    };
-
-    auto visitMethodList = ^(const objc_visitor::MethodList& methodList) {
-        if ( methodList.numMethods() == 0 )
-            return;
-        if ( methodList.usesRelativeOffsets() )
-            return;
-
-        // Check pointer based method lists
-        uint32_t numMethods = methodList.numMethods();
-        for ( uint32_t i = 0; i != numMethods; ++i ) {
-            const objc_visitor::Method& method = methodList.getMethod(objcVisitor, i);
-            visitPointerBasedMethod(method);
-        }
-    };
+        auto visitClass = ^(uint64_t classVMAddr, uint64_t classSuperclassVMAddr, uint64_t classDataVMAddr,
+                            const dyld3::MachOAnalyzer::ObjCClassInfo& objcClass, bool isMetaClass, bool& stop) {
+            visitMethodList(objcClass.baseMethodsVMAddr(pointerSize));
+        };
+        ma->forEachObjCClass(binaryInfo.classListRuntimeOffset, binaryInfo.classListCount, vmAddrConverter, visitClass);
+    }
 
     if ( binaryInfo.hasCategoryMethodListsToUnique && (binaryInfo.categoryCount != 0) ) {
-        // FIXME: Use binaryInfo.categoryListRuntimeOffset and binaryInfo.categoryCount
-        objcVisitor.forEachCategory(^(const objc_visitor::Category& objcCategory, bool &stopCategory) {
-            objc_visitor::MethodList instanceMethodList = objcCategory.getInstanceMethods(objcVisitor);
-            objc_visitor::MethodList classMethodList    = objcCategory.getClassMethods(objcVisitor);
-
-            visitMethodList(instanceMethodList);
-            visitMethodList(classMethodList);
-        });
-    }
-}
-
-__attribute__((noinline))
-static void forEachProtocolSelectorReferenceToUnique(objc_visitor::Visitor& objcVisitor,
-                                                     uint64_t                loadAddress,
-                                                     const ObjCBinaryInfo&   binaryInfo,
-                                                     void (^callback)(uint64_t selectorReferenceRuntimeOffset,
-                                                                      uint64_t selectorStringRuntimeOffset,
-                                                                      const char* selectorString))
-{
-    // We only make the callback for method list selrefs which are not already covered by the __objc_selrefs section.
-    // For pointer based method lists, this is all sel ref pointers.
-    // For relative method lists, we should always point to the __objc_selrefs section.  This was checked earlier, so
-    // we skip this callback on relative method lists as we know here they must point to the (already uniqied) __objc_selrefs.
-    auto visitPointerBasedMethod = ^(const objc_visitor::Method& method) {
-        VMAddress nameVMAddr = method.getNameVMAddr(objcVisitor);
-        VMAddress nameLocationVMAddr = method.getNameField(objcVisitor).vmAddress();
-        const char* selectorString = method.getName(objcVisitor);
-
-        VMOffset selectorStringRuntimeOffset    = nameVMAddr - VMAddress(loadAddress);
-        VMOffset selectorReferenceRuntimeOffset = nameLocationVMAddr - VMAddress(loadAddress);
-        callback(selectorReferenceRuntimeOffset.rawValue(), selectorStringRuntimeOffset.rawValue(), selectorString);
-    };
-
-    auto visitMethodList = ^(const objc_visitor::MethodList& methodList) {
-        if ( methodList.numMethods() == 0 )
-            return;
-        if ( methodList.usesRelativeOffsets() )
-            return;
-
-        // Check pointer based method lists
-        uint32_t numMethods = methodList.numMethods();
-        for ( uint32_t i = 0; i != numMethods; ++i ) {
-            const objc_visitor::Method& method = methodList.getMethod(objcVisitor, i);
-            visitPointerBasedMethod(method);
-        }
-    };
+        auto visitCategory = ^(uint64_t categoryVMAddr, const dyld3::MachOAnalyzer::ObjCCategory& objcCategory, bool& stop) {
+            visitMethodList(objcCategory.instanceMethodsVMAddr);
+            visitMethodList(objcCategory.classMethodsVMAddr);
+        };
+        ma->forEachObjCCategory(binaryInfo.categoryListRuntimeOffset, binaryInfo.categoryCount, vmAddrConverter, visitCategory);
+    }
 
     if ( binaryInfo.hasProtocolMethodListsToUnique && (binaryInfo.protocolListCount != 0) ) {
-        // FIXME: Use binaryInfo.protocolListRuntimeOffset and binaryInfo.protocolListCount
-        objcVisitor.forEachProtocol(^(const objc_visitor::Protocol& objcProtocol, bool& stopProtocol) {
-            objc_visitor::MethodList instanceMethodList         = objcProtocol.getInstanceMethods(objcVisitor);
-            objc_visitor::MethodList classMethodList            = objcProtocol.getClassMethods(objcVisitor);
-            objc_visitor::MethodList optionalInstanceMethodList = objcProtocol.getOptionalInstanceMethods(objcVisitor);
-            objc_visitor::MethodList optionalClassMethodList    = objcProtocol.getOptionalClassMethods(objcVisitor);
-
-            visitMethodList(instanceMethodList);
-            visitMethodList(classMethodList);
-            visitMethodList(optionalInstanceMethodList);
-            visitMethodList(optionalClassMethodList);
-        });
-    }
-}
-
-// Visits each selector reference once, in order.  Note the order this visits selector references has to
-// match for serializing/deserializing the PrebuiltLoader.
-void PrebuiltObjC::forEachSelectorReferenceToUnique(RuntimeState&           state,
-                                                    const Loader*           ldr,
-                                                    uint64_t                loadAddress,
-                                                    const ObjCBinaryInfo&   binaryInfo,
-                                                    void (^callback)(uint64_t selectorReferenceRuntimeOffset,
-                                                                     uint64_t selectorStringRuntimeOffset,
-                                                                     const char* selectorString))
-
-{
-    // FIXME: Don't make a duplicate one of these if we can pass one in instead
-    Diagnostics diag;
-    __block objc_visitor::Visitor objcVisitor = makeObjCVisitor(diag, state, ldr);
-    assert(!diag.hasError());
-
-    dyld4::forEachSelectorReferenceToUnique(objcVisitor, loadAddress, binaryInfo, callback);
-    dyld4::forEachClassSelectorReferenceToUnique(objcVisitor, loadAddress, binaryInfo, callback);
-    dyld4::forEachCategorySelectorReferenceToUnique(objcVisitor, loadAddress, binaryInfo, callback);
-    dyld4::forEachProtocolSelectorReferenceToUnique(objcVisitor, loadAddress, binaryInfo, callback);
-}
-
-static std::optional<VMOffset> getImageInfo(Diagnostics& diag, RuntimeState& state,
-                                            const Loader* ldr, const Header* hdr)
-{
-    __block std::optional<VMOffset> objcImageInfoRuntimeOffset;
-    hdr->forEachSection(^(const Header::SectionInfo& sectionInfo, bool& stop) {
-        if ( !sectionInfo.segmentName.starts_with("__DATA") )
-            return;
-        if ( sectionInfo.sectionName != "__objc_imageinfo" )
-            return;
-        if ( sectionInfo.size != 8 ) {
-            stop = true;
-            return;
-        }
-
-        // We can't just access the image info directly from the MachOFile.  Instead we have to
-        // use the layout to find the actual location of the segment, as we might be in the cache builder
-        ldr->withLayout(diag, state, ^(const mach_o::Layout& layout) {
-            const mach_o::SegmentLayout& segment = layout.segments[sectionInfo.segIndex];
-            uint64_t offsetInSegment = sectionInfo.address - segment.vmAddr;
-            const auto* imageInfo = (MachOAnalyzer::ObjCImageInfo*)(segment.buffer + offsetInSegment);
-
-            if ( (imageInfo->flags & MachOAnalyzer::ObjCImageInfo::dyldPreoptimized) != 0 )
-                return;
-
-            objcImageInfoRuntimeOffset = VMOffset(sectionInfo.address - layout.textUnslidVMAddr());
-        });
-        stop = true;
-    });
-
-    return objcImageInfoRuntimeOffset;
-}
-
-static std::optional<VMOffset> getProtocolClassCacheOffset(RuntimeState& state)
-{
-#if BUILDING_CACHE_BUILDER || BUILDING_CACHE_BUILDER_UNIT_TESTS
-        assert(state.config.dyldCache.objcProtocolClassCacheOffset != 0);
-        return VMOffset(state.config.dyldCache.objcProtocolClassCacheOffset);
-#else
-        // Make sure we have the pointers section with the pointer to the protocol class
-        const void* objcOptPtrs = state.config.dyldCache.addr->objcOptPtrs();
-        if ( objcOptPtrs == nullptr )
-            return { };
-
-        uint32_t pointerSize = state.mainExecutableLoader->loadAddress(state)->pointerSize();
+        auto visitProtocol = ^(uint64_t protocolVMAddr, const dyld3::MachOAnalyzer::ObjCProtocol& objCProtocol, bool& stop) {
+            visitMethodList(objCProtocol.instanceMethodsVMAddr);
+            visitMethodList(objCProtocol.classMethodsVMAddr);
+            visitMethodList(objCProtocol.optionalInstanceMethodsVMAddr);
+            visitMethodList(objCProtocol.optionalClassMethodsVMAddr);
+        };
+        ma->forEachObjCProtocol(binaryInfo.protocolListRuntimeOffset, binaryInfo.protocolListCount, vmAddrConverter, visitProtocol);
+    }
+}
+
+void PrebuiltObjC::make(Diagnostics& diag, RuntimeState& state)
+{
+    const DyldSharedCache* dyldCache = state.config.dyldCache.addr;
+    if ( dyldCache == nullptr )
+        return;
+
+    STACK_ALLOC_ARRAY(const Loader*, jitLoaders, state.loaded.size());
+    for (const Loader* ldr : state.loaded)
+        jitLoaders.push_back(ldr);
+
+    // If we have the read only data, make sure it has a valid selector table inside.
+    const objc::ClassHashTable*    objcClassOpt    = nullptr;
+    const objc::SelectorHashTable* objcSelOpt      = nullptr;
+    const objc::ProtocolHashTable* objcProtocolOpt = nullptr;
+    const void*                    headerInfoRO    = nullptr;
+    const void*                    headerInfoRW    = nullptr;
+    if ( const objc_opt::objc_opt_t* optObjCHeader = dyldCache->objcOpt() ) {
+        objcClassOpt    = optObjCHeader->classOpt();
+        objcSelOpt      = optObjCHeader->selectorOpt();
+        objcProtocolOpt = optObjCHeader->protocolOpt();
+        headerInfoRO    = optObjCHeader->headeropt_ro();
+        headerInfoRW    = optObjCHeader->headeropt_rw();
+    }
+
+    if ( !objcClassOpt || !objcSelOpt || !objcProtocolOpt )
+        return;
+
+    // Make sure we have the pointers section with the pointer to the protocol class
+    const void* objcOptPtrs = dyldCache->objcOptPtrs();
+    if ( objcOptPtrs == nullptr )
+        return;
+
+    uint32_t pointerSize = state.mainExecutableLoader->loadAddress(state)->pointerSize();
+
+    {
         uint64_t classProtocolVMAddr = (pointerSize == 8) ? *(uint64_t*)objcOptPtrs : *(uint32_t*)objcOptPtrs;
-
-#if BUILDING_DYLD || BUILDING_UNIT_TESTS
-        // As we are running in dyld/tests, the cache is live
-
-#if __has_feature(ptrauth_calls)
+#if BUILDING_DYLD
+        // As we are running in dyld, the cache is live
+    #if __has_feature(ptrauth_calls)
         // If we are on arm64e, the protocol ISA in the shared cache was signed.  We don't
         // want the signature bits in the encoded value
         classProtocolVMAddr = (uint64_t)__builtin_ptrauth_strip((void*)classProtocolVMAddr, ptrauth_key_asda);
-#endif // __has_feature(ptrauth_calls)
-
-        return VMOffset(classProtocolVMAddr - (uint64_t)state.config.dyldCache.addr);
+    #endif
+        objcProtocolClassCacheOffset = classProtocolVMAddr - (uint64_t)dyldCache;
 #elif BUILDING_CLOSURE_UTIL
         // FIXME: This assumes an on-disk cache
-        classProtocolVMAddr          = state.config.dyldCache.addr->makeVMAddrConverter(false).convertToVMAddr(classProtocolVMAddr);
-        return VMOffset(classProtocolVMAddr - state.config.dyldCache.addr->unslidLoadAddress());
+        classProtocolVMAddr          = dyldCache->makeVMAddrConverter(false).convertToVMAddr(classProtocolVMAddr);
+        objcProtocolClassCacheOffset = classProtocolVMAddr - dyldCache->unslidLoadAddress();
 #else
         // Running offline so the cache is not live
-        //objcProtocolClassCacheOffset = classProtocolVMAddr - dyldCache->unslidLoadAddress();
-#error Unknown tool
+        objcProtocolClassCacheOffset = classProtocolVMAddr - dyldCache->unslidLoadAddress();
 #endif // BUILDING_DYLD
-
-#endif // BUILDING_CACHE_BUILDER
-}
-
-void PrebuiltObjC::make(Diagnostics& diag, RuntimeState& state)
-{
-
-    // If we have the read only data, make sure it has a valid selector table inside.
-    const objc::ClassHashTable*    objcClassOpt             = state.config.dyldCache.objcClassHashTable;
-    const objc::SelectorHashTable* objcSelOpt               = state.config.dyldCache.objcSelectorHashTable;
-    const objc::ProtocolHashTable* objcProtocolOpt          = state.config.dyldCache.objcProtocolHashTable;
-    const void*                    headerInfoRO             = state.config.dyldCache.objcHeaderInfoRO;
-    const void*                    headerInfoRW             = state.config.dyldCache.objcHeaderInfoRW;
-    VMAddress                      headerInfoROUnslidVMAddr(state.config.dyldCache.objcHeaderInfoROUnslidVMAddr);
-
-    if ( !objcClassOpt || !objcSelOpt || !objcProtocolOpt)
-        return;
-
-    if ( std::optional<VMOffset> offset = getProtocolClassCacheOffset(state); offset.has_value() )
-        objcProtocolClassCacheOffset = offset.value();
-
-    for ( const Loader* ldr : state.delayLoaded ) {
-        if ( ldr->isJustInTimeLoader() ) {
-            // TODO: Handle apps which delay-init on-disk dylibs
-            // This will lead to the closure not optimizing the objc, and libobjc will do it instead
-            // in map_images().  This is safe as we tell objc (via dyldDoesObjCFixups()) whether we optimized or not
-            return;
-        }
     }
 
     // Find all the images with valid objc info
     SharedCacheImagesMapTy sharedCacheImagesMap;
-
-    // Note we have done the delay-init partitioning by this point, so state.loaded is just the loaders
-    // we known we need at launch.  This is important for the shared cache in particular as the shared cache
-    // classes/protocols are always preferred over the app ones, so a shared cache image being delayed or not
-    // impacts the choice of classes/protocols.  See protocolIsInSharedCache() for example.
-    for ( const Loader* ldr : state.loaded ) {
-        const Header* hdr = (const Header*)ldr->mf(state);
-        uint32_t pointerSize = hdr->pointerSize();
-
-        std::optional<VMOffset> objcImageInfoRuntimeOffset = getImageInfo(diag, state, ldr, hdr);
-
-        if ( !objcImageInfoRuntimeOffset.has_value() )
+    for ( const Loader* ldr : jitLoaders ) {
+        const dyld3::MachOAnalyzer* ma = (const dyld3::MachOAnalyzer*)ldr->loadAddress(state);
+
+        const MachOAnalyzer::ObjCImageInfo* objcImageInfo = ma->objcImageInfo();
+        if ( objcImageInfo == nullptr )
             continue;
 
         if ( ldr->dylibInDyldCache ) {
             // Add shared cache images to a map so that we can see them later for looking up classes
-            uint64_t dylibUnslidVMAddr = hdr->preferredLoadAddress();
-
-            std::optional<uint16_t> objcIndex;
-            objcIndex = objc::getPreoptimizedHeaderROIndex(headerInfoRO, headerInfoRW,
-                                                           headerInfoROUnslidVMAddr.rawValue(),
-                                                           dylibUnslidVMAddr,
-                                                           hdr->is64());
+            std::optional<uint16_t> objcIndex = objc::getPreoptimizedHeaderRWIndex(headerInfoRO, headerInfoRW, ma);
             if ( !objcIndex.has_value() )
                 return;
-            sharedCacheImagesMap.insert({ *objcIndex, { VMAddress(dylibUnslidVMAddr), ldr } });
+            sharedCacheImagesMap.insert({ *objcIndex, { ma, ldr } });
             continue;
         }
 
         // If we have a root of libobjc, just give up for now
-        if ( ldr->matchesPath(state, "/usr/lib/libobjc.A.dylib") )
+        if ( ldr->matchesPath("/usr/lib/libobjc.A.dylib") )
             return;
 
         // dyld can see the strings in Fairplay binaries and protected segments, but other tools cannot.
@@ -1302,12 +1148,12 @@
         // Find FairPlay encryption range if encrypted
         uint32_t fairPlayFileOffset;
         uint32_t fairPlaySize;
-        if ( hdr->isFairPlayEncrypted(fairPlayFileOffset, fairPlaySize) )
+        if ( ma->isFairPlayEncrypted(fairPlayFileOffset, fairPlaySize) )
             return;
 
         __block bool hasProtectedSegment = false;
-        hdr->forEachSegment(^(const Header::SegmentInfo& segInfo, bool& stop) {
-            if ( segInfo.isProtected() ) {
+        ma->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo& segInfo, bool& stop) {
+            if ( segInfo.isProtected ) {
                 hasProtectedSegment = true;
                 stop                = true;
             }
@@ -1316,26 +1162,19 @@
             return;
 #endif
 
-#if BUILDING_CACHE_BUILDER
-        // The cache builder will crash if it gets a binary with cheaper roots and bind opcodes
-        // Given up if we see this case
-        if ( hdr->hasOpcodeFixups() )
-            return;
-#endif
-
         // This image is good so record it for use later.
-        objcImages.emplace_back((const JustInTimeLoader*)ldr, hdr->preferredLoadAddress(), pointerSize);
+        objcImages.emplace_back((const JustInTimeLoader*)ldr, ma->preferredLoadAddress(), pointerSize);
         ObjCOptimizerImage& image = objcImages.back();
         image.jitLoader           = (const JustInTimeLoader*)ldr;
 
         // Set the offset to the objc image info
-        image.binaryInfo.imageInfoRuntimeOffset = objcImageInfoRuntimeOffset->rawValue();
+        image.binaryInfo.imageInfoRuntimeOffset = (uint64_t)objcImageInfo - (uint64_t)ma;
 
         // Get the range of a section which is required to contain pointers, i.e., be pointer sized.
         auto getPointerBasedSection = ^(const char* name, uint64_t& runtimeOffset, uint32_t& pointerCount) {
             uint64_t offset;
             uint64_t count;
-            if ( hdr->findObjCDataSection(name, offset, count) ) {
+            if ( ma->findObjCDataSection(name, offset, count) ) {
                 if ( (count % pointerSize) != 0 ) {
                     image.diag.error("Invalid objc pointer section size");
                     return;
@@ -1354,7 +1193,6 @@
         getPointerBasedSection("__objc_classlist", image.binaryInfo.classListRuntimeOffset, image.binaryInfo.classListCount);
         getPointerBasedSection("__objc_catlist", image.binaryInfo.categoryListRuntimeOffset, image.binaryInfo.categoryCount);
         getPointerBasedSection("__objc_protolist", image.binaryInfo.protocolListRuntimeOffset, image.binaryInfo.protocolListCount);
-        getPointerBasedSection("__objc_protorefs", image.binaryInfo.protocolRefsRuntimeOffset, image.binaryInfo.protocolRefsCount);
     }
 
     for ( ObjCOptimizerImage& image : objcImages ) {
@@ -1369,32 +1207,21 @@
         if ( image.diag.hasError() )
             continue;
 
-        optimizeObjCSelectors(state, objcSelOpt, selectorMap, image);
+        optimizeObjCSelectors(state, objcSelOpt, closureSelectorMap, image);
         if ( image.diag.hasError() )
             continue;
 
         commitImage(image);
     }
 
-    // If we successfully analyzed the classes and selectors, we can now make the maps
-    generateHashTables();
-
-    // Once we have the hash tables with the canonical protocols, we can generate the fixups
-    // for the protorefs, which need to point to the canonical protocol
-    for ( ObjCOptimizerImage& image : objcImages ) {
-        if ( image.diag.hasError() )
-            continue;
-
-        optimizeObjCProtocolReferences(state, objcProtocolOpt, sharedCacheImagesMap, protocolMap, image);
-    }
-
-    uint32_t pointerSize = state.mainExecutableLoader->mf(state)->pointerSize();
+    // If we successfully analyzed the classes and selectors, we can now emit their data
+    generateHashTables(state);
     generatePerImageFixups(state, pointerSize);
 
     builtObjC = true;
 }
 
-uint32_t PrebuiltObjC::serializeFixups(const Loader& jitLoader, BumpAllocator& allocator)
+uint32_t PrebuiltObjC::serializeFixups(const Loader& jitLoader, BumpAllocator& allocator) const
 {
     if ( !builtObjC )
         return 0;
@@ -1424,34 +1251,45 @@
         allocator.zeroFill(fixups.protocolISAFixups.count() * sizeof(uint8_t));
         allocator.align(8);
         BumpAllocatorPtr<uint8_t> protocolArray(allocator, serializationStart + protocolArrayOff);
-        memcpy(protocolArray.get(), fixups.protocolISAFixups.data(), (size_t)(fixups.protocolISAFixups.count() * sizeof(uint8_t)));
+        memcpy(protocolArray.get(), fixups.protocolISAFixups.begin(), fixups.protocolISAFixups.count() * sizeof(uint8_t));
     }
 
     // Selector references
     if ( !fixups.selectorReferenceFixups.empty() ) {
-        uint64_t selectorsArrayOff                = allocator.size() - serializationStart;
-        fixupInfo->selectorReferencesFixupsOffset = (uint32_t)selectorsArrayOff;
+        uint16_t selectorsArrayOff                = allocator.size() - serializationStart;
+        fixupInfo->selectorReferencesFixupsOffset = selectorsArrayOff;
         fixupInfo->selectorReferencesFixupsCount  = (uint32_t)fixups.selectorReferenceFixups.count();
         allocator.zeroFill(fixups.selectorReferenceFixups.count() * sizeof(PrebuiltLoader::BindTargetRef));
         BumpAllocatorPtr<uint8_t> selectorsArray(allocator, serializationStart + selectorsArrayOff);
-        memcpy(selectorsArray.get(), fixups.selectorReferenceFixups.data(), (size_t)(fixups.selectorReferenceFixups.count() * sizeof(PrebuiltLoader::BindTargetRef)));
-    }
-
-    // Protocol references
-    if ( !fixups.protocolReferenceFixups.empty() ) {
-        uint64_t protocolsArrayOff                = allocator.size() - serializationStart;
-        fixupInfo->protocolReferencesFixupsOffset = (uint32_t)protocolsArrayOff;
-        fixupInfo->protocolReferencesFixupsCount  = (uint32_t)fixups.protocolReferenceFixups.count();
-        allocator.zeroFill(fixups.protocolReferenceFixups.count() * sizeof(PrebuiltLoader::BindTargetRef));
-        BumpAllocatorPtr<uint8_t> protocolsArray(allocator, serializationStart + protocolsArrayOff);
-        memcpy(protocolsArray.get(), fixups.protocolReferenceFixups.data(), (size_t)(fixups.protocolReferenceFixups.count() * sizeof(PrebuiltLoader::BindTargetRef)));
+        memcpy(selectorsArray.get(), fixups.selectorReferenceFixups.begin(), fixups.selectorReferenceFixups.count() * sizeof(PrebuiltLoader::BindTargetRef));
     }
 
     return serializationStart;
 }
 
 } // namespace dyld4
-#endif // SUPPORT_PREBUILTLOADERS || BUILDING_UNIT_TESTS || BUILDING_CACHE_BUILDER_UNIT_TESTS
-
-#endif // !TARGET_OS_EXCLAVEKIT
-
+
+
+// Temporary copy of the old hash tables, to let the split cache branch load old hash tables
+namespace legacy_objc_opt
+{
+
+uint32_t objc_stringhash_t::hash(const char *key, size_t keylen) const
+{
+    uint64_t val = objc::lookup8((uint8_t*)key, keylen, salt);
+    uint32_t index = (uint32_t)(val>>shift) ^ scramble[tab[val&mask]];
+    return index;
+}
+
+
+const header_info_rw *getPreoptimizedHeaderRW(const struct header_info *const hdr,
+                                              void* headerInfoRO, void* headerInfoRW)
+{
+    const objc_headeropt_ro_t* hinfoRO = (const objc_headeropt_ro_t*)headerInfoRO;
+    const objc_headeropt_rw_t* hinfoRW = (const objc_headeropt_rw_t*)headerInfoRW;
+    int32_t index = hinfoRO->index(hdr);
+    assert(hinfoRW->entsize == sizeof(header_info_rw));
+    return &hinfoRW->headers[index];
+}
+
+}