Skip to content

Commit eb56ef3

Browse files
cjacekmstorsjo
authored andcommitted
[llvm-lib] Add support for ARM64EC libraries.
ARM64EC allows having both pure ARM64 objects and ARM64EC in the same archive. This allows using single static library for linking pure ARM64, pure ARM64EC or mixed modules (what MS calls ARM64X: a single module that may be used in both modes). To achieve that, such static libraries need two separated symbol maps. The usual map contains only pure ARM64 symbols, while a new /<ECSYMBOLS>/ section contains EC symbols. EC symbols map has very similar format to the usual map, except it doesn't contain object offsets and uses offsets from regular map instead. This is true even for pure ARM64EC static library: it will simply have 0 symbols in the symbol map. Reviewed By: efriedma Differential Revision: https://reviews.llvm.org/D143541
1 parent 9d7785b commit eb56ef3

File tree

5 files changed

+128
-16
lines changed

5 files changed

+128
-16
lines changed

llvm/include/llvm/Object/ArchiveWriter.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ Expected<std::string> computeArchiveRelativePath(StringRef From, StringRef To);
4343
Error writeArchive(StringRef ArcName, ArrayRef<NewArchiveMember> NewMembers,
4444
bool WriteSymtab, object::Archive::Kind Kind,
4545
bool Deterministic, bool Thin,
46-
std::unique_ptr<MemoryBuffer> OldArchiveBuf = nullptr);
46+
std::unique_ptr<MemoryBuffer> OldArchiveBuf = nullptr,
47+
bool IsEC = false);
4748

4849
// writeArchiveToBuffer is similar to writeArchive but returns the Archive in a
4950
// buffer instead of writing it out to a file.

llvm/lib/Object/ArchiveWriter.cpp

Lines changed: 73 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@
4545
using namespace llvm;
4646

4747
struct SymMap {
48+
bool UseECMap;
4849
std::map<std::string, uint16_t> Map;
50+
std::map<std::string, uint16_t> ECMap;
4951
};
5052

5153
NewArchiveMember::NewArchiveMember(MemoryBufferRef BufRef)
@@ -414,6 +416,20 @@ static uint64_t computeSymbolMapSize(uint64_t NumObj, SymMap &SymMap,
414416
return Size;
415417
}
416418

