Index: llvm/CMakeLists.txt =================================================================== --- llvm/CMakeLists.txt +++ llvm/CMakeLists.txt @@ -860,6 +860,7 @@ add_subdirectory(utils/PerfectShuffle) add_subdirectory(utils/count) add_subdirectory(utils/not) + add_subdirectory(utils/trace) add_subdirectory(utils/yaml-bench) else() if ( LLVM_INCLUDE_TESTS ) Index: llvm/test/Support/execute.test =================================================================== --- /dev/null +++ llvm/test/Support/execute.test @@ -0,0 +1,42 @@ +; RUN: rm -f %t.stdout +; RUN: trace -execfile=%s 2>&1 | FileCheck -check-prefix=NONE_REDIRECTED %s +; RUN: trace -stdout=%t.stdout -execfile=%s 2>&1 | FileCheck -check-prefix=STDOUT_REDIRECTED %s +; RUN: FileCheck -input-file=%t.stdout -check-prefix=STDOUT_CONTENTS %s +; RUN: rm -f %t.stderr +; RUN: trace -stderr=%t.stderr -execfile=%s 2>&1 | FileCheck -check-prefix=STDERR_REDIRECTED %s +; RUN: FileCheck -input-file=%t.stderr -check-prefix=STDERR_CONTENTS %s +; RUN: rm -f %t.stdout %t.stderr +; RUN: trace -stdout=%t.stdout -stderr=%t.stderr -execfile=%s 2>&1 | FileCheck -check-prefix=BOTH_REDIRECTED %s +; RUN: FileCheck -input-file=%t.stdout -check-prefix=STDOUT_CONTENTS %s +; RUN: FileCheck -input-file=%t.stderr -check-prefix=STDERR_CONTENTS %s + + +write stdout This goes to stdout +write stderr This goes to stderr +exit 42 + +; NONE_REDIRECTED: PARENT(stdout): Running +; NONE_REDIRECTED-NEXT: CHILD(stdout): Running +; NONE_REDIRECTED-NEXT: CHILD(stdout): This goes to stdout +; NONE_REDIRECTED-NEXT: CHILD(stderr): This goes to stderr +; NONE_REDIRECTED-NEXT: CHILD(stdout): exiting with code 42 +; NONE_REDIRECTED-NEXT: PARENT(stdout): Child exited with code 42 + +; STDOUT_REDIRECTED: PARENT(stdout): Running +; STDOUT_REDIRECTED-NEXT: CHILD(stderr): This goes to stderr +; STDOUT_REDIRECTED-NEXT: PARENT(stdout): Child exited with code 42 + +; STDOUT_CONTENTS: CHILD(stdout): Running +; STDOUT_CONTENTS-NEXT: CHILD(stdout): This goes to stdout +; STDOUT_CONTENTS-NEXT: CHILD(stdout): exiting with code 42 + +; STDERR_REDIRECTED: PARENT(stdout): Running +; STDERR_REDIRECTED-NEXT: CHILD(stdout): Running +; STDERR_REDIRECTED-NEXT: CHILD(stdout): This goes to stdout +; STDERR_REDIRECTED-NEXT: CHILD(stdout): exiting with code 42 +; STDERR_REDIRECTED-NEXT: PARENT(stdout): Child exited with code 42 + +; STDERR_CONTENTS: CHILD(stderr): This goes to stderr + +; BOTH_REDIRECTED: PARENT(stdout): Running +; BOTH_REDIRECTED-NEXT: PARENT(stdout): Child exited with code 42 Index: llvm/test/lit.cfg.py =================================================================== --- llvm/test/lit.cfg.py +++ llvm/test/lit.cfg.py @@ -149,7 +149,9 @@ 'llvm-rtdyld', 'llvm-size', 'llvm-split', 'llvm-strings', 'llvm-strip', 'llvm-tblgen', 'llvm-c-test', 'llvm-cxxfilt', 'llvm-xray', 'yaml2obj', 'obj2yaml', 'yaml-bench', 'verify-uselistorder', - 'bugpoint', 'llc', 'llvm-symbolizer', 'opt', 'sancov', 'sanstats']) + 'bugpoint', 'llc', 'llvm-symbolizer', 'opt', 'sancov', 'sanstats', + ToolSubst(r'\| \btrace\b', command=FindTool('trace'), verbatim=True, unresolved='fatal'), + ]) # The following tools are optional tools.extend([ Index: llvm/utils/trace/CMakeLists.txt =================================================================== --- /dev/null +++ llvm/utils/trace/CMakeLists.txt @@ -0,0 +1,7 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_llvm_utility(trace + Trace.cpp + ) Index: llvm/utils/trace/Trace.cpp =================================================================== --- /dev/null +++ llvm/utils/trace/Trace.cpp @@ -0,0 +1,170 @@ +//===- Trace.cpp - The 'trace' testing tool -------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/InitLLVM.h" +#include "llvm/Support/LineIterator.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Program.h" + +#include + +#include + +using namespace llvm; + +static cl::opt StdinFile("stdin", cl::desc(""), + cl::Optional); + +static cl::opt StdoutFile("stdout", cl::desc(""), + cl::Optional); + +static cl::opt StderrFile("stderr", cl::desc(""), + cl::Optional); + +static cl::list + EnvVar("env", cl::desc("KEY:VALUE pair to set in target process"), + cl::Optional, cl::ZeroOrMore); + +static cl::opt + Cwd("cwd", cl::desc("The current working directory of the process"), + cl::Optional); + +static cl::opt + ExecFile("execfile", cl::desc("The command file to execute"), cl::Required); + +static cl::opt ExecNow("execnow", cl::ReallyHidden); + +static void handleWrite(StringRef Line) { + Line = Line.trim(); + + StringRef Dest; + StringRef Text; + std::tie(Dest, Text) = Line.split(' '); + int FD = -1; + if (Dest.equals_lower("stdout")) { + outs() << "CHILD(stdout): " << Text << "\n"; + outs().flush(); + } else if (Dest.equals_lower("stderr")) { + errs() << "CHILD(stderr): " << Text << "\n"; + errs().flush(); + } else if (to_integer(Dest, FD, 10)) { + raw_fd_ostream OS(FD, false, true); + OS << "CHILD(" << FD << "): " << Text << "\n"; + OS.flush(); + } else { + errs() << formatv( + "CHILD(stderr): Invalid argument '{0}' to command 'write'\n", Dest); + exit(-1); + } +} + +static void handleRead(StringRef Line) { + Line = Line.trim(); + + StringRef Source; + StringRef LengthStr; + + std::tie(Source, LengthStr) = Line.split(' '); + + unsigned Length; + int FD = -1; + if (!to_integer(LengthStr, Length, 10)) { + errs() << formatv( + "CHILD(stderr): Invalid argument '{0}' to command 'read'\n", LengthStr); + exit(-1); + } + + std::vector InputBuffer; + InputBuffer.resize(Length); + + if (Source.equals_lower("stdin")) + std::cin.get(InputBuffer.data(), Length); + else if (to_integer(Source, FD, 10)) { + ::read(FD, InputBuffer.data(), Length); + } else { + errs() << formatv( + "CHILD(stderr): Invalid argument '{0}' to command 'read'\n", Source); + exit(-1); + } + outs() << formatv("CHILD(stdout): Read {0} bytes from {1}: '{2}'\n", Length, + Source, StringRef(InputBuffer.data(), InputBuffer.size())); +} + +static void handleExit(StringRef Line) { + Line = Line.trim(); + + int Code; + if (!to_integer(Line, Code, 10)) { + errs() << formatv( + "CHILD(stderr): Invalid argument '{0}' to command 'exit'\n", Line); + exit(-1); + } + + outs() << "CHILD(stdout): exiting with code " << Code << "\n"; + outs().flush(); + exit(Code); +} + +static int run() { + auto FileContents = MemoryBuffer::getFile(ExecFile); + if (!FileContents) { + errs() << "CHILD(stderr): Unable to open execfile " << ExecFile << "\n"; + return -1; + } + std::unique_ptr Buffer = std::move(*FileContents); + for (StringRef Line : + make_range(line_iterator(*Buffer, true, ';'), line_iterator())) { + Line = Line.trim(); + if (Line.consume_front("write")) + handleWrite(Line); + else if (Line.consume_front("read")) + handleRead(Line); + else if (Line.consume_front("exit")) + handleExit(Line); + else { + errs() << "CHILD(stderr): Error in command: " << Line << "\n"; + return -1; + } + } + outs() << "CHILD(stdout): EOF encountered, exiting\n"; + return 0; +} + +int main(int argc, char **argv) { + InitLLVM X(argc, argv); + cl::ParseCommandLineOptions(argc, argv); + + if (ExecNow) { + outs() << "CHILD(stdout): Running\n"; + outs().flush(); + int ExitCode = run(); + return ExitCode; + } + + std::array, 3> Redirects; + + if (StdinFile.getNumOccurrences() > 0) + Redirects[0] = StdinFile; + if (StdoutFile.getNumOccurrences() > 0) + Redirects[1] = StdoutFile; + if (StderrFile.getNumOccurrences() > 0) + Redirects[2] = StderrFile; + + std::string Arg1 = formatv("-execfile={0}", ExecFile.getValue()); + const char *Args[] = {argv[0], "-execnow", Arg1.c_str(), nullptr}; + + outs() << "PARENT(stdout): Running\n"; + outs().flush(); + int Result = sys::ExecuteAndWait(argv[0], Args, nullptr, Redirects); + outs() << "PARENT(stdout): Child exited with code " << Result << "\n"; + outs().flush(); + return 0; +}