diff --git a/llvm/test/tools/llvm-foreach/llvm-foreach-lin.ll b/llvm/test/tools/llvm-foreach/llvm-foreach-lin.ll index 2fa674bf1d8e3..7aeaa800db13b 100644 --- a/llvm/test/tools/llvm-foreach/llvm-foreach-lin.ll +++ b/llvm/test/tools/llvm-foreach/llvm-foreach-lin.ll @@ -4,17 +4,26 @@ ; RUN: echo 'Content of second file' > %t2.tgt ; RUN: echo "%t1.tgt" > %t.list ; RUN: echo "%t2.tgt" >> %t.list -; RUN: llvm-foreach --in-replace="{}" --in-file-list=%t.list -- echo "{}" > %t.res +; RUN: llvm-foreach --jobs=2 --in-replace="{}" --in-file-list=%t.list -- echo "{}" > %t.res ; RUN: FileCheck < %t.res %s -; CHECK: [[FIRST:.+1.tgt]] -; CHECK: [[SECOND:.+2.tgt]] -; +; CHECK-DAG: [[FIRST:.+1.tgt]] +; CHECK-DAG: [[SECOND:.+2.tgt]] + +; RUN: llvm-foreach --in-replace="{}" --in-file-list=%t.list -- echo "{}" > %t.res +; RUN: FileCheck < %t.res %s --check-prefix=CHECK-ORDER +; CHECK-ORDER: [[FIRST:.+1.tgt]] +; CHECK-ORDER: [[SECOND:.+2.tgt]] + ; RUN: llvm-foreach --in-replace="{}" --out-replace=%t --out-ext=out --in-file-list=%t.list --out-file-list=%t.out.list -- cp "{}" %t ; RUN: FileCheck < %t.out.list %s --check-prefix=CHECK-LIST +; RUN: llvm-foreach --jobs=2 --in-replace="{}" --out-replace=%t --out-ext=out --in-file-list=%t.list --out-file-list=%t.out.list -- cp "{}" %t +; RUN: FileCheck < %t.out.list %s --check-prefix=CHECK-LIST ; CHECK-LIST: [[FIRST:.+\.out]] ; CHECK-LIST: [[SECOND:.+\.out]] -; RUN: llvm-foreach --in-replace="{}" --in-file-list=%t.out.list -- FileCheck --input-file="{}" %s --check-prefix=CHECK-CONTENT -; CHECK-CONTENT: Content of +; RUN: llvm-foreach --in-replace="{}" --in-file-list=%t.out.list -- cat "{}" > %t.order +; RUN: FileCheck < %t.order %s --check-prefix=CHECK-CONTENT +; CHECK-CONTENT: Content of first file +; CHECK-CONTENT-NEXT: Content of second file ; RUN: echo 'something' > %t3.tgt ; RUN: echo 'something again' > %t4.tgt @@ -22,5 +31,18 @@ ; RUN: echo "%t4.tgt" >> %t1.list ; RUN: llvm-foreach --in-replace="{}" --in-replace="inrep" --in-file-list=%t.list --in-file-list=%t1.list --out-increment="%t_out.prj" -- echo -first-part-of-arg={}.out -first-part-of-arg=inrep.out -another-arg=%t_out.prj > %t1.res ; RUN: FileCheck < %t1.res %s --check-prefix=CHECK-DOUBLE-LISTS -; CHECK-DOUBLE-LISTS: -first-part-of-arg=[[FIRST:.+1.tgt.out]] -first-part-of-arg=[[THIRD:.+3.tgt.out]] -another-arg={{.+}}_out.prj -; CHECK-DOUBLE-LISTS: -first-part-of-arg=[[SECOND:.+2.tgt.out]] -first-part-of-arg=[[FOURTH:.+4.tgt.out]] -another-arg={{.+}}_out.prj_1 +; RUN: llvm-foreach --jobs=2 --in-replace="{}" --in-replace="inrep" --in-file-list=%t.list --in-file-list=%t1.list --out-increment="%t_out.prj" -- echo -first-part-of-arg={}.out -first-part-of-arg=inrep.out -another-arg=%t_out.prj > %t1.res +; RUN: FileCheck < %t1.res %s --check-prefix=CHECK-DOUBLE-LISTS +; CHECK-DOUBLE-LISTS-DAG: -first-part-of-arg=[[FIRST:.+1.tgt.out]] -first-part-of-arg=[[THIRD:.+3.tgt.out]] -another-arg={{.+}}_out.prj +; CHECK-DOUBLE-LISTS-DAG: -first-part-of-arg=[[SECOND:.+2.tgt.out]] -first-part-of-arg=[[FOURTH:.+4.tgt.out]] -another-arg={{.+}}_out.prj_1 + +; RUN: echo "%t1.tgt" > %t2.list +; RUN: echo "%t2.tgt" >> %t2.list +; RUN: echo "%t3.tgt" >> %t2.list +; RUN: echo "%t4.tgt" >> %t2.list +; RUN: llvm-foreach -j 2 --in-replace="{}" --in-file-list=%t2.list -- echo "{}" > %t2.res +; RUN: FileCheck < %t2.res %s --check-prefix=CHECK-PARALLEL-JOBS +; CHECK-PARALLEL-JOBS-DAG: [[FIRST:.+1.tgt]] +; CHECK-PARALLEL-JOBS-DAG: [[SECOND:.+2.tgt]] +; CHECK-PARALLEL-JOBS-DAG: [[THIRD:.+3.tgt]] +; CHECK-PARALLEL-JOBS-DAG: [[FOURTH:.+4.tgt]] diff --git a/llvm/test/tools/llvm-foreach/llvm-foreach-win.ll b/llvm/test/tools/llvm-foreach/llvm-foreach-win.ll index 9053e13c74a64..0072ab71956f4 100644 --- a/llvm/test/tools/llvm-foreach/llvm-foreach-win.ll +++ b/llvm/test/tools/llvm-foreach/llvm-foreach-win.ll @@ -4,17 +4,26 @@ ; RUN: echo 'Content of second file' > %t2.tgt ; RUN: echo "%t1.tgt" > %t.list ; RUN: echo "%t2.tgt" >> %t.list -; RUN: llvm-foreach --in-replace="{}" --in-file-list=%t.list -- echo "{}" > %t.res +; RUN: llvm-foreach --jobs=2 --in-replace="{}" --in-file-list=%t.list -- echo "{}" > %t.res ; RUN: FileCheck < %t.res %s -; CHECK: [[FIRST:.+1.tgt]] -; CHECK: [[SECOND:.+2.tgt]] -; +; CHECK-DAG: [[FIRST:.+1.tgt]] +; CHECK-DAG: [[SECOND:.+2.tgt]] + +; RUN: llvm-foreach --in-replace="{}" --in-file-list=%t.list -- echo "{}" > %t.res +; RUN: FileCheck < %t.res %s --check-prefix=CHECK-ORDER +; CHECK-ORDER: [[FIRST:.+1.tgt]] +; CHECK-ORDER: [[SECOND:.+2.tgt]] + ; RUN: llvm-foreach --in-replace="{}" --out-replace=%t --out-ext=out --in-file-list=%t.list --out-file-list=%t.out.list -- xcopy /y "{}" %t ; RUN: FileCheck < %t.out.list %s --check-prefix=CHECK-LIST +; RUN: llvm-foreach --jobs=2 --in-replace="{}" --out-replace=%t --out-ext=out --in-file-list=%t.list --out-file-list=%t.out.list -- xcopy /y "{}" %t +; RUN: FileCheck < %t.out.list %s --check-prefix=CHECK-LIST ; CHECK-LIST: [[FIRST:.+\.out]] ; CHECK-LIST: [[SECOND:.+\.out]] -; RUN: llvm-foreach --in-replace="{}" --in-file-list=%t.out.list -- FileCheck --input-file="{}" %s --check-prefix=CHECK-CONTENT -; CHECK-CONTENT: Content of +; RUN: llvm-foreach --in-replace="{}" --in-file-list=%t.out.list -- cat "{}" > %t.order +; RUN: FileCheck < %t.order %s --check-prefix=CHECK-CONTENT +; CHECK-CONTENT: Content of first file +; CHECK-CONTENT-NEXT: Content of second file ; RUN: echo 'something' > %t3.tgt ; RUN: echo 'something again' > %t4.tgt @@ -22,5 +31,18 @@ ; RUN: echo "%t4.tgt" >> %t1.list ; RUN: llvm-foreach --in-replace="{}" --in-replace="inrep" --in-file-list=%t.list --in-file-list=%t1.list --out-increment=%t_out.prj -- echo -first-part-of-arg={}.out -first-part-of-arg=inrep.out -another-arg=%t_out.prj > %t1.res ; RUN: FileCheck < %t1.res %s --check-prefix=CHECK-DOUBLE-LISTS -; CHECK-DOUBLE-LISTS: -first-part-of-arg=[[FIRST:.+1.tgt.out]] -first-part-of-arg=[[THIRD:.+3.tgt.out]] -another-arg={{.+}}_out.prj -; CHECK-DOUBLE-LISTS: -first-part-of-arg=[[SECOND:.+2.tgt.out]] -first-part-of-arg=[[FOURTH:.+4.tgt.out]] -another-arg={{.+}}_out.prj_1 +; RUN: llvm-foreach --jobs=2 --in-replace="{}" --in-replace="inrep" --in-file-list=%t.list --in-file-list=%t1.list --out-increment=%t_out.prj -- echo -first-part-of-arg={}.out -first-part-of-arg=inrep.out -another-arg=%t_out.prj > %t1.res +; RUN: FileCheck < %t1.res %s --check-prefix=CHECK-DOUBLE-LISTS +; CHECK-DOUBLE-LISTS-DAG: -first-part-of-arg=[[FIRST:.+1.tgt.out]] -first-part-of-arg=[[THIRD:.+3.tgt.out]] -another-arg={{.+}}_out.prj +; CHECK-DOUBLE-LISTS-DAG: -first-part-of-arg=[[SECOND:.+2.tgt.out]] -first-part-of-arg=[[FOURTH:.+4.tgt.out]] -another-arg={{.+}}_out.prj_1 + +; RUN: echo "%t1.tgt" > %t2.list +; RUN: echo "%t2.tgt" >> %t2.list +; RUN: echo "%t3.tgt" >> %t2.list +; RUN: echo "%t4.tgt" >> %t2.list +; RUN: llvm-foreach --jobs=2 --in-replace="{}" --in-file-list=%t2.list -- echo "{}" > %t2.res +; RUN: FileCheck < %t2.res %s --check-prefix=CHECK-PARALLEL-JOBS +; CHECK-PARALLEL-JOBS-DAG: [[FIRST:.+1.tgt]] +; CHECK-PARALLEL-JOBS-DAG: [[SECOND:.+2.tgt]] +; CHECK-PARALLEL-JOBS-DAG: [[THIRD:.+3.tgt]] +; CHECK-PARALLEL-JOBS-DAG: [[FOURTH:.+4.tgt]] diff --git a/llvm/tools/llvm-foreach/llvm-foreach.cpp b/llvm/tools/llvm-foreach/llvm-foreach.cpp index 9a7f1a6031860..11d6b24af693e 100644 --- a/llvm/tools/llvm-foreach/llvm-foreach.cpp +++ b/llvm/tools/llvm-foreach/llvm-foreach.cpp @@ -18,7 +18,9 @@ #include "llvm/Support/Path.h" #include "llvm/Support/Program.h" #include "llvm/Support/SystemUtils.h" +#include "llvm/Support/Threading.h" +#include #include using namespace llvm; @@ -69,6 +71,17 @@ static cl::opt OutIncrement{ "pass."), cl::init(""), cl::value_desc("R")}; +static cl::opt JobsInParallel{ + "jobs", + cl::Optional, + cl::init(1), + cl::desc("Specify the number of threads for launching input commands in " + "parallel mode"), +}; + +static cl::alias JobsInParallelShort{"j", cl::desc("Alias for --jobs"), + cl::aliasopt(JobsInParallel)}; + static void error(const Twine &Msg) { errs() << "llvm-foreach: " << Msg << '\n'; exit(1); @@ -79,6 +92,32 @@ static void error(std::error_code EC, const Twine &Prefix) { error(Prefix + ": " + EC.message()); } +// With BlockingWait=false this function just goes through the all +// submitted jobs to check if some of them have finished. +int checkIfJobsAreFinished(std::list &JobsSubmitted, + bool BlockingWait = true) { + std::string ErrMsg; + auto It = JobsSubmitted.begin(); + while (It != JobsSubmitted.end()) { + sys::ProcessInfo WaitResult = + sys::Wait(*It, 0, /*WaitUntilTerminates*/ BlockingWait, &ErrMsg); + + // Check if the job has finished (PID will be 0 if it's not). + if (!BlockingWait && !WaitResult.Pid) { + It++; + continue; + } + assert(BlockingWait || WaitResult.Pid); + It = JobsSubmitted.erase(It); + + if (WaitResult.ReturnCode != 0) { + errs() << "llvm-foreach: " << ErrMsg << '\n'; + return WaitResult.ReturnCode; + } + } + return 0; +} + int main(int argc, char **argv) { cl::ParseCommandLineOptions( argc, argv, @@ -160,6 +199,16 @@ int main(int argc, char **argv) { PrevNumOfLines = FileLists[i].size(); } + if (!JobsInParallel) + error("Number of parallel threads should be a positive integer"); + + size_t MaxSafeNumThreads = optimal_concurrency().compute_thread_count(); + if (JobsInParallel > MaxSafeNumThreads) { + JobsInParallel = MaxSafeNumThreads; + outs() << "llvm-foreach: adjusted number of threads to " + << MaxSafeNumThreads << " (max safe available).\n"; + } + std::error_code EC; raw_fd_ostream OS{OutputFileList, EC, sys::fs::OpenFlags::OF_None}; if (!OutputFileList.empty()) @@ -170,6 +219,7 @@ int main(int argc, char **argv) { std::string IncOutArg; std::vector ResInArgs(InReplaceArgs.size()); std::string ResFileList = ""; + std::list JobsSubmitted; for (size_t j = 0; j != FileLists[0].size(); ++j) { for (size_t i = 0; i < InReplaceArgs.size(); ++i) { ArgumentReplace CurReplace = InReplaceArgs[i]; @@ -221,17 +271,23 @@ int main(int argc, char **argv) { Args[OutIncrementArg.ArgNum] = IncOutArg; } - std::string ErrMsg; - // TODO: Add possibility to execute commands in parallel. - int Result = - sys::ExecuteAndWait(Prog, Args, /*Env=*/None, /*Redirects=*/None, - /*SecondsToWait=*/0, /*MemoryLimit=*/0, &ErrMsg); - if (Result != 0) { - errs() << "llvm-foreach: " << ErrMsg << '\n'; - Res = Result; - } + // Do not start execution of a new job until previous one(s) are finished, + // if the maximum number of parallel workers is reached. + while (JobsSubmitted.size() == JobsInParallel) + if (int Result = + checkIfJobsAreFinished(JobsSubmitted, /*BlockingWait*/ false)) + Res = Result; + + JobsSubmitted.emplace_back(sys::ExecuteNoWait( + Prog, Args, /*Env=*/None, /*Redirects=*/None, /*MemoryLimit=*/0)); } + // Wait for all commands to be executed. + while (!JobsSubmitted.empty()) + if (int Result = + checkIfJobsAreFinished(JobsSubmitted, /*BlockingWait*/ true)) + Res = Result; + if (!OutputFileList.empty()) { OS.close(); }