[Driver][gcov] Derive .gcno .gcda file names from -o and -dumpdir
authorFangrui Song <i@maskray.me>
Wed, 17 May 2023 19:43:49 +0000 (12:43 -0700)
committerFangrui Song <i@maskray.me>
Wed, 17 May 2023 19:43:49 +0000 (12:43 -0700)
Resolve a FIXME.
When -ftest-profile, -fprofile-arcs, or --coverage is specified and the
driver performs both compilation and linking phases, derive the .gcno &
.gcda file names from -o and -dumpdir.

`clang --coverage d/a.c d/b.c -o e/x && e/x` will now emit
`e/x-[ab].gc{no,da}` like GCC.

For -fprofile-dir=, we make the deliberate decision to not mangle the
input filename if relative.

The following script demonstrates the .gcno and .gcda filenames.

```
PATH=/tmp/Rel/bin:$PATH                # adapt according to your build directory
mkdir -p d e f
echo 'int main() {}' > d/a.c
echo > d/b.c

a() { rm $@ || exit 1; }

clang --coverage d/a.c d/b.c && ./a.out
a a-[ab].gc{no,da}
clang --coverage d/a.c d/b.c -o e/x && e/x
a e/x-[ab].gc{no,da}
clang --coverage d/a.c d/b.c -o e/x -dumpdir f/ && e/x
a f/[ab].gc{no,da}
clang --coverage -fprofile-dir=f d/a.c d/b.c -o e/x && e/x
a e/x-[ab].gcno f/e/x-[ab].gcda

clang -c --coverage d/a.c d/b.c && clang --coverage a.o b.o && ./a.out
a [ab].gc{no,da}
clang -c --coverage -fprofile-dir=f d/a.c d/b.c && clang --coverage a.o b.o && ./a.out
a [ab].gcno f/[ab].gcda

clang -c --coverage d/a.c -o e/xa.o && clang --coverage e/xa.o && ./a.out
a e/xa.gc{no,da}
clang -c --coverage d/a.c -o e/xa.o -dumpdir f/g && clang --coverage e/xa.o && ./a.out
a f/ga.gc{no,da}
```

The gcov code accidentally claims -c and -S, so -fsyntax-only -c/-S and
-E -c/-S don't lead to a -Wunused-command-line-argument warning. Keep
the unintended code for now.

clang/lib/Driver/ToolChains/Clang.cpp
clang/test/Driver/coverage.c
clang/test/Driver/working-directory.c

