Index: lld/trunk/COFF/Driver.cpp =================================================================== --- lld/trunk/COFF/Driver.cpp +++ lld/trunk/COFF/Driver.cpp @@ -971,6 +971,32 @@ Config->PDBAltPath = Buf; } +/// Check that at most one resource obj file was used. +/// Call after ObjFile::Instances is complete. +static void diagnoseMultipleResourceObjFiles() { + // The .rsrc$01 section in a resource obj file contains a tree description + // of resources. Merging multiple resource obj files would require merging + // the trees instead of using usual linker section merging semantics. + // Since link.exe disallows linking more than one resource obj file with + // LNK4078, mirror that. The normal use of resource files is to give the + // linker many .res files, which are then converted to a single resource obj + // file internally, so this is not a big restriction in practice. + ObjFile *ResourceObjFile = nullptr; + for (ObjFile *F : ObjFile::Instances) { + if (!F->IsResourceObjFile) + continue; + + if (!ResourceObjFile) { + ResourceObjFile = F; + continue; + } + + error(toString(F) + + ": more than one resource obj file not allowed, already got " + + toString(ResourceObjFile)); + } +} + // In MinGW, if no symbols are chosen to be exported, then all symbols are // automatically exported by default. This behavior can be forced by the // -export-all-symbols option, so that it happens even when exports are @@ -1812,6 +1838,9 @@ if (Config->DoGC) markLive(Symtab->getChunks()); + // Needs to happen after the last call to addFile(). + diagnoseMultipleResourceObjFiles(); + // Identify identical COMDAT sections to merge them. if (Config->DoICF) { findKeepUniqueSections(); Index: lld/trunk/COFF/DriverUtils.cpp =================================================================== --- lld/trunk/COFF/DriverUtils.cpp +++ lld/trunk/COFF/DriverUtils.cpp @@ -697,6 +697,7 @@ } // Convert Windows resource files (.res files) to a .obj file. +// Does what cvtres.exe does, but in-process and cross-platform. MemoryBufferRef convertResToCOFF(ArrayRef MBs) { object::WindowsResourceParser Parser; Index: lld/trunk/COFF/InputFiles.h =================================================================== --- lld/trunk/COFF/InputFiles.h +++ lld/trunk/COFF/InputFiles.h @@ -162,10 +162,13 @@ // precompiled object. Any difference indicates out-of-date objects. llvm::Optional PCHSignature; - // Tells whether this file was compiled with /hotpatch + // Whether this is an object file created from .res files. + bool IsResourceObjFile = false; + + // Whether this file was compiled with /hotpatch. bool HotPatchable = false; - // Whether the object was already merged into the final PDB or not + // Whether the object was already merged into the final PDB. bool MergedIntoPDB = false; // If the OBJ has a .debug$T stream, this tells how it will be handled. Index: lld/trunk/COFF/InputFiles.cpp =================================================================== --- lld/trunk/COFF/InputFiles.cpp +++ lld/trunk/COFF/InputFiles.cpp @@ -206,6 +206,10 @@ if (Def) C->Checksum = Def->CheckSum; + // link.exe uses the presence of .rsrc$01 for LNK4078, so match that. + if (Name == ".rsrc$01") + IsResourceObjFile = true; + // CodeView sections are stored to a different vector because they are not // linked in the regular manner. if (C->isCodeView()) @@ -667,7 +671,8 @@ // types of external files: Precomp/PCH OBJs, when compiling with /Yc and /Yu. // And PDB type servers, when compiling with /Zi. This function extracts these // dependencies and makes them available as a TpiSource interface (see -// DebugTypes.h). +// DebugTypes.h). Both cases only happen with cl.exe: clang-cl produces regular +// output even with /Yc and /Yu and with /Zi. void ObjFile::initializeDependencies() { if (!Config->Debug) return; Index: lld/trunk/test/CMakeLists.txt =================================================================== --- lld/trunk/test/CMakeLists.txt +++ lld/trunk/test/CMakeLists.txt @@ -34,8 +34,8 @@ set(LLD_TEST_DEPS lld) if (NOT LLD_BUILT_STANDALONE) list(APPEND LLD_TEST_DEPS - FileCheck count llc llvm-ar llvm-as llvm-bcanalyzer llvm-config llvm-dis - llvm-dwarfdump llvm-lib llvm-mc llvm-nm llvm-objcopy llvm-objdump + FileCheck count llc llvm-ar llvm-as llvm-bcanalyzer llvm-config llvm-cvtres + llvm-dis llvm-dwarfdump llvm-lib llvm-mc llvm-nm llvm-objcopy llvm-objdump llvm-pdbutil llvm-readelf llvm-readobj not obj2yaml opt yaml2obj ) endif() Index: lld/trunk/test/COFF/multiple-resource-objs.test =================================================================== --- lld/trunk/test/COFF/multiple-resource-objs.test +++ lld/trunk/test/COFF/multiple-resource-objs.test @@ -0,0 +1,11 @@ +# RUN: llvm-cvtres /out:%t_resource.obj %S/Inputs/resource.res +# RUN: llvm-cvtres /out:%t_id.obj %S/Inputs/id.res +# RUN: not lld-link /out:%t.exe /dll /noentry %t_id.obj %t_resource.obj 2>&1 | \ +# RUN: FileCheck --check-prefix=TWOOBJ %s + +TWOOBJ: error: {{.*}}_resource.obj: more than one resource obj file not allowed, already got {{.*}}_id.obj + +# RUN: not lld-link /out:%t.exe /dll /noentry %S/Inputs/id.res %t_resource.obj 2>&1 | \ +# RUN: FileCheck --check-prefix=OBJRES %s + +OBJRES: error: internal .obj file created from .res files: more than one resource obj file not allowed, already got {{.*}}_resource.obj Index: llvm/trunk/lib/Object/WindowsResource.cpp =================================================================== --- llvm/trunk/lib/Object/WindowsResource.cpp +++ llvm/trunk/lib/Object/WindowsResource.cpp @@ -442,7 +442,8 @@ Data(Parser.getData()), StringTable(Parser.getStringTable()) { performFileLayout(); - OutputBuffer = WritableMemoryBuffer::getNewMemBuffer(FileSize); + OutputBuffer = WritableMemoryBuffer::getNewMemBuffer( + FileSize, "internal .obj file created from .res files"); } void WindowsResourceCOFFWriter::performFileLayout() { @@ -521,7 +522,7 @@ Header->NumberOfSections = 2; Header->TimeDateStamp = TimeDateStamp; Header->PointerToSymbolTable = SymbolTableOffset; - // One symbol for every resource plus 2 for each section and @feat.00 + // One symbol for every resource plus 2 for each section and 1 for @feat.00 Header->NumberOfSymbols = Data.size() + 5; Header->SizeOfOptionalHeader = 0; // cvtres.exe sets 32BIT_MACHINE even for 64-bit machine types. Match it. Index: llvm/trunk/utils/gn/secondary/lld/test/BUILD.gn =================================================================== --- llvm/trunk/utils/gn/secondary/lld/test/BUILD.gn +++ llvm/trunk/utils/gn/secondary/lld/test/BUILD.gn @@ -82,6 +82,7 @@ "//llvm/tools/llvm-ar:symlinks", "//llvm/tools/llvm-as", "//llvm/tools/llvm-bcanalyzer", + "//llvm/tools/llvm-cvtres", "//llvm/tools/llvm-dis", "//llvm/tools/llvm-dwarfdump", "//llvm/tools/llvm-mc",