CCTLib
Calling-context and data-object attribution library for Intel Pin
rvn_integration_test.cpp
Go to the documentation of this file.
1 // GoogleTest-driven integration tests for runtime_value_numbering_client
2 // (RVN).
3 //
4 // Semantics: RVN assigns value numbers to intermediate computations at
5 // runtime. If two dynamic instructions produce the same value number on the
6 // same operand value numbers, the second is REDUNDANT (its result was
7 // already computed).
8 //
9 // User note: RVN has known correctness TODOs; these tests focus on the
10 // smoke-level and coarse redundancy fraction rather than fine-grained
11 // context accuracy.
12 //
13 // Output format: `Redundancy: <fraction>` -- fraction of redundant
14 // instructions among total instrumented instructions.
15 
16 #include <cerrno>
17 #include <cstdio>
18 #include <cstdlib>
19 #include <cstring>
20 #include <fcntl.h>
21 #include <fstream>
22 #include <regex>
23 #include <sstream>
24 #include <string>
25 #include <sys/wait.h>
26 #include <unistd.h>
27 #include <vector>
28 
29 #include <gtest/gtest.h>
30 
31 namespace {
32 
33 std::string env(const char* n) { const char* v = getenv(n); return v ? v : ""; }
34 std::string cctlib_root() { std::string r = env("CCTLIB_ROOT"); return r.empty() ? "../.." : r; }
35 std::string pin_root() { return env("PIN_ROOT"); }
36 
37 int run_pin(const std::string& tool, const std::vector<std::string>& args) {
38  std::string pin = pin_root() + "/pin";
39  std::vector<char*> argv;
40  argv.push_back(const_cast<char*>(pin.c_str()));
41  argv.push_back(const_cast<char*>("-t"));
42  argv.push_back(const_cast<char*>(tool.c_str()));
43  argv.push_back(const_cast<char*>("--"));
44  for (auto& a : args) argv.push_back(const_cast<char*>(a.c_str()));
45  argv.push_back(nullptr);
46  pid_t pid = fork();
47  if (pid < 0) return -1;
48  if (pid == 0) {
49  if (!env("CCTLIB_TEST_VERBOSE").size()) {
50  int devnull = open("/dev/null", O_WRONLY);
51  if (devnull >= 0) { dup2(devnull, 1); dup2(devnull, 2); close(devnull); }
52  }
53  execv(pin.c_str(), argv.data());
54  _exit(127);
55  }
56  int status = 0;
57  if (waitpid(pid, &status, 0) < 0) return -1;
58  if (WIFEXITED(status)) return WEXITSTATUS(status);
59  return -2;
60 }
61 
62 std::string find_newest(const std::string& dir, const std::string& prefix) {
63  std::string cmd = "ls -t " + dir + "/" + prefix + "* 2>/dev/null | head -1";
64  FILE* p = popen(cmd.c_str(), "r");
65  if (!p) return {};
66  char buf[4096]; std::string out;
67  if (fgets(buf, sizeof(buf), p)) out = buf;
68  pclose(p);
69  if (!out.empty() && out.back() == '\n') out.pop_back();
70  return out;
71 }
72 std::string read_file(const std::string& path) {
73  std::ifstream in(path); std::stringstream ss; ss << in.rdbuf(); return ss.str();
74 }
75 void cleanup(const std::string& dir, const std::string& prefix) {
76  std::string cmd = "rm -f " + dir + "/" + prefix + "*"; (void)system(cmd.c_str());
77 }
78 
79 // Parses the FIRST `Redundancy: <fraction>` line.
80 double parse_redundancy(const std::string& content) {
81  std::regex re(R"(Redundancy:\s*([\d.]+))");
82  std::smatch m;
83  if (!std::regex_search(content, m, re)) return -1;
84  return std::stod(m[1]);
85 }
86 
87 class RVNIntegration : public ::testing::Test {
88  protected:
89  std::string root_, tool_;
90  void SetUp() override {
91  root_ = cctlib_root();
92  ASSERT_FALSE(pin_root().empty());
93  tool_ = root_ + "/clients/obj-intel64/runtime_value_numbering_client.so";
94  ASSERT_EQ(0, access(tool_.c_str(), F_OK)) << tool_ << " missing";
95  ASSERT_EQ(0, chdir(root_.c_str())) << "chdir failed";
96  }
97  double run_and_parse_red(const std::string& victim) {
98  cleanup(root_, "ValueNumbering.out.");
99  int rc = run_pin(tool_, {victim});
100  EXPECT_EQ(0, rc);
101  std::string out = find_newest(root_, "ValueNumbering.out.");
102  EXPECT_FALSE(out.empty());
103  return parse_redundancy(read_file(out));
104  }
105 };
106 
107 TEST_F(RVNIntegration, RunsCleanlyOnLs) {
108  double r = run_and_parse_red("/bin/ls");
109  ASSERT_GE(r, 0.0);
110  EXPECT_LT(r, 1.0);
111 }
112 
113 // The TP victim recomputes the SAME lea twice per iteration on identical
114 // operands; RVN should mark the second lea's result as redundant. The TN
115 // victim varies operands so neither result matches a previous computation.
116 //
117 // User note: RVN has open correctness TODOs, so this test asserts only
118 // that TP >= TN (not TP > TN by a large margin) -- if the tool ever
119 // starts flagging the TP victim's redundant recomputations, this test
120 // will catch a regression. If it stops flagging, that would be a
121 // correctness regression this test surfaces.
122 TEST_F(RVNIntegration, TruePositiveAtLeastAsRedundantAsTrueNegative) {
123  double tp = run_and_parse_red(root_ + "/tests/gtest/obj/apps/rvn_tp_repeated");
124  double tn = run_and_parse_red(root_ + "/tests/gtest/obj/apps/rvn_tn_different");
125  ASSERT_GE(tp, 0.0);
126  ASSERT_GE(tn, 0.0);
127  EXPECT_GE(tp, tn - 0.02) // small tolerance
128  << "TP=" << tp << " TN=" << tn;
129 }
130 
131 // ISA breadth: repeated identical scalar ADD in inline asm. The victim
132 // runs a tight loop that computes a+b twice per iteration on the same
133 // operands. The second addq should get the same value number as the
134 // first. RVN should report non-zero redundancy on this workload.
135 TEST_F(RVNIntegration, RepeatedAddRunsCleanly) {
136  double r = run_and_parse_red(root_ + "/tests/gtest/obj/apps/isa/rvn_repeated_add");
137  ASSERT_GE(r, 0.0);
138  EXPECT_LT(r, 1.0);
139 }
140 
141 } // namespace
static uint64_t buf[ITERS]
volatile uint8_t status
Definition: cctlib.cpp:230
std::string find_newest(const std::string &dir, const std::string &prefix)
void cleanup(const std::string &dir, const std::string &prefix)
TEST_F(RVNIntegration, RepeatedAddRunsCleanly)
int run_pin(const std::string &tool, const std::vector< std::string > &args)
double parse_redundancy(const std::string &content)
std::string read_file(const std::string &path)
int a[1000]
Definition: testArray.c:5