Index: lld/trunk/test/lit.cfg.py =================================================================== --- lld/trunk/test/lit.cfg.py +++ lld/trunk/test/lit.cfg.py @@ -40,7 +40,8 @@ tool_patterns = [ 'llc', 'llvm-as', 'llvm-mc', 'llvm-nm', 'llvm-objdump', 'llvm-pdbutil', - 'llvm-dwarfdump', 'llvm-readelf', 'llvm-readobj', 'obj2yaml', 'yaml2obj'] + 'llvm-dwarfdump', 'llvm-readelf', 'llvm-readobj', 'obj2yaml', 'yaml2obj', + 'opt', 'llvm-dis'] llvm_config.add_tool_substitutions(tool_patterns) Index: lld/trunk/test/wasm/lto/Inputs/cache.ll =================================================================== --- lld/trunk/test/wasm/lto/Inputs/cache.ll +++ lld/trunk/test/wasm/lto/Inputs/cache.ll @@ -0,0 +1,10 @@ +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown" + +define i32 @_start() { +entry: + call void (...) @globalfunc() + ret i32 0 +} + +declare void @globalfunc(...) Index: lld/trunk/test/wasm/lto/Inputs/save-temps.ll =================================================================== --- lld/trunk/test/wasm/lto/Inputs/save-temps.ll +++ lld/trunk/test/wasm/lto/Inputs/save-temps.ll @@ -0,0 +1,6 @@ +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown" + +define void @bar() { + ret void +} Index: lld/trunk/test/wasm/lto/Inputs/thinlto.ll =================================================================== --- lld/trunk/test/wasm/lto/Inputs/thinlto.ll +++ lld/trunk/test/wasm/lto/Inputs/thinlto.ll @@ -0,0 +1,7 @@ +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown" + +define void @g() { +entry: + ret void +} Index: lld/trunk/test/wasm/lto/cache.ll =================================================================== --- lld/trunk/test/wasm/lto/cache.ll +++ lld/trunk/test/wasm/lto/cache.ll @@ -0,0 +1,38 @@ +; RUN: opt -module-hash -module-summary %s -o %t.o +; RUN: opt -module-hash -module-summary %p/Inputs/cache.ll -o %t2.o + +; RUN: rm -Rf %t.cache && mkdir %t.cache +; Create two files that would be removed by cache pruning due to age. +; We should only remove files matching the pattern "llvmcache-*". +; RUN: touch -t 197001011200 %t.cache/llvmcache-foo %t.cache/foo +; RUN: wasm-ld --thinlto-cache-dir=%t.cache --thinlto-cache-policy prune_after=1h:prune_interval=0s -o %t.wasm %t2.o %t.o + +; Two cached objects, plus a timestamp file and "foo", minus the file we removed. +; RUN: ls %t.cache | count 4 + +; Create a file of size 64KB. +; RUN: "%python" -c "print(' ' * 65536)" > %t.cache/llvmcache-foo + +; This should leave the file in place. +; RUN: wasm-ld --thinlto-cache-dir=%t.cache --thinlto-cache-policy cache_size_bytes=128k:prune_interval=0s -o %t.wasm %t2.o %t.o +; RUN: ls %t.cache | count 5 + +; This should remove it. +; RUN: wasm-ld --thinlto-cache-dir=%t.cache --thinlto-cache-policy cache_size_bytes=32k:prune_interval=0s -o %t.wasm %t2.o %t.o +; RUN: ls %t.cache | count 4 + +; Setting max number of files to 0 should disable the limit, not delete everything. +; RUN: wasm-ld --thinlto-cache-dir=%t.cache --thinlto-cache-policy prune_after=0s:cache_size=0%:cache_size_files=0:prune_interval=0s -o %t.wasm %t2.o %t.o +; RUN: ls %t.cache | count 4 + +; Delete everything except for the timestamp, "foo" and one cache file. +; RUN: wasm-ld --thinlto-cache-dir=%t.cache --thinlto-cache-policy prune_after=0s:cache_size=0%:cache_size_files=1:prune_interval=0s -o %t.wasm %t2.o %t.o +; RUN: ls %t.cache | count 3 + +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown-wasm" + +define void @globalfunc() #0 { +entry: + ret void +} Index: lld/trunk/test/wasm/lto/incompatible.ll =================================================================== --- lld/trunk/test/wasm/lto/incompatible.ll +++ lld/trunk/test/wasm/lto/incompatible.ll @@ -0,0 +1,8 @@ +; REQUIRES: x86 +; RUN: llvm-as %s -o %t.bc +; RUN: not wasm-ld %t.bc -o out.wasm 2>&1 | FileCheck %s + +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +; CHECK: {{.*}}incompatible.ll.tmp.bc: machine type must be wasm32 Index: lld/trunk/test/wasm/lto/internalize-basic.ll =================================================================== --- lld/trunk/test/wasm/lto/internalize-basic.ll +++ lld/trunk/test/wasm/lto/internalize-basic.ll @@ -0,0 +1,20 @@ +; RUN: llvm-as %s -o %t.o +; RUN: wasm-ld %t.o -o %t2 -save-temps +; RUN: llvm-dis < %t2.0.2.internalize.bc | FileCheck %s + +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown-wasm" + +define void @_start() { + ret void +} + +define hidden void @foo() { + ret void +} + +; Check that _start is not internalized. +; CHECK: define void @_start() + +; Check that foo function is correctly internalized. +; CHECK: define internal void @foo() Index: lld/trunk/test/wasm/lto/lto-start.ll =================================================================== --- lld/trunk/test/wasm/lto/lto-start.ll +++ lld/trunk/test/wasm/lto/lto-start.ll @@ -0,0 +1,18 @@ +; RUN: llvm-as %s -o %t.o +; RUN: wasm-ld %t.o -o %t.wasm +; RUN: obj2yaml %t.wasm | FileCheck %s + +; CHECK: - Type: CUSTOM +; CHECK-NEXT: Name: name +; CHECK-NEXT: FunctionNames: +; CHECK-NEXT: - Index: 0 +; CHECK-NEXT: Name: __wasm_call_ctors +; CHECK-NEXT: - Index: 1 +; CHECK-NEXT: Name: _start + +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown-wasm" + +define void @_start() { + ret void +} Index: lld/trunk/test/wasm/lto/opt-level.ll =================================================================== --- lld/trunk/test/wasm/lto/opt-level.ll +++ lld/trunk/test/wasm/lto/opt-level.ll @@ -0,0 +1,30 @@ +; RUN: llvm-as -o %t.o %s +; RUN: wasm-ld -o %t0 -e main --lto-O0 %t.o +; RUN: obj2yaml %t0 | FileCheck --check-prefix=CHECK-O0 %s +; RUN: wasm-ld -o %t2 -e main --lto-O2 %t.o +; RUN: obj2yaml %t2 | FileCheck --check-prefix=CHECK-O2 %s +; RUN: wasm-ld -o %t2a -e main %t.o +; RUN: obj2yaml %t2a | FileCheck --check-prefix=CHECK-O2 %s + +; Reject invalid optimization levels. +; RUN: not ld.lld -o %t3 -e main --lto-O6 %t.o 2>&1 | \ +; RUN: FileCheck --check-prefix=INVALID %s +; INVALID: invalid optimization level for LTO: 6 + +; RUN: not ld.lld -o %t3 -m elf_x86_64 -e main --lto-O-1 %t.o 2>&1 | \ +; RUN: FileCheck --check-prefix=INVALIDNEGATIVE %s +; INVALIDNEGATIVE: invalid optimization level for LTO: 4294967295 + +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown-wasm" + +; CHECK-O0: Name: foo +; CHECK-O2-NOT: Name: foo +define internal void @foo() { + ret void +} + +define void @main() { + call void @foo() + ret void +} Index: lld/trunk/test/wasm/lto/parallel.ll =================================================================== --- lld/trunk/test/wasm/lto/parallel.ll +++ lld/trunk/test/wasm/lto/parallel.ll @@ -0,0 +1,24 @@ +; RUN: llvm-as -o %t.bc %s +; RUN: rm -f %t.lto.o %t1.lto.o +; RUN: wasm-ld --lto-partitions=2 -save-temps -o %t %t.bc -r +; RUN: llvm-nm %t.lto.o | FileCheck --check-prefix=CHECK0 %s +; RUN: llvm-nm %t1.lto.o | FileCheck --check-prefix=CHECK1 %s + +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown-wasm" + +; CHECK0-NOT: bar +; CHECK0: T foo +; CHECK0-NOT: bar +define void @foo() { + call void @bar() + ret void +} + +; CHECK1-NOT: foo +; CHECK1: T bar +; CHECK1-NOT: foo +define void @bar() { + call void @foo() + ret void +} Index: lld/trunk/test/wasm/lto/save-temps.ll =================================================================== --- lld/trunk/test/wasm/lto/save-temps.ll +++ lld/trunk/test/wasm/lto/save-temps.ll @@ -0,0 +1,19 @@ +; RUN: cd %T +; RUN: rm -f a.out a.out.lto.bc a.out.lto.o +; RUN: llvm-as %s -o %t.o +; RUN: llvm-as %p/Inputs/save-temps.ll -o %t2.o +; RUN: wasm-ld -r -o a.out %t.o %t2.o -save-temps +; RUN: llvm-nm a.out | FileCheck %s +; RUN: llvm-nm a.out.0.0.preopt.bc | FileCheck %s +; RUN: llvm-nm a.out.lto.o | FileCheck %s +; RUN: llvm-dis a.out.0.0.preopt.bc + +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown" + +define void @foo() { + ret void +} + +; CHECK: T bar +; CHECK: T foo Index: lld/trunk/test/wasm/lto/thinlto.ll =================================================================== --- lld/trunk/test/wasm/lto/thinlto.ll +++ lld/trunk/test/wasm/lto/thinlto.ll @@ -0,0 +1,34 @@ +; Basic ThinLTO tests. +; RUN: opt -module-summary %s -o %t1.o +; RUN: opt -module-summary %p/Inputs/thinlto.ll -o %t2.o + +; First force single-threaded mode +; RUN: rm -f %t31.lto.o %t32.lto.o +; RUN: wasm-ld -r -save-temps --thinlto-jobs=1 %t1.o %t2.o -o %t3 +; RUN: llvm-nm %t31.lto.o | FileCheck %s --check-prefix=NM1 +; RUN: llvm-nm %t32.lto.o | FileCheck %s --check-prefix=NM2 + +; Next force multi-threaded mode +; RUN: rm -f %t31.lto.o %t32.lto.o +; RUN: wasm-ld -r -save-temps --thinlto-jobs=2 %t1.o %t2.o -o %t3 +; RUN: llvm-nm %t31.lto.o | FileCheck %s --check-prefix=NM1 +; RUN: llvm-nm %t32.lto.o | FileCheck %s --check-prefix=NM2 + +; Check without --thinlto-jobs (which currently default to hardware_concurrency) +; RUN: wasm-ld -r %t1.o %t2.o -o %t3 +; RUN: llvm-nm %t31.lto.o | FileCheck %s --check-prefix=NM1 +; RUN: llvm-nm %t32.lto.o | FileCheck %s --check-prefix=NM2 + +; NM1: T f +; NM2: T g + +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown" + +declare void @g(...) + +define void @f() { +entry: + call void (...) @g() + ret void +} Index: lld/trunk/test/wasm/lto/verify-invalid.ll =================================================================== --- lld/trunk/test/wasm/lto/verify-invalid.ll +++ lld/trunk/test/wasm/lto/verify-invalid.ll @@ -0,0 +1,16 @@ +; RUN: llvm-as %s -o %t.o +; RUN: wasm-ld %t.o -o %t2 -mllvm -debug-pass=Arguments \ +; RUN: 2>&1 | FileCheck -check-prefix=DEFAULT %s +; RUN: wasm-ld %t.o -o %t2 -mllvm -debug-pass=Arguments \ +; RUN: -disable-verify 2>&1 | FileCheck -check-prefix=DISABLE %s + +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown" + +define void @_start() { + ret void +} + +; -disable-verify should disable the verification of bitcode. +; DEFAULT: Pass Arguments: {{.*}} -verify {{.*}} -verify +; DISABLE-NOT: Pass Arguments: {{.*}} -verify {{.*}} -verify Index: lld/trunk/test/wasm/lto/weak.ll =================================================================== --- lld/trunk/test/wasm/lto/weak.ll +++ lld/trunk/test/wasm/lto/weak.ll @@ -0,0 +1,16 @@ +; RUN: llvm-as %s -o %t.o +; RUN: wasm-ld %t.o %t.o -o %t.wasm -r +; RUN: llvm-readobj -t %t.wasm | FileCheck %s + +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown-wasm" + +define weak void @f() { + ret void +} + +; CHECK: Symbol { +; CHECK-NEXT: Name: f +; CHECK-NEXT: Type: FUNCTION (0x0) +; CHECK-NEXT: Flags: 0x1 +; CHECK-NEXT: } Index: lld/trunk/wasm/CMakeLists.txt =================================================================== --- lld/trunk/wasm/CMakeLists.txt +++ lld/trunk/wasm/CMakeLists.txt @@ -6,6 +6,7 @@ Driver.cpp InputChunks.cpp InputFiles.cpp + LTO.cpp MarkLive.cpp OutputSections.cpp SymbolTable.cpp @@ -18,6 +19,8 @@ BinaryFormat Core Demangle + LTO + MC Object Option Support Index: lld/trunk/wasm/Config.h =================================================================== --- lld/trunk/wasm/Config.h +++ lld/trunk/wasm/Config.h @@ -13,6 +13,7 @@ #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSet.h" #include "llvm/BinaryFormat/Wasm.h" +#include "llvm/Support/CachePruning.h" namespace lld { namespace wasm { @@ -21,6 +22,7 @@ bool AllowUndefined; bool CompressRelocTargets; bool Demangle; + bool DisableVerify; bool ExportTable; bool GcSections; bool ImportMemory; @@ -28,19 +30,25 @@ bool MergeDataSegments; bool PrintGcSections; bool Relocatable; + bool SaveTemps; bool StripAll; bool StripDebug; bool StackFirst; uint32_t GlobalBase; uint32_t InitialMemory; uint32_t MaxMemory; - uint32_t Optimize; uint32_t ZStackSize; + unsigned LTOPartitions; + unsigned LTOO; + unsigned Optimize; + unsigned ThinLTOJobs; llvm::StringRef Entry; llvm::StringRef OutputFile; + llvm::StringRef ThinLTOCacheDir; llvm::StringSet<> AllowUndefinedSymbols; std::vector SearchPaths; + llvm::CachePruningPolicy ThinLTOCachePolicy; }; // The only instance of Configuration struct. Index: lld/trunk/wasm/Driver.cpp =================================================================== --- lld/trunk/wasm/Driver.cpp +++ lld/trunk/wasm/Driver.cpp @@ -26,6 +26,7 @@ #include "llvm/Support/CommandLine.h" #include "llvm/Support/Path.h" #include "llvm/Support/Process.h" +#include "llvm/Support/TargetSelect.h" #define DEBUG_TYPE "lld" @@ -48,6 +49,17 @@ #undef OPTION }; +// This function is called on startup. We need this for LTO since +// LTO calls LLVM functions to compile bitcode files to native code. +// Technically this can be delayed until we read bitcode files, but +// we don't bother to do lazily because the initialization is fast. +static void initLLVM() { + InitializeAllTargets(); + InitializeAllTargetMCs(); + InitializeAllAsmPrinters(); + InitializeAllAsmParsers(); +} + class LinkerDriver { public: void link(ArrayRef ArgsArr); @@ -72,6 +84,7 @@ Config = make(); Symtab = make(); + initLLVM(); LinkerDriver().link(Args); // Exit immediately if we don't need to return to the caller. @@ -173,7 +186,8 @@ return; MemoryBufferRef MBRef = *Buffer; - if (identify_magic(MBRef.getBuffer()) == file_magic::archive) { + switch (identify_magic(MBRef.getBuffer())) { + case file_magic::archive: { SmallString<128> ImportFile = Path; path::replace_extension(ImportFile, ".imports"); if (fs::exists(ImportFile)) @@ -182,8 +196,12 @@ Files.push_back(make(MBRef)); return; } - - Files.push_back(make(MBRef)); + case file_magic::bitcode: + Files.push_back(make(MBRef)); + break; + default: + Files.push_back(make(MBRef)); + } } // Add a given library by searching it from input search paths. @@ -261,6 +279,17 @@ } } +// Force Sym to be entered in the output. Used for -u or equivalent. +static Symbol *addUndefined(StringRef Name) { + Symbol *S = Symtab->addUndefinedFunction(Name, 0, nullptr, nullptr); + + // Since symbol S may not be used inside the program, LTO may + // eliminate it. Mark the symbol as "used" to prevent it. + S->IsUsedInRegularObj = true; + + return S; +} + void LinkerDriver::link(ArrayRef ArgsArr) { WasmOptTable Parser; opt::InputArgList Args = Parser.parse(ArgsArr.slice(1)); @@ -288,12 +317,15 @@ Config->AllowUndefined = Args.hasArg(OPT_allow_undefined); Config->Demangle = Args.hasFlag(OPT_demangle, OPT_no_demangle, true); + Config->DisableVerify = Args.hasArg(OPT_disable_verify); Config->Entry = getEntry(Args, Args.hasArg(OPT_relocatable) ? "" : "_start"); Config->ExportTable = Args.hasArg(OPT_export_table); errorHandler().FatalWarnings = Args.hasFlag(OPT_fatal_warnings, OPT_no_fatal_warnings, false); Config->ImportMemory = Args.hasArg(OPT_import_memory); Config->ImportTable = Args.hasArg(OPT_import_table); + Config->LTOO = args::getInteger(Args, OPT_lto_O, 2); + Config->LTOPartitions = args::getInteger(Args, OPT_lto_partitions, 1); Config->Optimize = args::getInteger(Args, OPT_O, 0); Config->OutputFile = Args.getLastArgValue(OPT_o); Config->Relocatable = Args.hasArg(OPT_relocatable); @@ -304,10 +336,16 @@ !Config->Relocatable); Config->PrintGcSections = Args.hasFlag(OPT_print_gc_sections, OPT_no_print_gc_sections, false); + Config->SaveTemps = Args.hasArg(OPT_save_temps); Config->SearchPaths = args::getStrings(Args, OPT_L); Config->StripAll = Args.hasArg(OPT_strip_all); Config->StripDebug = Args.hasArg(OPT_strip_debug); Config->StackFirst = Args.hasArg(OPT_stack_first); + Config->ThinLTOCacheDir = Args.getLastArgValue(OPT_thinlto_cache_dir); + Config->ThinLTOCachePolicy = CHECK( + parseCachePruningPolicy(Args.getLastArgValue(OPT_thinlto_cache_policy)), + "--thinlto-cache-policy: invalid cache policy"); + Config->ThinLTOJobs = args::getInteger(Args, OPT_thinlto_jobs, -1u); errorHandler().Verbose = Args.hasArg(OPT_verbose); ThreadsEnabled = Args.hasFlag(OPT_threads, OPT_no_threads, true); @@ -319,6 +357,13 @@ Config->CompressRelocTargets = Config->Optimize > 0 && !Config->Relocatable; + if (Config->LTOO > 3) + error("invalid optimization level for LTO: " + Twine(Config->LTOO)); + if (Config->LTOPartitions == 0) + error("--lto-partitions: number of threads must be > 0"); + if (Config->ThinLTOJobs == 0) + error("--thinlto-jobs: number of threads must be > 0"); + if (auto *Arg = Args.getLastArg(OPT_allow_undefined_file)) readImportFile(Arg->getValue()); @@ -372,12 +417,11 @@ // For now, since we don't actually use the start function as the // wasm start symbol, we don't need to care about it signature. if (!Config->Entry.empty()) - EntrySym = - Symtab->addUndefinedFunction(Config->Entry, 0, nullptr, nullptr); + EntrySym = addUndefined(Config->Entry); // Handle the `--undefined ` options. for (auto *Arg : Args.filtered(OPT_undefined)) - Symtab->addUndefinedFunction(Arg->getValue(), 0, nullptr, nullptr); + addUndefined(Arg->getValue()); } createFiles(Args); @@ -388,11 +432,19 @@ // symbols that we need to the symbol table. for (InputFile *F : Files) Symtab->addFile(F); + if (errorCount()) + return; // Add synthetic dummies for weak undefined functions. if (!Config->Relocatable) handleWeakUndefines(); + // Do link-time optimization if given files are LLVM bitcode files. + // This compiles bitcode files into real object files. + Symtab->addCombinedLTOObject(); + if (errorCount()) + return; + // Make sure we have resolved all symbols. if (!Config->Relocatable && !Config->AllowUndefined) { Symtab->reportRemainingUndefines(); Index: lld/trunk/wasm/InputFiles.h =================================================================== --- lld/trunk/wasm/InputFiles.h +++ lld/trunk/wasm/InputFiles.h @@ -14,6 +14,7 @@ #include "lld/Common/LLVM.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseSet.h" +#include "llvm/LTO/LTO.h" #include "llvm/Object/Archive.h" #include "llvm/Object/Wasm.h" #include "llvm/Support/MemoryBuffer.h" @@ -28,6 +29,12 @@ using llvm::wasm::WasmRelocation; using llvm::wasm::WasmSignature; +namespace llvm { +namespace lto { +class InputFile; +} +} // namespace llvm + namespace lld { namespace wasm { @@ -42,6 +49,7 @@ enum Kind { ObjectKind, ArchiveKind, + BitcodeKind, }; virtual ~InputFile() {} @@ -57,10 +65,15 @@ // An archive file name if this file is created from an archive. StringRef ParentName; + ArrayRef getSymbols() const { return Symbols; } + protected: InputFile(Kind K, MemoryBufferRef M) : MB(M), FileKind(K) {} MemoryBufferRef MB; + // List of all symbols referenced or defined by this file. + std::vector Symbols; + private: const Kind FileKind; }; @@ -113,7 +126,6 @@ std::vector CustomSections; llvm::DenseMap CustomSectionsByIndex; - ArrayRef getSymbols() const { return Symbols; } Symbol *getSymbol(uint32_t Index) const { return Symbols[Index]; } FunctionSymbol *getFunctionSymbol(uint32_t Index) const; DataSymbol *getDataSymbol(uint32_t Index) const; @@ -126,12 +138,18 @@ bool isExcludedByComdat(InputChunk *Chunk) const; - // List of all symbols referenced or defined by this file. - std::vector Symbols; - std::unique_ptr WasmObj; }; +class BitcodeFile : public InputFile { +public: + explicit BitcodeFile(MemoryBufferRef M) : InputFile(BitcodeKind, M) {} + static bool classof(const InputFile *F) { return F->kind() == BitcodeKind; } + + void parse() override; + std::unique_ptr Obj; +}; + // Opens a given file. llvm::Optional readFile(StringRef Path); Index: lld/trunk/wasm/InputFiles.cpp =================================================================== --- lld/trunk/wasm/InputFiles.cpp +++ lld/trunk/wasm/InputFiles.cpp @@ -370,6 +370,48 @@ Symtab->addFile(Obj); } +static uint8_t mapVisibility(GlobalValue::VisibilityTypes GvVisibility) { + switch (GvVisibility) { + case GlobalValue::DefaultVisibility: + return WASM_SYMBOL_VISIBILITY_DEFAULT; + case GlobalValue::HiddenVisibility: + case GlobalValue::ProtectedVisibility: + return WASM_SYMBOL_VISIBILITY_HIDDEN; + } + llvm_unreachable("unknown visibility"); +} + +static Symbol *createBitcodeSymbol(const lto::InputFile::Symbol &ObjSym, + BitcodeFile &F) { + StringRef Name = Saver.save(ObjSym.getName()); + + uint32_t Flags = ObjSym.isWeak() ? WASM_SYMBOL_BINDING_WEAK : 0; + Flags |= mapVisibility(ObjSym.getVisibility()); + + if (ObjSym.isUndefined()) { + if (ObjSym.isExecutable()) + return Symtab->addUndefinedFunction(Name, Flags, &F, nullptr); + return Symtab->addUndefinedData(Name, Flags, &F); + } + + if (ObjSym.isExecutable()) + return Symtab->addDefinedFunction(Name, Flags, &F, nullptr); + return Symtab->addDefinedData(Name, Flags, &F, nullptr, 0, 0); +} + +void BitcodeFile::parse() { + Obj = check(lto::InputFile::create(MemoryBufferRef( + MB.getBuffer(), Saver.save(ParentName + MB.getBufferIdentifier())))); + Triple T(Obj->getTargetTriple()); + if (T.getArch() != Triple::wasm32) { + error(toString(MB.getBufferIdentifier()) + ": machine type must be wasm32"); + return; + } + + for (const lto::InputFile::Symbol &ObjSym : Obj->symbols()) + Symbols.push_back(createBitcodeSymbol(ObjSym, *this)); +} + // Returns a string in the format of "foo.o" or "foo.a(bar.o)". std::string lld::toString(const wasm::InputFile *File) { if (!File) Index: lld/trunk/wasm/LTO.h =================================================================== --- lld/trunk/wasm/LTO.h +++ lld/trunk/wasm/LTO.h @@ -0,0 +1,57 @@ +//===- LTO.h ----------------------------------------------------*- C++ -*-===// +// +// The LLVM Linker +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file provides a way to combine bitcode files into one wasm +// file by compiling them using LLVM. +// +// If LTO is in use, your input files are not in regular wasm files +// but instead LLVM bitcode files. In that case, the linker has to +// convert bitcode files into the native format so that we can create +// a wasm file that contains native code. This file provides that +// functionality. +// +//===----------------------------------------------------------------------===// + +#ifndef LLD_WASM_LTO_H +#define LLD_WASM_LTO_H + +#include "lld/Common/LLVM.h" +#include "llvm/ADT/SmallString.h" +#include +#include + +namespace llvm { +namespace lto { +class LTO; +} +} // namespace llvm + +namespace lld { +namespace wasm { + +class BitcodeFile; +class InputFile; + +class BitcodeCompiler { +public: + BitcodeCompiler(); + ~BitcodeCompiler(); + + void add(BitcodeFile &F); + std::vector compile(); + +private: + std::unique_ptr LTOObj; + std::vector> Buf; + std::vector> Files; +}; +} // namespace wasm +} // namespace lld + +#endif Index: lld/trunk/wasm/LTO.cpp =================================================================== --- lld/trunk/wasm/LTO.cpp +++ lld/trunk/wasm/LTO.cpp @@ -0,0 +1,151 @@ +//===- LTO.cpp ------------------------------------------------------------===// +// +// The LLVM Linker +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "LTO.h" +#include "Config.h" +#include "InputFiles.h" +#include "Symbols.h" +#include "lld/Common/ErrorHandler.h" +#include "lld/Common/Strings.h" +#include "lld/Common/TargetOptionsCommandFlags.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/Twine.h" +#include "llvm/IR/DiagnosticPrinter.h" +#include "llvm/LTO/Caching.h" +#include "llvm/LTO/Config.h" +#include "llvm/LTO/LTO.h" +#include "llvm/Object/SymbolicFile.h" +#include "llvm/Support/CodeGen.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/raw_ostream.h" +#include +#include +#include +#include +#include +#include + +using namespace llvm; +using namespace llvm::object; + +using namespace lld; +using namespace lld::wasm; + +static std::unique_ptr createLTO() { + lto::Config C; + C.Options = InitTargetOptionsFromCodeGenFlags(); + + // Always emit a section per function/datum with LTO. + C.Options.FunctionSections = true; + C.Options.DataSections = true; + + C.DisableVerify = Config->DisableVerify; + C.DiagHandler = diagnosticHandler; + C.OptLevel = Config->LTOO; + + if (Config->SaveTemps) + checkError(C.addSaveTemps(Config->OutputFile.str() + ".", + /*UseInputModulePath*/ true)); + + lto::ThinBackend Backend; + if (Config->ThinLTOJobs != -1U) + Backend = lto::createInProcessThinBackend(Config->ThinLTOJobs); + return llvm::make_unique(std::move(C), Backend, + Config->LTOPartitions); +} + +BitcodeCompiler::BitcodeCompiler() : LTOObj(createLTO()) {} + +BitcodeCompiler::~BitcodeCompiler() = default; + +static void undefine(Symbol *S) { + if (isa(S)) + replaceSymbol(S, S->getName(), 0); + else if (isa(S)) + replaceSymbol(S, S->getName(), 0); + else + llvm_unreachable("unexpected symbol kind"); +} + +void BitcodeCompiler::add(BitcodeFile &F) { + lto::InputFile &Obj = *F.Obj; + unsigned SymNum = 0; + ArrayRef Syms = F.getSymbols(); + std::vector Resols(Syms.size()); + + // Provide a resolution to the LTO API for each symbol. + for (const lto::InputFile::Symbol &ObjSym : Obj.symbols()) { + Symbol *Sym = Syms[SymNum]; + lto::SymbolResolution &R = Resols[SymNum]; + ++SymNum; + + // Ideally we shouldn't check for SF_Undefined but currently IRObjectFile + // reports two symbols for module ASM defined. Without this check, lld + // flags an undefined in IR with a definition in ASM as prevailing. + // Once IRObjectFile is fixed to report only one symbol this hack can + // be removed. + R.Prevailing = !ObjSym.isUndefined() && Sym->getFile() == &F; + R.VisibleToRegularObj = Config->Relocatable || Sym->IsUsedInRegularObj; + if (R.Prevailing) + undefine(Sym); + } + checkError(LTOObj->add(std::move(F.Obj), Resols)); +} + +// Merge all the bitcode files we have seen, codegen the result +// and return the resulting objects. +std::vector BitcodeCompiler::compile() { + unsigned MaxTasks = LTOObj->getMaxTasks(); + Buf.resize(MaxTasks); + Files.resize(MaxTasks); + + // The --thinlto-cache-dir option specifies the path to a directory in which + // to cache native object files for ThinLTO incremental builds. If a path was + // specified, configure LTO to use it as the cache directory. + lto::NativeObjectCache Cache; + if (!Config->ThinLTOCacheDir.empty()) + Cache = check( + lto::localCache(Config->ThinLTOCacheDir, + [&](size_t Task, std::unique_ptr MB) { + Files[Task] = std::move(MB); + })); + + checkError(LTOObj->run( + [&](size_t Task) { + return llvm::make_unique( + llvm::make_unique(Buf[Task])); + }, + Cache)); + + if (!Config->ThinLTOCacheDir.empty()) + pruneCache(Config->ThinLTOCacheDir, Config->ThinLTOCachePolicy); + + std::vector Ret; + for (unsigned I = 0; I != MaxTasks; ++I) { + if (Buf[I].empty()) + continue; + if (Config->SaveTemps) { + if (I == 0) + saveBuffer(Buf[I], Config->OutputFile + ".lto.o"); + else + saveBuffer(Buf[I], Config->OutputFile + Twine(I) + ".lto.o"); + } + Ret.emplace_back(Buf[I].data(), Buf[I].size()); + } + + for (std::unique_ptr &File : Files) + if (File) + Ret.push_back(File->getBuffer()); + + return Ret; +} Index: lld/trunk/wasm/Options.td =================================================================== --- lld/trunk/wasm/Options.td +++ lld/trunk/wasm/Options.td @@ -136,3 +136,16 @@ def alias_max_memory_m: Flag<["-"], "m">, Alias; def alias_relocatable_r: Flag<["-"], "r">, Alias; def alias_undefined_u: JoinedOrSeparate<["-"], "u">, Alias; + +// LTO-related options. +def lto_O: J<"lto-O">, MetaVarName<"">, + HelpText<"Optimization level for LTO">; +def lto_partitions: J<"lto-partitions=">, + HelpText<"Number of LTO codegen partitions">; +def disable_verify: F<"disable-verify">; +def save_temps: F<"save-temps">; +def thinlto_cache_dir: J<"thinlto-cache-dir=">, + HelpText<"Path to ThinLTO cached object file directory">; +defm thinlto_cache_policy: Eq<"thinlto-cache-policy">, + HelpText<"Pruning policy for the ThinLTO cache">; +def thinlto_jobs: J<"thinlto-jobs=">, HelpText<"Number of ThinLTO jobs">; Index: lld/trunk/wasm/SymbolTable.h =================================================================== --- lld/trunk/wasm/SymbolTable.h +++ lld/trunk/wasm/SymbolTable.h @@ -11,6 +11,7 @@ #define LLD_WASM_SYMBOL_TABLE_H #include "InputFiles.h" +#include "LTO.h" #include "Symbols.h" #include "llvm/ADT/CachedHashString.h" #include "llvm/ADT/DenseSet.h" @@ -39,8 +40,10 @@ class SymbolTable { public: void addFile(InputFile *File); + void addCombinedLTOObject(); std::vector ObjectFiles; + std::vector BitcodeFiles; std::vector SyntheticFunctions; std::vector SyntheticGlobals; @@ -80,6 +83,9 @@ std::vector SymVector; llvm::DenseSet Comdats; + + // For LTO. + std::unique_ptr LTO; }; extern SymbolTable *Symtab; Index: lld/trunk/wasm/SymbolTable.cpp =================================================================== --- lld/trunk/wasm/SymbolTable.cpp +++ lld/trunk/wasm/SymbolTable.cpp @@ -29,17 +29,46 @@ log("Processing: " + toString(File)); File->parse(); - if (auto *F = dyn_cast(File)) + // LLVM bitcode file + if (auto *F = dyn_cast(File)) + BitcodeFiles.push_back(F); + else if (auto *F = dyn_cast(File)) ObjectFiles.push_back(F); } +// This function is where all the optimizations of link-time +// optimization happens. When LTO is in use, some input files are +// not in native object file format but in the LLVM bitcode format. +// This function compiles bitcode files into a few big native files +// using LLVM functions and replaces bitcode symbols with the results. +// Because all bitcode files that the program consists of are passed +// to the compiler at once, it can do whole-program optimization. +void SymbolTable::addCombinedLTOObject() { + if (BitcodeFiles.empty()) + return; + + // Compile bitcode files and replace bitcode symbols. + LTO.reset(new BitcodeCompiler); + for (BitcodeFile *F : BitcodeFiles) + LTO->add(*F); + + for (StringRef Filename : LTO->compile()) { + auto *Obj = make(MemoryBufferRef(Filename, "lto.tmp")); + Obj->parse(); + ObjectFiles.push_back(Obj); + } +} + void SymbolTable::reportRemainingUndefines() { SetVector Undefs; for (Symbol *Sym : SymVector) { - if (Sym->isUndefined() && !Sym->isWeak() && - Config->AllowUndefinedSymbols.count(Sym->getName()) == 0) { - Undefs.insert(Sym); - } + if (!Sym->isUndefined() || Sym->isWeak()) + continue; + if (Config->AllowUndefinedSymbols.count(Sym->getName()) != 0) + continue; + if (!Sym->IsUsedInRegularObj) + continue; + Undefs.insert(Sym); } if (Undefs.empty()) @@ -64,6 +93,7 @@ if (Sym) return {Sym, false}; Sym = reinterpret_cast(make()); + Sym->IsUsedInRegularObj = false; SymVector.emplace_back(Sym); return {Sym, true}; } @@ -178,12 +208,16 @@ bool WasInserted; std::tie(S, WasInserted) = insert(Name); + if (!File || File->kind() == InputFile::ObjectKind) + S->IsUsedInRegularObj = true; + if (WasInserted || S->isLazy()) { replaceSymbol(S, Name, Flags, File, Function); return S; } - checkFunctionType(S, File, &Function->Signature); + if (Function) + checkFunctionType(S, File, &Function->Signature); if (shouldReplace(S, File, Flags)) replaceSymbol(S, Name, Flags, File, Function); @@ -199,6 +233,9 @@ bool WasInserted; std::tie(S, WasInserted) = insert(Name); + if (!File || File->kind() == InputFile::ObjectKind) + S->IsUsedInRegularObj = true; + if (WasInserted || S->isLazy()) { replaceSymbol(S, Name, Flags, File, Segment, Address, Size); return S; @@ -218,6 +255,9 @@ bool WasInserted; std::tie(S, WasInserted) = insert(Name); + if (!File || File->kind() == InputFile::ObjectKind) + S->IsUsedInRegularObj = true; + if (WasInserted || S->isLazy()) { replaceSymbol(S, Name, Flags, File, Global); return S; @@ -239,6 +279,9 @@ bool WasInserted; std::tie(S, WasInserted) = insert(Name); + if (!File || File->kind() == InputFile::ObjectKind) + S->IsUsedInRegularObj = true; + if (WasInserted) replaceSymbol(S, Name, Flags, File, Sig); else if (auto *Lazy = dyn_cast(S)) @@ -274,6 +317,9 @@ bool WasInserted; std::tie(S, WasInserted) = insert(Name); + if (!File || File->kind() == InputFile::ObjectKind) + S->IsUsedInRegularObj = true; + if (WasInserted) replaceSymbol(S, Name, Flags, File, Type); else if (auto *Lazy = dyn_cast(S)) Index: lld/trunk/wasm/Symbols.h =================================================================== --- lld/trunk/wasm/Symbols.h +++ lld/trunk/wasm/Symbols.h @@ -91,10 +91,13 @@ WasmSymbolType getWasmType() const; + // True if this symbol was referenced by a regular (non-bitcode) object. + unsigned IsUsedInRegularObj : 1; + protected: Symbol(StringRef Name, Kind K, uint32_t Flags, InputFile *F) - : Name(Name), SymbolKind(K), Flags(Flags), File(F), - Referenced(!Config->GcSections) {} + : IsUsedInRegularObj(false), Name(Name), SymbolKind(K), Flags(Flags), + File(F), Referenced(!Config->GcSections) {} StringRef Name; Kind SymbolKind; @@ -332,7 +335,12 @@ "SymbolUnion not aligned enough"); assert(static_cast(static_cast(nullptr)) == nullptr && "Not a Symbol"); - return new (S) T(std::forward(Arg)...); + + Symbol SymCopy = *S; + + T *S2 = new (S) T(std::forward(Arg)...); + S2->IsUsedInRegularObj = SymCopy.IsUsedInRegularObj; + return S2; } } // namespace wasm Index: lld/trunk/wasm/Writer.cpp =================================================================== --- lld/trunk/wasm/Writer.cpp +++ lld/trunk/wasm/Writer.cpp @@ -715,6 +715,8 @@ continue; if (!Sym->isLive()) continue; + if (!Sym->IsUsedInRegularObj) + continue; LLVM_DEBUG(dbgs() << "import: " << Sym->getName() << "\n"); ImportedSymbols.emplace_back(Sym);