xref: /aosp_15_r20/external/stg/stgdiff.cc (revision 9e3b08ae94a55201065475453d799e8b1378bea6)
1 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
2 // -*- mode: C++ -*-
3 //
4 // Copyright 2020-2023 Google LLC
5 //
6 // Licensed under the Apache License v2.0 with LLVM Exceptions (the
7 // "License"); you may not use this file except in compliance with the
8 // License.  You may obtain a copy of the License at
9 //
10 //     https://llvm.org/LICENSE.txt
11 //
12 // Unless required by applicable law or agreed to in writing, software
13 // distributed under the License is distributed on an "AS IS" BASIS,
14 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 // See the License for the specific language governing permissions and
16 // limitations under the License.
17 //
18 // Author: Maria Teguiani
19 // Author: Giuliano Procida
20 // Author: Siddharth Nayyar
21 
22 #include <getopt.h>
23 
24 #include <cstring>
25 #include <fstream>
26 #include <iostream>
27 #include <optional>
28 #include <ostream>
29 #include <unordered_set>
30 #include <utility>
31 #include <vector>
32 
33 #include "comparison.h"
34 #include "equality.h"
35 #include "error.h"
36 #include "fidelity.h"
37 #include "graph.h"
38 #include "input.h"
39 #include "naming.h"
40 #include "reader_options.h"
41 #include "reporting.h"
42 #include "runtime.h"
43 
44 namespace {
45 
46 const int kAbiChange = 4;
47 const int kFidelityChange = 8;
48 
49 using Inputs = std::vector<std::pair<stg::InputFormat, const char*>>;
50 using Outputs =
51     std::vector<std::pair<stg::reporting::OutputFormat, const char*>>;
52 
Read(stg::Runtime & runtime,const Inputs & inputs,stg::Graph & graph,stg::ReadOptions options)53 std::vector<stg::Id> Read(stg::Runtime& runtime, const Inputs& inputs,
54                           stg::Graph& graph, stg::ReadOptions options) {
55   std::vector<stg::Id> roots;
56   for (const auto& [format, filename] : inputs) {
57     roots.push_back(stg::Read(runtime, graph, format, filename, options,
58                               nullptr));
59   }
60   return roots;
61 }
62 
RunFidelity(const char * filename,const stg::Graph & graph,const std::vector<stg::Id> & roots)63 int RunFidelity(const char* filename, const stg::Graph& graph,
64                 const std::vector<stg::Id>& roots) {
65   std::ofstream output(filename);
66   const auto fidelity_diff =
67       stg::GetFidelityTransitions(graph, roots[0], roots[1]);
68   const bool diffs_reported =
69       stg::reporting::FidelityDiff(fidelity_diff, output);
70   output << std::flush;
71   if (!output) {
72     stg::Die() << "error writing to " << '\'' << filename << '\'';
73   }
74   return diffs_reported ? kFidelityChange : 0;
75 }
76 
RunExact(stg::Runtime & runtime,const stg::Graph & graph,const std::vector<stg::Id> & roots)77 int RunExact(stg::Runtime& runtime, const stg::Graph& graph,
78              const std::vector<stg::Id>& roots) {
79   struct PairCache {
80     std::optional<bool> Query(const stg::Pair& comparison) const {
81       return equalities.find(comparison) != equalities.end()
82           ? std::make_optional(true)
83           : std::nullopt;
84     }
85     void AllSame(const std::vector<stg::Pair>& comparisons) {
86       for (const auto& comparison : comparisons) {
87         equalities.insert(comparison);
88       }
89     }
90     void AllDifferent(const std::vector<stg::Pair>&) {}
91     std::unordered_set<stg::Pair> equalities;
92   };
93 
94   const stg::Time compute(runtime, "equality check");
95   PairCache equalities;
96   return stg::Equals<PairCache>(graph, equalities)(roots[0], roots[1])
97              ? 0
98              : kAbiChange;
99 }
100 
Run(stg::Runtime & runtime,const stg::Graph & graph,const std::vector<stg::Id> & roots,const Outputs & outputs,stg::diff::Ignore ignore,std::optional<const char * > fidelity)101 int Run(stg::Runtime& runtime, const stg::Graph& graph,
102         const std::vector<stg::Id>& roots, const Outputs& outputs,
103         stg::diff::Ignore ignore, std::optional<const char*> fidelity) {
104   // Compute differences.
105   stg::diff::Outcomes outcomes;
106   stg::diff::Comparison comparison;
107   {
108     const stg::Time compute(runtime, "compute diffs");
109     comparison = stg::diff::Compare(
110         runtime, ignore, graph, roots[0], roots[1], outcomes);
111   }
112   const bool same = comparison == stg::diff::Comparison{};
113   int status = same ? 0 : kAbiChange;
114 
115   // Write reports.
116   stg::NameCache names;
117   for (const auto& [format, filename] : outputs) {
118     std::ofstream output(filename);
119     if (!same) {
120       const stg::Time report(runtime, "report diffs");
121       const stg::reporting::Options options{format};
122       const stg::reporting::Reporting reporting{graph, outcomes, options,
123         names};
124       Report(reporting, comparison, output);
125       output << std::flush;
126     }
127     if (!output) {
128       stg::Die() << "error writing to " << '\'' << filename << '\'';
129     }
130   }
131 
132   // Compute fidelity diff if requested.
133   if (fidelity) {
134     const stg::Time report(runtime, "fidelity");
135     status |= RunFidelity(*fidelity, graph, roots);
136   }
137 
138   return status;
139 }
140 
141 }  // namespace
142 
main(int argc,char * argv[])143 int main(int argc, char* argv[]) {
144   // Process arguments.
145   bool opt_metrics = false;
146   bool opt_exact = false;
147   stg::ReadOptions opt_read_options;
148   std::optional<const char*> opt_fidelity = std::nullopt;
149   stg::diff::Ignore opt_ignore;
150   stg::InputFormat opt_input_format = stg::InputFormat::ABI;
151   stg::reporting::OutputFormat opt_output_format =
152       stg::reporting::OutputFormat::SMALL;
153   Inputs inputs;
154   Outputs outputs;
155   static option opts[] = {
156       {"metrics",        no_argument,       nullptr, 'm'},
157       {"abi",            no_argument,       nullptr, 'a'},
158       {"btf",            no_argument,       nullptr, 'b'},
159       {"elf",            no_argument,       nullptr, 'e'},
160       {"stg",            no_argument,       nullptr, 's'},
161       {"exact",          no_argument,       nullptr, 'x'},
162       {"types",          no_argument,       nullptr, 't'},
163       {"ignore",         required_argument, nullptr, 'i'},
164       {"format",         required_argument, nullptr, 'f'},
165       {"output",         required_argument, nullptr, 'o'},
166       {"fidelity",       required_argument, nullptr, 'F'},
167       {nullptr,          0,                 nullptr, 0  },
168   };
169   auto usage = [&]() {
170     std::cerr << "usage: " << argv[0] << '\n'
171               << "  [-m|--metrics]\n"
172               << "  [-a|--abi|-b|--btf|-e|--elf|-s|--stg] file1\n"
173               << "  [-a|--abi|-b|--btf|-e|--elf|-s|--stg] file2\n"
174               << "  [-x|--exact]\n"
175               << "  [-t|--types]\n"
176               << "  [{-i|--ignore} <ignore-option>] ...\n"
177               << "  [{-f|--format} <output-format>] ...\n"
178               << "  [{-o|--output} {filename|-}] ...\n"
179               << "  [{-F|--fidelity} {filename|-}]\n"
180               << "implicit defaults: --abi --format small\n"
181               << "--exact (node equality) cannot be combined with --output\n"
182               << stg::reporting::OutputFormatUsage()
183               << stg::diff::IgnoreUsage();
184     return 1;
185   };
186   while (true) {
187     int ix;
188     const int c = getopt_long(argc, argv, "-mabesxti:f:o:F:", opts, &ix);
189     if (c == -1) {
190       break;
191     }
192     const char* argument = optarg;
193     switch (c) {
194       case 'm':
195         opt_metrics = true;
196         break;
197       case 'a':
198         opt_input_format = stg::InputFormat::ABI;
199         break;
200       case 'b':
201         opt_input_format = stg::InputFormat::BTF;
202         break;
203       case 'e':
204         opt_input_format = stg::InputFormat::ELF;
205         break;
206       case 's':
207         opt_input_format = stg::InputFormat::STG;
208         break;
209       case 'x':
210         opt_exact = true;
211         break;
212       case 't':
213         opt_read_options.Set(stg::ReadOptions::TYPE_ROOTS);
214         break;
215       case 1:
216         inputs.emplace_back(opt_input_format, argument);
217         break;
218       case 'i':
219         if (const auto ignore = stg::diff::ParseIgnore(argument)) {
220           opt_ignore.Set(ignore.value());
221         } else {
222           std::cerr << "unknown ignore option: " << argument << '\n'
223                     << stg::diff::IgnoreUsage();
224           return 1;
225         }
226         break;
227       case 'f':
228         if (const auto format = stg::reporting::ParseOutputFormat(argument)) {
229           opt_output_format = format.value();
230         } else {
231           std::cerr << "unknown output format: " << argument << '\n'
232                     << stg::reporting::OutputFormatUsage();
233           return 1;
234         }
235         break;
236       case 'o':
237         if (strcmp(argument, "-") == 0) {
238           argument = "/dev/stdout";
239         }
240         outputs.emplace_back(opt_output_format, argument);
241         break;
242       case 'F':
243         if (strcmp(argument, "-") == 0) {
244           argument = "/dev/stdout";
245         }
246         opt_fidelity.emplace(argument);
247         break;
248       default:
249         return usage();
250     }
251   }
252   if (inputs.size() != 2 || opt_exact > outputs.empty()) {
253     return usage();
254   }
255 
256   try {
257     stg::Runtime runtime(std::cerr, opt_metrics);
258     stg::Graph graph;
259     const auto roots = Read(runtime, inputs, graph, opt_read_options);
260     return opt_exact ? RunExact(runtime, graph, roots)
261                      : Run(runtime, graph, roots, outputs, opt_ignore,
262                            opt_fidelity);
263   } catch (const stg::Exception& e) {
264     std::cerr << e.what();
265     return 1;
266   }
267 }
268