[clang] [lld] [llvm] [COFF][ARM64X] Multi-architecture COFF object files for ARM64X (PR #202740)
Jacek Caban via cfe-commits
cfe-commits at lists.llvm.org
Tue Jun 9 12:59:35 PDT 2026
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 <jacek at codeweavers.com>
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 <jacek at codeweavers.com>
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 <jacek at codeweavers.com>
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 <jacek at codeweavers.com>
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 <jacek at codeweavers.com>
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 <jacek at codeweavers.com>
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]
More information about the cfe-commits
mailing list