diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp --- a/lld/COFF/Driver.cpp +++ b/lld/COFF/Driver.cpp @@ -1144,7 +1144,7 @@ return; } - lld::threadsEnabled = args.hasFlag(OPT_threads, OPT_threads_no, true); + lld::enabledThreads = args.hasFlag(OPT_threads, OPT_threads_no, true) ? 0 : 1; if (args.hasArg(OPT_show_timing)) config->showTiming = true; diff --git a/lld/Common/Filesystem.cpp b/lld/Common/Filesystem.cpp --- a/lld/Common/Filesystem.cpp +++ b/lld/Common/Filesystem.cpp @@ -43,7 +43,7 @@ #if defined(_WIN32) sys::fs::remove(path); #else - if (!threadsEnabled || !sys::fs::exists(path) || + if (enabledThreads == 1 || !sys::fs::exists(path) || !sys::fs::is_regular_file(path)) return; diff --git a/lld/Common/Threads.cpp b/lld/Common/Threads.cpp --- a/lld/Common/Threads.cpp +++ b/lld/Common/Threads.cpp @@ -8,4 +8,4 @@ #include "lld/Common/Threads.h" -bool lld::threadsEnabled = true; +unsigned lld::enabledThreads = 0; diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -860,7 +860,6 @@ args.hasFlag(OPT_fatal_warnings, OPT_no_fatal_warnings, false); errorHandler().vsDiagnostics = args.hasArg(OPT_visual_studio_diagnostics_format, false); - threadsEnabled = args.hasFlag(OPT_threads, OPT_no_threads, true); config->allowMultipleDefinition = args.hasFlag(OPT_allow_multiple_definition, @@ -974,7 +973,6 @@ config->thinLTOIndexOnly = args.hasArg(OPT_thinlto_index_only) || args.hasArg(OPT_thinlto_index_only_eq); config->thinLTOIndexOnlyArg = args.getLastArgValue(OPT_thinlto_index_only_eq); - config->thinLTOJobs = args.getLastArgValue(OPT_thinlto_jobs); config->thinLTOObjectSuffixReplace = getOldNewOptions(args, OPT_thinlto_object_suffix_replace_eq); config->thinLTOPrefixReplace = @@ -1036,6 +1034,21 @@ for (auto *arg : args.filtered(OPT_mllvm)) parseClangOption(arg->getValue(), arg->getSpelling()); + // --threads= takes 'all' or a positive integer. --threads= provides the default value + // for --thinlto-jobs. + if (auto *arg = args.getLastArg(OPT_threads)) { + StringRef v(arg->getValue()); + if (v == "all") + enabledThreads = 0; + else if (!llvm::to_integer(v, enabledThreads, 0) || enabledThreads == 0) + error(arg->getSpelling() + + ": expected 'all' or a positive integer, but got '" + + arg->getValue() + "'"); + config->thinLTOJobs = v; + } + if (auto *arg = args.getLastArg(OPT_thinlto_jobs)) + config->thinLTOJobs = arg->getValue(); + if (config->ltoo > 3) error("invalid optimization level for LTO: " + Twine(config->ltoo)); if (config->ltoPartitions == 0) diff --git a/lld/ELF/ICF.cpp b/lld/ELF/ICF.cpp --- a/lld/ELF/ICF.cpp +++ b/lld/ELF/ICF.cpp @@ -400,7 +400,7 @@ void ICF::forEachClass(llvm::function_ref fn) { // If threading is disabled or the number of sections are // too small to use threading, call Fn sequentially. - if (!threadsEnabled || sections.size() < 1024) { + if (enabledThreads == 1 || sections.size() < 1024) { forEachClassRange(0, sections.size(), fn); ++cnt; return; diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td --- a/lld/ELF/Options.td +++ b/lld/ELF/Options.td @@ -350,9 +350,8 @@ Eq<"target2", "Interpret R_ARM_TARGET2 as , where is one of rel, abs, or got-rel">, MetaVarName<"">; -defm threads: B<"threads", - "Run the linker multi-threaded (default)", - "Do not run the linker multi-threaded">; +defm threads : Eq<"threads", "Number of threads. 'all' (default) means all of " + "concurrent threads supported">; def time_trace: F<"time-trace">, HelpText<"Record time trace">; def time_trace_file_eq: J<"time-trace-file=">, HelpText<"Specify time trace output file">; @@ -509,7 +508,8 @@ def thinlto_emit_imports_files: F<"thinlto-emit-imports-files">; def thinlto_index_only: F<"thinlto-index-only">; def thinlto_index_only_eq: J<"thinlto-index-only=">; -def thinlto_jobs: J<"thinlto-jobs=">, HelpText<"Number of ThinLTO jobs">; +def thinlto_jobs: J<"thinlto-jobs=">, + HelpText<"Number of ThinLTO jobs. Default to --threads=">; def thinlto_object_suffix_replace_eq: J<"thinlto-object-suffix-replace=">; def thinlto_prefix_replace_eq: J<"thinlto-prefix-replace=">; diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp --- a/lld/ELF/SyntheticSections.cpp +++ b/lld/ELF/SyntheticSections.cpp @@ -2747,10 +2747,9 @@ // of millions for very large executables, so we use multi-threading to // speed it up. size_t numShards = 32; - size_t concurrency = 1; - if (threadsEnabled) - concurrency = std::min( - hardware_concurrency().compute_thread_count(), numShards); + size_t concurrency = hardware_concurrency().compute_thread_count(); + if (enabledThreads != 0) + concurrency = std::min(concurrency, enabledThreads); // A sharded map to uniquify symbols by name. std::vector> map(numShards); @@ -3194,7 +3193,7 @@ // Concurrency level. Must be a power of 2 to avoid expensive modulo // operations in the following tight loop. size_t concurrency = 1; - if (threadsEnabled) + if (enabledThreads) concurrency = std::min( hardware_concurrency().compute_thread_count(), numShards); diff --git a/lld/docs/ld.lld.1 b/lld/docs/ld.lld.1 --- a/lld/docs/ld.lld.1 +++ b/lld/docs/ld.lld.1 @@ -302,8 +302,6 @@ Do not set the text data sections to be writable, page align sections. .It Fl -no-rosegment Do not put read-only non-executable sections in their own segment. -.It Fl -no-threads -Do not run the linker multi-threaded. .It Fl -no-undefined-version Report version scripts that refer undefined symbols. .It Fl -no-undefined @@ -525,9 +523,10 @@ Pruning policy for the ThinLTO cache. .It Fl -thinlto-jobs Ns = Ns Ar value Number of ThinLTO jobs. -.It Fl -threads -Run the linker multi-threaded. -This option is enabled by default. +.It Fl -threads Ns = Ns Ar N +Number of threads. +.Cm all +(default) means all of concurrent threads supported. .It Fl -trace Print the names of the input files. .It Fl -trace-symbol Ns = Ns Ar symbol , Fl y Ar symbol diff --git a/lld/include/lld/Common/Threads.h b/lld/include/lld/Common/Threads.h --- a/lld/include/lld/Common/Threads.h +++ b/lld/include/lld/Common/Threads.h @@ -63,10 +63,12 @@ namespace lld { -extern bool threadsEnabled; +// Number of threads. 0 means all of concurrent threads supported. +extern unsigned enabledThreads; template void parallelForEach(R &&range, FuncTy fn) { - if (threadsEnabled) + // TODO Make llvm::parallel::par support different numbers of threads. + if (enabledThreads != 1) for_each(llvm::parallel::par, std::begin(range), std::end(range), fn); else for_each(llvm::parallel::seq, std::begin(range), std::end(range), fn); @@ -74,14 +76,14 @@ inline void parallelForEachN(size_t begin, size_t end, llvm::function_ref fn) { - if (threadsEnabled) + if (enabledThreads != 1) for_each_n(llvm::parallel::par, begin, end, fn); else for_each_n(llvm::parallel::seq, begin, end, fn); } template void parallelSort(R &&range, FuncTy fn) { - if (threadsEnabled) + if (enabledThreads != 1) sort(llvm::parallel::par, std::begin(range), std::end(range), fn); else sort(llvm::parallel::seq, std::begin(range), std::end(range), fn); diff --git a/lld/test/ELF/build-id.s b/lld/test/ELF/build-id.s --- a/lld/test/ELF/build-id.s +++ b/lld/test/ELF/build-id.s @@ -5,26 +5,26 @@ # RUN: ld.lld --build-id %t -o %t2 # RUN: llvm-readobj -S %t2 | FileCheck -check-prefix=ALIGN %s -# RUN: ld.lld --build-id %t -o %t2 -threads +# RUN: ld.lld --build-id %t -o %t2 --threads=all # RUN: llvm-objdump -s %t2 | FileCheck --check-prefix=DEFAULT %s -# RUN: ld.lld --build-id=fast %t -o %t2 -threads +# RUN: ld.lld --build-id=fast %t -o %t2 --threads=all # RUN: llvm-objdump -s %t2 | FileCheck --check-prefix=DEFAULT %s -# RUN: ld.lld --build-id %t -o %t2 -no-threads +# RUN: ld.lld --build-id %t -o %t2 --threads=1 # RUN: llvm-objdump -s %t2 | FileCheck --check-prefix=DEFAULT %s -# RUN: ld.lld --build-id=md5 %t -o %t2 -threads +# RUN: ld.lld --build-id=md5 %t -o %t2 --threads=all # RUN: llvm-objdump -s %t2 | FileCheck --check-prefix=MD5 %s -# RUN: ld.lld --build-id=md5 %t -o %t2 -no-threads +# RUN: ld.lld --build-id=md5 %t -o %t2 --threads=1 # RUN: llvm-objdump -s %t2 | FileCheck --check-prefix=MD5 %s -# RUN: ld.lld --build-id=sha1 %t -o %t2 -threads +# RUN: ld.lld --build-id=sha1 %t -o %t2 --threads=all # RUN: llvm-objdump -s %t2 | FileCheck --check-prefix=SHA1 %s -# RUN: ld.lld --build-id=sha1 %t -o %t2 -no-threads +# RUN: ld.lld --build-id=sha1 %t -o %t2 --threads=1 # RUN: llvm-objdump -s %t2 | FileCheck --check-prefix=SHA1 %s -# RUN: ld.lld --build-id=tree %t -o %t2 -threads +# RUN: ld.lld --build-id=tree %t -o %t2 --threads=all # RUN: llvm-objdump -s %t2 | FileCheck --check-prefix=SHA1 %s -# RUN: ld.lld --build-id=tree %t -o %t2 -no-threads +# RUN: ld.lld --build-id=tree %t -o %t2 --threads=1 # RUN: llvm-objdump -s %t2 | FileCheck --check-prefix=SHA1 %s # RUN: ld.lld --build-id=uuid %t -o %t2 diff --git a/lld/test/ELF/lto/thinlto.ll b/lld/test/ELF/lto/thinlto.ll --- a/lld/test/ELF/lto/thinlto.ll +++ b/lld/test/ELF/lto/thinlto.ll @@ -22,6 +22,18 @@ ; RUN: llvm-nm %t31.lto.o | FileCheck %s --check-prefix=NM1 ; RUN: llvm-nm %t32.lto.o | FileCheck %s --check-prefix=NM2 +;; --thinlto-jobs= defaults to --threads=. +; RUN: rm -f %t31.lto.o %t32.lto.o +; RUN: ld.lld -save-temps --threads=2 -shared %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 + +;; --thinlto-jobs= overrides --threads=. +; RUN: rm -f %t31.lto.o %t32.lto.o +; RUN: ld.lld -save-temps --threads=1 --plugin-opt=jobs=2 -shared %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 + ; Test with all threads, on all cores, on all CPU sockets ; RUN: rm -f %t31.lto.o %t32.lto.o ; RUN: ld.lld -save-temps --thinlto-jobs=all -shared %t1.o %t2.o -o %t3 diff --git a/lld/test/ELF/threads.test b/lld/test/ELF/threads.test new file mode 100644 --- /dev/null +++ b/lld/test/ELF/threads.test @@ -0,0 +1,19 @@ +# RUN: yaml2obj %s -o %t.o + +## 'all' or a positive integer is allowed. +# RUN: ld.lld --threads=all %t.o -o /dev/null +# RUN: ld.lld --threads=1 %t.o -o /dev/null +# RUN: ld.lld --threads=2 %t.o -o /dev/null + +# RUN: not ld.lld --threads=0 %t.o -o /dev/null 2>&1 | FileCheck %s +# RUN: not ld.lld --threads=-1 %t.o -o /dev/null 2>&1 | FileCheck %s +# RUN: not ld.lld --threads=nan %t.o -o /dev/null 2>&1 | FileCheck %s + +# CHECK: error: --threads: expected 'all' or a positive integer, but got '{{.*}}' + +!ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_REL + Machine: EM_X86_64 diff --git a/lld/test/wasm/lto/thinlto.ll b/lld/test/wasm/lto/thinlto.ll --- a/lld/test/wasm/lto/thinlto.ll +++ b/lld/test/wasm/lto/thinlto.ll @@ -14,6 +14,18 @@ ; RUN: llvm-nm %t31.lto.o | FileCheck %s --check-prefix=NM1 ; RUN: llvm-nm %t32.lto.o | FileCheck %s --check-prefix=NM2 +;; --thinlto-jobs= defaults to --threads=. +; RUN: rm -f %t31.lto.o %t32.lto.o +; RUN: wasm-ld -r -save-temps --threads=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 + +;; --thinlto-jobs= overrides --threads=. +; RUN: rm -f %t31.lto.o %t32.lto.o +; RUN: wasm-ld -r -save-temps --threads=1 --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 + ; Test with all threads, on all cores, on all CPU sockets ; RUN: rm -f %t31.lto.o %t32.lto.o ; RUN: wasm-ld -r -save-temps --thinlto-jobs=all %t1.o %t2.o -o %t3 diff --git a/lld/test/wasm/threads.yaml b/lld/test/wasm/threads.yaml new file mode 100644 --- /dev/null +++ b/lld/test/wasm/threads.yaml @@ -0,0 +1,20 @@ +# RUN: yaml2obj %s -o %t.o + +## 'all' or a positive integer is allowed. +# RUN: wasm-ld --no-entry --threads=all %t.o -o /dev/null +# RUN: wasm-ld --no-entry --threads=1 %t.o -o /dev/null +# RUN: wasm-ld --no-entry --threads=2 %t.o -o /dev/null + +# RUN: not wasm-ld --threads=0 %t.o -o /dev/null 2>&1 | FileCheck %s +# RUN: not wasm-ld --threads=-1 %t.o -o /dev/null 2>&1 | FileCheck %s +# RUN: not wasm-ld --threads=nan %t.o -o /dev/null 2>&1 | FileCheck %s + +# CHECK: error: --threads: expected 'all' or a positive integer, but got '{{.*}}' + +!WASM +FileHeader: + Version: 0x00000001 +Sections: + - Type: CUSTOM + Name: linking + Version: 2 diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp --- a/lld/wasm/Driver.cpp +++ b/lld/wasm/Driver.cpp @@ -362,10 +362,8 @@ config->thinLTOCachePolicy = CHECK( parseCachePruningPolicy(args.getLastArgValue(OPT_thinlto_cache_policy)), "--thinlto-cache-policy: invalid cache policy"); - config->thinLTOJobs = args.getLastArgValue(OPT_thinlto_jobs); errorHandler().verbose = args.hasArg(OPT_verbose); LLVM_DEBUG(errorHandler().verbose = true); - threadsEnabled = args.hasFlag(OPT_threads, OPT_no_threads, true); config->initialMemory = args::getInteger(args, OPT_initial_memory, 0); config->globalBase = args::getInteger(args, OPT_global_base, 1024); @@ -377,6 +375,21 @@ config->exportDynamic = args.hasFlag(OPT_export_dynamic, OPT_no_export_dynamic, config->shared); + // --threads= takes 'all' or a positive integer. --threads= provides the default value + // for --thinlto-jobs. + if (auto *arg = args.getLastArg(OPT_threads)) { + StringRef v(arg->getValue()); + if (v == "all") + enabledThreads = 0; + else if (!llvm::to_integer(v, enabledThreads, 0) || enabledThreads == 0) + error(arg->getSpelling() + + ": expected 'all' or a positive integer, but got '" + + arg->getValue() + "'"); + config->thinLTOJobs = v; + } + if (auto *arg = args.getLastArg(OPT_thinlto_jobs)) + config->thinLTOJobs = arg->getValue(); + if (auto *arg = args.getLastArg(OPT_features)) { config->features = llvm::Optional>(std::vector()); diff --git a/lld/wasm/Options.td b/lld/wasm/Options.td --- a/lld/wasm/Options.td +++ b/lld/wasm/Options.td @@ -64,9 +64,6 @@ def mllvm: S<"mllvm">, HelpText<"Options to pass to LLVM">; -def no_threads: F<"no-threads">, - HelpText<"Do not run the linker multi-threaded">; - def no_color_diagnostics: F<"no-color-diagnostics">, HelpText<"Do not use colors in diagnostics">; @@ -98,7 +95,8 @@ def strip_debug: F<"strip-debug">, HelpText<"Strip debugging information">; -def threads: F<"threads">, HelpText<"Run the linker multi-threaded">; +defm threads : Eq<"threads", "Number of threads. 'all' (default) means all of " + "concurrent threads supported">; def trace: F<"trace">, HelpText<"Print the names of the input files">; @@ -198,4 +196,5 @@ def thinlto_cache_dir: J<"thinlto-cache-dir=">, HelpText<"Path to ThinLTO cached object file directory">; defm thinlto_cache_policy: Eq<"thinlto-cache-policy", "Pruning policy for the ThinLTO cache">; -def thinlto_jobs: J<"thinlto-jobs=">, HelpText<"Number of ThinLTO jobs">; +def thinlto_jobs: J<"thinlto-jobs=">, + HelpText<"Number of ThinLTO jobs. Default to --threads=">;