https://github.com/cjacek updated https://github.com/llvm/llvm-project/pull/202740
>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 9404f0016d39b15450e218db751066e68bf41099 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..27d4a1c444cdf --- /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 748698c313cacc1040112f869f232089d67fb78f 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 6cfd7ec80f14aefb87bc7b50e8fa0d93c0a5c904 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 84d8dbdd34d05aa5d998a4af26b6e7f4a7404f2f 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