419+
static uint64_t computeECSymbolsSize(SymMap &SymMap,
420+
uint32_t *Padding = nullptr) {
421+
uint64_t Size = sizeof(uint32_t); // Number of symbols
422+
423+
for (auto S : SymMap.ECMap)
424+
Size += sizeof(uint16_t) + S.first.length() + 1;
425+
426+
uint32_t Pad = offsetToAlignment(Size, Align(2));
427+
Size += Pad;
428+
if (Padding)
429+
*Padding = Pad;
430+
return Size;
431+
}
432+
417433
static void writeSymbolTableHeader(raw_ostream &Out, object::Archive::Kind Kind,
418434
bool Deterministic, uint64_t Size,
419435
uint64_t PrevMemberOffset = 0) {
@@ -446,8 +462,11 @@ static uint64_t computeHeadersSize(object::Archive::Kind Kind,
446462
uint32_t HeaderSize = computeSymbolTableHeaderSize();
447463
uint64_t Size = strlen("!<arch>\n") + HeaderSize + SymtabSize;
448464

449-
if (SymMap)
465+
if (SymMap) {
450466
Size += HeaderSize + computeSymbolMapSize(NumMembers, *SymMap);
467+
if (SymMap->ECMap.size())
468+
Size += HeaderSize + computeECSymbolsSize(*SymMap);
469+
}
451470

452471
return Size + StringMemberSize;
453472
}
@@ -521,6 +540,41 @@ static void writeSymbolMap(raw_ostream &Out, object::Archive::Kind Kind,
521540
Out.write(uint8_t(0));
522541
}
523542

543+
static void writeECSymbols(raw_ostream &Out, object::Archive::Kind Kind,
544+
bool Deterministic, ArrayRef<MemberData> Members,
545+
SymMap &SymMap) {
546+
uint32_t Pad;
547+
uint64_t Size = computeECSymbolsSize(SymMap, &Pad);
548+
printGNUSmallMemberHeader(Out, "/<ECSYMBOLS>", now(Deterministic), 0, 0, 0,
549+
Size);
550+
551+
printLE<uint32_t>(Out, SymMap.ECMap.size());
552+
553+
for (auto S : SymMap.ECMap)
554+
printLE(Out, S.second);
555+
for (auto S : SymMap.ECMap)
556+
Out << S.first << '\0';
557+
while (Pad--)
558+
Out.write(uint8_t(0));
559+
}
560+
561+
static bool isECObject(object::SymbolicFile &Obj) {
562+
if (Obj.isCOFF())
563+
return cast<llvm::object::COFFObjectFile>(&Obj)->getMachine() !=
564+
COFF::IMAGE_FILE_MACHINE_ARM64;
565+
566+
if (Obj.isIR()) {
567+
Expected<std::string> TripleStr =
568+
getBitcodeTargetTriple(Obj.getMemoryBufferRef());
569+
if (!TripleStr)
570+
return false;
571+
Triple T(*TripleStr);
572+
return T.isWindowsArm64EC() || T.getArch() == Triple::x86_64;
573+
}
574+
575+
return false;
576+
}
577+
524578
static Expected<std::vector<unsigned>>
525579
getSymbols(MemoryBufferRef Buf, uint16_t Index, raw_ostream &SymNames,
526580
SymMap *SymMap, bool &HasObject) {
@@ -548,20 +602,25 @@ getSymbols(MemoryBufferRef Buf, uint16_t Index, raw_ostream &SymNames,
548602
Obj = std::move(*ObjOrErr);
549603
}
550604

605+
std::map<std::string, uint16_t> *Map = nullptr;
606+
if (SymMap)
607+
Map = SymMap->UseECMap && isECObject(*Obj) ? &SymMap->ECMap : &SymMap->Map;
551608
HasObject = true;
552609
for (const object::BasicSymbolRef &S : Obj->symbols()) {
553610
if (!isArchiveSymbol(S))
554611
continue;
555-
if (SymMap) {
612+
if (Map) {
556613
std::string Name;
557614
raw_string_ostream NameStream(Name);
558615
if (Error E = S.printName(NameStream))
559616
return std::move(E);
560-
if (SymMap->Map.find(Name) != SymMap->Map.end())
617+
if (Map->find(Name) != Map->end())
561618
continue; // ignore duplicated symbol
562-
SymMap->Map[Name] = Index;
563-
Ret.push_back(SymNames.tell());
564-
SymNames << Name << '\0';
619+
(*Map)[Name] = Index;
620+
if (Map == &SymMap->Map) {
621+
Ret.push_back(SymNames.tell());
622+
SymNames << Name << '\0';
623+
}
565624
} else {
566625
Ret.push_back(SymNames.tell());
567626
if (Error E = S.printName(SymNames))
@@ -755,7 +814,7 @@ Expected<std::string> computeArchiveRelativePath(StringRef From, StringRef To) {
755814
static Error writeArchiveToStream(raw_ostream &Out,
756815
ArrayRef<NewArchiveMember> NewMembers,
757816
bool WriteSymtab, object::Archive::Kind Kind,
758-
bool Deterministic, bool Thin) {
817+
bool Deterministic, bool Thin, bool IsEC) {
759818
assert((!Thin || !isBSDLike(Kind)) && "Only the gnu format has a thin mode");
760819

761820
SmallString<0> SymNamesBuf;
@@ -769,6 +828,7 @@ static Error writeArchiveToStream(raw_ostream &Out,
769828
if (isCOFFArchive(Kind) && NewMembers.size() > 0xfffe)
770829
Kind = object::Archive::K_GNU;
771830

831+
SymMap.UseECMap = IsEC;
772832
Expected<std::vector<MemberData>> DataOrErr = computeMemberData(
773833
StringTable, SymNames, Kind, Thin, Deterministic, WriteSymtab,
774834
isCOFFArchive(Kind) ? &SymMap : nullptr, NewMembers);
@@ -855,6 +915,9 @@ static Error writeArchiveToStream(raw_ostream &Out,
855915
Out << StringTableMember.Header << StringTableMember.Data
856916
<< StringTableMember.Padding;
857917

918+
if (WriteSymtab && SymMap.ECMap.size())
919+
writeECSymbols(Out, Kind, Deterministic, Data, SymMap);
920+
858921
for (const MemberData &M : Data)
859922
Out << M.Header << M.Data << M.Padding;
860923
} else {
@@ -944,15 +1007,15 @@ static Error writeArchiveToStream(raw_ostream &Out,
9441007
Error writeArchive(StringRef ArcName, ArrayRef<NewArchiveMember> NewMembers,
9451008
bool WriteSymtab, object::Archive::Kind Kind,
9461009
bool Deterministic, bool Thin,
947-
std::unique_ptr<MemoryBuffer> OldArchiveBuf) {
1010+
std::unique_ptr<MemoryBuffer> OldArchiveBuf, bool IsEC) {
9481011
Expected<sys::fs::TempFile> Temp =
9491012
sys::fs::TempFile::create(ArcName + ".temp-archive-%%%%%%%.a");
9501013
if (!Temp)
9511014
return Temp.takeError();
9521015
raw_fd_ostream Out(Temp->FD, false);
9531016

9541017
if (Error E = writeArchiveToStream(Out, NewMembers, WriteSymtab, Kind,
955-
Deterministic, Thin)) {
1018+
Deterministic, Thin, IsEC)) {
9561019
if (Error DiscardError = Temp->discard())
9571020
return joinErrors(std::move(E), std::move(DiscardError));
9581021
return E;
@@ -981,7 +1044,7 @@ writeArchiveToBuffer(ArrayRef<NewArchiveMember> NewMembers, bool WriteSymtab,
9811044
raw_svector_ostream ArchiveStream(ArchiveBufferVector);
9821045

9831046
if (Error E = writeArchiveToStream(ArchiveStream, NewMembers, WriteSymtab,
984-
Kind, Deterministic, Thin))
1047+
Kind, Deterministic, Thin, false))
9851048
return std::move(E);
9861049

9871050
return std::make_unique<SmallVectorMemoryBuffer>(

llvm/lib/ToolDrivers/llvm-lib/LibDriver.cpp

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,8 @@ static Expected<COFF::MachineTypes> getCOFFFileMachine(MemoryBufferRef MB) {
168168
if (Machine != COFF::IMAGE_FILE_MACHINE_I386 &&
169169
Machine != COFF::IMAGE_FILE_MACHINE_AMD64 &&
170170
Machine != COFF::IMAGE_FILE_MACHINE_ARMNT &&
171-
Machine != COFF::IMAGE_FILE_MACHINE_ARM64) {
171+
Machine != COFF::IMAGE_FILE_MACHINE_ARM64 &&
172+
Machine != COFF::IMAGE_FILE_MACHINE_ARM64EC) {
172173
return createStringError(inconvertibleErrorCode(),
173174
"unknown machine: " + std::to_string(Machine));
174175
}
@@ -181,21 +182,34 @@ static Expected<COFF::MachineTypes> getBitcodeFileMachine(MemoryBufferRef MB) {
181182
if (!TripleStr)
182183
return TripleStr.takeError();
183184

184-
switch (Triple(*TripleStr).getArch()) {
185+
Triple T(*TripleStr);
186+
switch (T.getArch()) {
185187
case Triple::x86:
186188
return COFF::IMAGE_FILE_MACHINE_I386;
187189
case Triple::x86_64:
188190
return COFF::IMAGE_FILE_MACHINE_AMD64;
189191
case Triple::arm:
190192
return COFF::IMAGE_FILE_MACHINE_ARMNT;
191193
case Triple::aarch64:
192-
return COFF::IMAGE_FILE_MACHINE_ARM64;
194+
return T.isWindowsArm64EC() ? COFF::IMAGE_FILE_MACHINE_ARM64EC
195+
: COFF::IMAGE_FILE_MACHINE_ARM64;
193196
default:
194197
return createStringError(inconvertibleErrorCode(),
195198
"unknown arch in target triple: " + *TripleStr);
196199
}
197200
}
198201

202+
static bool machineMatches(COFF::MachineTypes LibMachine,
203+
COFF::MachineTypes FileMachine) {
204+
if (LibMachine == FileMachine)
205+
return true;
206+
// ARM64EC mode allows both pure ARM64, ARM64EC and X64 objects to be mixed in
207+
// the archive.
208+
return LibMachine == COFF::IMAGE_FILE_MACHINE_ARM64EC &&
209+
(FileMachine == COFF::IMAGE_FILE_MACHINE_ARM64 ||
210+
FileMachine == COFF::IMAGE_FILE_MACHINE_AMD64);
211+
}
212+
199213
static void appendFile(std::vector<NewArchiveMember> &Members,
200214
COFF::MachineTypes &LibMachine,
201215
std::string &LibMachineSource, MemoryBufferRef MB) {
@@ -263,11 +277,18 @@ static void appendFile(std::vector<NewArchiveMember> &Members,
263277
// this check. See PR42180.
264278
if (FileMachine != COFF::IMAGE_FILE_MACHINE_UNKNOWN) {
265279
if (LibMachine == COFF::IMAGE_FILE_MACHINE_UNKNOWN) {
280+
if (FileMachine == COFF::IMAGE_FILE_MACHINE_ARM64EC) {
281+
llvm::errs() << MB.getBufferIdentifier() << ": file machine type "
282+
<< machineToStr(FileMachine)
283+
<< " conflicts with inferred library machine type,"
284+
<< " use /machine:arm64ec or /machine:arm64x\n";
285+
exit(1);
286+
}
266287
LibMachine = FileMachine;
267288
LibMachineSource =
268289
(" (inferred from earlier file '" + MB.getBufferIdentifier() + "')")
269290
.str();
270-
} else if (LibMachine != FileMachine) {
291+
} else if (!machineMatches(LibMachine, FileMachine)) {
271292
llvm::errs() << MB.getBufferIdentifier() << ": file machine type "
272293
<< machineToStr(FileMachine)
273294
<< " conflicts with library machine type "
@@ -460,7 +481,8 @@ int llvm::libDriverMain(ArrayRef<const char *> ArgsArr) {
460481
writeArchive(OutputPath, Members,
461482
/*WriteSymtab=*/true,
462483
Thin ? object::Archive::K_GNU : object::Archive::K_COFF,
463-
/*Deterministic*/ true, Thin)) {
484+
/*Deterministic*/ true, Thin, nullptr,
485+
LibMachine == COFF::IMAGE_FILE_MACHINE_ARM64EC)) {
464486
handleAllErrors(std::move(E), [&](const ErrorInfoBase &EI) {
465487
llvm::errs() << OutputPath << ": " << EI.message() << "\n";
466488
});

llvm/test/tools/llvm-lib/ecsymbols.test

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,11 @@ Members:
1515
- Name: '/<ECSYMBOLS>/'
1616
Size: '0'
1717
...
18+
19+
# Check that llvm-lib produces /<ECSYMBOLS>/ members for ARM64EC libraries.
20+
# RUN: rm -rf %t && mkdir -p %t && cd %t
21+
# RUN: llvm-mc -triple=arm64ec-pc-windows-msvc -filetype=obj -o arm64ec-foo.o %S/Inputs/a.s
22+
# RUN: llvm-mc -triple=aarch64-pc-windows-msvc -filetype=obj -o arm64-foo.o %S/Inputs/a.s
23+
# RUN: llvm-mc -triple=x86_64-pc-windows-msvc -filetype=obj -o x64-foo.o %S/Inputs/b.s
24+
# RUN: llvm-lib -machine:arm64ec -out:foo.lib arm64-foo.o arm64ec-foo.o x64-foo.o
25+
# RUN: grep -q '/<ECSYMBOLS>/' foo.lib

llvm/test/tools/llvm-lib/machine-mismatch.test

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ Prepare inputs:
33
RUN: rm -rf %t && mkdir -p %t
44
RUN: llvm-mc -triple=i386-pc-windows-msvc -filetype=obj -o %t/i386.obj %S/Inputs/a.s
55
RUN: llvm-mc -triple=x86_64-pc-windows-msvc -filetype=obj -o %t/x86_64.obj %S/Inputs/a.s
6+
RUN: llvm-mc -triple=aarch64-pc-windows-msvc -filetype=obj -o %t/arm64.obj %S/Inputs/a.s
7+
RUN: llvm-mc -triple=arm64ec-pc-windows-msvc -filetype=obj -o %t/arm64ec.obj %S/Inputs/a.s
68
RUN: llvm-as -o %t/i386.bc %S/Inputs/i386.ll
79
RUN: llvm-as -o %t/x86_64.bc %S/Inputs/x86_64.ll
810
RUN: llvm-as -o %t/arm64.bc %S/Inputs/arm64.ll
@@ -43,3 +45,19 @@ If /machine: is passed, its value is authoritative.
4345
RUN: not llvm-lib /machine:X86 %t/x86_64.obj %t/i386.obj 2>&1 | \
4446
RUN: FileCheck --check-prefix=OBJ64 %s
4547
OBJ64: x86_64.obj: file machine type x64 conflicts with library machine type x86 (from '/machine:X86' flag)
48+
49+
50+
Mixing arm64 and x86_64 is possible using arm64ec:
51+
52+
RUN: llvm-lib -machine:arm64ec %t/arm64.bc %t/x86_64.bc %t/arm64.obj %t/x86_64.obj %t/arm64ec.obj
53+
54+
RUN: not llvm-lib %t/arm64ec.obj 2>&1 | FileCheck --check-prefix=NOEC %s
55+
NOEC: arm64ec.obj: file machine type arm64ec conflicts with inferred library machine type, use /machine:arm64ec or /machine:arm64x
56+
57+
RUN: not llvm-lib -machine:arm64ec %t/arm64ec.obj %t/i386.obj 2>&1 | \
58+
RUN: FileCheck --check-prefix=OBJEC %s
59+
OBJEC: i386.obj: file machine type x86 conflicts with library machine type arm64ec (from '/machine:arm64ec' flag)
60+
61+
RUN: not llvm-lib -machine:arm64ec %t/arm64.bc %t/x86_64.bc %t/i386.bc 2>&1 | \
62+
RUN: FileCheck --check-prefix=BCEC %s
63+
BCEC: i386.bc: file machine type x86 conflicts with library machine type arm64ec (from '/machine:arm64ec' flag)

0 commit comments

Comments
 (0)