Instantiator
Generate c++ template instantiations
Loading...
Searching...
No Matches
Instantiator.cpp
Go to the documentation of this file.
1#include <filesystem>
2#include <fstream>
3#include <iostream>
4#include <system_error>
5#include <unordered_set>
6
7#include "fmt/ranges.h"
8#include "fmt/std.h"
9#include "spdlog/cfg/env.h"
10#include "spdlog/spdlog.h"
11
12#include "clang/AST/ASTImporter.h"
13#include "clang/ASTMatchers/ASTMatchFinder.h"
14#include "clang/ASTMatchers/ASTMatchers.h"
15#include "clang/Rewrite/Core/Rewriter.h"
16#include "clang/Tooling/CommonOptionsParser.h"
17#include "clang/Tooling/Tooling.h"
18#include "llvm/Support/CommandLine.h"
19
20#include "ASTCreation.hpp"
24#include "IO/ProgressBar.hpp"
25#include "Injection.hpp"
26#include "Matcher/Matcher.hpp"
27
28//
29// Command line options
30//
31static llvm::cl::OptionCategory InstantiatorOptions("Instantiator options");
32
33static llvm::cl::opt<bool> Invasive("invasive",
34 llvm::cl::desc("Inject instantiations invasively. (Directly after function definition.)"),
35 llvm::cl::cat(InstantiatorOptions));
36
37static llvm::cl::opt<bool> Progress("progress", llvm::cl::desc("Print out a progress bar."), llvm::cl::cat(InstantiatorOptions));
38
39static llvm::cl::opt<bool> Clean("clean", llvm::cl::desc("Delete all explicit instantiations."), llvm::cl::cat(InstantiatorOptions));
40static llvm::cl::list<std::string>
42 llvm::cl::desc("List of namespaces which should be ignored. Several namespaces can be added by multiple --ignore calls."),
43 llvm::cl::value_desc("namespace"),
44 llvm::cl::cat(InstantiatorOptions));
45static llvm::cl::alias IgnorePatternsA("i", llvm::cl::desc("Alias for --ignore"), llvm::cl::aliasopt(IgnorePatterns));
46
47static llvm::cl::extrahelp CommonHelp(clang::tooling::CommonOptionsParser::HelpMessage);
48
49int main(int argc, const char** argv)
50{
51 spdlog::cfg::load_env_levels();
52
53 auto ExpectedParser = clang::tooling::CommonOptionsParser::create(argc, argv, InstantiatorOptions, llvm::cl::OneOrMore);
54 if(!ExpectedParser) {
55 // Fail gracefully for unsupported options.
56 llvm::errs() << ExpectedParser.takeError();
57 return 1;
58 }
59 clang::tooling::CommonOptionsParser& OptionsParser = ExpectedParser.get();
60 if(IgnorePatterns.size() == 0) { IgnorePatterns.push_back("std"); }
61
62 clang::ast_matchers::internal::Matcher<clang::NamedDecl> nameMatcher = clang::ast_matchers::matchesName(IgnorePatterns[0] + "::");
63 for(auto it = IgnorePatterns.begin() + 1; it != IgnorePatterns.end(); it++) {
64 nameMatcher = clang::ast_matchers::anyOf(nameMatcher, clang::ast_matchers::matchesName(*it + "::"));
65 }
66
67 clang::ast_matchers::DeclarationMatcher TemplateInstantiationMatcher = TemplInstWithoutDef(nameMatcher);
68
69 clang::ast_matchers::DeclarationMatcher FunctionDefMatcher = FuncWithDef(nameMatcher);
70
71 std::error_code tmp_create_error;
72 auto tmpdir = std::filesystem::temp_directory_path(tmp_create_error);
73 if(tmp_create_error) {
74 std::cerr << "Error (" << tmp_create_error.value() << ") while getting temporary directory:\n" << tmp_create_error.message() << std::endl;
75 return 1;
76 }
77
78 std::vector<std::filesystem::path> main_and_injection_files;
79 for(const auto& file : OptionsParser.getCompilations().getAllFiles()) { main_and_injection_files.push_back(std::filesystem::path(file)); }
80
81 spdlog::info("Source files from CompilationDatabase:\n{}", main_and_injection_files);
82 llvm::ArrayRef<std::filesystem::path> sources(main_and_injection_files.data(), main_and_injection_files.size());
83
84 if(Clean) {
85 ProgressBar deletion_bar(sources.size());
86 for(std::size_t i = 0; i < sources.size(); i++) {
87 // deletion_bar.message = sources[i].string();
88 bool HAS_INJECTED_INSTANTIATION = true;
89 while(HAS_INJECTED_INSTANTIATION) {
90 // deletion_bar.set_option(indicators::option::PostfixText{"Processing: " + sources[i].string()});
91 if(not Invasive) {
92 auto gen_file = sources[i];
93 gen_file.replace_extension("gen.cpp");
94 std::ofstream f(gen_file, std::ios::out | std::ios::trunc);
95 f.close();
96 HAS_INJECTED_INSTANTIATION = false;
97 } else {
98 std::unique_ptr<clang::ASTUnit> AST;
99 parseOrLoadAST(AST, OptionsParser.getCompilations(), sources[i], tmpdir);
100 clang::Rewriter rewriter(AST->getSourceManager(), AST->getLangOpts());
101 DeleteInstantiations Deleter;
102 Deleter.rewriter = &rewriter;
103 clang::ast_matchers::MatchFinder Inst_Finder;
104 Inst_Finder.addMatcher(/*Matcher*/ TemplInst(nameMatcher), /*Callback*/ &Deleter);
105 Inst_Finder.matchAST(AST->getASTContext());
106 rewriter.overwriteChangedFiles();
107 HAS_INJECTED_INSTANTIATION = rewriter.buffer_begin() != rewriter.buffer_end();
108 }
109 }
110 if(Progress) { deletion_bar.step(); }
111 }
112 fmt::print("\n");
113 return 0;
114 }
115
117
118 std::vector<Injection> toDoList;
119 Getter.toDoList = &toDoList;
120 clang::ast_matchers::MatchFinder Finder;
121 Finder.addMatcher(/*Matcher*/ TemplateInstantiationMatcher, /*Callback*/ &Getter);
122
123 // match in current main file.
124 std::unordered_set<std::string> workList;
125 workList.insert(OptionsParser.getSourcePathList()[0]);
126
127 while(workList.size() > 0) {
128 auto copyOf_workList = workList;
129 for(const auto& item : copyOf_workList) {
130 spdlog::info("Processing file {}", item);
131 workList.erase(item);
132 std::unique_ptr<clang::ASTUnit> source_AST;
133 parseOrLoadAST(source_AST, OptionsParser.getCompilations(), item, tmpdir);
134 spdlog::debug("Got AST for file {}", item);
135 Finder.matchAST(source_AST->getASTContext());
136 spdlog::info("Found {} todos for file {}", toDoList.size(), item);
137 for(const auto& toDo : toDoList) { spdlog::debug("\t{}", toDo); }
138 ProgressBar inner_bar(main_and_injection_files.size());
139
140 for(const auto& file_for_search : main_and_injection_files) {
141 if(toDoList.size() == 0) {
142 // inner_bar.set_option(indicators::option::PostfixText{"ToDoList became empty. "});
143 if(Progress) { inner_bar.update(inner_bar.numTasks()); }
144 break;
145 }
146 std::unique_ptr<clang::ASTUnit> target_AST;
147 parseOrLoadAST(target_AST, OptionsParser.getCompilations(), file_for_search, tmpdir);
148 spdlog::info("Search in AST of file {}", file_for_search);
149 clang::Rewriter rewriter(target_AST->getSourceManager(), target_AST->getLangOpts());
150 clang::ast_matchers::MatchFinder FuncFinder;
151 InjectInstantiation instantiator;
152 instantiator.toDoList = &toDoList;
153 instantiator.rewriter = &rewriter;
154 instantiator.invasive = Invasive;
155 FuncFinder.addMatcher(/*Matcher*/ FunctionDefMatcher, /*Callback*/ &instantiator);
156 FuncFinder.matchAST(target_AST->getASTContext());
157 spdlog::debug("Called matchAST()");
158 rewriter.overwriteChangedFiles();
159 spdlog::debug("Called rewriter");
160 bool HAS_INJECTED_INTANTIATION = rewriter.buffer_begin() != rewriter.buffer_end();
161 spdlog::info("HAS_INJECTED={}", HAS_INJECTED_INTANTIATION);
162 if(HAS_INJECTED_INTANTIATION) { workList.insert(file_for_search); }
163 if(Progress) { inner_bar.step(); }
164 }
165 }
166 }
167 spdlog::critical("#toDos that are left: {}", toDoList.size());
168 if(toDoList.size() > 0) { spdlog::critical("toDos left:"); }
169 std::size_t count = 0ul;
170 for(const auto& toDo : toDoList) { spdlog::critical("{}: {}", count++, toDo); }
171}
void parseOrLoadAST(std::unique_ptr< clang::ASTUnit > &AST, const clang::tooling::CompilationDatabase &db, const std::filesystem::path &filename, const std::filesystem::path &tmpdir)
int main(int argc, const char **argv)
static llvm::cl::opt< bool > Clean("clean", llvm::cl::desc("Delete all explicit instantiations."), llvm::cl::cat(InstantiatorOptions))
static llvm::cl::OptionCategory InstantiatorOptions("Instantiator options")
static llvm::cl::list< std::string > IgnorePatterns("ignore", llvm::cl::desc("List of namespaces which should be ignored. Several namespaces can be added by multiple --ignore calls."), llvm::cl::value_desc("namespace"), llvm::cl::cat(InstantiatorOptions))
static llvm::cl::extrahelp CommonHelp(clang::tooling::CommonOptionsParser::HelpMessage)
static llvm::cl::alias IgnorePatternsA("i", llvm::cl::desc("Alias for --ignore"), llvm::cl::aliasopt(IgnorePatterns))
static llvm::cl::opt< bool > Progress("progress", llvm::cl::desc("Print out a progress bar."), llvm::cl::cat(InstantiatorOptions))
static llvm::cl::opt< bool > Invasive("invasive", llvm::cl::desc("Inject instantiations invasively. (Directly after function definition.)"), llvm::cl::cat(InstantiatorOptions))
AST matcher expressions to filter the relevant nodes for template instantiations.
clang::ast_matchers::DeclarationMatcher TemplInst(const clang::ast_matchers::internal::Matcher< clang::NamedDecl > &excluded_names)
clang::ast_matchers::DeclarationMatcher TemplInstWithoutDef(const clang::ast_matchers::internal::Matcher< clang::NamedDecl > &excluded_names)
clang::ast_matchers::DeclarationMatcher FuncWithDef(const clang::ast_matchers::internal::Matcher< clang::NamedDecl > &excluded_names)
MatchCallback for template instantiation deletions.
MatchCallback for template instantiation detections.
std::vector< Injection > * toDoList
MatchCallback for injection of explicit instantiations into the source files at appropriate places.
clang::Rewriter * rewriter
std::vector< Injection > * toDoList
void update(int curr, std::string message="")
void step(std::string message="")
int numTasks() const