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