blob: 18cc733d47c1f35db32f0fec356a4b8ba5c94582 [file] [log] [blame]
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// -*- mode: C++ -*-
//
// Copyright 2020-2023 Google LLC
//
// Licensed under the Apache License v2.0 with LLVM Exceptions (the
// "License"); you may not use this file except in compliance with the
// License. You may obtain a copy of the License at
//
// https://llvm.org/LICENSE.txt
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: Maria Teguiani
// Author: Giuliano Procida
// Author: Siddharth Nayyar
#include <getopt.h>
#include <cstddef>
#include <cstring>
#include <fstream>
#include <iostream>
#include <optional>
#include <ostream>
#include <unordered_set>
#include <utility>
#include <vector>
#include "equality.h"
#include "error.h"
#include "fidelity.h"
#include "graph.h"
#include "input.h"
#include "metrics.h"
#include "reader_options.h"
#include "reporting.h"
namespace {
const int kAbiChange = 4;
const int kFidelityChange = 8;
const size_t kMaxCrcOnlyChanges = 3;
using Inputs = std::vector<std::pair<stg::InputFormat, const char*>>;
using Outputs =
std::vector<std::pair<stg::reporting::OutputFormat, const char*>>;
std::vector<stg::Id> Read(const Inputs& inputs, stg::Graph& graph,
stg::ReadOptions options, stg::Metrics& metrics) {
std::vector<stg::Id> roots;
for (const auto& [format, filename] : inputs) {
roots.push_back(stg::Read(graph, format, filename, options, nullptr,
metrics));
}
return roots;
}
int RunFidelity(const char* filename, const stg::Graph& graph,
const std::vector<stg::Id>& roots) {
std::ofstream output(filename);
const auto fidelity_diff =
stg::GetFidelityTransitions(graph, roots[0], roots[1]);
const bool diffs_reported =
stg::reporting::FidelityDiff(fidelity_diff, output);
output << std::flush;
if (!output) {
stg::Die() << "error writing to " << '\'' << filename << '\'';
}
return diffs_reported ? kFidelityChange : 0;
}
int RunExact(const Inputs& inputs, stg::ReadOptions options,
stg::Metrics& metrics) {
stg::Graph graph;
const auto roots = Read(inputs, graph, options, metrics);
struct PairCache {
std::optional<bool> Query(const stg::Pair& comparison) const {
return equalities.find(comparison) != equalities.end()
? std::make_optional(true)
: std::nullopt;
}
void AllSame(const std::vector<stg::Pair>& comparisons) {
for (const auto& comparison : comparisons) {
equalities.insert(comparison);
}
}
void AllDifferent(const std::vector<stg::Pair>&) {}
std::unordered_set<stg::Pair> equalities;
};
stg::Time compute(metrics, "equality check");
PairCache equalities;
return stg::Equals<PairCache>(graph, equalities)(roots[0], roots[1])
? 0
: kAbiChange;
}
int Run(const Inputs& inputs, const Outputs& outputs, stg::Ignore ignore,
stg::ReadOptions options, std::optional<const char*> fidelity,
stg::Metrics& metrics) {
// Read inputs.
stg::Graph graph;
const auto roots = Read(inputs, graph, options, metrics);
// Compute differences.
stg::Compare compare{graph, ignore, metrics};
std::pair<bool, std::optional<stg::Comparison>> result;
{
stg::Time compute(metrics, "compute diffs");
result = compare(roots[0], roots[1]);
}
stg::Check(compare.scc.Empty()) << "internal error: SCC state broken";
const auto& [equals, comparison] = result;
int status = equals ? 0 : kAbiChange;
// Write reports.
stg::NameCache names;
for (const auto& [format, filename] : outputs) {
std::ofstream output(filename);
if (comparison) {
stg::Time report(metrics, "report diffs");
stg::reporting::Options options{format, kMaxCrcOnlyChanges};
stg::reporting::Reporting reporting{graph, compare.outcomes, options,
names};
Report(reporting, *comparison, output);
output << std::flush;
}
if (!output) {
stg::Die() << "error writing to " << '\'' << filename << '\'';
}
}
// Compute fidelity diff if requested.
if (fidelity) {
const stg::Time report(metrics, "fidelity");
status |= RunFidelity(*fidelity, graph, roots);
}
return status;
}
} // namespace
int main(int argc, char* argv[]) {
// Process arguments.
bool opt_metrics = false;
bool opt_exact = false;
stg::ReadOptions opt_read_options;
std::optional<const char*> opt_fidelity = std::nullopt;
stg::Ignore opt_ignore;
stg::InputFormat opt_input_format = stg::InputFormat::ABI;
stg::reporting::OutputFormat opt_output_format =
stg::reporting::OutputFormat::PLAIN;
Inputs inputs;
Outputs outputs;
static option opts[] = {
{"metrics", no_argument, nullptr, 'm'},
{"abi", no_argument, nullptr, 'a'},
{"btf", no_argument, nullptr, 'b'},
{"elf", no_argument, nullptr, 'e'},
{"stg", no_argument, nullptr, 's'},
{"exact", no_argument, nullptr, 'x'},
{"types", no_argument, nullptr, 't'},
{"ignore", required_argument, nullptr, 'i'},
{"format", required_argument, nullptr, 'f'},
{"output", required_argument, nullptr, 'o'},
{"fidelity", required_argument, nullptr, 'F'},
{nullptr, 0, nullptr, 0 },
};
auto usage = [&]() {
std::cerr << "usage: " << argv[0] << '\n'
<< " [-m|--metrics]\n"
<< " [-a|--abi|-b|--btf|-e|--elf|-s|--stg] file1\n"
<< " [-a|--abi|-b|--btf|-e|--elf|-s|--stg] file2\n"
<< " [-x|--exact]\n"
<< " [-t|--types]\n"
<< " [{-i|--ignore} <ignore-option>] ...\n"
<< " [{-f|--format} <output-format>] ...\n"
<< " [{-o|--output} {filename|-}] ...\n"
<< " [{-F|--fidelity} {filename|-}]\n"
<< "implicit defaults: --abi --format plain\n"
<< "--exact (node equality) cannot be combined with --output\n"
<< stg::reporting::OutputFormatUsage()
<< stg::IgnoreUsage();
return 1;
};
while (true) {
int ix;
const int c = getopt_long(argc, argv, "-mabesxti:f:o:F:", opts, &ix);
if (c == -1) {
break;
}
const char* argument = optarg;
switch (c) {
case 'm':
opt_metrics = true;
break;
case 'a':
opt_input_format = stg::InputFormat::ABI;
break;
case 'b':
opt_input_format = stg::InputFormat::BTF;
break;
case 'e':
opt_input_format = stg::InputFormat::ELF;
break;
case 's':
opt_input_format = stg::InputFormat::STG;
break;
case 'x':
opt_exact = true;
break;
case 't':
opt_read_options.Set(stg::ReadOptions::TYPE_ROOTS);
break;
case 1:
inputs.emplace_back(opt_input_format, argument);
break;
case 'i':
if (const auto ignore = stg::ParseIgnore(argument)) {
opt_ignore.Set(ignore.value());
} else {
std::cerr << "unknown ignore option: " << argument << '\n'
<< stg::IgnoreUsage();
return 1;
}
break;
case 'f':
if (const auto format = stg::reporting::ParseOutputFormat(argument)) {
opt_output_format = format.value();
} else {
std::cerr << "unknown output format: " << argument << '\n'
<< stg::reporting::OutputFormatUsage();
return 1;
}
break;
case 'o':
if (strcmp(argument, "-") == 0) {
argument = "/dev/stdout";
}
outputs.emplace_back(opt_output_format, argument);
break;
case 'F':
if (strcmp(argument, "-") == 0) {
argument = "/dev/stdout";
}
opt_fidelity.emplace(argument);
break;
default:
return usage();
}
}
if (inputs.size() != 2 || opt_exact > outputs.empty()) {
return usage();
}
try {
stg::Metrics metrics;
const int status = opt_exact ? RunExact(inputs, opt_read_options, metrics)
: Run(inputs, outputs, opt_ignore,
opt_read_options, opt_fidelity, metrics);
if (opt_metrics) {
stg::Report(metrics, std::cerr);
}
return status;
} catch (const stg::Exception& e) {
std::cerr << e.what();
return 1;
}
}