Index: lld/COFF/Driver.cpp =================================================================== --- lld/COFF/Driver.cpp +++ lld/COFF/Driver.cpp @@ -2009,6 +2009,12 @@ while (run()); } + // Create wrapped symbols for -wrap option. + std::vector wrapped = addWrappedSymbols(args); + // Load more object files that might be needed for wrapped symbols. + if (!wrapped.empty()) + while (run()); + if (config->autoImport) { // MinGW specific. // Load any further object files that might be needed for doing automatic @@ -2052,6 +2058,10 @@ // references to the symbols we use from them. run(); + // Apply symbol renames for -wrap. + if (!wrapped.empty()) + wrapSymbols(wrapped); + // Resolve remaining undefined symbols and warn about imported locals. symtab->resolveRemainingUndefines(); if (errorCount()) Index: lld/COFF/InputFiles.h =================================================================== --- lld/COFF/InputFiles.h +++ lld/COFF/InputFiles.h @@ -147,6 +147,8 @@ ArrayRef getGuardLJmpChunks() { return guardLJmpChunks; } ArrayRef getSymbols() { return symbols; } + MutableArrayRef getMutableSymbols() { return symbols; } + ArrayRef getDebugSection(StringRef secName); // Returns a Symbol object for the symbolIndex'th symbol in the Index: lld/COFF/LTO.cpp =================================================================== --- lld/COFF/LTO.cpp +++ lld/COFF/LTO.cpp @@ -139,6 +139,11 @@ r.VisibleToRegularObj = sym->isUsedInRegularObj; if (r.Prevailing) undefine(sym); + + // We tell LTO to not apply interprocedural optimization for wrapped + // (with -wrap) symbols because otherwise LTO would inline them while + // their values are still not final. + r.LinkerRedefined = !sym->canInline; } checkError(ltoObj->add(std::move(f.obj), resols)); } Index: lld/COFF/MinGW.h =================================================================== --- lld/COFF/MinGW.h +++ lld/COFF/MinGW.h @@ -12,7 +12,10 @@ #include "Config.h" #include "Symbols.h" #include "lld/Common/LLVM.h" +#include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/StringSet.h" +#include "llvm/Option/ArgList.h" +#include namespace lld { namespace coff { @@ -36,6 +39,24 @@ void writeDefFile(StringRef name); +// The -wrap option is a feature to rename symbols so that you can write +// wrappers for existing functions. If you pass `-wrap:foo`, all +// occurrences of symbol `foo` are resolved to `__wrap_foo` (so, you are +// expected to write `__wrap_foo` function as a wrapper). The original +// symbol becomes accessible as `__real_foo`, so you can call that from your +// wrapper. +// +// This data structure is instantiated for each -wrap option. +struct WrappedSymbol { + Symbol *sym; + Symbol *real; + Symbol *wrap; +}; + +std::vector addWrappedSymbols(llvm::opt::InputArgList &args); + +void wrapSymbols(ArrayRef wrapped); + } // namespace coff } // namespace lld Index: lld/COFF/MinGW.cpp =================================================================== --- lld/COFF/MinGW.cpp +++ lld/COFF/MinGW.cpp @@ -7,9 +7,14 @@ //===----------------------------------------------------------------------===// #include "MinGW.h" +#include "Driver.h" +#include "InputFiles.h" #include "SymbolTable.h" #include "lld/Common/ErrorHandler.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" #include "llvm/Object/COFF.h" +#include "llvm/Support/Parallel.h" #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" @@ -173,3 +178,73 @@ os << "\n"; } } + +static StringRef mangle(Twine sym) { + assert(config->machine != IMAGE_FILE_MACHINE_UNKNOWN); + if (config->machine == I386) + return saver.save("_" + sym); + return saver.save(sym); +} + +// Handles -wrap option. +// +// This function instantiates wrapper symbols. At this point, they seem +// like they are not being used at all, so we explicitly set some flags so +// that LTO won't eliminate them. +std::vector +lld::coff::addWrappedSymbols(opt::InputArgList &args) { + std::vector v; + DenseSet seen; + + for (auto *arg : args.filtered(OPT_wrap)) { + StringRef name = arg->getValue(); + if (!seen.insert(name).second) + continue; + + Symbol *sym = symtab->findUnderscore(name); + if (!sym) + continue; + + Symbol *real = symtab->addUndefined(mangle("__real_" + name)); + Symbol *wrap = symtab->addUndefined(mangle("__wrap_" + name)); + v.push_back({sym, real, wrap}); + + // These symbols may seem undefined initially, but don't bail out + // at symtab->reportUnresolvable() due to them, but let wrapSymbols + // below sort things out before checking finally with + // symtab->resolveRemainingUndefines(). + sym->deferUndefined = true; + real->deferUndefined = true; + // We want to tell LTO not to inline symbols to be overwritten + // because LTO doesn't know the final symbol contents after renaming. + real->canInline = false; + sym->canInline = false; + + // Tell LTO not to eliminate these symbols. + sym->isUsedInRegularObj = true; + if (!isa(wrap)) + wrap->isUsedInRegularObj = true; + } + return v; +} + +// Do renaming for -wrap by updating pointers to symbols. +// +// When this function is executed, only InputFiles and symbol table +// contain pointers to symbol objects. We visit them to replace pointers, +// so that wrapped symbols are swapped as instructed by the command line. +void lld::coff::wrapSymbols(ArrayRef wrapped) { + DenseMap map; + for (const WrappedSymbol &w : wrapped) { + map[w.sym] = w.wrap; + map[w.real] = w.sym; + } + + // Update pointers in input files. + parallelForEach(ObjFile::instances, [&](ObjFile *file) { + MutableArrayRef syms = file->getMutableSymbols(); + for (size_t i = 0, e = syms.size(); i != e; ++i) + if (Symbol *s = map.lookup(syms[i])) + syms[i] = s; + }); +} Index: lld/COFF/Options.td =================================================================== --- lld/COFF/Options.td +++ lld/COFF/Options.td @@ -252,6 +252,7 @@ "print-symbol-order", "Print a symbol order specified by /call-graph-ordering-file and " "/call-graph-profile-sort into the specified file">; +def wrap : P_priv<"wrap">; // Flags for debugging def lldmap : F<"lldmap">; Index: lld/COFF/SymbolTable.cpp =================================================================== --- lld/COFF/SymbolTable.cpp +++ lld/COFF/SymbolTable.cpp @@ -390,7 +390,7 @@ for (auto &i : symMap) { Symbol *sym = i.second; auto *undef = dyn_cast(sym); - if (!undef) + if (!undef || sym->deferUndefined) continue; if (undef->getWeakAlias()) continue; @@ -483,6 +483,7 @@ sym = reinterpret_cast(make()); sym->isUsedInRegularObj = false; sym->pendingArchiveLoad = false; + sym->canInline = true; inserted = true; } return {sym, inserted}; Index: lld/COFF/Symbols.h =================================================================== --- lld/COFF/Symbols.h +++ lld/COFF/Symbols.h @@ -103,8 +103,8 @@ explicit Symbol(Kind k, StringRef n = "") : symbolKind(k), isExternal(true), isCOMDAT(false), writtenToSymtab(false), pendingArchiveLoad(false), isGCRoot(false), - isRuntimePseudoReloc(false), nameSize(n.size()), - nameData(n.empty() ? nullptr : n.data()) {} + isRuntimePseudoReloc(false), deferUndefined(false), canInline(true), + nameSize(n.size()), nameData(n.empty() ? nullptr : n.data()) {} const unsigned symbolKind : 8; unsigned isExternal : 1; @@ -130,6 +130,16 @@ unsigned isRuntimePseudoReloc : 1; + // True if we want to allow this symbol to be undefined in the early + // undefined check pass in SymbolTable::reportUnresolvable(), as it + // might be fixed up later. + unsigned deferUndefined : 1; + + // False if LTO shouldn't inline whatever this symbol points to. If a symbol + // is overwritten after LTO, LTO shouldn't inline the symbol because it + // doesn't know the final contents of the symbol. + uint8_t canInline : 1; + protected: // Symbol name length. Assume symbol lengths fit in a 32-bit integer. uint32_t nameSize; @@ -461,7 +471,9 @@ "SymbolUnion not aligned enough"); assert(static_cast(static_cast(nullptr)) == nullptr && "Not a Symbol"); + bool canInline = s->canInline; new (s) T(std::forward(arg)...); + s->canInline = canInline; } } // namespace coff Index: lld/MinGW/Driver.cpp =================================================================== --- lld/MinGW/Driver.cpp +++ lld/MinGW/Driver.cpp @@ -377,6 +377,8 @@ add("-includeoptional:" + StringRef(a->getValue())); for (auto *a : args.filtered(OPT_delayload)) add("-delayload:" + StringRef(a->getValue())); + for (auto *a : args.filtered(OPT_wrap)) + add("-wrap:" + StringRef(a->getValue())); std::vector searchPaths; for (auto *a : args.filtered(OPT_L)) { Index: lld/MinGW/Options.td =================================================================== --- lld/MinGW/Options.td +++ lld/MinGW/Options.td @@ -91,6 +91,8 @@ def v: Flag<["-"], "v">, HelpText<"Display the version number">; def verbose: F<"verbose">, HelpText<"Verbose mode">; def version: F<"version">, HelpText<"Display the version number and exit">; +defm wrap: Eq<"wrap", "Use wrapper functions for symbol">, + MetaVarName<"">; // LLD specific options def _HASH_HASH_HASH : Flag<["-"], "###">, Index: lld/test/COFF/wrap-i386.s =================================================================== --- /dev/null +++ lld/test/COFF/wrap-i386.s @@ -0,0 +1,49 @@ +// REQUIRES: x86 +// RUN: split-file %s %t.dir +// RUN: llvm-mc -filetype=obj -triple=i686-win32-gnu %t.dir/main.s -o %t.main.obj +// RUN: llvm-mc -filetype=obj -triple=i686-win32-gnu %t.dir/other.s -o %t.other.obj + +// RUN: lld-link -out:%t.exe %t.main.obj %t.other.obj -entry:entry -subsystem:console -debug:symtab -safeseh:no -wrap:foo -wrap:nosuchsym +// RUN: llvm-objdump -d --print-imm-hex %t.exe | FileCheck %s + +// CHECK: <_entry>: +// CHECK-NEXT: movl $0x11010, %edx +// CHECK-NEXT: movl $0x11010, %edx +// CHECK-NEXT: movl $0x11000, %edx + +// RUN: llvm-readobj --symbols %t.exe > %t.dump +// RUN: FileCheck --check-prefix=SYM1 %s < %t.dump +// RUN: FileCheck --check-prefix=SYM2 %s < %t.dump +// RUN: FileCheck --check-prefix=SYM3 %s < %t.dump + +// _foo = 0xffc11000 = 4290842624 +// ___wrap_foo = ffc11010 = 4290842640 +// SYM1: Name: _foo +// SYM1-NEXT: Value: 4290842624 +// SYM1-NEXT: Section: IMAGE_SYM_ABSOLUTE +// SYM1-NEXT: BaseType: Null +// SYM1-NEXT: ComplexType: Null +// SYM1-NEXT: StorageClass: External +// SYM2: Name: ___wrap_foo +// SYM2-NEXT: Value: 4290842640 +// SYM2-NEXT: Section: IMAGE_SYM_ABSOLUTE +// SYM2-NEXT: BaseType: Null +// SYM2-NEXT: ComplexType: Null +// SYM2-NEXT: StorageClass: External +// SYM3-NOT: Name: ___real_foo + +#--- main.s +.global _entry +_entry: + movl $_foo, %edx + movl $___wrap_foo, %edx + movl $___real_foo, %edx + +#--- other.s +.global _foo +.global ___wrap_foo +.global ___real_foo + +_foo = 0x11000 +___wrap_foo = 0x11010 +___real_foo = 0x11020 Index: lld/test/COFF/wrap-import.ll =================================================================== --- /dev/null +++ lld/test/COFF/wrap-import.ll @@ -0,0 +1,36 @@ +// REQUIRES: x86 + +// Check that wrapping works when the wrapped symbol is imported from a +// different DLL. + +// RUN: split-file %s %t.dir +// RUN: llc %t.dir/main.ll -o %t.main.obj --filetype=obj +// RUN: llvm-as %t.dir/main.ll -o %t.main.bc +// RUN: llvm-mc -filetype=obj -triple=x86_64-win32-gnu %t.dir/lib.s -o %t.lib.obj + +// RUN: lld-link -dll -out:%t.lib.dll %t.lib.obj -noentry -export:func -implib:%t.lib.lib +// RUN: lld-link -out:%t.exe %t.main.obj %t.lib.lib -entry:entry -subsystem:console -wrap:func +// RUN: lld-link -out:%t.exe %t.main.bc %t.lib.lib -entry:entry -subsystem:console -wrap:func + +#--- main.ll +target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-w64-windows-gnu" + +declare void @func() + +define void @entry() { + call void @func() + ret void +} + +declare void @__real_func() + +define void @__wrap_func() { + call void @__real_func() + ret void +} + +#--- lib.s +.global func +func: + ret Index: lld/test/COFF/wrap-lto-1.ll =================================================================== --- /dev/null +++ lld/test/COFF/wrap-lto-1.ll @@ -0,0 +1,36 @@ +; REQUIRES: x86 +; LTO +; RUN: llvm-as %s -o %t.obj +; RUN: lld-link -out:%t.exe %t.obj -entry:entry -subsystem:console -wrap:bar -debug:symtab -lldsavetemps +; RUN: cat %t.exe.resolution.txt | FileCheck -check-prefix=RESOLS %s + +; ThinLTO +; RUN: opt -module-summary %s -o %t.obj +; RUN: lld-link -out:%t.exe %t.obj -entry:entry -subsystem:console -wrap:bar -debug:symtab -lldsavetemps +; RUN: cat %t.exe.resolution.txt | FileCheck -check-prefix=RESOLS %s + +; Make sure that the 'r' (linker redefined) bit is set for bar and __real_bar +; in the resolutions file. The calls to bar and __real_bar will be routed to +; __wrap_bar and bar, respectively. So they cannot be inlined. +; RESOLS: ,bar,pxr{{$}} +; RESOLS: ,__real_bar,xr{{$}} +; RESOLS: ,__wrap_bar,px{{$}} + +target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-w64-windows-gnu" + +define void @bar() { + ret void +} + +define void @entry() { + call void @bar() + ret void +} + +declare void @__real_bar() + +define void @__wrap_bar() { + call void @__real_bar() + ret void +} Index: lld/test/COFF/wrap-lto-2.ll =================================================================== --- /dev/null +++ lld/test/COFF/wrap-lto-2.ll @@ -0,0 +1,84 @@ +; REQUIRES: x86 +; RUN: split-file %s %t.dir +;; LTO +; RUN: llvm-as %t.dir/main.ll -o %t.main.bc +; RUN: llvm-as %t.dir/wrap.ll -o %t.wrap.bc +; RUN: llvm-as %t.dir/other.ll -o %t.other.bc +; RUN: rm -f %t.bc.lib +; RUN: llvm-ar rcs %t.bc.lib %t.wrap.bc %t.other.bc +;; ThinLTO +; RUN: opt -module-summary %t.dir/main.ll -o %t.main.thin +; RUN: opt -module-summary %t.dir/wrap.ll -o %t.wrap.thin +; RUN: opt -module-summary %t.dir/other.ll -o %t.other.thin +; RUN: rm -f %t.thin.lib +; RUN: llvm-ar rcs %t.thin.lib %t.wrap.thin %t.other.thin +;; Object +; RUN: llc %t.dir/main.ll -o %t.main.obj --filetype=obj +; RUN: llc %t.dir/wrap.ll -o %t.wrap.obj --filetype=obj +; RUN: llc %t.dir/other.ll -o %t.other.obj --filetype=obj +; RUN: rm -f %t.obj.lib +; RUN: llvm-ar rcs %t.obj.lib %t.wrap.obj %t.other.obj + +;; This test verifies that -wrap works correctly for inter-module references to +;; the wrapped symbol, when LTO or ThinLTO is involved. It checks for various +;; combinations of bitcode and regular objects. + +;; LTO + LTO +; RUN: lld-link -out:%t.bc-bc.exe %t.main.bc -libpath:%T %t.bc.lib -entry:entry -subsystem:console -wrap:bar -debug:symtab -lldsavetemps +; RUN: llvm-objdump -d %t.bc-bc.exe | FileCheck %s --check-prefixes=CHECK,JMP + +;; LTO + Object +; RUN: lld-link -out:%t.bc-obj.exe %t.main.bc -libpath:%T %t.obj.lib -entry:entry -subsystem:console -wrap:bar -debug:symtab -lldsavetemps +; RUN: llvm-objdump -d %t.bc-obj.exe | FileCheck %s --check-prefixes=CHECK,JMP + +;; Object + LTO +; RUN: lld-link -out:%t.obj-bc.exe %t.main.obj -libpath:%T %t.bc.lib -entry:entry -subsystem:console -wrap:bar -debug:symtab -lldsavetemps +; RUN: llvm-objdump -d %t.obj-bc.exe | FileCheck %s --check-prefixes=CHECK,CALL + +;; ThinLTO + ThinLTO +; RUN: lld-link -out:%t.thin-thin.exe %t.main.thin -libpath:%T %t.thin.lib -entry:entry -subsystem:console -wrap:bar -debug:symtab -lldsavetemps +; RUN: llvm-objdump -d %t.thin-thin.exe | FileCheck %s --check-prefixes=CHECK,JMP + +;; ThinLTO + Object +; RUN: lld-link -out:%t.thin-obj.exe %t.main.thin -libpath:%T %t.obj.lib -entry:entry -subsystem:console -wrap:bar -debug:symtab -lldsavetemps +; RUN: llvm-objdump -d %t.thin-obj.exe | FileCheck %s --check-prefixes=CHECK,JMP + +;; Object + ThinLTO +; RUN: lld-link -out:%t.obj-thin.exe %t.main.obj -libpath:%T %t.thin.lib -entry:entry -subsystem:console -wrap:bar -debug:symtab -lldsavetemps +; RUN: llvm-objdump -d %t.obj-thin.exe | FileCheck %s --check-prefixes=CHECK,CALL + +;; Make sure that calls in entry() are not eliminated and that bar is +;; routed to __wrap_bar. + +; CHECK: : +; CHECK: {{jmp|callq}}{{.*}}<__wrap_bar> + +;--- main.ll +target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-w64-windows-gnu" + +declare void @bar() + +define void @entry() { + call void @bar() + ret void +} + +;--- wrap.ll +target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-w64-windows-gnu" + +declare void @other() + +define void @__wrap_bar() { + call void @other() + ret void +} + +;--- other.ll +target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-w64-windows-gnu" + +define void @other() { + ret void +} Index: lld/test/COFF/wrap-real-missing.s =================================================================== --- /dev/null +++ lld/test/COFF/wrap-real-missing.s @@ -0,0 +1,21 @@ +// REQUIRES: x86 +// RUN: llvm-mc -filetype=obj -triple=x86_64-win32-gnu %s -o %t.obj + +// RUN: not lld-link -lldmingw -out:%t.exe %t.obj -entry:entry -subsystem:console -wrap:foo 2>&1 | FileCheck %s + +// Check that we error out properly with an undefined symbol, if +// __real_foo is referenced and missing, even if the -lldmingw flag is set +// (which otherwise tolerates certain cases of references to missing +// sections, to tolerate certain GCC pecularities). + +// CHECK: error: undefined symbol: foo + +.global entry +entry: + call foo + ret + +.global __wrap_foo +__wrap_foo: + call __real_foo + ret Index: lld/test/COFF/wrap-with-archive.s =================================================================== --- /dev/null +++ lld/test/COFF/wrap-with-archive.s @@ -0,0 +1,29 @@ +// REQUIRES: x86 +// RUN: split-file %s %t.dir +// RUN: llvm-mc -filetype=obj -triple=x86_64-win32-gnu %t.dir/main.s -o %t.main.obj +// RUN: llvm-mc -filetype=obj -triple=x86_64-win32-gnu %t.dir/wrap.s -o %t.wrap.obj +// RUN: llvm-mc -filetype=obj -triple=x86_64-win32-gnu %t.dir/other.s -o %t.other.obj +// RUN: rm -f %t.lib +// RUN: llvm-ar rcs %t.lib %t.wrap.obj %t.other.obj + +// RUN: lld-link -out:%t.exe %t.main.obj -libpath:%T %t.lib -entry:entry -subsystem:console -wrap:foo + +// Note: No real definition of foo exists here, but that works fine as long +// as there's no actual references to __real_foo. + +#--- main.s +.global entry +entry: + call foo + ret + +#--- wrap.s +.global __wrap_foo +__wrap_foo: + call other_func + ret + +#--- other.s +.global other_func +other_func: + ret Index: lld/test/COFF/wrap.s =================================================================== --- /dev/null +++ lld/test/COFF/wrap.s @@ -0,0 +1,51 @@ +// REQUIRES: x86 +// RUN: split-file %s %t.dir +// RUN: llvm-mc -filetype=obj -triple=x86_64-win32-gnu %t.dir/main.s -o %t.main.obj +// RUN: llvm-mc -filetype=obj -triple=x86_64-win32-gnu %t.dir/other.s -o %t.other.obj + +// RUN: lld-link -out:%t.exe %t.main.obj %t.other.obj -entry:entry -subsystem:console -debug:symtab -wrap:foo -wrap:nosuchsym +// RUN: llvm-objdump -d --print-imm-hex %t.exe | FileCheck %s +// RUN: lld-link -out:%t.exe %t.main.obj %t.other.obj -entry:entry -subsystem:console -debug:symtab -wrap:foo -wrap:foo -wrap:nosuchsym +// RUN: llvm-objdump -d --print-imm-hex %t.exe | FileCheck %s + +// CHECK: : +// CHECK-NEXT: movl $0x11010, %edx +// CHECK-NEXT: movl $0x11010, %edx +// CHECK-NEXT: movl $0x11000, %edx + +// RUN: llvm-readobj --symbols %t.exe > %t.dump +// RUN: FileCheck --check-prefix=SYM1 %s < %t.dump +// RUN: FileCheck --check-prefix=SYM2 %s < %t.dump +// RUN: FileCheck --check-prefix=SYM3 %s < %t.dump + +// foo = 0xC0011000 = 3221295104 +// __wrap_foo = 0xC0011010 = 3221295120 +// SYM1: Name: foo +// SYM1-NEXT: Value: 3221295104 +// SYM1-NEXT: Section: IMAGE_SYM_ABSOLUTE +// SYM1-NEXT: BaseType: Null +// SYM1-NEXT: ComplexType: Null +// SYM1-NEXT: StorageClass: External +// SYM2: Name: __wrap_foo +// SYM2-NEXT: Value: 3221295120 +// SYM2-NEXT: Section: IMAGE_SYM_ABSOLUTE +// SYM2-NEXT: BaseType: Null +// SYM2-NEXT: ComplexType: Null +// SYM2-NEXT: StorageClass: External +// SYM3-NOT: Name: __real_foo + +#--- main.s +.global entry +entry: + movl $foo, %edx + movl $__wrap_foo, %edx + movl $__real_foo, %edx + +#--- other.s +.global foo +.global __wrap_foo +.global __real_foo + +foo = 0x11000 +__wrap_foo = 0x11010 +__real_foo = 0x11020 Index: lld/test/MinGW/driver.test =================================================================== --- lld/test/MinGW/driver.test +++ lld/test/MinGW/driver.test @@ -281,3 +281,7 @@ RUN: ld.lld -### -m i386pep foo.o --allow-multiple-definition --no-allow-multiple-definition | FileCheck -check-prefix NO_ALLOW_MULTIPLE_DEFINITION %s RUN: ld.lld -### -m i386pep foo.o -allow-multiple-definition -no-allow-multiple-definition | FileCheck -check-prefix NO_ALLOW_MULTIPLE_DEFINITION %s NO_ALLOW_MULTIPLE_DEFINITION-NOT: -force:multiple + +RUN: ld.lld -### -m i386pep foo.o -wrap foo1 --wrap foo2 | FileCheck -check-prefix WRAP %s +RUN: ld.lld -### -m i386pep foo.o -wrap=foo1 --wrap=foo2 | FileCheck -check-prefix WRAP %s +WRAP: -wrap:foo1 -wrap:foo2