[Remarks] Add support for linking remarks

Remarks are usually emitted per-TU, and for generating a standalone
remark file that can be shipped with the linked binary we need some kind
of tool to merge everything together.

The remarks::RemarkLinker class takes care of this and:

* Deduplicates remarks
* Filters remarks with no debug location
* Merges string tables from all the entries

As an output, it provides an iterator range that can be used to
serialize the remarks to a file.

Differential Revision: https://reviews.llvm.org/D69141
diff --git a/llvm/unittests/Remarks/RemarksLinkingTest.cpp b/llvm/unittests/Remarks/RemarksLinkingTest.cpp
new file mode 100644
index 0000000..8d38c6b
--- /dev/null
+++ b/llvm/unittests/Remarks/RemarksLinkingTest.cpp
@@ -0,0 +1,217 @@
+//===- unittest/Support/RemarksLinkingTest.cpp - Linking tests ------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Bitcode/BitcodeAnalyzer.h"
+#include "llvm/Remarks/RemarkLinker.h"
+#include "llvm/Remarks/RemarkSerializer.h"
+#include "llvm/Support/raw_ostream.h"
+#include "gtest/gtest.h"
+#include <string>
+
+using namespace llvm;
+
+static void serializeAndCheck(remarks::RemarkLinker &RL,
+                              remarks::Format OutputFormat,
+                              StringRef ExpectedOutput) {
+  // 1. Create a serializer.
+  // 2. Serialize all the remarks from the linker.
+  // 3. Check that it matches the output.
+  std::string Buf;
+  raw_string_ostream OS(Buf);
+  Error E = RL.serialize(OS, OutputFormat);
+  EXPECT_FALSE(static_cast<bool>(E));
+
+  // For bitstream, run it through the analyzer.
+  if (OutputFormat == remarks::Format::Bitstream) {
+    std::string AnalyzeBuf;
+    raw_string_ostream AnalyzeOS(AnalyzeBuf);
+    BCDumpOptions O(AnalyzeOS);
+    O.ShowBinaryBlobs = true;
+    BitcodeAnalyzer BA(OS.str());
+    EXPECT_FALSE(BA.analyze(O)); // Expect no errors.
+    EXPECT_EQ(AnalyzeOS.str(), ExpectedOutput);
+  } else {
+    EXPECT_EQ(OS.str(), ExpectedOutput);
+  }
+}
+
+static void check(remarks::Format InputFormat, StringRef Input,
+                  remarks::Format OutputFormat, StringRef ExpectedOutput) {
+  remarks::RemarkLinker RL;
+  EXPECT_FALSE(RL.link(Input, InputFormat));
+  serializeAndCheck(RL, OutputFormat, ExpectedOutput);
+}
+
+static void check(remarks::Format InputFormat, StringRef Input,
+                  remarks::Format InputFormat2, StringRef Input2,
+                  remarks::Format OutputFormat, StringRef ExpectedOutput) {
+  remarks::RemarkLinker RL;
+  EXPECT_FALSE(RL.link(Input, InputFormat));
+  EXPECT_FALSE(RL.link(Input2, InputFormat2));
+  serializeAndCheck(RL, OutputFormat, ExpectedOutput);
+}
+
+TEST(Remarks, LinkingGoodYAML) {
+  // One YAML remark.
+  check(remarks::Format::YAML,
+        "--- !Missed\n"
+        "Pass:            inline\n"
+        "Name:            NoDefinition\n"
+        "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n"
+        "Function:        foo\n"
+        "...\n",
+        remarks::Format::YAML,
+        "--- !Missed\n"
+        "Pass:            inline\n"
+        "Name:            NoDefinition\n"
+        "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n"
+        "Function:        foo\n"
+        "...\n");
+
+  // Check that we don't keep remarks without debug locations.
+  check(remarks::Format::YAML,
+        "--- !Missed\n"
+        "Pass:            inline\n"
+        "Name:            NoDefinition\n"
+        "Function:        foo\n"
+        "...\n",
+        remarks::Format::YAML, "");
+
+  // Check that we deduplicate remarks.
+  check(remarks::Format::YAML,
+        "--- !Missed\n"
+        "Pass:            inline\n"
+        "Name:            NoDefinition\n"
+        "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n"
+        "Function:        foo\n"
+        "...\n"
+        "--- !Missed\n"
+        "Pass:            inline\n"
+        "Name:            NoDefinition\n"
+        "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n"
+        "Function:        foo\n"
+        "...\n",
+        remarks::Format::YAML,
+        "--- !Missed\n"
+        "Pass:            inline\n"
+        "Name:            NoDefinition\n"
+        "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n"
+        "Function:        foo\n"
+        "...\n");
+}
+
+TEST(Remarks, LinkingGoodBitstream) {
+  // One YAML remark.
+  check(remarks::Format::YAML,
+        "--- !Missed\n"
+        "Pass:            inline\n"
+        "Name:            NoDefinition\n"
+        "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n"
+        "Function:        foo\n"
+        "...\n",
+        remarks::Format::Bitstream,
+        "<BLOCKINFO_BLOCK/>\n"
+        "<Meta BlockID=8 NumWords=12 BlockCodeSize=3>\n"
+        "  <Container info codeid=1 abbrevid=4 op0=0 op1=2/>\n"
+        "  <Remark version codeid=2 abbrevid=5 op0=0/>\n"
+        "  <String table codeid=3 abbrevid=6/> blob data = "
+        "'inline\\x00NoDefinition\\x00foo\\x00file.c\\x00'\n"
+        "</Meta>\n"
+        "<Remark BlockID=9 NumWords=4 BlockCodeSize=4>\n"
+        "  <Remark header codeid=5 abbrevid=4 op0=2 op1=1 op2=0 op3=2/>\n"
+        "  <Remark debug location codeid=6 abbrevid=5 op0=3 op1=3 op2=12/>\n"
+        "</Remark>\n");
+
+  // Check that we deduplicate remarks.
+  check(remarks::Format::YAML,
+        "--- !Missed\n"
+        "Pass:            inline\n"
+        "Name:            NoDefinition\n"
+        "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n"
+        "Function:        foo\n"
+        "...\n"
+        "--- !Missed\n"
+        "Pass:            inline\n"
+        "Name:            NoDefinition\n"
+        "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n"
+        "Function:        foo\n"
+        "...\n",
+        remarks::Format::Bitstream,
+        "<BLOCKINFO_BLOCK/>\n"
+        "<Meta BlockID=8 NumWords=12 BlockCodeSize=3>\n"
+        "  <Container info codeid=1 abbrevid=4 op0=0 op1=2/>\n"
+        "  <Remark version codeid=2 abbrevid=5 op0=0/>\n"
+        "  <String table codeid=3 abbrevid=6/> blob data = "
+        "'inline\\x00NoDefinition\\x00foo\\x00file.c\\x00'\n"
+        "</Meta>\n"
+        "<Remark BlockID=9 NumWords=4 BlockCodeSize=4>\n"
+        "  <Remark header codeid=5 abbrevid=4 op0=2 op1=1 op2=0 op3=2/>\n"
+        "  <Remark debug location codeid=6 abbrevid=5 op0=3 op1=3 op2=12/>\n"
+        "</Remark>\n");
+}
+
+TEST(Remarks, LinkingGoodStrTab) {
+  // Check that remarks from different entries use the same strtab.
+  check(remarks::Format::YAML,
+        "--- !Missed\n"
+        "Pass:            inline\n"
+        "Name:            NoDefinition\n"
+        "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n"
+        "Function:        foo\n"
+        "...\n",
+        remarks::Format::YAML,
+        "--- !Passed\n"
+        "Pass:            inline\n"
+        "Name:            Ok\n"
+        "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n"
+        "Function:        foo\n"
+        "...\n",
+        remarks::Format::YAMLStrTab,
+        StringRef("REMARKS\0\0\0\0\0\0\0\0\0\x22\0\0\0\0\0\0\0"
+                  "inline\0NoDefinition\0foo\0file.c\0Ok\0"
+                  "--- !Passed\n"
+                  "Pass:            0\n"
+                  "Name:            4\n"
+                  "DebugLoc:        { File: 3, Line: 3, Column: 12 }\n"
+                  "Function:        2\n"
+                  "...\n"
+                  "--- !Missed\n"
+                  "Pass:            0\n"
+                  "Name:            1\n"
+                  "DebugLoc:        { File: 3, Line: 3, Column: 12 }\n"
+                  "Function:        2\n"
+                  "...\n",
+                  304));
+}
+
+// Check that we propagate parsing errors.
+TEST(Remarks, LinkingError) {
+  remarks::RemarkLinker RL;
+  {
+    Error E = RL.link("badyaml", remarks::Format::YAML);
+    EXPECT_TRUE(static_cast<bool>(E));
+    EXPECT_EQ(toString(std::move(E)),
+              "YAML:1:1: error: document root is not of mapping type.\n"
+              "\n"
+              "badyaml\n"
+              "^~~~~~~\n"
+              "\n");
+  }
+
+  {
+    // Check that the prepend path is propagated and fails with the full path.
+    RL.setExternalFilePrependPath("/baddir/");
+    Error E = RL.link(
+        StringRef("REMARKS\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0badfile.opt.yaml",
+                  40),
+        remarks::Format::YAMLStrTab);
+    EXPECT_TRUE(static_cast<bool>(E));
+    EXPECT_EQ(toString(std::move(E)),
+              "'/baddir/badfile.opt.yaml': No such file or directory");
+  }
+}