[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");
+ }
+}