diff --git a/lld/MachO/Config.h b/lld/MachO/Config.h index f820513a111ea..7b45f7f4c39a1 100644 --- a/lld/MachO/Config.h +++ b/lld/MachO/Config.h @@ -135,6 +135,7 @@ struct Configuration { bool emitEncryptionInfo = false; bool emitInitOffsets = false; bool emitChainedFixups = false; + bool emitRelativeMethodLists = false; bool thinLTOEmitImportsFiles; bool thinLTOEmitIndexFiles; bool thinLTOIndexOnly; diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp index 14c111ce9685c..65de531db04b7 100644 --- a/lld/MachO/Driver.cpp +++ b/lld/MachO/Driver.cpp @@ -1086,6 +1086,22 @@ static bool shouldEmitChainedFixups(const InputArgList &args) { return isRequested; } +static bool shouldEmitRelativeMethodLists(const InputArgList &args) { + const Arg *arg = args.getLastArg(OPT_objc_relative_method_lists, + OPT_no_objc_relative_method_lists); + if (arg && arg->getOption().getID() == OPT_objc_relative_method_lists) + return true; + if (arg && arg->getOption().getID() == OPT_no_objc_relative_method_lists) + return false; + + // TODO: If no flag is specified, don't default to false, but instead: + // - default false on < ios14 + // - default true on >= ios14 + // For now, until this feature is confirmed stable, default to false if no + // flag is explicitly specified + return false; +} + void SymbolPatterns::clear() { literals.clear(); globs.clear(); @@ -1630,6 +1646,7 @@ bool link(ArrayRef argsArr, llvm::raw_ostream &stdoutOS, config->emitChainedFixups = shouldEmitChainedFixups(args); config->emitInitOffsets = config->emitChainedFixups || args.hasArg(OPT_init_offsets); + config->emitRelativeMethodLists = shouldEmitRelativeMethodLists(args); config->icfLevel = getICFLevel(args); config->dedupStrings = args.hasFlag(OPT_deduplicate_strings, OPT_no_deduplicate_strings, true); diff --git a/lld/MachO/InputSection.cpp b/lld/MachO/InputSection.cpp index 22930d52dd1db..e3d7a400e4ce9 100644 --- a/lld/MachO/InputSection.cpp +++ b/lld/MachO/InputSection.cpp @@ -46,6 +46,14 @@ void lld::macho::addInputSection(InputSection *inputSection) { if (auto *isec = dyn_cast(inputSection)) { if (isec->isCoalescedWeak()) return; + if (config->emitRelativeMethodLists && + ObjCMethListSection::isMethodList(isec)) { + if (in.objcMethList->inputOrder == UnspecifiedInputOrder) + in.objcMethList->inputOrder = inputSectionsOrder++; + in.objcMethList->addInput(isec); + isec->parent = in.objcMethList; + return; + } if (config->emitInitOffsets && sectionType(isec->getFlags()) == S_MOD_INIT_FUNC_POINTERS) { in.initOffsets->addInput(isec); diff --git a/lld/MachO/InputSection.h b/lld/MachO/InputSection.h index 694bdf734907b..a0e6afef92cb8 100644 --- a/lld/MachO/InputSection.h +++ b/lld/MachO/InputSection.h @@ -342,6 +342,7 @@ constexpr const char moduleTermFunc[] = "__mod_term_func"; constexpr const char nonLazySymbolPtr[] = "__nl_symbol_ptr"; constexpr const char objcCatList[] = "__objc_catlist"; constexpr const char objcClassList[] = "__objc_classlist"; +constexpr const char objcMethList[] = "__objc_methlist"; constexpr const char objcClassRefs[] = "__objc_classrefs"; constexpr const char objcConst[] = "__objc_const"; constexpr const char objCImageInfo[] = "__objc_imageinfo"; diff --git a/lld/MachO/MapFile.cpp b/lld/MachO/MapFile.cpp index f736360624ebd..2a31a5c09cdd2 100644 --- a/lld/MachO/MapFile.cpp +++ b/lld/MachO/MapFile.cpp @@ -197,18 +197,24 @@ void macho::writeMapFile() { seg->name.str().c_str(), osec->name.str().c_str()); } + // Shared function to print an array of symbols. + auto printIsecArrSyms = [&](const std::vector &arr) { + for (const ConcatInputSection *isec : arr) { + for (Defined *sym : isec->symbols) { + if (!(isPrivateLabel(sym->getName()) && sym->size == 0)) + os << format("0x%08llX\t0x%08llX\t[%3u] %s\n", sym->getVA(), + sym->size, readerToFileOrdinal[sym->getFile()], + sym->getName().str().data()); + } + } + }; + os << "# Symbols:\n"; os << "# Address\tSize \tFile Name\n"; for (const OutputSegment *seg : outputSegments) { for (const OutputSection *osec : seg->getSections()) { if (auto *concatOsec = dyn_cast(osec)) { - for (const InputSection *isec : concatOsec->inputs) { - for (Defined *sym : isec->symbols) - if (!(isPrivateLabel(sym->getName()) && sym->size == 0)) - os << format("0x%08llX\t0x%08llX\t[%3u] %s\n", sym->getVA(), - sym->size, readerToFileOrdinal[sym->getFile()], - sym->getName().str().data()); - } + printIsecArrSyms(concatOsec->inputs); } else if (osec == in.cStringSection || osec == in.objcMethnameSection) { const auto &liveCStrings = info.liveCStringsForSection.lookup(osec); uint64_t lastAddr = 0; // strings will never start at address 0, so this @@ -237,6 +243,8 @@ void macho::writeMapFile() { printNonLazyPointerSection(os, in.got); } else if (osec == in.tlvPointers) { printNonLazyPointerSection(os, in.tlvPointers); + } else if (osec == in.objcMethList) { + printIsecArrSyms(in.objcMethList->getInputs()); } // TODO print other synthetic sections } diff --git a/lld/MachO/ObjC.h b/lld/MachO/ObjC.h index 9fbe984e6223e..8081605670c51 100644 --- a/lld/MachO/ObjC.h +++ b/lld/MachO/ObjC.h @@ -22,6 +22,8 @@ constexpr const char klassPropList[] = "__OBJC_$_CLASS_PROP_LIST_"; constexpr const char metaclass[] = "_OBJC_METACLASS_$_"; constexpr const char ehtype[] = "_OBJC_EHTYPE_$_"; constexpr const char ivar[] = "_OBJC_IVAR_$_"; +constexpr const char instanceMethods[] = "__OBJC_$_INSTANCE_METHODS_"; +constexpr const char classMethods[] = "__OBJC_$_CLASS_METHODS_"; constexpr const char listProprieties[] = "__OBJC_$_PROP_LIST_"; constexpr const char category[] = "__OBJC_$_CATEGORY_"; diff --git a/lld/MachO/Options.td b/lld/MachO/Options.td index 0d8ee2a0926be..19f8509ba714b 100644 --- a/lld/MachO/Options.td +++ b/lld/MachO/Options.td @@ -1284,6 +1284,12 @@ def fixup_chains_section : Flag<["-"], "fixup_chains_section">, HelpText<"This option is undocumented in ld64">, Flags<[HelpHidden]>, Group; +def objc_relative_method_lists : Flag<["-"], "objc_relative_method_lists">, + HelpText<"Emit relative method lists (more compact representation)">, + Group; +def no_objc_relative_method_lists : Flag<["-"], "no_objc_relative_method_lists">, + HelpText<"Don't emit relative method lists (use traditional representation)">, + Group; def flto_codegen_only : Flag<["-"], "flto-codegen-only">, HelpText<"This option is undocumented in ld64">, Flags<[HelpHidden]>, diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp index 0afbbd478bb9f..808ea8eac6eb5 100644 --- a/lld/MachO/SyntheticSections.cpp +++ b/lld/MachO/SyntheticSections.cpp @@ -12,6 +12,7 @@ #include "ExportTrie.h" #include "InputFiles.h" #include "MachOStructs.h" +#include "ObjC.h" #include "OutputSegment.h" #include "SymbolTable.h" #include "Symbols.h" @@ -1975,6 +1976,241 @@ void InitOffsetsSection::setUp() { } } +ObjCMethListSection::ObjCMethListSection() + : SyntheticSection(segment_names::text, section_names::objcMethList) { + flags = S_ATTR_NO_DEAD_STRIP; + align = relativeOffsetSize; +} + +// Go through all input method lists and ensure that we have selrefs for all +// their method names. The selrefs will be needed later by ::writeTo. We need to +// create them early on here to ensure they are processed correctly by the lld +// pipeline. +void ObjCMethListSection::setUp() { + for (const ConcatInputSection *isec : inputs) { + uint32_t structSizeAndFlags = 0, structCount = 0; + readMethodListHeader(isec->data.data(), structSizeAndFlags, structCount); + uint32_t originalStructSize = structSizeAndFlags & structSizeMask; + // Method name is immediately after header + uint32_t methodNameOff = methodListHeaderSize; + + // Loop through all methods, and ensure a selref for each of them exists. + while (methodNameOff < isec->data.size()) { + const Reloc *reloc = isec->getRelocAt(methodNameOff); + assert(reloc && "Relocation expected at method list name slot"); + auto *def = dyn_cast_or_null(reloc->referent.get()); + assert(def && "Expected valid Defined at method list name slot"); + auto *cisec = cast(def->isec); + assert(cisec && "Expected method name to be in a CStringInputSection"); + auto methname = cisec->getStringRefAtOffset(def->value); + if (!ObjCSelRefsHelper::getSelRef(methname)) + ObjCSelRefsHelper::makeSelRef(methname); + + // Jump to method name offset in next struct + methodNameOff += originalStructSize; + } + } +} + +// Calculate section size and final offsets for where InputSection's need to be +// written. +void ObjCMethListSection::finalize() { + // sectionSize will be the total size of the __objc_methlist section + sectionSize = 0; + for (ConcatInputSection *isec : inputs) { + // We can also use sectionSize as write offset for isec + assert(sectionSize == alignToPowerOf2(sectionSize, relativeOffsetSize) && + "expected __objc_methlist to be aligned by default with the " + "required section alignment"); + isec->outSecOff = sectionSize; + + isec->isFinal = true; + uint32_t relativeListSize = + computeRelativeMethodListSize(isec->data.size()); + sectionSize += relativeListSize; + + // If encoding the method list in relative offset format shrinks the size, + // then we also need to adjust symbol sizes to match the new size. Note that + // on 32bit platforms the size of the method list will remain the same when + // encoded in relative offset format. + if (relativeListSize != isec->data.size()) { + for (Symbol *sym : isec->symbols) { + assert(isa(sym) && + "Unexpected undefined symbol in ObjC method list"); + auto *def = cast(sym); + // There can be 0-size symbols, check if this is the case and ignore + // them. + if (def->size) { + assert( + def->size == isec->data.size() && + "Invalid ObjC method list symbol size: expected symbol size to " + "match isec size"); + def->size = relativeListSize; + } + } + } + } +} + +void ObjCMethListSection::writeTo(uint8_t *bufStart) const { + uint8_t *buf = bufStart; + for (const ConcatInputSection *isec : inputs) { + assert(buf - bufStart == long(isec->outSecOff) && + "Writing at unexpected offset"); + uint32_t writtenSize = writeRelativeMethodList(isec, buf); + buf += writtenSize; + } + assert(buf - bufStart == sectionSize && + "Written size does not match expected section size"); +} + +// Check if an InputSection is a method list. To do this we scan the +// InputSection for any symbols who's names match the patterns we expect clang +// to generate for method lists. +bool ObjCMethListSection::isMethodList(const ConcatInputSection *isec) { + const char *symPrefixes[] = {objc::symbol_names::classMethods, + objc::symbol_names::instanceMethods, + objc::symbol_names::categoryInstanceMethods, + objc::symbol_names::categoryClassMethods}; + if (!isec) + return false; + for (const Symbol *sym : isec->symbols) { + auto *def = dyn_cast_or_null(sym); + if (!def) + continue; + for (const char *prefix : symPrefixes) { + if (def->getName().starts_with(prefix)) { + assert(def->size == isec->data.size() && + "Invalid ObjC method list symbol size: expected symbol size to " + "match isec size"); + assert(def->value == 0 && + "Offset of ObjC method list symbol must be 0"); + return true; + } + } + } + + return false; +} + +// Encode a single relative offset value. The input is the data/symbol at +// (&isec->data[inSecOff]). The output is written to (&buf[outSecOff]). +// 'createSelRef' indicates that we should not directly use the specified +// symbol, but instead get the selRef for the symbol and use that instead. +void ObjCMethListSection::writeRelativeOffsetForIsec( + const ConcatInputSection *isec, uint8_t *buf, uint32_t &inSecOff, + uint32_t &outSecOff, bool useSelRef) const { + const Reloc *reloc = isec->getRelocAt(inSecOff); + assert(reloc && "Relocation expected at __objc_methlist Offset"); + auto *def = dyn_cast_or_null(reloc->referent.get()); + assert(def && "Expected all syms in __objc_methlist to be defined"); + uint32_t symVA = def->getVA(); + + if (useSelRef) { + auto *cisec = cast(def->isec); + auto methname = cisec->getStringRefAtOffset(def->value); + ConcatInputSection *selRef = ObjCSelRefsHelper::getSelRef(methname); + assert(selRef && "Expected all selector names to already be already be " + "present in __objc_selrefs"); + symVA = selRef->getVA(); + assert(selRef->data.size() == sizeof(target->wordSize) && + "Expected one selref per ConcatInputSection"); + } + + uint32_t currentVA = isec->getVA() + outSecOff; + uint32_t delta = symVA - currentVA; + write32le(buf + outSecOff, delta); + + // Move one pointer forward in the absolute method list + inSecOff += target->wordSize; + // Move one relative offset forward in the relative method list (32 bits) + outSecOff += relativeOffsetSize; +} + +// Write a relative method list to buf, return the size of the written +// information +uint32_t +ObjCMethListSection::writeRelativeMethodList(const ConcatInputSection *isec, + uint8_t *buf) const { + // Copy over the header, and add the "this is a relative method list" magic + // value flag + uint32_t structSizeAndFlags = 0, structCount = 0; + readMethodListHeader(isec->data.data(), structSizeAndFlags, structCount); + // Set the struct size for the relative method list + uint32_t relativeStructSizeAndFlags = + (relativeOffsetSize * pointersPerStruct) & structSizeMask; + // Carry over the old flags from the input struct + relativeStructSizeAndFlags |= structSizeAndFlags & structFlagsMask; + // Set the relative method list flag + relativeStructSizeAndFlags |= relMethodHeaderFlag; + + writeMethodListHeader(buf, relativeStructSizeAndFlags, structCount); + + assert(methodListHeaderSize + + (structCount * pointersPerStruct * target->wordSize) == + isec->data.size() && + "Invalid computed ObjC method list size"); + + uint32_t inSecOff = methodListHeaderSize; + uint32_t outSecOff = methodListHeaderSize; + + // Go through the method list and encode input absolute pointers as relative + // offsets. writeRelativeOffsetForIsec will be incrementing inSecOff and + // outSecOff + for (uint32_t i = 0; i < structCount; i++) { + // Write the name of the method + writeRelativeOffsetForIsec(isec, buf, inSecOff, outSecOff, true); + // Write the type of the method + writeRelativeOffsetForIsec(isec, buf, inSecOff, outSecOff, false); + // Write reference to the selector of the method + writeRelativeOffsetForIsec(isec, buf, inSecOff, outSecOff, false); + } + + // Expecting to have read all the data in the isec + assert(inSecOff == isec->data.size() && + "Invalid actual ObjC method list size"); + assert( + outSecOff == computeRelativeMethodListSize(inSecOff) && + "Mismatch between input & output size when writing relative method list"); + return outSecOff; +} + +// Given the size of an ObjC method list InputSection, return the size of the +// method list when encoded in relative offsets format. We can do this without +// decoding the actual data, as it can be directly inferred from the size of the +// isec. +uint32_t ObjCMethListSection::computeRelativeMethodListSize( + uint32_t absoluteMethodListSize) const { + uint32_t oldPointersSize = absoluteMethodListSize - methodListHeaderSize; + uint32_t pointerCount = oldPointersSize / target->wordSize; + assert(((pointerCount % pointersPerStruct) == 0) && + "__objc_methlist expects method lists to have multiple-of-3 pointers"); + + uint32_t newPointersSize = pointerCount * relativeOffsetSize; + uint32_t newTotalSize = methodListHeaderSize + newPointersSize; + + assert((newTotalSize <= absoluteMethodListSize) && + "Expected relative method list size to be smaller or equal than " + "original size"); + return newTotalSize; +} + +// Read a method list header from buf +void ObjCMethListSection::readMethodListHeader(const uint8_t *buf, + uint32_t &structSizeAndFlags, + uint32_t &structCount) const { + structSizeAndFlags = read32le(buf); + structCount = read32le(buf + sizeof(uint32_t)); +} + +// Write a method list header to buf +void ObjCMethListSection::writeMethodListHeader(uint8_t *buf, + uint32_t structSizeAndFlags, + uint32_t structCount) const { + write32le(buf, structSizeAndFlags); + write32le(buf + sizeof(structSizeAndFlags), structCount); +} + void macho::createSyntheticSymbols() { auto addHeaderSymbol = [](const char *name) { symtab->addSynthetic(name, in.header->isec, /*value=*/0, diff --git a/lld/MachO/SyntheticSections.h b/lld/MachO/SyntheticSections.h index 4586a4a0bf436..e8fadfef56d4b 100644 --- a/lld/MachO/SyntheticSections.h +++ b/lld/MachO/SyntheticSections.h @@ -684,6 +684,54 @@ class InitOffsetsSection final : public SyntheticSection { std::vector sections; }; +// This SyntheticSection is for the __objc_methlist section, which contains +// relative method lists if the -objc_relative_method_lists option is enabled. +class ObjCMethListSection final : public SyntheticSection { +public: + ObjCMethListSection(); + + static bool isMethodList(const ConcatInputSection *isec); + void addInput(ConcatInputSection *isec) { inputs.push_back(isec); } + std::vector getInputs() { return inputs; } + + void setUp(); + void finalize() override; + bool isNeeded() const override { return !inputs.empty(); } + uint64_t getSize() const override { return sectionSize; } + void writeTo(uint8_t *bufStart) const override; + +private: + void readMethodListHeader(const uint8_t *buf, uint32_t &structSizeAndFlags, + uint32_t &structCount) const; + void writeMethodListHeader(uint8_t *buf, uint32_t structSizeAndFlags, + uint32_t structCount) const; + uint32_t computeRelativeMethodListSize(uint32_t absoluteMethodListSize) const; + void writeRelativeOffsetForIsec(const ConcatInputSection *isec, uint8_t *buf, + uint32_t &inSecOff, uint32_t &outSecOff, + bool useSelRef) const; + uint32_t writeRelativeMethodList(const ConcatInputSection *isec, + uint8_t *buf) const; + + static constexpr uint32_t methodListHeaderSize = + /*structSizeAndFlags*/ sizeof(uint32_t) + + /*structCount*/ sizeof(uint32_t); + // Relative method lists are supported only for 3-pointer method lists + static constexpr uint32_t pointersPerStruct = 3; + // The runtime identifies relative method lists via this magic value + static constexpr uint32_t relMethodHeaderFlag = 0x80000000; + // In the method list header, the first 2 bytes are the size of struct + static constexpr uint32_t structSizeMask = 0x0000FFFF; + // In the method list header, the last 2 bytes are the flags for the struct + static constexpr uint32_t structFlagsMask = 0xFFFF0000; + // Relative method lists have 4 byte alignment as all data in the InputSection + // is 4 byte + static constexpr uint32_t relativeOffsetSize = sizeof(uint32_t); + + // The output size of the __objc_methlist section, computed during finalize() + uint32_t sectionSize = 0; + std::vector inputs; +}; + // Chained fixups are a replacement for classic dyld opcodes. In this format, // most of the metadata necessary for binding symbols and rebasing addresses is // stored directly in the memory location that will have the fixup applied. @@ -810,6 +858,7 @@ struct InStruct { ObjCImageInfoSection *objCImageInfo = nullptr; ConcatInputSection *imageLoaderCache = nullptr; InitOffsetsSection *initOffsets = nullptr; + ObjCMethListSection *objcMethList = nullptr; ChainedFixupsSection *chainedFixups = nullptr; }; diff --git a/lld/MachO/Writer.cpp b/lld/MachO/Writer.cpp index a18b5268fd42a..fe989de648d78 100644 --- a/lld/MachO/Writer.cpp +++ b/lld/MachO/Writer.cpp @@ -1292,6 +1292,8 @@ template void Writer::run() { scanSymbols(); if (in.objcStubs->isNeeded()) in.objcStubs->setUp(); + if (in.objcMethList->isNeeded()) + in.objcMethList->setUp(); scanRelocations(); if (in.initOffsets->isNeeded()) in.initOffsets->setUp(); @@ -1363,6 +1365,7 @@ void macho::createSyntheticSections() { in.unwindInfo = makeUnwindInfoSection(); in.objCImageInfo = make(); in.initOffsets = make(); + in.objcMethList = make(); // This section contains space for just a single word, and will be used by // dyld to cache an address to the image loader it uses. diff --git a/lld/test/MachO/objc-relative-method-lists-simple.s b/lld/test/MachO/objc-relative-method-lists-simple.s new file mode 100644 index 0000000000000..1ffec3c6241cf --- /dev/null +++ b/lld/test/MachO/objc-relative-method-lists-simple.s @@ -0,0 +1,245 @@ +# REQUIRES: aarch64 +# RUN: rm -rf %t; split-file %s %t && cd %t + +## Compile a64_rel_dylib.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos -o a64_rel_dylib.o a64_simple_class.s + +## Test arm64 + relative method lists +# RUN: %no-lsystem-lld a64_rel_dylib.o -o a64_rel_dylib.dylib -map a64_rel_dylib.map -dylib -arch arm64 -objc_relative_method_lists +# RUN: llvm-objdump --macho --objc-meta-data a64_rel_dylib.dylib | FileCheck %s --check-prefix=CHK_REL + +## Test arm64 + traditional method lists (no relative offsets) +# RUN: %no-lsystem-lld a64_rel_dylib.o -o a64_rel_dylib.dylib -map a64_rel_dylib.map -dylib -arch arm64 -no_objc_relative_method_lists +# RUN: llvm-objdump --macho --objc-meta-data a64_rel_dylib.dylib | FileCheck %s --check-prefix=CHK_NO_REL + + +CHK_REL: Contents of (__DATA_CONST,__objc_classlist) section +CHK_REL-NEXT: _OBJC_CLASS_$_MyClass +CHK_REL: baseMethods +CHK_REL-NEXT: entsize 12 (relative) +CHK_REL-NEXT: count 3 +CHK_REL-NEXT: name 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) instance_method_00 +CHK_REL-NEXT: types 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) v16@0:8 +CHK_REL-NEXT: imp 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) -[MyClass instance_method_00] +CHK_REL-NEXT: name 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) instance_method_01 +CHK_REL-NEXT: types 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) v16@0:8 +CHK_REL-NEXT: imp 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) -[MyClass instance_method_01] +CHK_REL-NEXT: name 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) instance_method_02 +CHK_REL-NEXT: types 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) v16@0:8 +CHK_REL-NEXT: imp 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) -[MyClass instance_method_02] + +CHK_REL: Meta Class +CHK_REL-NEXT: isa 0x{{[0-9a-f]*}} _OBJC_METACLASS_$_MyClass +CHK_REL: baseMethods 0x{{[0-9a-f]*}} (struct method_list_t *) +CHK_REL-NEXT: entsize 12 (relative) +CHK_REL-NEXT: count 3 +CHK_REL-NEXT: name 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) class_method_00 +CHK_REL-NEXT: types 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) v16@0:8 +CHK_REL-NEXT: imp 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) +[MyClass class_method_00] +CHK_REL-NEXT: name 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) class_method_01 +CHK_REL-NEXT: types 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) v16@0:8 +CHK_REL-NEXT: imp 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) +[MyClass class_method_01] +CHK_REL-NEXT: name 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) class_method_02 +CHK_REL-NEXT: types 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) v16@0:8 +CHK_REL-NEXT: imp 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) +[MyClass class_method_02] + + +CHK_NO_REL-NOT: (relative) + +CHK_NO_REL: Contents of (__DATA_CONST,__objc_classlist) section +CHK_NO_REL-NEXT: _OBJC_CLASS_$_MyClass + +CHK_NO_REL: baseMethods 0x{{[0-9a-f]*}} (struct method_list_t *) +CHK_NO_REL-NEXT: entsize 24 +CHK_NO_REL-NEXT: count 3 +CHK_NO_REL-NEXT: name 0x{{[0-9a-f]*}} instance_method_00 +CHK_NO_REL-NEXT: types 0x{{[0-9a-f]*}} v16@0:8 +CHK_NO_REL-NEXT: imp -[MyClass instance_method_00] +CHK_NO_REL-NEXT: name 0x{{[0-9a-f]*}} instance_method_01 +CHK_NO_REL-NEXT: types 0x{{[0-9a-f]*}} v16@0:8 +CHK_NO_REL-NEXT: imp -[MyClass instance_method_01] +CHK_NO_REL-NEXT: name 0x{{[0-9a-f]*}} instance_method_02 +CHK_NO_REL-NEXT: types 0x{{[0-9a-f]*}} v16@0:8 +CHK_NO_REL-NEXT: imp -[MyClass instance_method_02] + + +CHK_NO_REL: Meta Class +CHK_NO_REL-NEXT: _OBJC_METACLASS_$_MyClass + +CHK_NO_REL: baseMethods 0x{{[0-9a-f]*}} (struct method_list_t *) +CHK_NO_REL-NEXT: entsize 24 +CHK_NO_REL-NEXT: count 3 +CHK_NO_REL-NEXT: name 0x{{[0-9a-f]*}} class_method_00 +CHK_NO_REL-NEXT: types 0x{{[0-9a-f]*}} v16@0:8 +CHK_NO_REL-NEXT: imp +[MyClass class_method_00] +CHK_NO_REL-NEXT: name 0x{{[0-9a-f]*}} class_method_01 +CHK_NO_REL-NEXT: types 0x{{[0-9a-f]*}} v16@0:8 +CHK_NO_REL-NEXT: imp +[MyClass class_method_01] +CHK_NO_REL-NEXT: name 0x{{[0-9a-f]*}} class_method_02 +CHK_NO_REL-NEXT: types 0x{{[0-9a-f]*}} v16@0:8 +CHK_NO_REL-NEXT: imp +[MyClass class_method_02] + + +######################## Generate a64_simple_class.s ######################### +# clang -c simple_class.mm -s -o a64_simple_class.s -target arm64-apple-macos -arch arm64 -Oz + +######################## simple_class.mm ######################## +# __attribute__((objc_root_class)) +# @interface MyClass +# - (void)instance_method_00; +# - (void)instance_method_01; +# - (void)instance_method_02; +# + (void)class_method_00; +# + (void)class_method_01; +# + (void)class_method_02; +# @end +# +# @implementation MyClass +# - (void)instance_method_00 {} +# - (void)instance_method_01 {} +# - (void)instance_method_02 {} +# + (void)class_method_00 {} +# + (void)class_method_01 {} +# + (void)class_method_02 {} +# @end +# +# void *_objc_empty_cache; +# void *_objc_empty_vtable; +# + +#--- objc-macros.s +.macro .objc_selector_def name + .p2align 2 +"\name": + .cfi_startproc + ret + .cfi_endproc +.endm + +#--- a64_simple_class.s +.include "objc-macros.s" + +.section __TEXT,__text,regular,pure_instructions +.build_version macos, 11, 0 + +.objc_selector_def "-[MyClass instance_method_00]" +.objc_selector_def "-[MyClass instance_method_01]" +.objc_selector_def "-[MyClass instance_method_02]" + +.objc_selector_def "+[MyClass class_method_00]" +.objc_selector_def "+[MyClass class_method_01]" +.objc_selector_def "+[MyClass class_method_02]" + +.globl __objc_empty_vtable +.zerofill __DATA,__common,__objc_empty_vtable,8,3 +.section __DATA,__objc_data +.globl _OBJC_CLASS_$_MyClass +.p2align 3, 0x0 + +_OBJC_CLASS_$_MyClass: + .quad _OBJC_METACLASS_$_MyClass + .quad 0 + .quad __objc_empty_cache + .quad __objc_empty_vtable + .quad __OBJC_CLASS_RO_$_MyClass + .globl _OBJC_METACLASS_$_MyClass + .p2align 3, 0x0 + +_OBJC_METACLASS_$_MyClass: + .quad _OBJC_METACLASS_$_MyClass + .quad _OBJC_CLASS_$_MyClass + .quad __objc_empty_cache + .quad __objc_empty_vtable + .quad __OBJC_METACLASS_RO_$_MyClass + + .section __TEXT,__objc_classname,cstring_literals +l_OBJC_CLASS_NAME_: + .asciz "MyClass" + .section __TEXT,__objc_methname,cstring_literals +l_OBJC_METH_VAR_NAME_: + .asciz "class_method_00" + .section __TEXT,__objc_methtype,cstring_literals +l_OBJC_METH_VAR_TYPE_: + .asciz "v16@0:8" + .section __TEXT,__objc_methname,cstring_literals +l_OBJC_METH_VAR_NAME_.1: + .asciz "class_method_01" +l_OBJC_METH_VAR_NAME_.2: + .asciz "class_method_02" + .section __DATA,__objc_const + .p2align 3, 0x0 +__OBJC_$_CLASS_METHODS_MyClass: + .long 24 + .long 3 + .quad l_OBJC_METH_VAR_NAME_ + .quad l_OBJC_METH_VAR_TYPE_ + .quad "+[MyClass class_method_00]" + .quad l_OBJC_METH_VAR_NAME_.1 + .quad l_OBJC_METH_VAR_TYPE_ + .quad "+[MyClass class_method_01]" + .quad l_OBJC_METH_VAR_NAME_.2 + .quad l_OBJC_METH_VAR_TYPE_ + .quad "+[MyClass class_method_02]" + .p2align 3, 0x0 + +__OBJC_METACLASS_RO_$_MyClass: + .long 3 + .long 40 + .long 40 + .space 4 + .quad 0 + .quad l_OBJC_CLASS_NAME_ + .quad __OBJC_$_CLASS_METHODS_MyClass + .quad 0 + .quad 0 + .quad 0 + .quad 0 + + .section __TEXT,__objc_methname,cstring_literals +l_OBJC_METH_VAR_NAME_.3: + .asciz "instance_method_00" +l_OBJC_METH_VAR_NAME_.4: + .asciz "instance_method_01" +l_OBJC_METH_VAR_NAME_.5: + .asciz "instance_method_02" + + .section __DATA,__objc_const + .p2align 3, 0x0 +__OBJC_$_INSTANCE_METHODS_MyClass: + .long 24 + .long 3 + .quad l_OBJC_METH_VAR_NAME_.3 + .quad l_OBJC_METH_VAR_TYPE_ + .quad "-[MyClass instance_method_00]" + .quad l_OBJC_METH_VAR_NAME_.4 + .quad l_OBJC_METH_VAR_TYPE_ + .quad "-[MyClass instance_method_01]" + .quad l_OBJC_METH_VAR_NAME_.5 + .quad l_OBJC_METH_VAR_TYPE_ + .quad "-[MyClass instance_method_02]" + .p2align 3, 0x0 + +__OBJC_CLASS_RO_$_MyClass: + .long 2 + .long 0 + .long 0 + .space 4 + .quad 0 + .quad l_OBJC_CLASS_NAME_ + .quad __OBJC_$_INSTANCE_METHODS_MyClass + .quad 0 + .quad 0 + .quad 0 + .quad 0 + .globl __objc_empty_cache + +.zerofill __DATA,__common,__objc_empty_cache,8,3 + .section __DATA,__objc_classlist,regular,no_dead_strip + .p2align 3, 0x0 +l_OBJC_LABEL_CLASS_$: + .quad _OBJC_CLASS_$_MyClass + .section __DATA,__objc_imageinfo,regular,no_dead_strip +L_OBJC_IMAGE_INFO: + .long 0 + .long 64 +.subsections_via_symbols