index e2c4012..be4fceb 100644 (file)
@@ -705,10 +705,10 @@ static void addDashXForInput(const ArgList &Args, const InputInfo &Input,
 }
 
 static void addPGOAndCoverageFlags(const ToolChain &TC, Compilation &C,
-                                   const Driver &D, const InputInfo &Output,
+                                   const JobAction &JA, const InputInfo &Output,
                                    const ArgList &Args, SanitizerArgs &SanArgs,
                                    ArgStringList &CmdArgs) {
-
+  const Driver &D = TC.getDriver();
   auto *PGOGenerateArg = Args.getLastArg(options::OPT_fprofile_generate,
                                          options::OPT_fprofile_generate_EQ,
                                          options::OPT_fno_profile_generate);
@@ -911,32 +911,39 @@ static void addPGOAndCoverageFlags(const ToolChain &TC, Compilation &C,
       Args.hasArg(options::OPT_coverage))
     FProfileDir = Args.getLastArg(options::OPT_fprofile_dir);
 
-  // Put the .gcno and .gcda files (if needed) next to the object file or
-  // bitcode file in the case of LTO.
-  // FIXME: There should be a simpler way to find the object file for this
-  // input, and this code probably does the wrong thing for commands that
-  // compile and link all at once.
-  if ((Args.hasArg(options::OPT_c) || Args.hasArg(options::OPT_S)) &&
-      (EmitCovNotes || EmitCovData) && Output.isFilename()) {
-    SmallString<128> OutputFilename;
-    if (Arg *FinalOutput = C.getArgs().getLastArg(options::OPT__SLASH_Fo))
-      OutputFilename = FinalOutput->getValue();
-    else if (Arg *FinalOutput = C.getArgs().getLastArg(options::OPT_o))
-      OutputFilename = FinalOutput->getValue();
-    else
-      OutputFilename = llvm::sys::path::filename(Output.getBaseInput());
-    SmallString<128> CoverageFilename = OutputFilename;
-    if (llvm::sys::path::is_relative(CoverageFilename))
-      (void)D.getVFS().makeAbsolute(CoverageFilename);
+  // TODO: Don't claim -c/-S to warn about -fsyntax-only -c/-S, -E -c/-S,
+  // like we warn about -fsyntax-only -E.
+  (void)(Args.hasArg(options::OPT_c) || Args.hasArg(options::OPT_S));
+
+  // Put the .gcno and .gcda files (if needed) next to the primary output file,
+  // or fall back to a file in the current directory for `clang -c --coverage
+  // d/a.c` in the absence of -o.
+  if (EmitCovNotes || EmitCovData) {
+    SmallString<128> CoverageFilename;
+    if (Arg *DumpDir = Args.getLastArgNoClaim(options::OPT_dumpdir)) {
+      // Form ${dumpdir}${basename}.gcno. Note that dumpdir may not end with a
+      // path separator.
+      CoverageFilename = DumpDir->getValue();
+      CoverageFilename += llvm::sys::path::filename(Output.getBaseInput());
+    } else if (Arg *FinalOutput =
+                   C.getArgs().getLastArg(options::OPT__SLASH_Fo)) {
+      CoverageFilename = FinalOutput->getValue();
+    } else if (Arg *FinalOutput = C.getArgs().getLastArg(options::OPT_o)) {
+      CoverageFilename = FinalOutput->getValue();
+    } else {
+      CoverageFilename = llvm::sys::path::filename(Output.getBaseInput());
+    }
     llvm::sys::path::replace_extension(CoverageFilename, "gcno");
-
-    CmdArgs.push_back("-coverage-notes-file");
-    CmdArgs.push_back(Args.MakeArgString(CoverageFilename));
+    if (EmitCovNotes) {
+      CmdArgs.push_back("-coverage-notes-file");
+      CmdArgs.push_back(Args.MakeArgString(CoverageFilename));
+    }
 
     if (EmitCovData) {
       if (FProfileDir) {
+        SmallString<128> Gcno = std::move(CoverageFilename);
         CoverageFilename = FProfileDir->getValue();
-        llvm::sys::path::append(CoverageFilename, OutputFilename);
+        llvm::sys::path::append(CoverageFilename, Gcno);
       }
       llvm::sys::path::replace_extension(CoverageFilename, "gcda");
       CmdArgs.push_back("-coverage-data-file");
@@ -5760,7 +5767,7 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
   // for sampling, overhead of call arc collection is way too high and there's
   // no way to collect the output.
   if (!Triple.isNVPTX() && !Triple.isAMDGCN())
-    addPGOAndCoverageFlags(TC, C, D, Output, Args, SanitizeArgs, CmdArgs);
+    addPGOAndCoverageFlags(TC, C, JA, Output, Args, SanitizeArgs, CmdArgs);
 
   Args.AddLastArg(CmdArgs, options::OPT_fclang_abi_compat_EQ);
 
index b1a0456..bb0bf45 100644 (file)
@@ -2,22 +2,22 @@
 // RUN: %clang -### -S -ftest-coverage -fno-test-coverage %s 2>&1 | FileCheck --check-prefix=NO-TEST-COVERAGE %s
 
 // TEST-COVERAGE: "-ftest-coverage"
-// TEST-COVERAGE: "-coverage-notes-file" "{{.*}}{{/|\\\\}}coverage.gcno"
+// TEST-COVERAGE: "-coverage-notes-file" "coverage.gcno"
 // NO-TEST-COVERAGE-NOT: "-coverage-notes-file"
 
 // RUN: %clang -### -S -fprofile-arcs %s 2>&1 | FileCheck --check-prefix=PROFILE-ARCS %s
 // RUN: %clang -### -S -fprofile-arcs -fno-profile-arcs %s 2>&1 | FileCheck --check-prefix=NO-PROFILE-ARCS %s
 
+// NO-PROFILE-ARCS-NOT: "-coverage-notes-file"
 // PROFILE-ARCS: "-fprofile-arcs"
-// PROFILE-ARCS: "-coverage-notes-file" "{{.*}}{{/|\\\\}}coverage.c"
-// NO-PROFILE-ARCS-NOT: "-ftest-coverage"
+// PROFILE-ARCS: "-coverage-data-file" "coverage.gcda"
 
-// RUN: %clang -### -S -fprofile-arcs %s -o /foo/bar.o 2>&1 | FileCheck --check-prefix=GCNO-LOCATION %s
+// RUN: %clang -### -S -ftest-coverage %s -o /foo/bar.o 2>&1 | FileCheck --check-prefix=GCNO-LOCATION %s
 // RUN: %clang_cl -### /c --coverage /Fo/foo/bar.obj -- %s 2>&1 | FileCheck --check-prefix=GCNO-LOCATION %s
-// RUN: %clang -### -c -fprofile-arcs %s -o foo/bar.o 2>&1 | FileCheck --check-prefix=GCNO-LOCATION-REL %s
+// RUN: %clang -### -c -ftest-coverage %s -o foo/bar.o 2>&1 | FileCheck --check-prefix=GCNO-LOCATION-REL %s
 
 // GCNO-LOCATION: "-coverage-notes-file" "{{.*}}/foo/bar.gcno"
-// GCNO-LOCATION-REL: "-coverage-notes-file" "{{.*}}{{/|\\\\}}foo/bar.gcno"
+// GCNO-LOCATION-REL: "-coverage-notes-file" "foo/bar.gcno"
 
 /// Don't warn -Wunused-command-line-argument.
 // RUN: %clang -E -Werror --coverage -ftest-coverage -fprofile-arcs %s
 // RUN: %clang -### -c %s 2>&1 | FileCheck --check-prefix=NO-COV %s
 // NO-COV-NOT: "-coverage-notes-file"
 // NO-COV-NOT: "-coverage-data-file"
+
+// RUN: rm -rf %t && mkdir %t && cd %t
+// RUN: mkdir d e f && cp %s d/a.c && touch d/b.c
+
+// RUN: %clang -### --coverage d/a.c d/b.c -o e/x 2>&1 | FileCheck %s --check-prefix=LINK1
+// LINK1: -cc1{{.*}} "-coverage-notes-file" "e/x-a.gcno" "-coverage-data-file" "e/x-a.gcda"
+// LINK1: -cc1{{.*}} "-coverage-notes-file" "e/x-b.gcno" "-coverage-data-file" "e/x-b.gcda"
+
+// RUN: %clang -### --coverage d/a.c d/b.c -o e/x -dumpdir f/g 2>&1 | FileCheck %s --check-prefix=LINK2
+// LINK2: -cc1{{.*}} "-coverage-notes-file" "f/ga.gcno" "-coverage-data-file" "f/ga.gcda"
+// LINK2: -cc1{{.*}} "-coverage-notes-file" "f/gb.gcno" "-coverage-data-file" "f/gb.gcda"
+
+// RUN: %clang -### --coverage d/a.c d/b.c -o e/x -fprofile-dir=f 2>&1 | FileCheck %s --check-prefix=LINK3
+// LINK3: -cc1{{.*}} "-coverage-notes-file" "e/x-a.gcno" "-coverage-data-file" "f{{/|\\\\}}e/x-a.gcda"
+// LINK3: -cc1{{.*}} "-coverage-notes-file" "e/x-b.gcno" "-coverage-data-file" "f{{/|\\\\}}e/x-b.gcda"
index 75c6dc4..8dd6be6 100644 (file)
@@ -6,6 +6,6 @@
 
 // CHECK_NO_FILE: no such file or directory: 'no_such_file.cpp'
 
-// CHECK_WORKS: "-coverage-notes-file" "{{[^"]+}}test{{/|\\\\}}Driver{{/|\\\\}}Inputs{{/|\\\\}}pchfile.gcno"
+// CHECK_WORKS: "-coverage-notes-file" "pchfile.gcno"
 // CHECK_WORKS: "-working-directory" "{{[^"]+}}test{{/|\\\\}}Driver{{/|\\\\}}Inputs"
 // CHECK_WORKS: "-fdebug-compilation-dir={{[^"]+}}test{{/|\\\\}}Driver{{/|\\\\}}Inputs"