From c729c1b47d21706a314c15fe8075dd6835a32618 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Wed, 30 May 2018 18:07:52 +0000 Subject: [PATCH] [WebAssembly] Initial support for LTO Differential Revision: https://reviews.llvm.org/D47162 llvm-svn: 333570 --- lld/test/lit.cfg.py | 3 +- lld/test/wasm/lto/Inputs/cache.ll | 10 ++ lld/test/wasm/lto/Inputs/save-temps.ll | 6 + lld/test/wasm/lto/Inputs/thinlto.ll | 7 ++ lld/test/wasm/lto/cache.ll | 38 +++++++ lld/test/wasm/lto/incompatible.ll | 8 ++ lld/test/wasm/lto/internalize-basic.ll | 20 ++++ lld/test/wasm/lto/lto-start.ll | 18 +++ lld/test/wasm/lto/opt-level.ll | 30 +++++ lld/test/wasm/lto/parallel.ll | 24 ++++ lld/test/wasm/lto/save-temps.ll | 19 ++++ lld/test/wasm/lto/thinlto.ll | 34 ++++++ lld/test/wasm/lto/verify-invalid.ll | 16 +++ lld/test/wasm/lto/weak.ll | 16 +++ lld/wasm/CMakeLists.txt | 3 + lld/wasm/Config.h | 10 +- lld/wasm/Driver.cpp | 64 ++++++++++- lld/wasm/InputFiles.cpp | 42 +++++++ lld/wasm/InputFiles.h | 26 ++++- lld/wasm/LTO.cpp | 151 +++++++++++++++++++++++++ lld/wasm/LTO.h | 57 ++++++++++ lld/wasm/Options.td | 13 +++ lld/wasm/SymbolTable.cpp | 58 +++++++++- lld/wasm/SymbolTable.h | 6 + lld/wasm/Symbols.h | 14 ++- lld/wasm/Writer.cpp | 2 + 26 files changed, 674 insertions(+), 21 deletions(-) create mode 100644 lld/test/wasm/lto/Inputs/cache.ll create mode 100644 lld/test/wasm/lto/Inputs/save-temps.ll create mode 100644 lld/test/wasm/lto/Inputs/thinlto.ll create mode 100644 lld/test/wasm/lto/cache.ll create mode 100644 lld/test/wasm/lto/incompatible.ll create mode 100644 lld/test/wasm/lto/internalize-basic.ll create mode 100644 lld/test/wasm/lto/lto-start.ll create mode 100644 lld/test/wasm/lto/opt-level.ll create mode 100644 lld/test/wasm/lto/parallel.ll create mode 100644 lld/test/wasm/lto/save-temps.ll create mode 100644 lld/test/wasm/lto/thinlto.ll create mode 100644 lld/test/wasm/lto/verify-invalid.ll create mode 100644 lld/test/wasm/lto/weak.ll create mode 100644 lld/wasm/LTO.cpp create mode 100644 lld/wasm/LTO.h diff --git a/lld/test/lit.cfg.py b/lld/test/lit.cfg.py index 456fe504448f..41b57e579a9b 100644 --- a/lld/test/lit.cfg.py +++ b/lld/test/lit.cfg.py @@ -40,7 +40,8 @@ llvm_config.use_lld() 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) diff --git a/lld/test/wasm/lto/Inputs/cache.ll b/lld/test/wasm/lto/Inputs/cache.ll new file mode 100644 index 000000000000..a66f36aef9cb --- /dev/null +++ b/lld/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(...) diff --git a/lld/test/wasm/lto/Inputs/save-temps.ll b/lld/test/wasm/lto/Inputs/save-temps.ll new file mode 100644 index 000000000000..6f4de417c380 --- /dev/null +++ b/lld/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 +} diff --git a/lld/test/wasm/lto/Inputs/thinlto.ll b/lld/test/wasm/lto/Inputs/thinlto.ll new file mode 100644 index 000000000000..39e573b1c21e --- /dev/null +++ b/lld/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 +} diff --git a/lld/test/wasm/lto/cache.ll b/lld/test/wasm/lto/cache.ll new file mode 100644 index 000000000000..b0a7820c1e19 --- /dev/null +++ b/lld/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 +} diff --git a/lld/test/wasm/lto/incompatible.ll b/lld/test/wasm/lto/incompatible.ll new file mode 100644 index 000000000000..ee98cb4b4e63 --- /dev/null +++ b/lld/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 diff --git a/lld/test/wasm/lto/internalize-basic.ll b/lld/test/wasm/lto/internalize-basic.ll new file mode 100644 index 000000000000..313a05ecbae4 --- /dev/null +++ b/lld/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() diff --git a/lld/test/wasm/lto/lto-start.ll b/lld/test/wasm/lto/lto-start.ll new file mode 100644 index 000000000000..6e8f99c95308 --- /dev/null +++ b/lld/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 +} diff --git a/lld/test/wasm/lto/opt-level.ll b/lld/test/wasm/lto/opt-level.ll new file mode 100644 index 000000000000..b7e6a4cdc70a --- /dev/null +++ b/lld/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 +} diff --git a/lld/test/wasm/lto/parallel.ll b/lld/test/wasm/lto/parallel.ll new file mode 100644 index 000000000000..a93c3558d969 --- /dev/null +++ b/lld/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 +} diff --git a/lld/test/wasm/lto/save-temps.ll b/lld/test/wasm/lto/save-temps.ll new file mode 100644 index 000000000000..2734d86815c7 --- /dev/null +++ b/lld/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 diff --git a/lld/test/wasm/lto/thinlto.ll b/lld/test/wasm/lto/thinlto.ll new file mode 100644 index 000000000000..062da1a33b89 --- /dev/null +++ b/lld/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 +} diff --git a/lld/test/wasm/lto/verify-invalid.ll b/lld/test/wasm/lto/verify-invalid.ll new file mode 100644 index 000000000000..c4a5bcdc67d1 --- /dev/null +++ b/lld/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 diff --git a/lld/test/wasm/lto/weak.ll b/lld/test/wasm/lto/weak.ll new file mode 100644 index 000000000000..03a017c1a183 --- /dev/null +++ b/lld/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: } diff --git a/lld/wasm/CMakeLists.txt b/lld/wasm/CMakeLists.txt index d3bef0f62e2d..308c4e270183 100644 --- a/lld/wasm/CMakeLists.txt +++ b/lld/wasm/CMakeLists.txt @@ -6,6 +6,7 @@ add_lld_library(lldWasm Driver.cpp InputChunks.cpp InputFiles.cpp + LTO.cpp MarkLive.cpp OutputSections.cpp SymbolTable.cpp @@ -18,6 +19,8 @@ add_lld_library(lldWasm BinaryFormat Core Demangle + LTO + MC Object Option Support diff --git a/lld/wasm/Config.h b/lld/wasm/Config.h index 08355b569e5f..4b11320e7cf1 100644 --- a/lld/wasm/Config.h +++ b/lld/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 @@ struct Configuration { bool AllowUndefined; bool CompressRelocTargets; bool Demangle; + bool DisableVerify; bool ExportTable; bool GcSections; bool ImportMemory; @@ -28,19 +30,25 @@ struct Configuration { 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. diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp index f705c5d8d777..5a9fe3632b66 100644 --- a/lld/wasm/Driver.cpp +++ b/lld/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 @@ enum { #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 @@ bool lld::wasm::link(ArrayRef Args, bool CanExitEarly, Config = make(); Symtab = make(); + initLLVM(); LinkerDriver().link(Args); // Exit immediately if we don't need to return to the caller. @@ -173,7 +186,8 @@ void LinkerDriver::addFile(StringRef Path) { 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 @@ void LinkerDriver::addFile(StringRef Path) { 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 @@ static void handleWeakUndefines() { } } +// 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 @@ void LinkerDriver::link(ArrayRef ArgsArr) { 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 @@ void LinkerDriver::link(ArrayRef ArgsArr) { !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 @@ void LinkerDriver::link(ArrayRef ArgsArr) { 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 @@ void LinkerDriver::link(ArrayRef ArgsArr) { // 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 @@ void LinkerDriver::link(ArrayRef ArgsArr) { // 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(); diff --git a/lld/wasm/InputFiles.cpp b/lld/wasm/InputFiles.cpp index 1d680024becd..ee9e99bd7755 100644 --- a/lld/wasm/InputFiles.cpp +++ b/lld/wasm/InputFiles.cpp @@ -370,6 +370,48 @@ void ArchiveFile::addMember(const Archive::Symbol *Sym) { 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) diff --git a/lld/wasm/InputFiles.h b/lld/wasm/InputFiles.h index e08079a41ebe..75d20e63cf5f 100644 --- a/lld/wasm/InputFiles.h +++ b/lld/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::WasmImport; using llvm::wasm::WasmRelocation; using llvm::wasm::WasmSignature; +namespace llvm { +namespace lto { +class InputFile; +} +} // namespace llvm + namespace lld { namespace wasm { @@ -42,6 +49,7 @@ public: enum Kind { ObjectKind, ArchiveKind, + BitcodeKind, }; virtual ~InputFile() {} @@ -57,10 +65,15 @@ public: // 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 @@ public: 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 @@ private: 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); diff --git a/lld/wasm/LTO.cpp b/lld/wasm/LTO.cpp new file mode 100644 index 000000000000..58f32aaf2440 --- /dev/null +++ b/lld/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; +} diff --git a/lld/wasm/LTO.h b/lld/wasm/LTO.h new file mode 100644 index 000000000000..cf726de5643a --- /dev/null +++ b/lld/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 diff --git a/lld/wasm/Options.td b/lld/wasm/Options.td index 7fb6881107c9..4dbed35595c3 100644 --- a/lld/wasm/Options.td +++ b/lld/wasm/Options.td @@ -136,3 +136,16 @@ def alias_initial_memory_i: Flag<["-"], "i">, Alias; 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">; diff --git a/lld/wasm/SymbolTable.cpp b/lld/wasm/SymbolTable.cpp index c895ab5e9dc0..6cc7c36db885 100644 --- a/lld/wasm/SymbolTable.cpp +++ b/lld/wasm/SymbolTable.cpp @@ -29,17 +29,46 @@ void SymbolTable::addFile(InputFile *File) { 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 @@ std::pair SymbolTable::insert(StringRef Name) { if (Sym) return {Sym, false}; Sym = reinterpret_cast(make()); + Sym->IsUsedInRegularObj = false; SymVector.emplace_back(Sym); return {Sym, true}; } @@ -178,12 +208,16 @@ Symbol *SymbolTable::addDefinedFunction(StringRef Name, uint32_t Flags, 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 @@ Symbol *SymbolTable::addDefinedData(StringRef Name, uint32_t Flags, 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 @@ Symbol *SymbolTable::addDefinedGlobal(StringRef Name, uint32_t Flags, 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 @@ Symbol *SymbolTable::addUndefinedFunction(StringRef Name, uint32_t Flags, 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 @@ Symbol *SymbolTable::addUndefinedGlobal(StringRef Name, uint32_t Flags, 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)) diff --git a/lld/wasm/SymbolTable.h b/lld/wasm/SymbolTable.h index 6fb5c15782dc..26242e6cddd6 100644 --- a/lld/wasm/SymbolTable.h +++ b/lld/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 InputSegment; 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 @@ private: std::vector SymVector; llvm::DenseSet Comdats; + + // For LTO. + std::unique_ptr LTO; }; extern SymbolTable *Symtab; diff --git a/lld/wasm/Symbols.h b/lld/wasm/Symbols.h index 8556d32bcf8a..b2ec1e8c66e1 100644 --- a/lld/wasm/Symbols.h +++ b/lld/wasm/Symbols.h @@ -91,10 +91,13 @@ public: 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 @@ T *replaceSymbol(Symbol *S, ArgT &&... Arg) { "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 diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp index 8e05a4d46f52..ee6056ab76dd 100644 --- a/lld/wasm/Writer.cpp +++ b/lld/wasm/Writer.cpp @@ -715,6 +715,8 @@ void Writer::calculateImports() { continue; if (!Sym->isLive()) continue; + if (!Sym->IsUsedInRegularObj) + continue; LLVM_DEBUG(dbgs() << "import: " << Sym->getName() << "\n"); ImportedSymbols.emplace_back(Sym);