Index: llvm/include/llvm/Object/ArchiveWriter.h =================================================================== --- llvm/include/llvm/Object/ArchiveWriter.h +++ llvm/include/llvm/Object/ArchiveWriter.h @@ -43,7 +43,8 @@ Error writeArchive(StringRef ArcName, ArrayRef NewMembers, bool WriteSymtab, object::Archive::Kind Kind, bool Deterministic, bool Thin, - std::unique_ptr OldArchiveBuf = nullptr); + std::unique_ptr OldArchiveBuf = nullptr, + bool IsEC = false); // writeArchiveToBuffer is similar to writeArchive but returns the Archive in a // buffer instead of writing it out to a file. Index: llvm/lib/Object/ArchiveWriter.cpp =================================================================== --- llvm/lib/Object/ArchiveWriter.cpp +++ llvm/lib/Object/ArchiveWriter.cpp @@ -45,7 +45,9 @@ using namespace llvm; struct SymMap { + bool UseECMap; std::map Map; + std::map ECMap; }; NewArchiveMember::NewArchiveMember(MemoryBufferRef BufRef) @@ -414,6 +416,20 @@ return Size; } +static uint64_t computeECSymbolsSize(SymMap &SymMap, + uint32_t *Padding = nullptr) { + uint64_t Size = sizeof(uint32_t); // Number of symbols + + for (auto S : SymMap.ECMap) + Size += sizeof(uint16_t) + S.first.length() + 1; + + uint32_t Pad = offsetToAlignment(Size, Align(2)); + Size += Pad; + if (Padding) + *Padding = Pad; + return Size; +} + static void writeSymbolTableHeader(raw_ostream &Out, object::Archive::Kind Kind, bool Deterministic, uint64_t Size, uint64_t PrevMemberOffset = 0) { @@ -446,8 +462,11 @@ uint32_t HeaderSize = computeSymbolTableHeaderSize(); uint64_t Size = strlen("!\n") + HeaderSize + SymtabSize; - if (SymMap) + if (SymMap) { Size += HeaderSize + computeSymbolMapSize(NumMembers, *SymMap); + if (SymMap->ECMap.size()) + Size += HeaderSize + computeECSymbolsSize(*SymMap); + } return Size + StringMemberSize; } @@ -521,6 +540,41 @@ Out.write(uint8_t(0)); } +static void writeECSymbols(raw_ostream &Out, object::Archive::Kind Kind, + bool Deterministic, ArrayRef Members, + SymMap &SymMap) { + uint32_t Pad; + uint64_t Size = computeECSymbolsSize(SymMap, &Pad); + printGNUSmallMemberHeader(Out, "/", now(Deterministic), 0, 0, 0, + Size); + + printLE(Out, SymMap.ECMap.size()); + + for (auto S : SymMap.ECMap) + printLE(Out, S.second); + for (auto S : SymMap.ECMap) + Out << S.first << '\0'; + while (Pad--) + Out.write(uint8_t(0)); +} + +static bool isECObject(object::SymbolicFile &Obj) { + if (Obj.isCOFF()) + return cast(&Obj)->getMachine() != + COFF::IMAGE_FILE_MACHINE_ARM64; + + if (Obj.isIR()) { + Expected TripleStr = + getBitcodeTargetTriple(Obj.getMemoryBufferRef()); + if (!TripleStr) + return false; + Triple T(*TripleStr); + return T.isWindowsArm64EC() || T.getArch() == Triple::x86_64; + } + + return false; +} + static Expected> getSymbols(MemoryBufferRef Buf, uint16_t Index, raw_ostream &SymNames, SymMap *SymMap, bool &HasObject) { @@ -548,20 +602,25 @@ Obj = std::move(*ObjOrErr); } + std::map *Map = nullptr; + if (SymMap) + Map = SymMap->UseECMap && isECObject(*Obj) ? &SymMap->ECMap : &SymMap->Map; HasObject = true; for (const object::BasicSymbolRef &S : Obj->symbols()) { if (!isArchiveSymbol(S)) continue; - if (SymMap) { + if (Map) { std::string Name; raw_string_ostream NameStream(Name); if (Error E = S.printName(NameStream)) return std::move(E); - if (SymMap->Map.find(Name) != SymMap->Map.end()) + if (Map->find(Name) != Map->end()) continue; // ignore duplicated symbol - SymMap->Map[Name] = Index; - Ret.push_back(SymNames.tell()); - SymNames << Name << '\0'; + (*Map)[Name] = Index; + if (Map == &SymMap->Map) { + Ret.push_back(SymNames.tell()); + SymNames << Name << '\0'; + } } else { Ret.push_back(SymNames.tell()); if (Error E = S.printName(SymNames)) @@ -755,7 +814,7 @@ static Error writeArchiveToStream(raw_ostream &Out, ArrayRef NewMembers, bool WriteSymtab, object::Archive::Kind Kind, - bool Deterministic, bool Thin) { + bool Deterministic, bool Thin, bool IsEC) { assert((!Thin || !isBSDLike(Kind)) && "Only the gnu format has a thin mode"); SmallString<0> SymNamesBuf; @@ -769,6 +828,7 @@ if (isCOFFArchive(Kind) && NewMembers.size() > 0xfffe) Kind = object::Archive::K_GNU; + SymMap.UseECMap = IsEC; Expected> DataOrErr = computeMemberData( StringTable, SymNames, Kind, Thin, Deterministic, WriteSymtab, isCOFFArchive(Kind) ? &SymMap : nullptr, NewMembers); @@ -856,6 +916,9 @@ Out << StringTableMember.Header << StringTableMember.Data << StringTableMember.Padding; + if (WriteSymtab && SymMap.ECMap.size()) + writeECSymbols(Out, Kind, Deterministic, Data, SymMap); + for (const MemberData &M : Data) Out << M.Header << M.Data << M.Padding; } else { @@ -942,7 +1005,7 @@ Error writeArchive(StringRef ArcName, ArrayRef NewMembers, bool WriteSymtab, object::Archive::Kind Kind, bool Deterministic, bool Thin, - std::unique_ptr OldArchiveBuf) { + std::unique_ptr OldArchiveBuf, bool IsEC) { Expected Temp = sys::fs::TempFile::create(ArcName + ".temp-archive-%%%%%%%.a"); if (!Temp) @@ -950,7 +1013,7 @@ raw_fd_ostream Out(Temp->FD, false); if (Error E = writeArchiveToStream(Out, NewMembers, WriteSymtab, Kind, - Deterministic, Thin)) { + Deterministic, Thin, IsEC)) { if (Error DiscardError = Temp->discard()) return joinErrors(std::move(E), std::move(DiscardError)); return E; @@ -979,7 +1042,7 @@ raw_svector_ostream ArchiveStream(ArchiveBufferVector); if (Error E = writeArchiveToStream(ArchiveStream, NewMembers, WriteSymtab, - Kind, Deterministic, Thin)) + Kind, Deterministic, Thin, false)) return std::move(E); return std::make_unique( Index: llvm/lib/ToolDrivers/llvm-lib/LibDriver.cpp =================================================================== --- llvm/lib/ToolDrivers/llvm-lib/LibDriver.cpp +++ llvm/lib/ToolDrivers/llvm-lib/LibDriver.cpp @@ -168,7 +168,8 @@ if (Machine != COFF::IMAGE_FILE_MACHINE_I386 && Machine != COFF::IMAGE_FILE_MACHINE_AMD64 && Machine != COFF::IMAGE_FILE_MACHINE_ARMNT && - Machine != COFF::IMAGE_FILE_MACHINE_ARM64) { + Machine != COFF::IMAGE_FILE_MACHINE_ARM64 && + Machine != COFF::IMAGE_FILE_MACHINE_ARM64EC) { return createStringError(inconvertibleErrorCode(), "unknown machine: " + std::to_string(Machine)); } @@ -181,7 +182,8 @@ if (!TripleStr) return TripleStr.takeError(); - switch (Triple(*TripleStr).getArch()) { + Triple T(*TripleStr); + switch (T.getArch()) { case Triple::x86: return COFF::IMAGE_FILE_MACHINE_I386; case Triple::x86_64: @@ -189,15 +191,27 @@ case Triple::arm: return COFF::IMAGE_FILE_MACHINE_ARMNT; case Triple::aarch64: - return COFF::IMAGE_FILE_MACHINE_ARM64; + return T.isWindowsArm64EC() ? COFF::IMAGE_FILE_MACHINE_ARM64EC + : COFF::IMAGE_FILE_MACHINE_ARM64; default: return createStringError(inconvertibleErrorCode(), "unknown arch in target triple: " + *TripleStr); } } +static bool machineMatches(COFF::MachineTypes LibMachine, + COFF::MachineTypes FileMachine) { + if (LibMachine == FileMachine) + return true; + // ARM64EC mode allows both pure ARM64, ARM64EC and X64 objects to be mixed in + // the archive. + return LibMachine == COFF::IMAGE_FILE_MACHINE_ARM64EC && + (FileMachine == COFF::IMAGE_FILE_MACHINE_ARM64 || + FileMachine == COFF::IMAGE_FILE_MACHINE_AMD64); +} + static void appendFile(std::vector &Members, - COFF::MachineTypes &LibMachine, + COFF::MachineTypes &LibMachine, bool &InferredLibMachine, std::string &LibMachineSource, MemoryBufferRef MB) { file_magic Magic = identify_magic(MB.getBuffer()); @@ -229,7 +243,8 @@ exit(1); } - appendFile(Members, LibMachine, LibMachineSource, *ChildMB); + appendFile(Members, LibMachine, InferredLibMachine, LibMachineSource, + *ChildMB); } fatalOpenError(std::move(Err), MB.getBufferIdentifier()); @@ -264,15 +279,24 @@ if (FileMachine != COFF::IMAGE_FILE_MACHINE_UNKNOWN) { if (LibMachine == COFF::IMAGE_FILE_MACHINE_UNKNOWN) { LibMachine = FileMachine; + InferredLibMachine = true; LibMachineSource = (" (inferred from earlier file '" + MB.getBufferIdentifier() + "')") .str(); - } else if (LibMachine != FileMachine) { - llvm::errs() << MB.getBufferIdentifier() << ": file machine type " - << machineToStr(FileMachine) - << " conflicts with library machine type " - << machineToStr(LibMachine) << LibMachineSource << '\n'; - exit(1); + } else if (!machineMatches(LibMachine, FileMachine)) { + // If LibMachine is inferred, check if we could use ARM64EC instead. + if (InferredLibMachine && + machineMatches(COFF::IMAGE_FILE_MACHINE_ARM64EC, LibMachine) && + machineMatches(COFF::IMAGE_FILE_MACHINE_ARM64EC, FileMachine)) { + LibMachine = COFF::IMAGE_FILE_MACHINE_ARM64EC; + LibMachineSource = " (inferred from earlier files)"; + } else { + llvm::errs() << MB.getBufferIdentifier() << ": file machine type " + << machineToStr(FileMachine) + << " conflicts with library machine type " + << machineToStr(LibMachine) << LibMachineSource << '\n'; + exit(1); + } } } } @@ -323,6 +347,7 @@ } COFF::MachineTypes LibMachine = COFF::IMAGE_FILE_MACHINE_UNKNOWN; + bool InferredLibMachine = false; std::string LibMachineSource; if (auto *Arg = Args.getLastArg(OPT_machine)) { LibMachine = getMachineType(Arg->getValue()); @@ -425,7 +450,8 @@ MemoryBufferRef MBRef = (*MOrErr)->getMemBufferRef(); // Append a file. - appendFile(Members, LibMachine, LibMachineSource, MBRef); + appendFile(Members, LibMachine, InferredLibMachine, LibMachineSource, + MBRef); // Take the ownership of the file buffer to keep the file open. MBs.push_back(std::move(*MOrErr)); @@ -460,7 +486,8 @@ writeArchive(OutputPath, Members, /*WriteSymtab=*/true, Thin ? object::Archive::K_GNU : object::Archive::K_COFF, - /*Deterministic*/ true, Thin)) { + /*Deterministic*/ true, Thin, nullptr, + LibMachine == COFF::IMAGE_FILE_MACHINE_ARM64EC)) { handleAllErrors(std::move(E), [&](const ErrorInfoBase &EI) { llvm::errs() << OutputPath << ": " << EI.message() << "\n"; }); Index: llvm/test/tools/llvm-lib/ecsymbols.test =================================================================== --- llvm/test/tools/llvm-lib/ecsymbols.test +++ llvm/test/tools/llvm-lib/ecsymbols.test @@ -15,3 +15,11 @@ - Name: '//' Size: '0' ... + +# Check that llvm-lib produces // members for ARM64EC libraries. +# RUN: rm -rf %t && mkdir -p %t && cd %t +# RUN: llvm-mc -triple=arm64ec-pc-windows-msvc -filetype=obj -o arm64ec-foo.o %S/Inputs/a.s +# RUN: llvm-mc -triple=aarch64-pc-windows-msvc -filetype=obj -o arm64-foo.o %S/Inputs/a.s +# RUN: llvm-mc -triple=x86_64-pc-windows-msvc -filetype=obj -o x64-foo.o %S/Inputs/b.s +# RUN: llvm-lib -machine:arm64ec -out:foo.lib arm64-foo.o arm64ec-foo.o x64-foo.o +# RUN: grep -q '//' foo.lib Index: llvm/test/tools/llvm-lib/machine-mismatch.test =================================================================== --- llvm/test/tools/llvm-lib/machine-mismatch.test +++ llvm/test/tools/llvm-lib/machine-mismatch.test @@ -3,6 +3,8 @@ RUN: rm -rf %t && mkdir -p %t RUN: llvm-mc -triple=i386-pc-windows-msvc -filetype=obj -o %t/i386.obj %S/Inputs/a.s RUN: llvm-mc -triple=x86_64-pc-windows-msvc -filetype=obj -o %t/x86_64.obj %S/Inputs/a.s +RUN: llvm-mc -triple=aarch64-pc-windows-msvc -filetype=obj -o %t/arm64.obj %S/Inputs/a.s +RUN: llvm-mc -triple=arm64ec-pc-windows-msvc -filetype=obj -o %t/arm64ec.obj %S/Inputs/a.s RUN: llvm-as -o %t/i386.bc %S/Inputs/i386.ll RUN: llvm-as -o %t/x86_64.bc %S/Inputs/x86_64.ll RUN: llvm-as -o %t/arm64.bc %S/Inputs/arm64.ll @@ -14,6 +16,11 @@ RUN: llvm-lib %t/x86_64.obj %t/x86_64.bc +Mixing arm64 and x86_64 is possible using arm64ec: + +RUN: llvm-lib %t/arm64.bc %t/x86_64.bc %t/arm64.obj %t/x86_64.obj %t/arm64ec.obj + + As is including resource files: RUN: llvm-lib /out:%t.lib %S/Inputs/resource.res %t/i386.obj %t/i386.bc @@ -26,6 +33,10 @@ RUN: FileCheck --check-prefix=OBJ32 %s OBJ32: i386.obj: file machine type x86 conflicts with library machine type x64 (inferred from earlier file '{{.*}}x86_64.obj') +RUN: not llvm-lib %t/arm64ec.obj %t/i386.obj 2>&1 | \ +RUN: FileCheck --check-prefix=OBJEC %s +OBJEC: i386.obj: file machine type x86 conflicts with library machine type arm64ec (inferred from earlier file '{{.*}}arm64ec.obj') + Neither is mixing object and bitcode files with different machine type: @@ -33,10 +44,13 @@ RUN: FileCheck --check-prefix=BC32 %s BC32: i386.bc: file machine type x86 conflicts with library machine type x64 (inferred from earlier file '{{.*}}x86_64.obj') -RUN: not llvm-lib %t/arm64.bc %t/x86_64.bc 2>&1 | \ +RUN: not llvm-lib %t/arm64.bc %t/i386.bc 2>&1 | \ RUN: FileCheck --check-prefix=BC64 %s -BC64: x86_64.bc: file machine type x64 conflicts with library machine type arm64 (inferred from earlier file '{{.*}}arm64.bc') +BC64: i386.bc: file machine type x86 conflicts with library machine type arm64 (inferred from earlier file '{{.*}}arm64.bc') +RUN: not llvm-lib %t/arm64.bc %t/x86_64.bc %t/i386.bc 2>&1 | \ +RUN: FileCheck --check-prefix=BCEC %s +BCEC: i386.bc: file machine type x86 conflicts with library machine type arm64ec (inferred from earlier files) If /machine: is passed, its value is authoritative.