https://github.com/cjacek created https://github.com/llvm/llvm-project/pull/202740
This is a draft of an alternative solution for https://discourse.llvm.org/t/rfc-multi-architecture-coff-object-files-for-arm64x/90723 and https://discourse.llvm.org/t/rfc-introduce-a-wholearchive-marker-embedded-in-archives-themselves/90721. Unlike the previous attempt, this approach does not alter the archive format. Instead, it introduces the `.llvm.arm64x` section, similar to how `.llvm.lto` is used for "fat LTO" object files. This allows aware tools to leverage the additional capability while ensuring unaware tools simply ignore the section. On the Clang side, this is achieved by compiling separately for both the ARM64 and ARM64EC targets and merging the results via an additional llvm-objcopy job. lld-link then selects the correct view of the file depending on the target architecture. Additionally, the archive writer splits the object file into separate members so that they are properly reflected in the symbol map. An advantage of such treatment in archives is that the resulting static library can be used seamlessly by the MSVC linker, as it no longer relies on the `.llvm.arm64x` section. >From ebc5a77ce0fd36fa7a6ff6bc0bb3850fc9b66425 Mon Sep 17 00:00:00 2001 From: Jacek Caban <[email protected]> Date: Fri, 22 May 2026 22:39:11 +0200 Subject: [PATCH 1/6] [Object][COFF] Introduce the .llvm.arm64x section Introduce a new extension section allowing the embedding of ARM64EC object files inside native ARM64 object files. Its content consists of an entire, valid ARM64EC COFF object file. --- llvm/include/llvm/Object/COFF.h | 3 + llvm/lib/Object/COFFObjectFile.cpp | 22 ++++ .../llvm-readobj/COFF/arm64x-hybridobj.yaml | 102 ++++++++++++++++++ llvm/tools/llvm-readobj/llvm-readobj.cpp | 11 +- 4 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 llvm/test/tools/llvm-readobj/COFF/arm64x-hybridobj.yaml diff --git a/llvm/include/llvm/Object/COFF.h b/llvm/include/llvm/Object/COFF.h index ed2addbc69487..ac8c6abd7e027 100644 --- a/llvm/include/llvm/Object/COFF.h +++ b/llvm/include/llvm/Object/COFF.h @@ -877,6 +877,8 @@ struct coff_dynamic_relocation64_v2 { support::ulittle32_t Flags; }; +static constexpr StringLiteral kArm64XSectionName = ".llvm.arm64x"; + class LLVM_ABI COFFObjectFile : public ObjectFile { private: COFFObjectFile(MemoryBufferRef Object); @@ -1100,6 +1102,7 @@ class LLVM_ABI COFFObjectFile : public ObjectFile { return SubtargetFeatures(); } std::unique_ptr<MemoryBuffer> getHybridObjectView() const; + std::optional<MemoryBufferRef> getHybridObjectSection() const; import_directory_iterator import_directory_begin() const; import_directory_iterator import_directory_end() const; diff --git a/llvm/lib/Object/COFFObjectFile.cpp b/llvm/lib/Object/COFFObjectFile.cpp index 14a2343811a38..e321cd8918527 100644 --- a/llvm/lib/Object/COFFObjectFile.cpp +++ b/llvm/lib/Object/COFFObjectFile.cpp @@ -1560,6 +1560,28 @@ std::unique_ptr<MemoryBuffer> COFFObjectFile::getHybridObjectView() const { return HybridView; } +std::optional<MemoryBufferRef> COFFObjectFile::getHybridObjectSection() const { + if (getDOSHeader() || getMachine() != COFF::IMAGE_FILE_MACHINE_ARM64) + return std::nullopt; + + // Search the .llvm.arm64x section, which may be used to embed a full ARM64EC + // object file into an ARM64 object file. + for (const SectionRef &S : sections()) { + Expected<StringRef> Name = S.getName(); + if (errorToBool(Name.takeError())) + return std::nullopt; + + if (*Name == kArm64XSectionName) { + Expected<StringRef> ContentsOrErr = S.getContents(); + if (errorToBool(ContentsOrErr.takeError())) + return std::nullopt; + return MemoryBufferRef(*ContentsOrErr, getFileName()); + } + } + + return std::nullopt; +} + bool ImportDirectoryEntryRef:: operator==(const ImportDirectoryEntryRef &Other) const { return ImportTable == Other.ImportTable && Index == Other.Index; diff --git a/llvm/test/tools/llvm-readobj/COFF/arm64x-hybridobj.yaml b/llvm/test/tools/llvm-readobj/COFF/arm64x-hybridobj.yaml new file mode 100644 index 0000000000000..bd5d07e23f51f --- /dev/null +++ b/llvm/test/tools/llvm-readobj/COFF/arm64x-hybridobj.yaml @@ -0,0 +1,102 @@ +# RUN: yaml2obj -DMACHINE=IMAGE_FILE_MACHINE_ARM64 %s -o %t-aarch64.o +# RUN: yaml2obj -DMACHINE=IMAGE_FILE_MACHINE_ARM64EC %s -o %t-arm64ec.o +# RUN: llvm-objcopy --add-section=.llvm.arm64x=%t-arm64ec.o --set-section-flags=.llvm.arm64x=debug %t-aarch64.o %t.o +# RUN: llvm-readobj --sections %t.o | FileCheck %s + +# CHECK: Format: COFF-ARM64 +# CHECK-NEXT: Arch: aarch64 +# CHECK-NEXT: AddressSize: 64bit +# CHECK-NEXT: Sections [ +# CHECK-NEXT: Section { +# CHECK-NEXT: Number: 1 +# CHECK-NEXT: Name: .data (2E 64 61 74 61 00 00 00) +# CHECK-NEXT: VirtualSize: 0x0 +# CHECK-NEXT: VirtualAddress: 0x0 +# CHECK-NEXT: RawDataSize: 4 +# CHECK-NEXT: PointerToRawData: 0x64 +# CHECK-NEXT: PointerToRelocations: 0x0 +# CHECK-NEXT: PointerToLineNumbers: 0x0 +# CHECK-NEXT: RelocationCount: 0 +# CHECK-NEXT: LineNumberCount: 0 +# CHECK-NEXT: Characteristics [ (0xC0300040) +# CHECK-NEXT: IMAGE_SCN_ALIGN_4BYTES (0x300000) +# CHECK-NEXT: IMAGE_SCN_CNT_INITIALIZED_DATA (0x40) +# CHECK-NEXT: IMAGE_SCN_MEM_READ (0x40000000) +# CHECK-NEXT: IMAGE_SCN_MEM_WRITE (0x80000000) +# CHECK-NEXT: ] +# CHECK-NEXT: } +# CHECK-NEXT: Section { +# CHECK-NEXT: Number: 2 +# CHECK-NEXT: Name: .llvm.arm64x (2F 34 00 00 00 00 00 00) +# CHECK-NEXT: VirtualSize: 0x7A +# CHECK-NEXT: VirtualAddress: 0x0 +# CHECK-NEXT: RawDataSize: 122 +# CHECK-NEXT: PointerToRawData: 0x68 +# CHECK-NEXT: PointerToRelocations: 0x0 +# CHECK-NEXT: PointerToLineNumbers: 0x0 +# CHECK-NEXT: RelocationCount: 0 +# CHECK-NEXT: LineNumberCount: 0 +# CHECK-NEXT: Characteristics [ (0xC2000040) +# CHECK-NEXT: IMAGE_SCN_CNT_INITIALIZED_DATA (0x40) +# CHECK-NEXT: IMAGE_SCN_MEM_DISCARDABLE (0x2000000) +# CHECK-NEXT: IMAGE_SCN_MEM_READ (0x40000000) +# CHECK-NEXT: IMAGE_SCN_MEM_WRITE (0x80000000) +# CHECK-NEXT: ] +# CHECK-NEXT: } +# CHECK-NEXT: ] +# CHECK-NEXT: HybridObject { +# CHECK: Format: COFF-ARM64EC +# CHECK-NEXT: Arch: aarch64 +# CHECK-NEXT: AddressSize: 64bit +# CHECK-NEXT: Sections [ +# CHECK-NEXT: Section { +# CHECK-NEXT: Number: 1 +# CHECK-NEXT: Name: .data (2E 64 61 74 61 00 00 00) +# CHECK-NEXT: VirtualSize: 0x0 +# CHECK-NEXT: VirtualAddress: 0x0 +# CHECK-NEXT: RawDataSize: 4 +# CHECK-NEXT: PointerToRawData: 0x3C +# CHECK-NEXT: PointerToRelocations: 0x0 +# CHECK-NEXT: PointerToLineNumbers: 0x0 +# CHECK-NEXT: RelocationCount: 0 +# CHECK-NEXT: LineNumberCount: 0 +# CHECK-NEXT: Characteristics [ (0xC0300040) +# CHECK-NEXT: IMAGE_SCN_ALIGN_4BYTES (0x300000) +# CHECK-NEXT: IMAGE_SCN_CNT_INITIALIZED_DATA (0x40) +# CHECK-NEXT: IMAGE_SCN_MEM_READ (0x40000000) +# CHECK-NEXT: IMAGE_SCN_MEM_WRITE (0x80000000) +# CHECK-NEXT: ] +# CHECK-NEXT: } +# CHECK-NEXT: ] +# CHECK-NEXT: } + +--- !COFF +header: + Machine: [[MACHINE]] + Characteristics: [ ] +sections: + - Name: .data + Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_READ, IMAGE_SCN_MEM_WRITE ] + Alignment: 4 + SectionData: '00000000' + SizeOfRawData: 4 +symbols: + - Name: .data + Value: 0 + SectionNumber: 1 + SimpleType: IMAGE_SYM_TYPE_NULL + ComplexType: IMAGE_SYM_DTYPE_NULL + StorageClass: IMAGE_SYM_CLASS_STATIC + SectionDefinition: + Length: 4 + NumberOfRelocations: 0 + NumberOfLinenumbers: 0 + CheckSum: 0 + Number: 2 + - Name: sym + Value: 0 + SectionNumber: 1 + SimpleType: IMAGE_SYM_TYPE_NULL + ComplexType: IMAGE_SYM_DTYPE_NULL + StorageClass: IMAGE_SYM_CLASS_EXTERNAL +... diff --git a/llvm/tools/llvm-readobj/llvm-readobj.cpp b/llvm/tools/llvm-readobj/llvm-readobj.cpp index fa56e3e48e58c..cfb51c18b8484 100644 --- a/llvm/tools/llvm-readobj/llvm-readobj.cpp +++ b/llvm/tools/llvm-readobj/llvm-readobj.cpp @@ -604,11 +604,16 @@ static void dumpCOFFObject(COFFObjectFile *Obj, ScopedPrinter &Writer) { dumpObject(*Obj, Writer); // Dump a hybrid object when available. - std::unique_ptr<MemoryBuffer> HybridView = Obj->getHybridObjectView(); - if (!HybridView) + MemoryBufferRef HybridView; + std::unique_ptr<MemoryBuffer> HybridViewBuf; + if (std::optional<MemoryBufferRef> HybridSec = Obj->getHybridObjectSection()) + HybridView = *HybridSec; + else if ((HybridViewBuf = Obj->getHybridObjectView())) + HybridView = HybridViewBuf->getMemBufferRef(); + else return; Expected<std::unique_ptr<COFFObjectFile>> HybridObjOrErr = - COFFObjectFile::create(*HybridView); + COFFObjectFile::create(HybridView); if (!HybridObjOrErr) reportError(HybridObjOrErr.takeError(), Obj->getFileName().str()); DictScope D(Writer, "HybridObject"); >From 168d83b4d476a49757b8ea3bd06e4e3aa7a449ad Mon Sep 17 00:00:00 2001 From: Jacek Caban <[email protected]> Date: Tue, 26 May 2026 16:45:40 +0200 Subject: [PATCH 2/6] [Object][ArchiveWriter] Factor out MemberData allocation Factor out MemberData allocation in preparation for handling hybrid object files, which may require creating additional archive members. --- llvm/lib/Object/ArchiveWriter.cpp | 64 ++++++++++++++----------------- 1 file changed, 29 insertions(+), 35 deletions(-) diff --git a/llvm/lib/Object/ArchiveWriter.cpp b/llvm/lib/Object/ArchiveWriter.cpp index 4610fb4303274..584a5c2c597e8 100644 --- a/llvm/lib/Object/ArchiveWriter.cpp +++ b/llvm/lib/Object/ArchiveWriter.cpp @@ -338,6 +338,7 @@ struct MemberData { StringRef Padding; uint64_t PreHeadPadSize = 0; std::unique_ptr<SymbolicFile> SymFile = nullptr; + const NewArchiveMember *NewMember = nullptr; }; } // namespace @@ -783,7 +784,6 @@ computeMemberData(raw_ostream &StringTable, raw_ostream &SymNames, LLVMContext &Context, ArrayRef<NewArchiveMember> NewMembers, std::optional<bool> IsEC, function_ref<void(Error)> Warn) { static char PaddingData[8] = {'\n', '\n', '\n', '\n', '\n', '\n', '\n', '\n'}; - uint64_t MemHeadPadSize = 0; uint64_t Pos = isAIXBigArchive(Kind) ? sizeof(object::BigArchive::FixLenHdr) : 0; @@ -845,17 +845,18 @@ computeMemberData(raw_ostream &StringTable, raw_ostream &SymNames, Entry.second = Entry.second > 1 ? 1 : 0; } - std::vector<std::unique_ptr<SymbolicFile>> SymFiles; + for (const NewArchiveMember &M : NewMembers) { + MemberData &D = Ret.emplace_back(); + D.NewMember = &M; - if (NeedSymbols != SymtabWritingMode::NoSymtab || isAIXBigArchive(Kind)) { - for (const NewArchiveMember &M : NewMembers) { + if (NeedSymbols != SymtabWritingMode::NoSymtab || isAIXBigArchive(Kind)) { Expected<std::unique_ptr<SymbolicFile>> SymFileOrErr = getSymbolicFile( M.Buf->getMemBufferRef(), Context, Kind, [&](Error Err) { Warn(createFileError(M.MemberName, std::move(Err))); }); if (!SymFileOrErr) return createFileError(M.MemberName, SymFileOrErr.takeError()); - SymFiles.push_back(std::move(*SymFileOrErr)); + D.SymFile = std::move(*SymFileOrErr); } } @@ -868,13 +869,13 @@ computeMemberData(raw_ostream &StringTable, raw_ostream &SymNames, // AMD64). This may be a single ARM64EC object, but may also be separate // ARM64 and AMD64 objects. bool HaveArm64 = false, HaveEC = false; - for (std::unique_ptr<SymbolicFile> &SymFile : SymFiles) { - if (!SymFile) + for (const MemberData &D : Ret) { + if (!D.SymFile) continue; if (!HaveArm64) - HaveArm64 = isAnyArm64COFF(*SymFile); + HaveArm64 = isAnyArm64COFF(*D.SymFile); if (!HaveEC) - HaveEC = isECObject(*SymFile); + HaveEC = isECObject(*D.SymFile); if (HaveArm64 && HaveEC) { SymMap->UseECMap = true; break; @@ -888,23 +889,23 @@ computeMemberData(raw_ostream &StringTable, raw_ostream &SymNames, uint64_t PrevOffset = 0; uint64_t NextMemHeadPadSize = 0; - for (uint32_t Index = 0; Index < NewMembers.size(); ++Index) { - const NewArchiveMember *M = &NewMembers[Index]; - std::string Header; - raw_string_ostream Out(Header); + for (uint32_t Index = 0; Index < Ret.size(); ++Index) { + MemberData &D = Ret[Index]; + const NewArchiveMember *M = D.NewMember; + raw_string_ostream Out(D.Header); MemoryBufferRef Buf = M->Buf->getMemBufferRef(); - StringRef Data = Thin ? "" : Buf.getBuffer(); + D.Data = Thin ? "" : Buf.getBuffer(); // ld64 expects the members to be 8-byte aligned for 64-bit content and at // least 4-byte aligned for 32-bit content. Opt for the larger encoding // uniformly. This matches the behaviour with cctools and ensures that ld64 // is happy with archives that we generate. unsigned MemberPadding = - isDarwin(Kind) ? offsetToAlignment(Data.size(), Align(8)) : 0; + isDarwin(Kind) ? offsetToAlignment(D.Data.size(), Align(8)) : 0; unsigned TailPadding = - offsetToAlignment(Data.size() + MemberPadding, Align(2)); - StringRef Padding = StringRef(PaddingData, MemberPadding + TailPadding); + offsetToAlignment(D.Data.size() + MemberPadding, Align(2)); + D.Padding = StringRef(PaddingData, MemberPadding + TailPadding); sys::TimePoint<std::chrono::seconds> ModTime; if (UniqueTimestamps) @@ -921,36 +922,32 @@ computeMemberData(raw_ostream &StringTable, raw_ostream &SymNames, std::move(StringMsg), object::object_error::parse_failed); } - std::unique_ptr<SymbolicFile> CurSymFile; - if (!SymFiles.empty()) - CurSymFile = std::move(SymFiles[Index]); - // In the big archive file format, we need to calculate and include the next // member offset and previous member offset in the file member header. if (isAIXBigArchive(Kind)) { uint64_t OffsetToMemData = Pos + sizeof(object::BigArMemHdrType) + alignTo(M->MemberName.size(), 2); - if (M == NewMembers.begin()) + if (Index == 0) NextMemHeadPadSize = alignToPowerOf2(OffsetToMemData, - getMemberAlignment(CurSymFile.get())) - + getMemberAlignment(D.SymFile.get())) - OffsetToMemData; - MemHeadPadSize = NextMemHeadPadSize; - Pos += MemHeadPadSize; + D.PreHeadPadSize = NextMemHeadPadSize; + Pos += D.PreHeadPadSize; uint64_t NextOffset = Pos + sizeof(object::BigArMemHdrType) + alignTo(M->MemberName.size(), 2) + alignTo(Size, 2); // If there is another member file after this, we need to calculate the // padding before the header. - if (Index + 1 != SymFiles.size()) { + if (Index + 1 != Ret.size()) { uint64_t OffsetToNextMemData = NextOffset + sizeof(object::BigArMemHdrType) + - alignTo(NewMembers[Index + 1].MemberName.size(), 2); + alignTo(Ret[Index + 1].NewMember->MemberName.size(), 2); NextMemHeadPadSize = alignToPowerOf2(OffsetToNextMemData, - getMemberAlignment(SymFiles[Index + 1].get())) - + getMemberAlignment(Ret[Index + 1].SymFile.get())) - OffsetToNextMemData; NextOffset += NextMemHeadPadSize; } @@ -962,20 +959,17 @@ computeMemberData(raw_ostream &StringTable, raw_ostream &SymNames, ModTime, Size); } - std::vector<unsigned> Symbols; if (NeedSymbols != SymtabWritingMode::NoSymtab) { Expected<std::vector<unsigned>> SymbolsOrErr = - getSymbols(CurSymFile.get(), Index + 1, SymNames, SymMap); + getSymbols(D.SymFile.get(), Index + 1, SymNames, SymMap); if (!SymbolsOrErr) return createFileError(M->MemberName, SymbolsOrErr.takeError()); - Symbols = std::move(*SymbolsOrErr); - if (CurSymFile) + D.Symbols = std::move(*SymbolsOrErr); + if (D.SymFile) HasObject = true; } - Pos += Header.size() + Data.size() + Padding.size(); - Ret.push_back({std::move(Symbols), std::move(Header), Data, Padding, - MemHeadPadSize, std::move(CurSymFile)}); + Pos += D.Header.size() + D.Data.size() + D.Padding.size(); } // If there are no symbols, emit an empty symbol table, to satisfy Solaris // tools, older versions of which expect a symbol table in a non-empty >From 5041437ce6e49274a9f5fffcfc0ffd71d0d87f9b Mon Sep 17 00:00:00 2001 From: Jacek Caban <[email protected]> Date: Wed, 27 May 2026 16:07:54 +0200 Subject: [PATCH 3/6] [Archive][COFF] Split hybrid COFF files when adding them to an archive Create a separate member for the embedded hybrid object so that it's properly reflected in the archive's symbol map and can be correctly processed by tools unaware of multi-arch object files. Prefix the embedded member name with 'llvm.arm64x/' to avoid name collisions. We could also strip the .llvm.arm64x section from the original file. I have a draft patch doing that I plan as a follow-up. However, that is mostly an optimization, as the original file remains a valid ARM64 file regardless. --- llvm/lib/Object/ArchiveWriter.cpp | 55 +++++++++++++------ llvm/test/tools/llvm-ar/arm64x-hybridobj.yaml | 50 +++++++++++++++++ .../test/tools/llvm-lib/arm64x-hybridobj.yaml | 49 +++++++++++++++++ 3 files changed, 138 insertions(+), 16 deletions(-) create mode 100644 llvm/test/tools/llvm-ar/arm64x-hybridobj.yaml create mode 100644 llvm/test/tools/llvm-lib/arm64x-hybridobj.yaml diff --git a/llvm/lib/Object/ArchiveWriter.cpp b/llvm/lib/Object/ArchiveWriter.cpp index 584a5c2c597e8..cc873fa8cb68a 100644 --- a/llvm/lib/Object/ArchiveWriter.cpp +++ b/llvm/lib/Object/ArchiveWriter.cpp @@ -301,24 +301,24 @@ static bool is64BitKind(object::Archive::Kind Kind) { static void printMemberHeader(raw_ostream &Out, uint64_t Pos, raw_ostream &StringTable, StringMap<uint64_t> &MemberNames, object::Archive::Kind Kind, - bool Thin, const NewArchiveMember &M, + bool Thin, const NewArchiveMember &M, StringRef MemberName, sys::TimePoint<std::chrono::seconds> ModTime, uint64_t Size) { if (isBSDLike(Kind)) - return printBSDMemberHeader(Out, Pos, M.MemberName, ModTime, M.UID, M.GID, + return printBSDMemberHeader(Out, Pos, MemberName, ModTime, M.UID, M.GID, M.Perms, Size); - if (!useStringTable(Thin, M.MemberName)) - return printGNUSmallMemberHeader(Out, M.MemberName, ModTime, M.UID, M.GID, + if (!useStringTable(Thin, MemberName)) + return printGNUSmallMemberHeader(Out, MemberName, ModTime, M.UID, M.GID, M.Perms, Size); Out << '/'; uint64_t NamePos; if (Thin) { NamePos = StringTable.tell(); - StringTable << M.MemberName << "/\n"; + StringTable << MemberName << "/\n"; } else { - auto Insertion = MemberNames.insert({M.MemberName, uint64_t(0)}); + auto Insertion = MemberNames.insert({MemberName, uint64_t(0)}); if (Insertion.second) { Insertion.first->second = StringTable.tell(); - StringTable << M.MemberName; + StringTable << MemberName; if (isCOFFArchive(Kind)) StringTable << '\0'; else @@ -339,6 +339,7 @@ struct MemberData { uint64_t PreHeadPadSize = 0; std::unique_ptr<SymbolicFile> SymFile = nullptr; const NewArchiveMember *NewMember = nullptr; + std::string HybridName = ""; }; } // namespace @@ -857,6 +858,25 @@ computeMemberData(raw_ostream &StringTable, raw_ostream &SymNames, if (!SymFileOrErr) return createFileError(M.MemberName, SymFileOrErr.takeError()); D.SymFile = std::move(*SymFileOrErr); + + if (SymMap && D.SymFile.get()) { + auto COFFObj = dyn_cast<COFFObjectFile>(D.SymFile.get()); + std::optional<MemoryBufferRef> HybridView; + if (COFFObj && (HybridView = COFFObj->getHybridObjectSection())) { + // Create a separate archive member for the hybrid ARM64X object. + MemberData &HybridData = Ret.emplace_back(); + HybridData.NewMember = &M; + + SymFileOrErr = + getSymbolicFile(*HybridView, Context, Kind, [&](Error Err) { + Warn(createFileError(M.MemberName, std::move(Err))); + }); + if (!SymFileOrErr) + return createFileError(M.MemberName, SymFileOrErr.takeError()); + HybridData.SymFile = std::move(*SymFileOrErr); + HybridData.HybridName = ("llvm.arm64x/" + M.MemberName).str(); + } + } } } @@ -894,7 +914,8 @@ computeMemberData(raw_ostream &StringTable, raw_ostream &SymNames, const NewArchiveMember *M = D.NewMember; raw_string_ostream Out(D.Header); - MemoryBufferRef Buf = M->Buf->getMemBufferRef(); + MemoryBufferRef Buf = + D.SymFile ? D.SymFile->getMemoryBufferRef() : M->Buf->getMemBufferRef(); D.Data = Thin ? "" : Buf.getBuffer(); // ld64 expects the members to be 8-byte aligned for 64-bit content and at @@ -907,17 +928,19 @@ computeMemberData(raw_ostream &StringTable, raw_ostream &SymNames, offsetToAlignment(D.Data.size() + MemberPadding, Align(2)); D.Padding = StringRef(PaddingData, MemberPadding + TailPadding); + StringRef MemberName = D.HybridName.size() ? D.HybridName : M->MemberName; + sys::TimePoint<std::chrono::seconds> ModTime; if (UniqueTimestamps) // Increment timestamp for each file of a given name. - ModTime = sys::toTimePoint(FilenameCount[M->MemberName]++); + ModTime = sys::toTimePoint(FilenameCount[MemberName]++); else ModTime = M->ModTime; uint64_t Size = Buf.getBufferSize() + MemberPadding; if (Size > object::Archive::MaxMemberSize) { std::string StringMsg = - "File " + M->MemberName.str() + " exceeds size limit"; + "File " + MemberName.str() + " exceeds size limit"; return make_error<object::GenericBinaryError>( std::move(StringMsg), object::object_error::parse_failed); } @@ -925,8 +948,8 @@ computeMemberData(raw_ostream &StringTable, raw_ostream &SymNames, // In the big archive file format, we need to calculate and include the next // member offset and previous member offset in the file member header. if (isAIXBigArchive(Kind)) { - uint64_t OffsetToMemData = Pos + sizeof(object::BigArMemHdrType) + - alignTo(M->MemberName.size(), 2); + uint64_t OffsetToMemData = + Pos + sizeof(object::BigArMemHdrType) + alignTo(MemberName.size(), 2); if (Index == 0) NextMemHeadPadSize = @@ -937,7 +960,7 @@ computeMemberData(raw_ostream &StringTable, raw_ostream &SymNames, D.PreHeadPadSize = NextMemHeadPadSize; Pos += D.PreHeadPadSize; uint64_t NextOffset = Pos + sizeof(object::BigArMemHdrType) + - alignTo(M->MemberName.size(), 2) + alignTo(Size, 2); + alignTo(MemberName.size(), 2) + alignTo(Size, 2); // If there is another member file after this, we need to calculate the // padding before the header. @@ -951,19 +974,19 @@ computeMemberData(raw_ostream &StringTable, raw_ostream &SymNames, OffsetToNextMemData; NextOffset += NextMemHeadPadSize; } - printBigArchiveMemberHeader(Out, M->MemberName, ModTime, M->UID, M->GID, + printBigArchiveMemberHeader(Out, MemberName, ModTime, M->UID, M->GID, M->Perms, Size, PrevOffset, NextOffset); PrevOffset = Pos; } else { printMemberHeader(Out, Pos, StringTable, MemberNames, Kind, Thin, *M, - ModTime, Size); + MemberName, ModTime, Size); } if (NeedSymbols != SymtabWritingMode::NoSymtab) { Expected<std::vector<unsigned>> SymbolsOrErr = getSymbols(D.SymFile.get(), Index + 1, SymNames, SymMap); if (!SymbolsOrErr) - return createFileError(M->MemberName, SymbolsOrErr.takeError()); + return createFileError(MemberName, SymbolsOrErr.takeError()); D.Symbols = std::move(*SymbolsOrErr); if (D.SymFile) HasObject = true; diff --git a/llvm/test/tools/llvm-ar/arm64x-hybridobj.yaml b/llvm/test/tools/llvm-ar/arm64x-hybridobj.yaml new file mode 100644 index 0000000000000..884489e6701a4 --- /dev/null +++ b/llvm/test/tools/llvm-ar/arm64x-hybridobj.yaml @@ -0,0 +1,50 @@ +# RUN: yaml2obj -DMACHINE=IMAGE_FILE_MACHINE_ARM64 %s -o %t-aarch64.o +# RUN: yaml2obj -DMACHINE=IMAGE_FILE_MACHINE_ARM64EC %s -o %t-arm64ec.o +# RUN: llvm-objcopy --add-section=.llvm.arm64x=%t-arm64ec.o --set-section-flags=.llvm.arm64x=debug %t-aarch64.o %t.o +# RUN: rm -f %t.lib +# RUN: llvm-ar rc %t.lib %t.o +# RUN: llvm-nm --print-armap %t.lib | FileCheck %s + +# CHECK: Archive map +# CHECK-NEXT: sym in arm64x-hybridobj.yaml.tmp.o +# CHECK-EMPTY: +# CHECK-NEXT: Archive EC map +# CHECK-NEXT: sym in llvm.arm64x/arm64x-hybridobj.yaml.tmp.o +# CHECK-EMPTY: +# CHECK-EMPTY: +# CHECK-NEXT: arm64x-hybridobj.yaml.tmp.o: +# CHECK-NEXT: 00000000 D sym +# CHECK-EMPTY: +# CHECK-NEXT: llvm.arm64x/arm64x-hybridobj.yaml.tmp.o: +# CHECK-NEXT: 00000000 D sym + +--- !COFF +header: + Machine: [[MACHINE]] + Characteristics: [ ] +sections: + - Name: .data + Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_READ, IMAGE_SCN_MEM_WRITE ] + Alignment: 4 + SectionData: '00000000' + SizeOfRawData: 4 +symbols: + - Name: .data + Value: 0 + SectionNumber: 1 + SimpleType: IMAGE_SYM_TYPE_NULL + ComplexType: IMAGE_SYM_DTYPE_NULL + StorageClass: IMAGE_SYM_CLASS_STATIC + SectionDefinition: + Length: 4 + NumberOfRelocations: 0 + NumberOfLinenumbers: 0 + CheckSum: 0 + Number: 2 + - Name: sym + Value: 0 + SectionNumber: 1 + SimpleType: IMAGE_SYM_TYPE_NULL + ComplexType: IMAGE_SYM_DTYPE_NULL + StorageClass: IMAGE_SYM_CLASS_EXTERNAL +... diff --git a/llvm/test/tools/llvm-lib/arm64x-hybridobj.yaml b/llvm/test/tools/llvm-lib/arm64x-hybridobj.yaml new file mode 100644 index 0000000000000..b893cc70d1c19 --- /dev/null +++ b/llvm/test/tools/llvm-lib/arm64x-hybridobj.yaml @@ -0,0 +1,49 @@ +# RUN: yaml2obj -DMACHINE=IMAGE_FILE_MACHINE_ARM64 %s -o %t-aarch64.o +# RUN: yaml2obj -DMACHINE=IMAGE_FILE_MACHINE_ARM64EC %s -o %t-arm64ec.o +# RUN: llvm-objcopy --add-section=.llvm.arm64x=%t-arm64ec.o --set-section-flags=.llvm.arm64x=debug %t-aarch64.o %t.o +# RUN: llvm-lib -machine:arm64x -out:%t.lib %t.o +# RUN: llvm-nm --print-armap %t.lib | FileCheck %s + +# CHECK: Archive map +# CHECK-NEXT: sym in {{.*}}/arm64x-hybridobj.yaml.tmp.o +# CHECK-EMPTY: +# CHECK-NEXT: Archive EC map +# CHECK-NEXT: sym in llvm.arm64x/{{.*}}/arm64x-hybridobj.yaml.tmp.o +# CHECK-EMPTY: +# CHECK-EMPTY: +# CHECK-NEXT: {{.*}}/arm64x-hybridobj.yaml.tmp.o: +# CHECK-NEXT: 00000000 D sym +# CHECK-EMPTY: +# CHECK-NEXT: llvm.arm64x/{{.*}}/arm64x-hybridobj.yaml.tmp.o: +# CHECK-NEXT: 00000000 D sym + +--- !COFF +header: + Machine: [[MACHINE]] + Characteristics: [ ] +sections: + - Name: .data + Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_READ, IMAGE_SCN_MEM_WRITE ] + Alignment: 4 + SectionData: '00000000' + SizeOfRawData: 4 +symbols: + - Name: .data + Value: 0 + SectionNumber: 1 + SimpleType: IMAGE_SYM_TYPE_NULL + ComplexType: IMAGE_SYM_DTYPE_NULL + StorageClass: IMAGE_SYM_CLASS_STATIC + SectionDefinition: + Length: 4 + NumberOfRelocations: 0 + NumberOfLinenumbers: 0 + CheckSum: 0 + Number: 2 + - Name: sym + Value: 0 + SectionNumber: 1 + SimpleType: IMAGE_SYM_TYPE_NULL + ComplexType: IMAGE_SYM_DTYPE_NULL + StorageClass: IMAGE_SYM_CLASS_EXTERNAL +... >From b729297aa7b3cc8d7b086e395e3b3199bccbd655 Mon Sep 17 00:00:00 2001 From: Jacek Caban <[email protected]> Date: Mon, 8 Jun 2026 17:59:55 +0200 Subject: [PATCH 4/6] [LLD][COFF] Factor out addObjectFile Avoid parsing the input COFF file twice by creating it earlier and replacing findBitcodeInMemBuffer with findBitcodeInObject. It's also a preparation for handling hybrid ARM64X object files. --- lld/COFF/Driver.cpp | 28 +++++++++++++++++----------- lld/COFF/Driver.h | 4 ++++ lld/COFF/InputFiles.cpp | 12 +++++++++--- lld/COFF/InputFiles.h | 9 ++++++++- 4 files changed, 38 insertions(+), 15 deletions(-) diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp index 024cb2c95cd20..34b8c85116984 100644 --- a/lld/COFF/Driver.cpp +++ b/lld/COFF/Driver.cpp @@ -262,21 +262,27 @@ MemoryBufferRef LinkerDriver::takeBuffer(std::unique_ptr<MemoryBuffer> mb) { return mbref; } -static InputFile *tryCreateFatLTOFile(COFFLinkerContext &ctx, - MemoryBufferRef mb, StringRef archiveName, - uint64_t offsetInArchive, bool lazy) { +InputFile *LinkerDriver::addObjectFile(COFFLinkerContext &ctx, + MemoryBufferRef mb, + StringRef archiveName, + uint64_t offsetInArchive, bool lazy) { + COFFObjectFile *coffObj = ObjFile::createCOFFObject(ctx, mb); + InputFile *obj = nullptr; + if (ctx.config.fatLTOObjects) { Expected<MemoryBufferRef> fatLTOData = - IRObjectFile::findBitcodeInMemBuffer(mb); + IRObjectFile::findBitcodeInObject(*coffObj); if (!errorToBool(fatLTOData.takeError())) { - return BitcodeFile::create(ctx, *fatLTOData, archiveName, offsetInArchive, - lazy); + obj = BitcodeFile::create(ctx, *fatLTOData, archiveName, offsetInArchive, + lazy); } } - InputFile *obj = ObjFile::create(ctx, mb, lazy); + if (!obj) + obj = ObjFile::create(ctx, coffObj, lazy); obj->parentName = archiveName; + addFile(obj); return obj; } @@ -323,7 +329,7 @@ void LinkerDriver::addBuffer(std::unique_ptr<MemoryBuffer> mb, addFile(BitcodeFile::create(ctx, mbref, "", 0, lazy)); break; case file_magic::coff_object: { - addFile(tryCreateFatLTOFile(ctx, mbref, "", 0, lazy)); + addObjectFile(ctx, mbref, "", 0, lazy); break; } case file_magic::coff_import_library: @@ -437,9 +443,11 @@ void LinkerDriver::addArchiveBuffer(MemoryBufferRef mb, StringRef symName, InputFile *obj; if (magic == file_magic::coff_object) { - obj = tryCreateFatLTOFile(ctx, mb, parentName, offsetInArchive, lazy); + obj = addObjectFile(ctx, mb, parentName, offsetInArchive, lazy); } else if (magic == file_magic::bitcode) { obj = BitcodeFile::create(ctx, mb, parentName, offsetInArchive, lazy); + obj->parentName = parentName; + addFile(obj); } else if (magic == file_magic::coff_cl_gl_object) { Err(ctx) << mb.getBufferIdentifier() << ": is not a native COFF file. Recompile without /GL?"; @@ -449,8 +457,6 @@ void LinkerDriver::addArchiveBuffer(MemoryBufferRef mb, StringRef symName, return; } - obj->parentName = parentName; - addFile(obj); Log(ctx) << "Loaded " << obj << " for " << symName; } diff --git a/lld/COFF/Driver.h b/lld/COFF/Driver.h index e7a7acebc6e4c..7eea9aee279a1 100644 --- a/lld/COFF/Driver.h +++ b/lld/COFF/Driver.h @@ -125,6 +125,10 @@ class LinkerDriver { bool isDecorated(StringRef sym); + InputFile *addObjectFile(COFFLinkerContext &ctx, MemoryBufferRef mb, + StringRef archiveName, uint64_t offsetInArchive, + bool lazy); + std::string getMapFile(const llvm::opt::InputArgList &args, llvm::opt::OptSpecifier os, llvm::opt::OptSpecifier osFile); diff --git a/lld/COFF/InputFiles.cpp b/lld/COFF/InputFiles.cpp index 3821c9366c56a..a9113f8260576 100644 --- a/lld/COFF/InputFiles.cpp +++ b/lld/COFF/InputFiles.cpp @@ -278,7 +278,8 @@ ObjFile::ObjFile(SymbolTable &symtab, COFFObjectFile *coffObj, bool lazy) : InputFile(symtab, ObjectKind, coffObj->getMemoryBufferRef(), lazy), coffObj(coffObj) {} -ObjFile *ObjFile::create(COFFLinkerContext &ctx, MemoryBufferRef m, bool lazy) { +COFFObjectFile *ObjFile::createCOFFObject(COFFLinkerContext &ctx, + MemoryBufferRef m) { // Parse a memory buffer as a COFF file. Expected<std::unique_ptr<Binary>> bin = createBinary(m); if (!bin) @@ -289,8 +290,13 @@ ObjFile *ObjFile::create(COFFLinkerContext &ctx, MemoryBufferRef m, bool lazy) { Fatal(ctx) << m.getBufferIdentifier() << " is not a COFF file"; bin->release(); - return make<ObjFile>(ctx.getSymtab(MachineTypes(obj->getMachine())), obj, - lazy); + return obj; +} + +ObjFile *ObjFile::create(COFFLinkerContext &ctx, COFFObjectFile *coffObj, + bool lazy) { + return make<ObjFile>(ctx.getSymtab(MachineTypes(coffObj->getMachine())), + coffObj, lazy); } void ObjFile::parseLazy() { diff --git a/lld/COFF/InputFiles.h b/lld/COFF/InputFiles.h index ce8bc6705e489..b9cf3bd0339c4 100644 --- a/lld/COFF/InputFiles.h +++ b/lld/COFF/InputFiles.h @@ -136,10 +136,17 @@ class ArchiveFile : public InputFile { // .obj or .o file. This may be a member of an archive file. class ObjFile : public InputFile { public: - static ObjFile *create(COFFLinkerContext &ctx, MemoryBufferRef mb, + static ObjFile *create(COFFLinkerContext &ctx, COFFObjectFile *coffObj, bool lazy = false); + static ObjFile *create(COFFLinkerContext &ctx, MemoryBufferRef mb, + bool lazy = false) { + return ObjFile::create(ctx, ObjFile::createCOFFObject(ctx, mb), lazy); + } explicit ObjFile(SymbolTable &symtab, COFFObjectFile *coffObj, bool lazy); + static COFFObjectFile *createCOFFObject(COFFLinkerContext &ctx, + MemoryBufferRef mb); + static bool classof(const InputFile *f) { return f->kind() == ObjectKind; } void parse() override; void parseLazy(); >From 86f66bb282aaffc4af0cf49092849ec0a553bc24 Mon Sep 17 00:00:00 2001 From: Jacek Caban <[email protected]> Date: Mon, 8 Jun 2026 18:02:40 +0200 Subject: [PATCH 5/6] [LLD][COFF] Add support for multi-atch ARM64X object files --- lld/COFF/Driver.cpp | 21 ++++++++-- lld/COFF/Driver.h | 4 +- lld/test/COFF/arm64x-hybridobj.s | 71 ++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 6 deletions(-) create mode 100644 lld/test/COFF/arm64x-hybridobj.s diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp index 34b8c85116984..aa3b880b2418a 100644 --- a/lld/COFF/Driver.cpp +++ b/lld/COFF/Driver.cpp @@ -265,10 +265,21 @@ MemoryBufferRef LinkerDriver::takeBuffer(std::unique_ptr<MemoryBuffer> mb) { InputFile *LinkerDriver::addObjectFile(COFFLinkerContext &ctx, MemoryBufferRef mb, StringRef archiveName, - uint64_t offsetInArchive, bool lazy) { + uint64_t offsetInArchive, bool lazy, + bool addHybrid) { COFFObjectFile *coffObj = ObjFile::createCOFFObject(ctx, mb); InputFile *obj = nullptr; + if (addHybrid && ctx.symtab.isEC()) { + if (std::optional<MemoryBufferRef> hybridView = + coffObj->getHybridObjectSection()) { + InputFile *hybridObj = addObjectFile(ctx, *hybridView, archiveName, + offsetInArchive, lazy, false); + if (ctx.config.machine != ARM64X) + return hybridObj; + } + } + if (ctx.config.fatLTOObjects) { Expected<MemoryBufferRef> fatLTOData = IRObjectFile::findBitcodeInObject(*coffObj); @@ -432,7 +443,8 @@ void LinkerDriver::enqueuePath(StringRef path, bool lazy, InputOpt inputOpt) { void LinkerDriver::addArchiveBuffer(MemoryBufferRef mb, StringRef symName, StringRef parentName, - uint64_t offsetInArchive, bool lazy) { + uint64_t offsetInArchive, bool lazy, + bool isThin) { file_magic magic = identify_magic(mb.getBuffer()); if (magic == file_magic::coff_import_library) { InputFile *imp = make<ImportFile>(ctx, mb); @@ -443,7 +455,7 @@ void LinkerDriver::addArchiveBuffer(MemoryBufferRef mb, StringRef symName, InputFile *obj; if (magic == file_magic::coff_object) { - obj = addObjectFile(ctx, mb, parentName, offsetInArchive, lazy); + obj = addObjectFile(ctx, mb, parentName, offsetInArchive, lazy, isThin); } else if (magic == file_magic::bitcode) { obj = BitcodeFile::create(ctx, mb, parentName, offsetInArchive, lazy); obj->parentName = parentName; @@ -466,7 +478,8 @@ void LinkerDriver::addThinArchiveBuffer(MemoryBufferRef mb, StringRef symName, // the original filename is used as the buffer identifier. This is // useful for DTLTO, where having the member identifier be the actual // path on disk enables distribution of bitcode files during ThinLTO. - addArchiveBuffer(mb, symName, /*parentName=*/"", /*OffsetInArchive=*/0, lazy); + addArchiveBuffer(mb, symName, /*parentName=*/"", /*OffsetInArchive=*/0, lazy, + true); } void LinkerDriver::enqueueArchiveMember(const Archive::Child &c, diff --git a/lld/COFF/Driver.h b/lld/COFF/Driver.h index 7eea9aee279a1..572ed85eed35e 100644 --- a/lld/COFF/Driver.h +++ b/lld/COFF/Driver.h @@ -127,7 +127,7 @@ class LinkerDriver { InputFile *addObjectFile(COFFLinkerContext &ctx, MemoryBufferRef mb, StringRef archiveName, uint64_t offsetInArchive, - bool lazy); + bool lazy, bool addHybrid = true); std::string getMapFile(const llvm::opt::InputArgList &args, llvm::opt::OptSpecifier os, @@ -183,7 +183,7 @@ class LinkerDriver { bool lazy); void addArchiveBuffer(MemoryBufferRef mbref, StringRef symName, StringRef parentName, uint64_t offsetInArchive, - bool lazy); + bool lazy, bool isThin = false); void addThinArchiveBuffer(MemoryBufferRef mbref, StringRef symName, bool lazy); diff --git a/lld/test/COFF/arm64x-hybridobj.s b/lld/test/COFF/arm64x-hybridobj.s new file mode 100644 index 0000000000000..8742e8fd03b8d --- /dev/null +++ b/lld/test/COFF/arm64x-hybridobj.s @@ -0,0 +1,71 @@ +// REQUIRES: aarch64 +// RUN: split-file %s %t.dir && cd %t.dir + +// RUN: llvm-mc -filetype=obj -triple=aarch64-windows sym.s -o sym-arm64.obj +// RUN: llvm-mc -filetype=obj -triple=arm64ec-windows sym.s -o sym-arm64ec.obj +// RUN: llvm-objcopy --add-section=.llvm.arm64x=sym-arm64ec.obj --set-section-flags=.llvm.arm64x=debug \ +// RUN: sym-arm64.obj sym.obj + +// RUN: llvm-mc -filetype=obj -triple=aarch64-windows ref.s -o ref-arm64.obj +// RUN: llvm-mc -filetype=obj -triple=arm64ec-windows ref.s -o ref-arm64ec.obj +// RUN: llvm-objcopy --add-section=.llvm.arm64x=ref-arm64ec.obj --set-section-flags=.llvm.arm64x=debug \ +// RUN: ref-arm64.obj ref.obj + +// RUN: llvm-mc -filetype=obj -triple=arm64ec-windows %S/Inputs/loadconfig-arm64ec.s -o loadconfig-arm64ec.obj +// RUN: llvm-mc -filetype=obj -triple=aarch64-windows %S/Inputs/loadconfig-arm64.s -o loadconfig-arm64.obj +// RUN: llvm-objcopy --add-section=.llvm.arm64x=loadconfig-arm64ec.obj --set-section-flags=.llvm.arm64x=debug \ +// RUN: loadconfig-arm64.obj loadconfig.obj + +// RUN: lld-link -machine:arm64x -dll -noentry -out:out.dll sym.obj loadconfig.obj +// RUN: llvm-readobj --coff-exports out.dll | FileCheck %s + +// RUN: lld-link -machine:arm64ec -dll -noentry -out:out-ec.dll sym.obj loadconfig.obj +// RUN: lld-link -machine:arm64 -dll -noentry -out:out-native.dll sym.obj loadconfig.obj + +// RUN: llvm-ar cr sym.lib sym.obj +// RUN: lld-link -machine:arm64x -dll -noentry -out:out2.dll ref.obj sym.lib loadconfig.obj +// RUN: llvm-readobj --coff-exports out2.dll | FileCheck %s +// RUN: lld-link -machine:arm64ec -dll -noentry -out:out-ec2.dll sym.obj loadconfig.obj +// RUN: lld-link -machine:arm64 -dll -noentry -out:out-native2.dll sym.obj loadconfig.obj + +// RUN: llvm-ar cr --thin sym-thin.lib sym.obj +// RUN: lld-link -machine:arm64x -dll -noentry -out:out3.dll ref.obj sym-thin.lib loadconfig.obj +// RUN: llvm-readobj --coff-exports out3.dll | FileCheck %s + +// RUN: lld-link -machine:arm64x -dll -noentry -out:out4.dll ref.obj -start-lib sym.obj loadconfig.obj -end-lib +// RUN: llvm-readobj --coff-exports out4.dll | FileCheck %s + +// RUN: lld-link -machine:arm64x -dll -noentry -out:out4.dll -wholearchive:sym-thin.lib loadconfig.obj +// RUN: llvm-readobj --coff-exports out4.dll | FileCheck %s + +// CHECK: Format: COFF-ARM64X +// CHECK-NEXT: Arch: aarch64 +// CHECK-NEXT: AddressSize: 64bit +// CHECK-NEXT: Export { +// CHECK-NEXT: Ordinal: 1 +// CHECK-NEXT: Name: sym +// CHECK-NEXT: RVA: 0x4004 +// CHECK-NEXT: } +// CHECK-NEXT: HybridObject { +// CHECK-NEXT: Format: COFF-ARM64EC +// CHECK-NEXT: Arch: aarch64 +// CHECK-NEXT: AddressSize: 64bit +// CHECK-NEXT: Export { +// CHECK-NEXT: Ordinal: 1 +// CHECK-NEXT: Name: sym +// CHECK-NEXT: RVA: 0x4000 +// CHECK-NEXT: } +// CHECK-NEXT: } + +#--- sym.s + .section .sym,"dr" + .globl sym +sym: + .long 0 + + .section .drectve + .ascii "-export:sym" + +#--- ref.s + .data + .rva sym >From 0bbe2fbbfa3a46743902e0c8fabf267abf78b8d7 Mon Sep 17 00:00:00 2001 From: Jacek Caban <[email protected]> Date: Wed, 1 Apr 2026 16:18:07 +0200 Subject: [PATCH 6/6] [clang][ARM64X] Support compiling both native and EC objects with -marm64x When -marm64x is used during the assembly phase, construct jobs for both native and EC targets and merge their outputs using llvm-objcopy. --- clang/include/clang/Driver/Action.h | 2 +- clang/include/clang/Options/Options.td | 2 +- clang/lib/Driver/Action.cpp | 4 +-- clang/lib/Driver/Driver.cpp | 37 +++++++++++++++++++------- clang/lib/Driver/ToolChain.cpp | 4 ++- clang/lib/Driver/ToolChains/MSVC.cpp | 11 ++++++++ clang/lib/Driver/ToolChains/MSVC.h | 4 +++ clang/lib/Driver/ToolChains/MinGW.cpp | 29 ++++++++++++++++++++ clang/lib/Driver/ToolChains/MinGW.h | 14 ++++++++++ clang/test/Driver/arm64x.c | 6 +++++ clang/test/Driver/msvc-link.c | 4 +-- 11 files changed, 101 insertions(+), 16 deletions(-) create mode 100644 clang/test/Driver/arm64x.c diff --git a/clang/include/clang/Driver/Action.h b/clang/include/clang/Driver/Action.h index 67937b00f6bcf..edad2e64e8d62 100644 --- a/clang/include/clang/Driver/Action.h +++ b/clang/include/clang/Driver/Action.h @@ -695,7 +695,7 @@ class ObjcopyJobAction : public JobAction { void anchor() override; public: - ObjcopyJobAction(Action *Input, types::ID Type); + ObjcopyJobAction(ActionList &Inputs, types::ID Type); static bool classof(const Action *A) { return A->getKind() == ObjcopyJobClass; diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index 4fd892e58df86..23a27c4db6985 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -5410,7 +5410,7 @@ def municode : Joined<["-"], "municode">, Group<m_Group>; def mthreads : Joined<["-"], "mthreads">, Group<m_Group>; def marm64x : Joined<["-"], "marm64x">, Group<m_Group>, Visibility<[ClangOption, CLOption]>, - HelpText<"Link as a hybrid ARM64X image">; + HelpText<"Build as a hybrid ARM64X image">; def mguard_EQ : Joined<["-"], "mguard=">, Group<m_Group>, HelpText<"Enable or disable Control Flow Guard checks and guard tables emission">, Values<"none,cf,cf-nochecks">; diff --git a/clang/lib/Driver/Action.cpp b/clang/lib/Driver/Action.cpp index 72a42a6f957ee..01045a97930f8 100644 --- a/clang/lib/Driver/Action.cpp +++ b/clang/lib/Driver/Action.cpp @@ -472,5 +472,5 @@ BinaryTranslatorJobAction::BinaryTranslatorJobAction(Action *Input, void ObjcopyJobAction::anchor() {} -ObjcopyJobAction::ObjcopyJobAction(Action *Input, types::ID Type) - : JobAction(ObjcopyJobClass, Input, Type) {} +ObjcopyJobAction::ObjcopyJobAction(ActionList &Inputs, types::ID Type) + : JobAction(ObjcopyJobClass, Inputs, Type) {} diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp index c2a2cd80b94b2..6ca9639276341 100644 --- a/clang/lib/Driver/Driver.cpp +++ b/clang/lib/Driver/Driver.cpp @@ -618,10 +618,9 @@ static void setZosTargetVersion(const Driver &D, llvm::Triple &Target, /// /// This routine provides the logic to compute a target triple from various /// args passed to the driver and the default triple string. -static llvm::Triple computeTargetTriple(const Driver &D, - StringRef TargetTriple, +static llvm::Triple computeTargetTriple(const Driver &D, StringRef TargetTriple, const ArgList &Args, - StringRef DarwinArchName = "") { + StringRef ArchName = "") { // FIXME: Already done in Compilation *Driver::BuildCompilation if (const Arg *A = Args.getLastArg(options::OPT_target)) TargetTriple = A->getValue(); @@ -637,9 +636,8 @@ static llvm::Triple computeTargetTriple(const Driver &D, // Handle Apple-specific options available here. if (Target.isOSBinFormatMachO()) { // If an explicit Darwin arch name is given, that trumps all. - if (!DarwinArchName.empty()) { - tools::darwin::setTripleTypeForMachOArchName(Target, DarwinArchName, - Args); + if (!ArchName.empty()) { + tools::darwin::setTripleTypeForMachOArchName(Target, ArchName, Args); return Target; } @@ -648,6 +646,9 @@ static llvm::Triple computeTargetTriple(const Driver &D, StringRef ArchName = A->getValue(); tools::darwin::setTripleTypeForMachOArchName(Target, ArchName, Args); } + } else if (!ArchName.empty()) { + Target.setArchName(ArchName); + return Target; } // Handle pseudo-target flags '-mlittle-endian'/'-EL' and @@ -700,6 +701,11 @@ static llvm::Triple computeTargetTriple(const Driver &D, D.Diag(diag::err_drv_unsupported_opt_for_target) << A->getAsString(Args) << Target.str(); + // The `-marm64x` flag is only valid for Windows targets. + if (Args.hasArgNoClaim(options::OPT_marm64x) && !Target.isOSWindows()) + D.Diag(diag::err_drv_unsupported_opt_for_target) + << "-marm64x" << Target.str(); + // Handle pseudo-target flags '-m64', '-mx32', '-m32' and '-m16'. Arg *A = Args.getLastArg(options::OPT_m64, options::OPT_mx32, options::OPT_m32, options::OPT_m16, @@ -4808,9 +4814,11 @@ void Driver::BuildActions(Compilation &C, DerivedArgList &Args, if (TC.requiresObjcopy(Args)) { Action *LastAction = Actions.back(); // llvm-objcopy expects an unvalidated DXIL container (TY_OBJECT). - if (LastAction->getType() == types::TY_Object) + if (LastAction->getType() == types::TY_Object) { + ActionList ObjcopyActions({LastAction}); Actions.push_back( - C.MakeAction<ObjcopyJobAction>(LastAction, types::TY_Object)); + C.MakeAction<ObjcopyJobAction>(ObjcopyActions, types::TY_Object)); + } } // Call validator when -Vd not in Args. @@ -5471,6 +5479,16 @@ Action *Driver::ConstructPhaseAction( return C.MakeAction<BackendJobAction>(Input, types::TY_PP_Asm); } case phases::Assemble: + // When -marm64x is used, construct jobs for the EC and native targets and + // merge them into an archive with llvm-objcopy. + if (Args.hasArg(options::OPT_marm64x)) { + Action *Act = + C.MakeAction<AssembleJobAction>(std::move(Input), types::TY_Object); + ActionList Inputs; + Inputs.push_back(C.MakeAction<BindArchAction>(Act, "arm64ec")); + Inputs.push_back(C.MakeAction<BindArchAction>(Act, "aarch64")); + return C.MakeAction<ObjcopyJobAction>(Inputs, types::TY_Object); + } return C.MakeAction<AssembleJobAction>(std::move(Input), types::TY_Object); } @@ -5565,7 +5583,8 @@ void Driver::BuildJobs(Compilation &C) const { BuildJobsForAction(C, A, &C.getDefaultToolChain(), /*BoundArch*/ StringRef(), /*AtTopLevel*/ true, - /*MultipleArchs*/ ArchNames.size() > 1, + /*MultipleArchs*/ ArchNames.size() > 1 || + C.getArgs().hasArgNoClaim(options::OPT_marm64x), /*LinkingOutput*/ LinkingOutput, CachedResults, /*TargetDeviceOffloadKind*/ Action::OFK_None); } diff --git a/clang/lib/Driver/ToolChain.cpp b/clang/lib/Driver/ToolChain.cpp index ccfc022f79427..eb1878594a779 100644 --- a/clang/lib/Driver/ToolChain.cpp +++ b/clang/lib/Driver/ToolChain.cpp @@ -776,9 +776,11 @@ Tool *ToolChain::getTool(Action::ActionClass AC) const { case Action::VerifyDebugInfoJobClass: case Action::BinaryAnalyzeJobClass: case Action::BinaryTranslatorJobClass: - case Action::ObjcopyJobClass: llvm_unreachable("Invalid tool kind."); + case Action::ObjcopyJobClass: + return nullptr; + case Action::CompileJobClass: case Action::PrecompileJobClass: case Action::PreprocessJobClass: diff --git a/clang/lib/Driver/ToolChains/MSVC.cpp b/clang/lib/Driver/ToolChains/MSVC.cpp index 8141f9f132421..ba8fdb9060b5c 100644 --- a/clang/lib/Driver/ToolChains/MSVC.cpp +++ b/clang/lib/Driver/ToolChains/MSVC.cpp @@ -525,6 +525,17 @@ MSVCToolChain::MSVCToolChain(const Driver &D, const llvm::Triple &Triple, llvm::findVCToolChainViaRegistry(VCToolChainPath, VSLayout); } +Tool *MSVCToolChain::getTool(Action::ActionClass AC) const { + switch (AC) { + case Action::ObjcopyJobClass: + if (!LLVMObjcopy) + LLVMObjcopy.reset(new tools::MinGW::LLVMObjcopy(*this)); + return LLVMObjcopy.get(); + default: + return ToolChain::getTool(AC); + } +} + Tool *MSVCToolChain::buildLinker() const { return new tools::visualstudio::Linker(*this); } diff --git a/clang/lib/Driver/ToolChains/MSVC.h b/clang/lib/Driver/ToolChains/MSVC.h index dcf4c0b488171..f231dd5824a6e 100644 --- a/clang/lib/Driver/ToolChains/MSVC.h +++ b/clang/lib/Driver/ToolChains/MSVC.h @@ -9,6 +9,7 @@ #ifndef LLVM_CLANG_LIB_DRIVER_TOOLCHAINS_MSVC_H #define LLVM_CLANG_LIB_DRIVER_TOOLCHAINS_MSVC_H +#include "MinGW.h" #include "clang/Driver/Compilation.h" #include "clang/Driver/CudaInstallationDetector.h" #include "clang/Driver/LazyDetector.h" @@ -137,8 +138,10 @@ class LLVM_LIBRARY_VISIBILITY MSVCToolChain : public ToolChain { const Twine &subfolder2 = "", const Twine &subfolder3 = "") const; + Tool *getTool(Action::ActionClass AC) const override; Tool *buildLinker() const override; Tool *buildAssembler() const override; + private: std::optional<llvm::StringRef> WinSdkDir, WinSdkVersion, WinSysRoot; std::string VCToolChainPath; @@ -146,6 +149,7 @@ class LLVM_LIBRARY_VISIBILITY MSVCToolChain : public ToolChain { LazyDetector<CudaInstallationDetector> CudaInstallation; LazyDetector<RocmInstallationDetector> RocmInstallation; LazyDetector<SYCLInstallationDetector> SYCLInstallation; + mutable std::unique_ptr<tools::MinGW::LLVMObjcopy> LLVMObjcopy; }; } // end namespace toolchains diff --git a/clang/lib/Driver/ToolChains/MinGW.cpp b/clang/lib/Driver/ToolChains/MinGW.cpp index e3f8cb8292fc8..f91b292f1037c 100644 --- a/clang/lib/Driver/ToolChains/MinGW.cpp +++ b/clang/lib/Driver/ToolChains/MinGW.cpp @@ -387,6 +387,31 @@ void tools::MinGW::Linker::ConstructJob(Compilation &C, const JobAction &JA, Exec, CmdArgs, Inputs, Output)); } +void tools::MinGW::LLVMObjcopy::ConstructJob(Compilation &C, + const JobAction &JA, + const InputInfo &Output, + const InputInfoList &Inputs, + const ArgList &Args, + const char *LinkingOutput) const { + + std::string ObjcopyPath = getToolChain().GetProgramPath("llvm-objcopy"); + const char *Exec = Args.MakeArgString(ObjcopyPath); + + // Assume llvm-objcopy is only used for hybrid ARM64X object files. + assert(Inputs.size() == 2 && "Expected 2 inputs."); + // Embed the hybrid object in the .llvm.arm64x section. + ArgStringList CmdArgs; + CmdArgs.push_back(Args.MakeArgString("--add-section=.llvm.arm64x=" + + Twine(Inputs[0].getFilename()))); + // Mark the .llvm.arm64x section as discardable. + CmdArgs.push_back("--set-section-flags=.llvm.arm64x=debug"); + CmdArgs.push_back(Inputs[1].getFilename()); + CmdArgs.push_back(Output.getFilename()); + + C.addCommand(std::make_unique<Command>(JA, *this, ResponseFileSupport::None(), + Exec, CmdArgs, Inputs, Output)); +} + static bool isCrossCompiling(const llvm::Triple &T, bool RequireArchMatch) { llvm::Triple HostTriple(llvm::Triple::normalize(LLVM_HOST_TRIPLE)); if (HostTriple.getOS() != llvm::Triple::Win32) @@ -575,6 +600,10 @@ Tool *toolchains::MinGW::getTool(Action::ActionClass AC) const { if (!Compiler) Compiler.reset(new tools::gcc::Compiler(*this)); return Compiler.get(); + case Action::ObjcopyJobClass: + if (!LLVMObjcopy) + LLVMObjcopy.reset(new tools::MinGW::LLVMObjcopy(*this)); + return LLVMObjcopy.get(); default: return ToolChain::getTool(AC); } diff --git a/clang/lib/Driver/ToolChains/MinGW.h b/clang/lib/Driver/ToolChains/MinGW.h index ddf9dc500960b..8c54609afddaa 100644 --- a/clang/lib/Driver/ToolChains/MinGW.h +++ b/clang/lib/Driver/ToolChains/MinGW.h @@ -52,6 +52,19 @@ class LLVM_LIBRARY_VISIBILITY Linker final : public Tool { void AddLibGCC(const llvm::opt::ArgList &Args, llvm::opt::ArgStringList &CmdArgs) const; }; + +class LLVM_LIBRARY_VISIBILITY LLVMObjcopy : public Tool { +public: + LLVMObjcopy(const ToolChain &TC) + : Tool("MinGW::LLVMObjcopy", "llvm-objcopy", TC) {} + + bool hasIntegratedCPP() const override { return false; } + + void ConstructJob(Compilation &C, const JobAction &JA, + const InputInfo &Output, const InputInfoList &Inputs, + const llvm::opt::ArgList &TCArgs, + const char *LinkingOutput) const override; +}; } // end namespace MinGW } // end namespace tools @@ -117,6 +130,7 @@ class LLVM_LIBRARY_VISIBILITY MinGW : public ToolChain { std::string TripleDirName; mutable std::unique_ptr<tools::gcc::Preprocessor> Preprocessor; mutable std::unique_ptr<tools::gcc::Compiler> Compiler; + mutable std::unique_ptr<tools::MinGW::LLVMObjcopy> LLVMObjcopy; void findGccLibDir(const llvm::Triple &LiteralTriple); bool NativeLLVMSupport; diff --git a/clang/test/Driver/arm64x.c b/clang/test/Driver/arm64x.c new file mode 100644 index 0000000000000..fc55703208187 --- /dev/null +++ b/clang/test/Driver/arm64x.c @@ -0,0 +1,6 @@ +// RUN: %clang -c -marm64x --target=arm64ec-pc-windows-msvc -### %s 2>&1 | FileCheck %s +// RUN: %clang -c -marm64x --target=arm64ec-pc-windows-gnu -### %s 2>&1 | FileCheck %s + +// CHECK: "-cc1" "-triple" "arm64ec-pc-windows-{{.*}}" "-emit-obj" +// CHECK-NEXT: "-cc1" "-triple" "aarch64-pc-windows-{{.*}}" "-emit-obj" +// CHECK-NEXT: llvm-objcopy" "--add-section=.llvm.arm64x={{.*}}arm64x-arm64ec-{{.*}}.o" "--set-section-flags=.llvm.arm64x=debug" "{{.*}}arm64x-aarch64-{{.*}}.o" "arm64x.o" diff --git a/clang/test/Driver/msvc-link.c b/clang/test/Driver/msvc-link.c index 5cb1653bf4db9..19027ff3e80ff 100644 --- a/clang/test/Driver/msvc-link.c +++ b/clang/test/Driver/msvc-link.c @@ -46,9 +46,9 @@ // ARM64X: "-machine:arm64x" // RUN: not %clang --target=x86_64-linux-gnu -marm64x -### %s 2>&1 | FileCheck --check-prefix=HYBRID-ERR %s -// HYBRID-ERR: error: unsupported option '-marm64x' for target 'x86_64-linux-gnu' +// HYBRID-ERR: error: unsupported option '-marm64x' for target 'x86_64-unknown-linux-gnu' -// RUN: %clang -c -marm64x --target=arm64ec-pc-windows-msvc -fuse-ld=link -### %s 2>&1 | \ +// RUN: %clang -S -marm64x --target=arm64ec-pc-windows-msvc -fuse-ld=link -### %s 2>&1 | \ // RUN: FileCheck --check-prefix=HYBRID-WARN %s // HYBRID-WARN: warning: argument unused during compilation: '-marm64x' [-Wunused-command-line-argument] _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
