blob: 82a372ee6c94388077253f1beb4536bc218f17f6 [file] [log] [blame]
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// -*- mode: C++ -*-
//
// Copyright 2022-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: Giuliano Procida
#include <getopt.h>
#include <cstring>
#include <fstream>
#include <iostream>
#include <map>
#include <memory>
#include <ostream>
#include <string>
#include <utility>
#include <vector>
#include "deduplication.h"
#include "error.h"
#include "filter.h"
#include "fingerprint.h"
#include "graph.h"
#include "input.h"
#include "metrics.h"
#include "proto_writer.h"
#include "reader_options.h"
#include "type_resolution.h"
#include "unification.h"
namespace stg {
namespace {
struct GetInterface {
Interface& operator()(Interface& x) const {
return x;
}
template <typename Node>
Interface& operator()(Node&) const {
Die() << "expected an Interface root node";
}
};
Id Merge(Graph& graph, const std::vector<Id>& roots, Metrics& metrics) {
bool failed = false;
// this rewrites the graph on destruction
Unification unification(graph, Id(0), metrics);
unification.Reserve(graph.Limit());
std::map<std::string, Id> symbols;
std::map<std::string, Id> types;
const GetInterface get;
for (auto root : roots) {
const auto& interface = graph.Apply<Interface&>(get, root);
for (const auto& x : interface.symbols) {
if (!symbols.insert(x).second) {
Warn() << "duplicate symbol during merge: " << x.first;
failed = true;
}
}
// TODO: test type roots merge
for (const auto& x : interface.types) {
const auto [it, inserted] = types.insert(x);
if (!inserted && !unification.Unify(x.second, it->second)) {
Warn() << "type conflict during merge: " << x.first;
failed = true;
}
}
graph.Remove(root);
}
if (failed) {
Die() << "merge failed";
}
return graph.Add<Interface>(symbols, types);
}
void FilterSymbols(Graph& graph, Id root, const Filter& filter) {
std::map<std::string, Id> symbols;
GetInterface get;
auto& interface = graph.Apply<Interface&>(get, root);
for (const auto& x : interface.symbols) {
if (filter(x.first)) {
symbols.insert(x);
}
}
std::swap(interface.symbols, symbols);
}
void Write(const Graph& graph, Id root, const char* output, Metrics& metrics) {
std::ofstream os(output);
{
Time x(metrics, "write");
proto::Writer writer(graph);
writer.Write(root, os);
os << std::flush;
}
if (!os) {
Die() << "error writing to " << '\'' << output << '\'';
}
}
} // namespace
} // namespace stg
int main(int argc, char* argv[]) {
// Process arguments.
bool opt_metrics = false;
bool opt_keep_duplicates = false;
std::unique_ptr<stg::Filter> opt_file_filter;
std::unique_ptr<stg::Filter> opt_symbol_filter;
stg::ReadOptions opt_read_options;
stg::InputFormat opt_input_format = stg::InputFormat::ABI;
std::vector<std::pair<stg::InputFormat, const char*>> inputs;
std::vector<const char*> outputs;
static option opts[] = {
{"metrics", no_argument, nullptr, 'm'},
{"keep-duplicates", no_argument, nullptr, 'd'},
{"types", no_argument, nullptr, 't'},
{"files", required_argument, nullptr, 'F'},
{"file-filter", required_argument, nullptr, 'F'},
{"symbols", required_argument, nullptr, 'S'},
{"symbol-filter", required_argument, nullptr, 'S'},
{"abi", no_argument, nullptr, 'a'},
{"btf", no_argument, nullptr, 'b'},
{"elf", no_argument, nullptr, 'e'},
{"stg", no_argument, nullptr, 's'},
{"output", required_argument, nullptr, 'o'},
{nullptr, 0, nullptr, 0 },
};
auto usage = [&]() {
std::cerr << "usage: " << argv[0] << '\n'
<< " [-m|--metrics]\n"
<< " [-d|--keep-duplicates]\n"
<< " [-t|--types]\n"
<< " [-F|--files|--file-filter <filter>]\n"
<< " [-S|--symbols|--symbol-filter <filter>]\n"
<< " [-a|--abi|-b|--btf|-e|--elf|-s|--stg] [file] ...\n"
<< " [{-o|--output} {filename|-}] ...\n"
<< "implicit defaults: --abi\n";
stg::FilterUsage(std::cerr);
return 1;
};
while (true) {
int ix;
const int c = getopt_long(argc, argv, "-mdtS:F:abeso:", opts, &ix);
if (c == -1) {
break;
}
const char* argument = optarg;
switch (c) {
case 'm':
opt_metrics = true;
break;
case 'd':
opt_keep_duplicates = true;
break;
case 't':
opt_read_options.Set(stg::ReadOptions::TYPE_ROOTS);
break;
case 'F':
opt_file_filter = stg::MakeFilter(argument);
break;
case 'S':
opt_symbol_filter = stg::MakeFilter(argument);
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 1:
inputs.emplace_back(opt_input_format, argument);
break;
case 'o':
if (strcmp(argument, "-") == 0) {
argument = "/dev/stdout";
}
outputs.push_back(argument);
break;
default:
return usage();
}
}
try {
stg::Graph graph;
stg::Metrics metrics;
std::vector<stg::Id> roots;
roots.reserve(inputs.size());
for (auto& [format, input] : inputs) {
roots.push_back(stg::Read(graph, format, input, opt_read_options,
opt_file_filter, metrics));
}
stg::Id root =
roots.size() == 1 ? roots[0] : stg::Merge(graph, roots, metrics);
if (opt_symbol_filter) {
stg::FilterSymbols(graph, root, *opt_symbol_filter);
}
if (!opt_keep_duplicates) {
{
stg::Unification unification(graph, stg::Id(0), metrics);
unification.Reserve(graph.Limit());
stg::ResolveTypes(graph, unification, {root}, metrics);
unification.Update(root);
}
const auto hashes = stg::Fingerprint(graph, root, metrics);
root = stg::Deduplicate(graph, root, hashes, metrics);
}
for (auto output : outputs) {
stg::Write(graph, root, output, metrics);
}
if (opt_metrics) {
stg::Report(metrics, std::cerr);
}
return 0;
} catch (const stg::Exception& e) {
std::cerr << e.what();
return 1;
}
}