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 <cstddef>
25 #include <cstring>
26 #include <fstream>
27 #include <iostream>
28 #include <optional>
29 #include <ostream>
30 #include <unordered_set>
31 #include <utility>
32 #include <vector>
33
34 #include "comparison.h"
35 #include "equality.h"
36 #include "error.h"
37 #include "fidelity.h"
38 #include "graph.h"
39 #include "input.h"
40 #include "naming.h"
41 #include "reader_options.h"
42 #include "reporting.h"
43 #include "runtime.h"
44
45 namespace {
46
47 const int kAbiChange = 4;
48 const int kFidelityChange = 8;
49 const size_t kMaxCrcOnlyChanges = 3;
50
51 using Inputs = std::vector<std::pair<stg::InputFormat, const char*>>;
52 using Outputs =
53 std::vector<std::pair<stg::reporting::OutputFormat, const char*>>;
54
Read(stg::Runtime & runtime,const Inputs & inputs,stg::Graph & graph,stg::ReadOptions options)55 std::vector<stg::Id> Read(stg::Runtime& runtime, const Inputs& inputs,
56 stg::Graph& graph, stg::ReadOptions options) {
57 std::vector<stg::Id> roots;
58 for (const auto& [format, filename] : inputs) {
59 roots.push_back(stg::Read(runtime, graph, format, filename, options,
60 nullptr));
61 }
62 return roots;
63 }
64
RunFidelity(const char * filename,const stg::Graph & graph,const std::vector<stg::Id> & roots)65 int RunFidelity(const char* filename, const stg::Graph& graph,
66 const std::vector<stg::Id>& roots) {
67 std::ofstream output(filename);
68 const auto fidelity_diff =
69 stg::GetFidelityTransitions(graph, roots[0], roots[1]);
70 const bool diffs_reported =
71 stg::reporting::FidelityDiff(fidelity_diff, output);
72 output << std::flush;
73 if (!output) {
74 stg::Die() << "error writing to " << '\'' << filename << '\'';
75 }
76 return diffs_reported ? kFidelityChange : 0;
77 }
78
RunExact(stg::Runtime & runtime,const Inputs & inputs,stg::ReadOptions options)79 int RunExact(stg::Runtime& runtime, const Inputs& inputs,
80 stg::ReadOptions options) {
81 stg::Graph graph;
82 const auto roots = Read(runtime, inputs, graph, options);
83
84 struct PairCache {
85 std::optional<bool> Query(const stg::Pair& comparison) const {
86 return equalities.find(comparison) != equalities.end()
87 ? std::make_optional(true)
88 : std::nullopt;
89 }
90 void AllSame(const std::vector<stg::Pair>& comparisons) {
91 for (const auto& comparison : comparisons) {
92 equalities.insert(comparison);
93 }
94 }
95 void AllDifferent(const std::vector<stg::Pair>&) {}
96 std::unordered_set<stg::Pair> equalities;
97 };
98
99 const stg::Time compute(runtime, "equality check");
100 PairCache equalities;
101 return stg::Equals<PairCache>(graph, equalities)(roots[0], roots[1])
102 ? 0
103 : kAbiChange;
104 }
105
Run(stg::Runtime & runtime,const Inputs & inputs,const Outputs & outputs,stg::Ignore ignore,stg::ReadOptions options,std::optional<const char * > fidelity)106 int Run(stg::Runtime& runtime, const Inputs& inputs, const Outputs& outputs,
107 stg::Ignore ignore, stg::ReadOptions options,
108 std::optional<const char*> fidelity) {
109 // Read inputs.
110 stg::Graph graph;
111 const auto roots = Read(runtime, inputs, graph, options);
112
113 // Compute differences.
114 stg::Compare compare{runtime, graph, ignore};
115 std::pair<bool, std::optional<stg::Comparison>> result;
116 {
117 const stg::Time compute(runtime, "compute diffs");
118 result = compare(roots[0], roots[1]);
119 }
120 stg::Check(compare.scc.Empty()) << "internal error: SCC state broken";
121 const auto& [equals, comparison] = result;
122 int status = equals ? 0 : kAbiChange;
123
124 // Write reports.
125 stg::NameCache names;
126 for (const auto& [format, filename] : outputs) {
127 std::ofstream output(filename);
128 if (comparison) {
129 const stg::Time report(runtime, "report diffs");
130 const stg::reporting::Options options{format, kMaxCrcOnlyChanges};
131 const stg::reporting::Reporting reporting{graph, compare.outcomes,
132 options, names};
133 Report(reporting, *comparison, output);
134 output << std::flush;
135 }
136 if (!output) {
137 stg::Die() << "error writing to " << '\'' << filename << '\'';
138 }
139 }
140
141 // Compute fidelity diff if requested.
142 if (fidelity) {
143 const stg::Time report(runtime, "fidelity");
144 status |= RunFidelity(*fidelity, graph, roots);
145 }
146
147 return status;
148 }
149
150 } // namespace
151
main(int argc,char * argv[])152 int main(int argc, char* argv[]) {
153 // Process arguments.
154 bool opt_metrics = false;
155 bool opt_exact = false;
156 stg::ReadOptions opt_read_options;
157 std::optional<const char*> opt_fidelity = std::nullopt;
158 stg::Ignore opt_ignore;
159 stg::InputFormat opt_input_format = stg::InputFormat::ABI;
160 stg::reporting::OutputFormat opt_output_format =
161 stg::reporting::OutputFormat::PLAIN;
162 Inputs inputs;
163 Outputs outputs;
164 static option opts[] = {
165 {"metrics", no_argument, nullptr, 'm'},
166 {"abi", no_argument, nullptr, 'a'},
167 {"btf", no_argument, nullptr, 'b'},
168 {"elf", no_argument, nullptr, 'e'},
169 {"stg", no_argument, nullptr, 's'},
170 {"exact", no_argument, nullptr, 'x'},
171 {"types", no_argument, nullptr, 't'},
172 {"ignore", required_argument, nullptr, 'i'},
173 {"format", required_argument, nullptr, 'f'},
174 {"output", required_argument, nullptr, 'o'},
175 {"fidelity", required_argument, nullptr, 'F'},
176 {nullptr, 0, nullptr, 0 },
177 };
178 auto usage = [&]() {
179 std::cerr << "usage: " << argv[0] << '\n'
180 << " [-m|--metrics]\n"
181 << " [-a|--abi|-b|--btf|-e|--elf|-s|--stg] file1\n"
182 << " [-a|--abi|-b|--btf|-e|--elf|-s|--stg] file2\n"
183 << " [-x|--exact]\n"
184 << " [-t|--types]\n"
185 << " [{-i|--ignore} <ignore-option>] ...\n"
186 << " [{-f|--format} <output-format>] ...\n"
187 << " [{-o|--output} {filename|-}] ...\n"
188 << " [{-F|--fidelity} {filename|-}]\n"
189 << "implicit defaults: --abi --format plain\n"
190 << "--exact (node equality) cannot be combined with --output\n"
191 << stg::reporting::OutputFormatUsage()
192 << stg::IgnoreUsage();
193 return 1;
194 };
195 while (true) {
196 int ix;
197 const int c = getopt_long(argc, argv, "-mabesxti:f:o:F:", opts, &ix);
198 if (c == -1) {
199 break;
200 }
201 const char* argument = optarg;
202 switch (c) {
203 case 'm':
204 opt_metrics = true;
205 break;
206 case 'a':
207 opt_input_format = stg::InputFormat::ABI;
208 break;
209 case 'b':
210 opt_input_format = stg::InputFormat::BTF;
211 break;
212 case 'e':
213 opt_input_format = stg::InputFormat::ELF;
214 break;
215 case 's':
216 opt_input_format = stg::InputFormat::STG;
217 break;
218 case 'x':
219 opt_exact = true;
220 break;
221 case 't':
222 opt_read_options.Set(stg::ReadOptions::TYPE_ROOTS);
223 break;
224 case 1:
225 inputs.emplace_back(opt_input_format, argument);
226 break;
227 case 'i':
228 if (const auto ignore = stg::ParseIgnore(argument)) {
229 opt_ignore.Set(ignore.value());
230 } else {
231 std::cerr << "unknown ignore option: " << argument << '\n'
232 << stg::IgnoreUsage();
233 return 1;
234 }
235 break;
236 case 'f':
237 if (const auto format = stg::reporting::ParseOutputFormat(argument)) {
238 opt_output_format = format.value();
239 } else {
240 std::cerr << "unknown output format: " << argument << '\n'
241 << stg::reporting::OutputFormatUsage();
242 return 1;
243 }
244 break;
245 case 'o':
246 if (strcmp(argument, "-") == 0) {
247 argument = "/dev/stdout";
248 }
249 outputs.emplace_back(opt_output_format, argument);
250 break;
251 case 'F':
252 if (strcmp(argument, "-") == 0) {
253 argument = "/dev/stdout";
254 }
255 opt_fidelity.emplace(argument);
256 break;
257 default:
258 return usage();
259 }
260 }
261 if (inputs.size() != 2 || opt_exact > outputs.empty()) {
262 return usage();
263 }
264
265 try {
266 stg::Runtime runtime(std::cerr, opt_metrics);
267 return opt_exact ? RunExact(runtime, inputs, opt_read_options)
268 : Run(runtime, inputs, outputs, opt_ignore,
269 opt_read_options, opt_fidelity);
270 } catch (const stg::Exception& e) {
271 std::cerr << e.what();
272 return 1;
273 }
274 }
275