mirror of
https://github.com/JakeHillion/object-introspection.git
synced 2024-12-21 13:07:00 +00:00
oilgen: migrate to source parsing (#421)
Summary: oilgen: migrate to source parsing Using debug information generated from partial source (that is, not the final binary) has been insufficient to generally generate OIL code. A particular example is pointers to templates: ```cpp #include <oi/oi.h> template <typename T> struct Foo { T t; }; template <typename T> struct Bar { Foo<T>& f; }; void foo(const Bar<int>& b) { oi::introspect(b); } ``` The pointer/reference to `Foo<int>` appears in DWARF with `DW_AT_declaration(true)` because it could be specialised before its usage. However, with OIL, we are creating an implicit usage site in the `oi::introspect` call that the compiler is unable to see. This change reworks OILGen to work from a Clang command line rather than debug information. We setup and run a compiler on the source, giving us access to an AST and Semantic Analyser. We then: - Find the `oi::introspect` template. - Iterate through each of its callsites for their type. - Run `ClangTypeParser::parse` on each type. - Run codegen. - Compile into an object file. Having access to the semantic analyser allows us to forcefully complete a type, as it would be if it was used in the initial code. Test Plan: hope `buck2 run fbcode//mode/opt fbcode//object-introspection/oil/examples/compile-time:compile-time` Reviewed By: tyroguru Differential Revision: D51854477 Pulled By: JakeHillion
This commit is contained in:
parent
37b89d789d
commit
55989a9156
@ -289,7 +289,7 @@ add_library(oicore
|
||||
oi/Serialize.cpp
|
||||
)
|
||||
add_dependencies(oicore libdrgn)
|
||||
target_include_directories(oicore SYSTEM PRIVATE ${LLVM_INCLUDE_DIRS} ${CLANG_INCLUDE_DIRS})
|
||||
target_include_directories(oicore SYSTEM PUBLIC ${LLVM_INCLUDE_DIRS} ${CLANG_INCLUDE_DIRS})
|
||||
target_compile_definitions(oicore PRIVATE ${LLVM_DEFINITIONS})
|
||||
target_include_directories(oicore PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
|
||||
@ -365,6 +365,7 @@ add_executable(oilgen
|
||||
target_link_libraries(oilgen
|
||||
drgn_utils
|
||||
oicore
|
||||
clangTooling
|
||||
)
|
||||
|
||||
### Object Introspection cache Printer (OIP)
|
||||
|
@ -299,6 +299,11 @@ void genDefsThriftClass(const Class& c, std::string& code) {
|
||||
|
||||
} // namespace
|
||||
|
||||
CodeGen::CodeGen(const OICodeGen::Config& config) : config_(config) {
|
||||
DCHECK(!config.features[Feature::PolymorphicInheritance])
|
||||
<< "polymorphic inheritance requires symbol service!";
|
||||
}
|
||||
|
||||
void CodeGen::genDefsThrift(const TypeGraph& typeGraph, std::string& code) {
|
||||
for (const Type& t : typeGraph.finalTypes) {
|
||||
if (const auto* c = dynamic_cast<const Class*>(&t)) {
|
||||
@ -542,7 +547,7 @@ void CodeGen::getClassSizeFuncDef(const Class& c, std::string& code) {
|
||||
std::string childVtableName = "vtable for ";
|
||||
childVtableName += fqChildName;
|
||||
|
||||
auto optVtableSym = symbols_.locateSymbol(childVtableName, true);
|
||||
auto optVtableSym = symbols_->locateSymbol(childVtableName, true);
|
||||
if (!optVtableSym) {
|
||||
// LOG(ERROR) << "Failed to find vtable address for '" <<
|
||||
// childVtableName; LOG(ERROR) << "Falling back to non dynamic
|
||||
@ -1115,15 +1120,8 @@ bool CodeGen::codegenFromDrgn(struct drgn_type* drgnType, std::string& code) {
|
||||
bool CodeGen::codegenFromDrgn(struct drgn_type* drgnType,
|
||||
std::string& code,
|
||||
RootFunctionName name) {
|
||||
try {
|
||||
containerInfos_.reserve(config_.containerConfigPaths.size());
|
||||
for (const auto& path : config_.containerConfigPaths) {
|
||||
registerContainer(path);
|
||||
}
|
||||
} catch (const ContainerInfoError& err) {
|
||||
LOG(ERROR) << "Error reading container TOML file " << err.what();
|
||||
if (!registerContainers())
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
addDrgnRoot(drgnType, typeGraph_);
|
||||
@ -1148,6 +1146,19 @@ void CodeGen::exportDrgnTypes(TypeHierarchy& th,
|
||||
}
|
||||
}
|
||||
|
||||
bool CodeGen::registerContainers() {
|
||||
try {
|
||||
containerInfos_.reserve(config_.containerConfigPaths.size());
|
||||
for (const auto& path : config_.containerConfigPaths) {
|
||||
registerContainer(path);
|
||||
}
|
||||
return true;
|
||||
} catch (const ContainerInfoError& err) {
|
||||
LOG(ERROR) << "Error reading container TOML file " << err.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void CodeGen::registerContainer(std::unique_ptr<ContainerInfo> info) {
|
||||
VLOG(1) << "Registered container: " << info->typeName;
|
||||
containerInfos_.emplace_back(std::move(info));
|
||||
@ -1189,7 +1200,7 @@ void CodeGen::transform(TypeGraph& typeGraph) {
|
||||
.chaseRawPointers = config_.features[Feature::ChaseRawPointers],
|
||||
};
|
||||
DrgnParser drgnParser{typeGraph, options};
|
||||
pm.addPass(AddChildren::createPass(drgnParser, symbols_));
|
||||
pm.addPass(AddChildren::createPass(drgnParser, *symbols_));
|
||||
|
||||
// Re-run passes over newly added children
|
||||
pm.addPass(IdentifyContainers::createPass(containerInfos_));
|
||||
|
@ -41,8 +41,9 @@ namespace oi::detail {
|
||||
|
||||
class CodeGen {
|
||||
public:
|
||||
CodeGen(const OICodeGen::Config& config);
|
||||
CodeGen(const OICodeGen::Config& config, SymbolService& symbols)
|
||||
: config_(config), symbols_(symbols) {
|
||||
: config_(config), symbols_(&symbols) {
|
||||
}
|
||||
|
||||
struct ExactName {
|
||||
@ -65,6 +66,7 @@ class CodeGen {
|
||||
std::list<drgn_type>& drgnTypes,
|
||||
drgn_type** rootType) const;
|
||||
|
||||
bool registerContainers();
|
||||
void registerContainer(std::unique_ptr<ContainerInfo> containerInfo);
|
||||
void registerContainer(const std::filesystem::path& path);
|
||||
void addDrgnRoot(struct drgn_type* drgnType,
|
||||
@ -77,7 +79,7 @@ class CodeGen {
|
||||
private:
|
||||
type_graph::TypeGraph typeGraph_;
|
||||
const OICodeGen::Config& config_;
|
||||
SymbolService& symbols_;
|
||||
SymbolService* symbols_ = nullptr;
|
||||
std::vector<std::unique_ptr<ContainerInfo>> containerInfos_;
|
||||
std::unordered_set<const ContainerInfo*> definedContainers_;
|
||||
std::unordered_map<const type_graph::Class*, const type_graph::Member*>
|
||||
|
@ -16,130 +16,149 @@
|
||||
|
||||
#include "oi/OIGenerator.h"
|
||||
|
||||
#include <clang/AST/Mangle.h>
|
||||
#include <clang/Frontend/CompilerInstance.h>
|
||||
#include <clang/Frontend/CompilerInvocation.h>
|
||||
#include <clang/Frontend/FrontendAction.h>
|
||||
#include <clang/Sema/Sema.h>
|
||||
#include <clang/Tooling/Tooling.h>
|
||||
#include <glog/logging.h>
|
||||
|
||||
#include <boost/core/demangle.hpp>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string_view>
|
||||
#include <range/v3/core.hpp>
|
||||
#include <range/v3/view/drop.hpp>
|
||||
#include <range/v3/view/filter.hpp>
|
||||
#include <range/v3/view/for_each.hpp>
|
||||
#include <range/v3/view/take.hpp>
|
||||
#include <range/v3/view/transform.hpp>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
|
||||
#include "oi/CodeGen.h"
|
||||
#include "oi/Config.h"
|
||||
#include "oi/DrgnUtils.h"
|
||||
#include "oi/Headers.h"
|
||||
#include "oi/type_graph/ClangTypeParser.h"
|
||||
#include "oi/type_graph/TypeGraph.h"
|
||||
#include "oi/type_graph/Types.h"
|
||||
|
||||
namespace oi::detail {
|
||||
namespace {
|
||||
|
||||
std::unordered_map<std::string, std::string>
|
||||
OIGenerator::oilStrongToWeakSymbolsMap(drgnplusplus::program& prog) {
|
||||
static constexpr std::string_view strongSymbolPrefix =
|
||||
"oi::IntrospectionResult oi::introspect<";
|
||||
static constexpr std::string_view weakSymbolPrefix =
|
||||
"oi::IntrospectionResult oi::introspectImpl<";
|
||||
class ConsumerContext;
|
||||
|
||||
std::unordered_map<std::string, std::pair<std::string, std::string>>
|
||||
templateArgsToSymbolsMap;
|
||||
|
||||
auto symbols = prog.find_all_symbols();
|
||||
for (drgn_symbol* sym : *symbols) {
|
||||
auto symName = drgnplusplus::symbol::name(sym);
|
||||
if (symName == nullptr || *symName == '\0')
|
||||
continue;
|
||||
auto demangled = boost::core::demangle(symName);
|
||||
|
||||
if (demangled.starts_with(strongSymbolPrefix)) {
|
||||
auto& matchedSyms = templateArgsToSymbolsMap[demangled.substr(
|
||||
strongSymbolPrefix.length())];
|
||||
if (!matchedSyms.first.empty()) {
|
||||
LOG(WARNING) << "non-unique symbols found: `" << matchedSyms.first
|
||||
<< "` and `" << symName << '`';
|
||||
}
|
||||
matchedSyms.first = symName;
|
||||
} else if (demangled.starts_with(weakSymbolPrefix)) {
|
||||
auto& matchedSyms =
|
||||
templateArgsToSymbolsMap[demangled.substr(weakSymbolPrefix.length())];
|
||||
if (!matchedSyms.second.empty()) {
|
||||
LOG(WARNING) << "non-unique symbols found: `" << matchedSyms.second
|
||||
<< "` and `" << symName << "`";
|
||||
}
|
||||
matchedSyms.second = symName;
|
||||
}
|
||||
class CreateTypeGraphConsumer;
|
||||
class CreateTypeGraphAction : public clang::ASTFrontendAction {
|
||||
public:
|
||||
CreateTypeGraphAction(ConsumerContext& ctx_) : ctx{ctx_} {
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::string> strongToWeakSymbols;
|
||||
for (auto& [_, val] : templateArgsToSymbolsMap) {
|
||||
if (val.first.empty() || val.second.empty()) {
|
||||
continue;
|
||||
}
|
||||
strongToWeakSymbols[std::move(val.first)] = std::move(val.second);
|
||||
void ExecuteAction() override;
|
||||
std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(
|
||||
clang::CompilerInstance& CI, clang::StringRef file) override;
|
||||
|
||||
private:
|
||||
ConsumerContext& ctx;
|
||||
};
|
||||
|
||||
class CreateTypeGraphActionFactory
|
||||
: public clang::tooling::FrontendActionFactory {
|
||||
public:
|
||||
CreateTypeGraphActionFactory(ConsumerContext& ctx_) : ctx{ctx_} {
|
||||
}
|
||||
|
||||
return strongToWeakSymbols;
|
||||
}
|
||||
std::unique_ptr<clang::FrontendAction> create() override {
|
||||
return std::make_unique<CreateTypeGraphAction>(ctx);
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, drgn_qualified_type>
|
||||
OIGenerator::findOilTypesAndNames(drgnplusplus::program& prog) {
|
||||
auto strongToWeakSymbols = oilStrongToWeakSymbolsMap(prog);
|
||||
private:
|
||||
ConsumerContext& ctx;
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, drgn_qualified_type> out;
|
||||
class ConsumerContext {
|
||||
public:
|
||||
ConsumerContext(const std::vector<std::unique_ptr<ContainerInfo>>& cis)
|
||||
: containerInfos{cis} {
|
||||
}
|
||||
|
||||
for (drgn_qualified_type& func : drgnplusplus::func_iterator(prog)) {
|
||||
std::string strongLinkageName;
|
||||
{
|
||||
const char* linkageNameCstr;
|
||||
if (auto err = drgnplusplus::error(
|
||||
drgn_type_linkage_name(func.type, &linkageNameCstr))) {
|
||||
// throw err;
|
||||
type_graph::TypeGraph typeGraph;
|
||||
std::unordered_map<std::string, type_graph::Type*> nameToTypeMap;
|
||||
std::optional<bool> pic;
|
||||
const std::vector<std::unique_ptr<ContainerInfo>>& containerInfos;
|
||||
|
||||
private:
|
||||
clang::Sema* sema = nullptr;
|
||||
friend CreateTypeGraphConsumer;
|
||||
friend CreateTypeGraphAction;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
int OIGenerator::generate(clang::tooling::CompilationDatabase& db,
|
||||
const std::vector<std::string>& sourcePaths) {
|
||||
std::map<Feature, bool> featuresMap = {
|
||||
{Feature::TypeGraph, true},
|
||||
{Feature::TreeBuilderV2, true},
|
||||
{Feature::Library, true},
|
||||
{Feature::PackStructs, true},
|
||||
{Feature::PruneTypeGraph, true},
|
||||
};
|
||||
|
||||
OICodeGen::Config generatorConfig{};
|
||||
OICompiler::Config compilerConfig{};
|
||||
|
||||
auto features = config::processConfigFiles(
|
||||
configFilePaths, featuresMap, compilerConfig, generatorConfig);
|
||||
if (!features) {
|
||||
LOG(ERROR) << "failed to process config file";
|
||||
return -1;
|
||||
}
|
||||
generatorConfig.features = *features;
|
||||
compilerConfig.features = *features;
|
||||
|
||||
std::vector<std::unique_ptr<ContainerInfo>> containerInfos;
|
||||
containerInfos.reserve(generatorConfig.containerConfigPaths.size());
|
||||
try {
|
||||
for (const auto& path : generatorConfig.containerConfigPaths) {
|
||||
auto info = std::make_unique<ContainerInfo>(path);
|
||||
if (info->requiredFeatures != (*features & info->requiredFeatures)) {
|
||||
VLOG(1) << "Skipping container (feature conflict): " << info->typeName;
|
||||
continue;
|
||||
}
|
||||
strongLinkageName = linkageNameCstr;
|
||||
containerInfos.emplace_back(std::move(info));
|
||||
}
|
||||
|
||||
std::string weakLinkageName;
|
||||
if (auto search = strongToWeakSymbols.find(strongLinkageName);
|
||||
search != strongToWeakSymbols.end()) {
|
||||
weakLinkageName = search->second;
|
||||
} else {
|
||||
continue; // not an oil strong symbol
|
||||
}
|
||||
|
||||
// IntrospectionResult (*)(const T&)
|
||||
CHECK(drgn_type_has_parameters(func.type)) << "functions have parameters";
|
||||
CHECK(drgn_type_num_parameters(func.type) == 1)
|
||||
<< "introspection func has one parameter";
|
||||
|
||||
auto* params = drgn_type_parameters(func.type);
|
||||
drgn_qualified_type tType;
|
||||
if (auto err =
|
||||
drgnplusplus::error(drgn_parameter_type(¶ms[0], &tType))) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (drgn_type_has_name(tType.type)) {
|
||||
LOG(INFO) << "found OIL type: " << drgn_type_name(tType.type);
|
||||
} else {
|
||||
LOG(INFO) << "found OIL type: (no name)";
|
||||
}
|
||||
out.emplace(std::move(weakLinkageName), tType);
|
||||
} catch (const ContainerInfoError& err) {
|
||||
LOG(ERROR) << "Error reading container TOML file " << err.what();
|
||||
return -1;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
ConsumerContext ctx{containerInfos};
|
||||
CreateTypeGraphActionFactory factory{ctx};
|
||||
|
||||
fs::path OIGenerator::generateForType(const OICodeGen::Config& generatorConfig,
|
||||
const OICompiler::Config& compilerConfig,
|
||||
const drgn_qualified_type& type,
|
||||
const std::string& linkageName,
|
||||
SymbolService& symbols) {
|
||||
CodeGen codegen{generatorConfig, symbols};
|
||||
clang::tooling::ClangTool tool{db, sourcePaths};
|
||||
if (auto ret = tool.run(&factory); ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (ctx.nameToTypeMap.size() > 1)
|
||||
throw std::logic_error(
|
||||
"found more than one site to generate for but we can't currently "
|
||||
"handle this case");
|
||||
|
||||
if (ctx.nameToTypeMap.empty()) {
|
||||
LOG(ERROR) << "Nothing to generate!";
|
||||
return failIfNothingGenerated ? -1 : 0;
|
||||
}
|
||||
const auto& linkageName = ctx.nameToTypeMap.begin()->first;
|
||||
|
||||
compilerConfig.usePIC = ctx.pic.value();
|
||||
CodeGen codegen{generatorConfig};
|
||||
for (auto&& ptr : containerInfos)
|
||||
codegen.registerContainer(std::move(ptr));
|
||||
codegen.transform(ctx.typeGraph);
|
||||
|
||||
std::string code;
|
||||
if (!codegen.codegenFromDrgn(type.type, linkageName, code)) {
|
||||
LOG(ERROR) << "codegen failed!";
|
||||
return {};
|
||||
}
|
||||
codegen.generate(ctx.typeGraph, code, CodeGen::ExactName{linkageName});
|
||||
|
||||
std::string sourcePath = sourceFileDumpPath;
|
||||
if (sourceFileDumpPath.empty()) {
|
||||
@ -152,78 +171,110 @@ fs::path OIGenerator::generateForType(const OICodeGen::Config& generatorConfig,
|
||||
}
|
||||
|
||||
OICompiler compiler{{}, compilerConfig};
|
||||
|
||||
// TODO: Revert to outputPath and remove printing when typegraph is done.
|
||||
fs::path tmpObject = outputPath;
|
||||
tmpObject.replace_extension(
|
||||
"." + std::to_string(std::hash<std::string>{}(linkageName)) + ".o");
|
||||
|
||||
if (!compiler.compile(code, sourcePath, tmpObject)) {
|
||||
return {};
|
||||
}
|
||||
return tmpObject;
|
||||
return compiler.compile(code, sourcePath, outputPath) ? 0 : -1;
|
||||
}
|
||||
|
||||
int OIGenerator::generate(fs::path& primaryObject, SymbolService& symbols) {
|
||||
drgnplusplus::program prog;
|
||||
namespace {
|
||||
|
||||
{
|
||||
std::array<const char*, 1> objectPaths = {{primaryObject.c_str()}};
|
||||
if (auto err = drgnplusplus::error(
|
||||
drgn_program_load_debug_info(prog.get(),
|
||||
std::data(objectPaths),
|
||||
std::size(objectPaths),
|
||||
false,
|
||||
false))) {
|
||||
LOG(ERROR) << "error loading debug info program: " << err;
|
||||
throw err;
|
||||
class CreateTypeGraphConsumer : public clang::ASTConsumer {
|
||||
private:
|
||||
ConsumerContext& ctx;
|
||||
|
||||
public:
|
||||
CreateTypeGraphConsumer(ConsumerContext& ctx_) : ctx(ctx_) {
|
||||
}
|
||||
|
||||
void HandleTranslationUnit(clang::ASTContext& Context) override {
|
||||
auto* tu_decl = Context.getTranslationUnitDecl();
|
||||
auto decls = tu_decl->decls();
|
||||
auto oi_namespaces = decls | ranges::views::transform([](auto* p) {
|
||||
return llvm::dyn_cast<clang::NamespaceDecl>(p);
|
||||
}) |
|
||||
ranges::views::filter([](auto* ns) {
|
||||
return ns != nullptr && ns->getName() == "oi";
|
||||
});
|
||||
if (oi_namespaces.empty()) {
|
||||
LOG(WARNING) << "Failed to find `oi` namespace. Does this input "
|
||||
"include <oi/oi.h>?";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto oilTypes = findOilTypesAndNames(prog);
|
||||
|
||||
std::map<Feature, bool> featuresMap = {
|
||||
{Feature::TypeGraph, true},
|
||||
{Feature::TreeBuilderV2, true},
|
||||
{Feature::Library, true},
|
||||
{Feature::PackStructs, true},
|
||||
{Feature::PruneTypeGraph, true},
|
||||
};
|
||||
|
||||
OICodeGen::Config generatorConfig{};
|
||||
OICompiler::Config compilerConfig{};
|
||||
compilerConfig.usePIC = pic;
|
||||
|
||||
auto features = config::processConfigFiles(
|
||||
configFilePaths, featuresMap, compilerConfig, generatorConfig);
|
||||
if (!features) {
|
||||
LOG(ERROR) << "failed to process config file";
|
||||
return -1;
|
||||
}
|
||||
generatorConfig.features = *features;
|
||||
compilerConfig.features = *features;
|
||||
|
||||
size_t failures = 0;
|
||||
for (const auto& [linkageName, type] : oilTypes) {
|
||||
if (auto obj = generateForType(
|
||||
generatorConfig, compilerConfig, type, linkageName, symbols);
|
||||
!obj.empty()) {
|
||||
std::cout << obj.string() << std::endl;
|
||||
} else {
|
||||
LOG(WARNING) << "failed to generate for symbol `" << linkageName
|
||||
<< "`. this is non-fatal but the call will not work.";
|
||||
failures++;
|
||||
auto introspectImpl =
|
||||
std::move(oi_namespaces) |
|
||||
ranges::views::for_each([](auto* ns) { return ns->decls(); }) |
|
||||
ranges::views::transform([](auto* p) {
|
||||
return llvm::dyn_cast<clang::FunctionTemplateDecl>(p);
|
||||
}) |
|
||||
ranges::views::filter([](auto* td) {
|
||||
return td != nullptr && td->getName() == "introspectImpl";
|
||||
}) |
|
||||
ranges::views::take(1) | ranges::to<std::vector>();
|
||||
if (introspectImpl.empty()) {
|
||||
LOG(WARNING)
|
||||
<< "Failed to find `oi::introspect` within the `oi` namespace. Did "
|
||||
"you compile with `OIL_AOT_COMPILATION=1`?";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
size_t successes = oilTypes.size() - failures;
|
||||
LOG(INFO) << "object introspection generation complete. " << successes
|
||||
<< " successes and " << failures << " failures.";
|
||||
auto nameToClangTypeMap =
|
||||
introspectImpl | ranges::views::for_each([](auto* td) {
|
||||
return td->specializations();
|
||||
}) |
|
||||
ranges::views::transform(
|
||||
[](auto* p) { return llvm::dyn_cast<clang::FunctionDecl>(p); }) |
|
||||
ranges::views::filter([](auto* p) { return p != nullptr; }) |
|
||||
ranges::views::transform(
|
||||
[](auto* fd) -> std::pair<std::string, const clang::Type*> {
|
||||
clang::ASTContext& Ctx = fd->getASTContext();
|
||||
clang::ASTNameGenerator ASTNameGen(Ctx);
|
||||
std::string name = ASTNameGen.getName(fd);
|
||||
|
||||
if (failures > 0 || (failIfNothingGenerated && successes == 0)) {
|
||||
return -1;
|
||||
assert(fd->getNumParams() == 1);
|
||||
const clang::Type* type =
|
||||
fd->parameters()[0]->getType().getTypePtr();
|
||||
return {name, type};
|
||||
}) |
|
||||
ranges::to<std::unordered_map>();
|
||||
if (nameToClangTypeMap.empty())
|
||||
return;
|
||||
|
||||
type_graph::ClangTypeParserOptions opts;
|
||||
type_graph::ClangTypeParser parser{ctx.typeGraph, ctx.containerInfos, opts};
|
||||
|
||||
auto& Sema = *ctx.sema;
|
||||
auto els = nameToClangTypeMap |
|
||||
ranges::views::transform(
|
||||
[&parser, &Context, &Sema](
|
||||
auto& p) -> std::pair<std::string, type_graph::Type*> {
|
||||
return {p.first, &parser.parse(Context, Sema, *p.second)};
|
||||
});
|
||||
ctx.nameToTypeMap.insert(els.begin(), els.end());
|
||||
|
||||
for (const auto& [name, type] : ctx.nameToTypeMap)
|
||||
ctx.typeGraph.addRoot(*type);
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
void CreateTypeGraphAction::ExecuteAction() {
|
||||
clang::CompilerInstance& CI = getCompilerInstance();
|
||||
|
||||
// Compile the output as position independent if any input is position
|
||||
// independent
|
||||
bool pic = CI.getCodeGenOpts().RelocationModel == llvm::Reloc::PIC_;
|
||||
ctx.pic = ctx.pic.value_or(false) || pic;
|
||||
|
||||
if (!CI.hasSema())
|
||||
CI.createSema(clang::TU_Complete, nullptr);
|
||||
ctx.sema = &CI.getSema();
|
||||
|
||||
clang::ASTFrontendAction::ExecuteAction();
|
||||
}
|
||||
|
||||
std::unique_ptr<clang::ASTConsumer> CreateTypeGraphAction::CreateASTConsumer(
|
||||
[[maybe_unused]] clang::CompilerInstance& CI,
|
||||
[[maybe_unused]] clang::StringRef file) {
|
||||
return std::make_unique<CreateTypeGraphConsumer>(ctx);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace oi::detail
|
||||
|
@ -23,11 +23,19 @@
|
||||
#include "oi/OICodeGen.h"
|
||||
#include "oi/OICompiler.h"
|
||||
|
||||
namespace clang::tooling {
|
||||
class CompilationDatabase;
|
||||
}
|
||||
|
||||
namespace oi::detail {
|
||||
namespace type_graph {
|
||||
class Type;
|
||||
}
|
||||
|
||||
class OIGenerator {
|
||||
public:
|
||||
int generate(fs::path& primaryObject, SymbolService& symbols);
|
||||
int generate(clang::tooling::CompilationDatabase&,
|
||||
const std::vector<std::string>&);
|
||||
|
||||
void setOutputPath(fs::path _outputPath) {
|
||||
outputPath = std::move(_outputPath);
|
||||
@ -41,8 +49,8 @@ class OIGenerator {
|
||||
void setFailIfNothingGenerated(bool fail) {
|
||||
failIfNothingGenerated = fail;
|
||||
}
|
||||
void setUsePIC(bool pic_) {
|
||||
pic = pic_;
|
||||
void setClangArgs(std::vector<std::string> args_) {
|
||||
clangArgs = std::move(args_);
|
||||
}
|
||||
|
||||
private:
|
||||
@ -50,20 +58,7 @@ class OIGenerator {
|
||||
std::vector<std::filesystem::path> configFilePaths;
|
||||
std::filesystem::path sourceFileDumpPath;
|
||||
bool failIfNothingGenerated = false;
|
||||
bool pic = false;
|
||||
|
||||
std::unordered_map<std::string, std::string> oilStrongToWeakSymbolsMap(
|
||||
drgnplusplus::program& prog);
|
||||
|
||||
std::unordered_map<std::string, drgn_qualified_type> findOilTypesAndNames(
|
||||
drgnplusplus::program& prog);
|
||||
|
||||
std::filesystem::path generateForType(
|
||||
const OICodeGen::Config& generatorConfig,
|
||||
const OICompiler::Config& compilerConfig,
|
||||
const drgn_qualified_type& type,
|
||||
const std::string& linkageName,
|
||||
SymbolService& symbols);
|
||||
std::vector<std::string> clangArgs;
|
||||
};
|
||||
|
||||
} // namespace oi::detail
|
||||
|
@ -2,6 +2,7 @@ add_library(type_graph
|
||||
AddChildren.cpp
|
||||
AddPadding.cpp
|
||||
AlignmentCalc.cpp
|
||||
ClangTypeParser.cpp
|
||||
DrgnExporter.cpp
|
||||
DrgnParser.cpp
|
||||
EnforceCompatibility.cpp
|
||||
@ -27,3 +28,4 @@ target_link_libraries(type_graph
|
||||
"-L${DRGN_PATH}/.libs"
|
||||
drgn
|
||||
)
|
||||
target_include_directories(type_graph SYSTEM PUBLIC ${LLVM_INCLUDE_DIRS} ${CLANG_INCLUDE_DIRS})
|
||||
|
418
oi/type_graph/ClangTypeParser.cpp
Normal file
418
oi/type_graph/ClangTypeParser.cpp
Normal file
@ -0,0 +1,418 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#include "ClangTypeParser.h"
|
||||
|
||||
#include <clang/AST/ASTContext.h>
|
||||
#include <clang/AST/Decl.h>
|
||||
#include <clang/AST/DeclTemplate.h>
|
||||
#include <clang/AST/QualTypeNames.h>
|
||||
#include <clang/AST/Type.h>
|
||||
#include <clang/Basic/DiagnosticSema.h>
|
||||
#include <clang/Sema/Sema.h>
|
||||
#include <glog/logging.h>
|
||||
|
||||
#include <regex>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "oi/type_graph/Types.h"
|
||||
|
||||
namespace oi::detail::type_graph {
|
||||
namespace {
|
||||
bool requireCompleteType(clang::Sema& sema, const clang::Type& ty);
|
||||
}
|
||||
|
||||
Type& ClangTypeParser::parse(clang::ASTContext& ast_,
|
||||
clang::Sema& sema_,
|
||||
const clang::Type& ty) {
|
||||
ast = &ast_;
|
||||
sema = &sema_;
|
||||
|
||||
depth_ = 0;
|
||||
return enumerateType(ty);
|
||||
}
|
||||
|
||||
Type& ClangTypeParser::enumerateType(const clang::Type& ty) {
|
||||
// Avoid re-enumerating an already-processsed type
|
||||
if (auto it = clang_types_.find(&ty); it != clang_types_.end())
|
||||
return it->second;
|
||||
|
||||
struct DepthTracker {
|
||||
DepthTracker(ClangTypeParser& self_) : self{self_} {
|
||||
self.depth_++;
|
||||
}
|
||||
~DepthTracker() {
|
||||
self.depth_--;
|
||||
}
|
||||
|
||||
private:
|
||||
ClangTypeParser& self;
|
||||
} d{*this};
|
||||
|
||||
if (VLOG_IS_ON(3)) {
|
||||
std::string fqName = clang::TypeName::getFullyQualifiedName(
|
||||
clang::QualType(&ty, 0), *ast, {ast->getLangOpts()});
|
||||
VLOG(3) << std::string(depth_ * 2, ' ') << fqName;
|
||||
}
|
||||
|
||||
// TODO: This check basically doesn't work. The action has failed before it
|
||||
// returns false. Fix this.
|
||||
if (!requireCompleteType(*sema, ty)) {
|
||||
std::string fqName = clang::TypeName::getFullyQualifiedName(
|
||||
clang::QualType(&ty, 0), *ast, {ast->getLangOpts()});
|
||||
return makeType<Incomplete>(ty, std::move(fqName));
|
||||
}
|
||||
|
||||
switch (ty.getTypeClass()) {
|
||||
case clang::Type::Record:
|
||||
return enumerateClass(llvm::cast<const clang::RecordType>(ty));
|
||||
case clang::Type::LValueReference:
|
||||
return enumerateReference(
|
||||
llvm::cast<const clang::LValueReferenceType>(ty));
|
||||
case clang::Type::Pointer:
|
||||
return enumeratePointer(llvm::cast<const clang::PointerType>(ty));
|
||||
case clang::Type::SubstTemplateTypeParm:
|
||||
return enumerateSubstTemplateTypeParm(
|
||||
llvm::cast<const clang::SubstTemplateTypeParmType>(ty));
|
||||
case clang::Type::Builtin:
|
||||
return enumeratePrimitive(llvm::cast<const clang::BuiltinType>(ty));
|
||||
case clang::Type::Elaborated:
|
||||
return enumerateElaboratedType(
|
||||
llvm::cast<const clang::ElaboratedType>(ty));
|
||||
case clang::Type::TemplateSpecialization:
|
||||
return enumerateTemplateSpecialization(
|
||||
llvm::cast<const clang::TemplateSpecializationType>(ty));
|
||||
case clang::Type::UnaryTransform:
|
||||
return enumerateUnaryTransformType(
|
||||
llvm::cast<const clang::UnaryTransformType>(ty));
|
||||
case clang::Type::Decltype:
|
||||
return enumerateDecltypeType(llvm::cast<const clang::DecltypeType>(ty));
|
||||
case clang::Type::Typedef:
|
||||
return enumerateTypedef(llvm::cast<const clang::TypedefType>(ty));
|
||||
case clang::Type::Using:
|
||||
return enumerateUsing(llvm::cast<const clang::UsingType>(ty));
|
||||
case clang::Type::ConstantArray:
|
||||
return enumerateArray(llvm::cast<const clang::ConstantArrayType>(ty));
|
||||
case clang::Type::Enum:
|
||||
return enumerateEnum(llvm::cast<const clang::EnumType>(ty));
|
||||
|
||||
default:
|
||||
throw std::logic_error(std::string("unsupported TypeClass `") +
|
||||
ty.getTypeClassName() + '`');
|
||||
}
|
||||
}
|
||||
|
||||
Type& ClangTypeParser::enumerateDecltypeType(const clang::DecltypeType& ty) {
|
||||
return enumerateType(*ty.getUnderlyingType());
|
||||
}
|
||||
|
||||
Type& ClangTypeParser::enumerateUnaryTransformType(
|
||||
const clang::UnaryTransformType& ty) {
|
||||
return enumerateType(*ty.getUnderlyingType());
|
||||
}
|
||||
|
||||
Typedef& ClangTypeParser::enumerateUsing(const clang::UsingType& ty) {
|
||||
auto& inner = enumerateType(*ty.desugar());
|
||||
std::string name = ty.getFoundDecl()->getNameAsString();
|
||||
return makeType<Typedef>(ty, std::move(name), inner);
|
||||
}
|
||||
|
||||
Typedef& ClangTypeParser::enumerateTypedef(const clang::TypedefType& ty) {
|
||||
auto& inner = enumerateType(*ty.desugar());
|
||||
|
||||
std::string name = ty.getDecl()->getNameAsString();
|
||||
return makeType<Typedef>(ty, std::move(name), inner);
|
||||
}
|
||||
|
||||
Enum& ClangTypeParser::enumerateEnum(const clang::EnumType& ty) {
|
||||
std::string name = ty.getDecl()->getNameAsString();
|
||||
auto size = ast->getTypeSize(clang::QualType(&ty, 0)) / 8;
|
||||
|
||||
std::map<int64_t, std::string> enumeratorMap;
|
||||
if (options_.readEnumValues) {
|
||||
for (const auto* enumerator : ty.getDecl()->enumerators()) {
|
||||
enumeratorMap.emplace(enumerator->getInitVal().getExtValue(),
|
||||
enumerator->getNameAsString());
|
||||
}
|
||||
}
|
||||
|
||||
return makeType<Enum>(ty, std::move(name), size, std::move(enumeratorMap));
|
||||
}
|
||||
|
||||
Array& ClangTypeParser::enumerateArray(const clang::ConstantArrayType& ty) {
|
||||
uint64_t len = ty.getSize().getLimitedValue();
|
||||
auto& t = enumerateType(*ty.getElementType());
|
||||
return makeType<Array>(ty, t, len);
|
||||
}
|
||||
|
||||
Type& ClangTypeParser::enumerateTemplateSpecialization(
|
||||
const clang::TemplateSpecializationType& ty) {
|
||||
if (ty.isSugared())
|
||||
return enumerateType(*ty.desugar());
|
||||
|
||||
LOG(WARNING) << "failed on a TemplateSpecializationType";
|
||||
ty.dump();
|
||||
return makeType<Primitive>(ty, Primitive::Kind::Int32);
|
||||
}
|
||||
|
||||
Type& ClangTypeParser::enumerateClass(const clang::RecordType& ty) {
|
||||
std::string fqName = clang::TypeName::getFullyQualifiedName(
|
||||
clang::QualType(&ty, 0), *ast, {ast->getLangOpts()});
|
||||
auto size = ast->getTypeSize(clang::QualType(&ty, 0)) / 8;
|
||||
|
||||
if (auto* info = getContainerInfo(fqName)) {
|
||||
auto& c = makeType<Container>(ty, *info, size, nullptr);
|
||||
enumerateClassTemplateParams(ty, c.templateParams);
|
||||
c.setAlign(ast->getTypeAlign(clang::QualType(&ty, 0)));
|
||||
return c;
|
||||
}
|
||||
|
||||
auto* decl = ty.getDecl();
|
||||
|
||||
std::string name = decl->getNameAsString();
|
||||
|
||||
auto kind = Class::Kind::Struct; // TODO: kind
|
||||
|
||||
int virtuality = 0;
|
||||
|
||||
auto& c = makeType<Class>(
|
||||
ty, kind, std::move(name), std::move(fqName), size, virtuality);
|
||||
|
||||
enumerateClassTemplateParams(ty, c.templateParams);
|
||||
// enumerateClassParents(type, c.parents);
|
||||
enumerateClassMembers(ty, c.members);
|
||||
// enumerateClassFunctions(type, c.functions);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
void ClangTypeParser::enumerateClassTemplateParams(
|
||||
const clang::RecordType& ty, std::vector<TemplateParam>& params) {
|
||||
assert(params.empty());
|
||||
|
||||
auto* decl = dyn_cast<clang::ClassTemplateSpecializationDecl>(ty.getDecl());
|
||||
if (decl == nullptr)
|
||||
return;
|
||||
|
||||
const auto& list = decl->getTemplateArgs();
|
||||
|
||||
params.reserve(list.size());
|
||||
for (const auto& arg : list.asArray()) {
|
||||
if (auto p = enumerateTemplateParam(arg))
|
||||
params.emplace_back(std::move(p.value()));
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<TemplateParam> ClangTypeParser::enumerateTemplateParam(
|
||||
const clang::TemplateArgument& p) {
|
||||
switch (p.getKind()) {
|
||||
case clang::TemplateArgument::Type: {
|
||||
auto qualType = p.getAsType();
|
||||
QualifierSet qualifiers;
|
||||
qualifiers[Qualifier::Const] = qualType.isConstQualified();
|
||||
Type& ttype = enumerateType(*qualType);
|
||||
return TemplateParam{ttype, qualifiers};
|
||||
}
|
||||
case clang::TemplateArgument::Integral: {
|
||||
auto& ty = enumerateType(*p.getIntegralType());
|
||||
llvm::SmallString<32> val;
|
||||
p.getAsIntegral().toString(val);
|
||||
return TemplateParam{ty, std::string(val)};
|
||||
}
|
||||
case clang::TemplateArgument::Template: {
|
||||
return enumerateTemplateTemplateParam(p.getAsTemplate());
|
||||
}
|
||||
|
||||
#define X(name) \
|
||||
case clang::TemplateArgument::name: \
|
||||
throw std::logic_error("unsupported template argument kind: " #name);
|
||||
|
||||
X(Null)
|
||||
X(Declaration)
|
||||
X(NullPtr)
|
||||
X(TemplateExpansion)
|
||||
X(Expression)
|
||||
X(Pack)
|
||||
#undef X
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<TemplateParam> ClangTypeParser::enumerateTemplateTemplateParam(
|
||||
const clang::TemplateName& tn) {
|
||||
switch (tn.getKind()) {
|
||||
case clang::TemplateName::Template:
|
||||
return std::nullopt;
|
||||
|
||||
#define X(name) \
|
||||
case clang::TemplateName::name: \
|
||||
throw std::logic_error("unsupported template name kind: " #name);
|
||||
|
||||
X(OverloadedTemplate)
|
||||
X(AssumedTemplate)
|
||||
X(QualifiedTemplate)
|
||||
X(DependentTemplate)
|
||||
X(SubstTemplateTemplateParm)
|
||||
X(SubstTemplateTemplateParmPack)
|
||||
X(UsingTemplate)
|
||||
#undef X
|
||||
}
|
||||
}
|
||||
|
||||
void ClangTypeParser::enumerateClassMembers(const clang::RecordType& ty,
|
||||
std::vector<Member>& members) {
|
||||
assert(members.empty());
|
||||
|
||||
auto* decl = ty.getDecl();
|
||||
|
||||
for (const auto* field : decl->fields()) {
|
||||
clang::QualType qualType = field->getType();
|
||||
std::string member_name = field->getNameAsString();
|
||||
|
||||
size_t size_in_bits = 0;
|
||||
if (field->isBitField()) {
|
||||
size_in_bits = field->getBitWidthValue(*ast);
|
||||
}
|
||||
|
||||
size_t offset_in_bits = decl->getASTContext().getFieldOffset(field);
|
||||
|
||||
auto& mtype = enumerateType(*qualType);
|
||||
Member m{mtype, std::move(member_name), offset_in_bits, size_in_bits};
|
||||
members.push_back(m);
|
||||
}
|
||||
|
||||
std::sort(members.begin(), members.end(), [](const auto& a, const auto& b) {
|
||||
return a.bitOffset < b.bitOffset;
|
||||
});
|
||||
}
|
||||
|
||||
Type& ClangTypeParser::enumerateReference(
|
||||
const clang::LValueReferenceType& ty) {
|
||||
// TODO: function references
|
||||
Type& t = enumerateType(*ty.getPointeeType());
|
||||
if (dynamic_cast<Incomplete*>(&t))
|
||||
return makeType<Pointer>(ty, t);
|
||||
|
||||
return makeType<Reference>(ty, t);
|
||||
}
|
||||
|
||||
Type& ClangTypeParser::enumeratePointer(const clang::PointerType& ty) {
|
||||
// TODO: function pointers
|
||||
if (!chasePointer())
|
||||
return makeType<Primitive>(ty, Primitive::Kind::StubbedPointer);
|
||||
|
||||
Type& t = enumerateType(*ty.getPointeeType());
|
||||
return makeType<Reference>(ty, t);
|
||||
}
|
||||
|
||||
Type& ClangTypeParser::enumerateSubstTemplateTypeParm(
|
||||
const clang::SubstTemplateTypeParmType& ty) {
|
||||
// Clang wraps any type that was substituted from e.g. `T` in this type. It
|
||||
// should have no representation in the type graph.
|
||||
return enumerateType(*ty.getReplacementType());
|
||||
}
|
||||
|
||||
Type& ClangTypeParser::enumerateElaboratedType(
|
||||
const clang::ElaboratedType& ty) {
|
||||
// Clang wraps any type that is name qualified in this type. It should have no
|
||||
// representation in the type graph.
|
||||
return enumerateType(*ty.getNamedType());
|
||||
}
|
||||
|
||||
Primitive& ClangTypeParser::enumeratePrimitive(const clang::BuiltinType& ty) {
|
||||
switch (ty.getKind()) {
|
||||
case clang::BuiltinType::Void:
|
||||
return makeType<Primitive>(ty, Primitive::Kind::Void);
|
||||
|
||||
case clang::BuiltinType::Bool:
|
||||
return makeType<Primitive>(ty, Primitive::Kind::Bool);
|
||||
|
||||
case clang::BuiltinType::Char_U:
|
||||
case clang::BuiltinType::UChar:
|
||||
return makeType<Primitive>(ty, Primitive::Kind::UInt8);
|
||||
case clang::BuiltinType::WChar_U:
|
||||
return makeType<Primitive>(ty, Primitive::Kind::UInt32);
|
||||
|
||||
case clang::BuiltinType::Char_S:
|
||||
case clang::BuiltinType::SChar:
|
||||
return makeType<Primitive>(ty, Primitive::Kind::Int8);
|
||||
case clang::BuiltinType::WChar_S:
|
||||
return makeType<Primitive>(ty, Primitive::Kind::Int32);
|
||||
case clang::BuiltinType::Char16:
|
||||
return makeType<Primitive>(ty, Primitive::Kind::Int16);
|
||||
case clang::BuiltinType::Char32:
|
||||
return makeType<Primitive>(ty, Primitive::Kind::Int32);
|
||||
|
||||
case clang::BuiltinType::UShort:
|
||||
return makeType<Primitive>(ty, Primitive::Kind::UInt16);
|
||||
case clang::BuiltinType::UInt:
|
||||
return makeType<Primitive>(ty, Primitive::Kind::UInt32);
|
||||
case clang::BuiltinType::ULong:
|
||||
return makeType<Primitive>(ty, Primitive::Kind::UInt64);
|
||||
case clang::BuiltinType::ULongLong:
|
||||
return makeType<Primitive>(ty, Primitive::Kind::Int64);
|
||||
|
||||
case clang::BuiltinType::Short:
|
||||
return makeType<Primitive>(ty, Primitive::Kind::Int16);
|
||||
case clang::BuiltinType::Int:
|
||||
return makeType<Primitive>(ty, Primitive::Kind::Int32);
|
||||
case clang::BuiltinType::Long:
|
||||
case clang::BuiltinType::LongLong:
|
||||
return makeType<Primitive>(ty, Primitive::Kind::Int64);
|
||||
|
||||
case clang::BuiltinType::Float:
|
||||
return makeType<Primitive>(ty, Primitive::Kind::Float32);
|
||||
case clang::BuiltinType::Double:
|
||||
case clang::BuiltinType::LongDouble:
|
||||
return makeType<Primitive>(ty, Primitive::Kind::Float64);
|
||||
|
||||
case clang::BuiltinType::UInt128:
|
||||
case clang::BuiltinType::Int128:
|
||||
default:
|
||||
throw std::logic_error(std::string("unsupported BuiltinType::Kind"));
|
||||
}
|
||||
}
|
||||
|
||||
bool ClangTypeParser::chasePointer() const {
|
||||
// Always chase top-level pointers
|
||||
if (depth_ == 1)
|
||||
return true;
|
||||
return options_.chaseRawPointers;
|
||||
}
|
||||
|
||||
ContainerInfo* ClangTypeParser::getContainerInfo(
|
||||
const std::string& fqName) const {
|
||||
for (const auto& containerInfo : containers_) {
|
||||
if (std::regex_search(fqName, containerInfo->matcher)) {
|
||||
return containerInfo.get();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool requireCompleteType(clang::Sema& sema, const clang::Type& ty) {
|
||||
if (ty.isSpecificBuiltinType(clang::BuiltinType::Void))
|
||||
return true; // treat as complete
|
||||
|
||||
// TODO: This is a terrible warning.
|
||||
return !sema.RequireCompleteType(
|
||||
sema.getASTContext().getTranslationUnitDecl()->getEndLoc(),
|
||||
clang::QualType{&ty, 0},
|
||||
clang::diag::warn_nsconsumed_attribute_mismatch);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace oi::detail::type_graph
|
131
oi/type_graph/ClangTypeParser.h
Normal file
131
oi/type_graph/ClangTypeParser.h
Normal file
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "oi/type_graph/TypeGraph.h"
|
||||
|
||||
namespace clang {
|
||||
class ASTContext;
|
||||
class BuiltinType;
|
||||
class ConstantArrayType;
|
||||
class DecltypeType;
|
||||
class ElaboratedType;
|
||||
class EnumType;
|
||||
class LValueReferenceType;
|
||||
class PointerType;
|
||||
class RecordType;
|
||||
class Sema;
|
||||
class SubstTemplateTypeParmType;
|
||||
class TemplateArgument;
|
||||
class TemplateName;
|
||||
class TemplateSpecializationType;
|
||||
class Type;
|
||||
class TypedefType;
|
||||
class UnaryTransformType;
|
||||
class UsingType;
|
||||
} // namespace clang
|
||||
|
||||
struct ContainerInfo;
|
||||
|
||||
namespace oi::detail::type_graph {
|
||||
|
||||
class Array;
|
||||
class Class;
|
||||
class Enum;
|
||||
class Member;
|
||||
class Primitive;
|
||||
class Reference;
|
||||
class Type;
|
||||
class TypeGraph;
|
||||
class Typedef;
|
||||
struct TemplateParam;
|
||||
|
||||
struct ClangTypeParserOptions {
|
||||
bool chaseRawPointers = false;
|
||||
bool readEnumValues = false;
|
||||
};
|
||||
|
||||
/*
|
||||
* ClangTypeParser
|
||||
*
|
||||
* Reads source information from a source file to build a type graph.
|
||||
* Returns a reference to the Type node corresponding to the given clang::Type.
|
||||
*/
|
||||
class ClangTypeParser {
|
||||
public:
|
||||
ClangTypeParser(TypeGraph& typeGraph,
|
||||
const std::vector<std::unique_ptr<ContainerInfo>>& containers,
|
||||
ClangTypeParserOptions options)
|
||||
: typeGraph_{typeGraph}, containers_{containers}, options_{options} {
|
||||
}
|
||||
|
||||
// Parse from a clang type.
|
||||
Type& parse(clang::ASTContext&, clang::Sema&, const clang::Type&);
|
||||
|
||||
private:
|
||||
TypeGraph& typeGraph_;
|
||||
const std::vector<std::unique_ptr<ContainerInfo>>& containers_;
|
||||
ClangTypeParserOptions options_;
|
||||
clang::ASTContext* ast;
|
||||
clang::Sema* sema;
|
||||
|
||||
uint_fast32_t depth_;
|
||||
std::unordered_map<const clang::Type*, std::reference_wrapper<Type>>
|
||||
clang_types_;
|
||||
|
||||
Type& enumerateType(const clang::Type&);
|
||||
Type& enumerateClass(const clang::RecordType&);
|
||||
Type& enumerateReference(const clang::LValueReferenceType&);
|
||||
Type& enumeratePointer(const clang::PointerType&);
|
||||
Type& enumerateSubstTemplateTypeParm(const clang::SubstTemplateTypeParmType&);
|
||||
Primitive& enumeratePrimitive(const clang::BuiltinType&);
|
||||
Type& enumerateElaboratedType(const clang::ElaboratedType&);
|
||||
Type& enumerateTemplateSpecialization(
|
||||
const clang::TemplateSpecializationType&);
|
||||
Typedef& enumerateTypedef(const clang::TypedefType&);
|
||||
Typedef& enumerateUsing(const clang::UsingType&);
|
||||
Type& enumerateUnaryTransformType(const clang::UnaryTransformType&);
|
||||
Type& enumerateDecltypeType(const clang::DecltypeType&);
|
||||
|
||||
Array& enumerateArray(const clang::ConstantArrayType&);
|
||||
Enum& enumerateEnum(const clang::EnumType&);
|
||||
|
||||
void enumerateClassTemplateParams(const clang::RecordType&,
|
||||
std::vector<TemplateParam>&);
|
||||
std::optional<TemplateParam> enumerateTemplateParam(
|
||||
const clang::TemplateArgument&);
|
||||
std::optional<TemplateParam> enumerateTemplateTemplateParam(
|
||||
const clang::TemplateName&);
|
||||
|
||||
void enumerateClassMembers(const clang::RecordType&, std::vector<Member>&);
|
||||
|
||||
ContainerInfo* getContainerInfo(const std::string& fqName) const;
|
||||
|
||||
template <typename T, typename... Args>
|
||||
T& makeType(const clang::Type& clangType, Args&&... args) {
|
||||
auto& newType = typeGraph_.makeType<T>(std::forward<Args>(args)...);
|
||||
clang_types_.insert({&clangType, newType});
|
||||
return newType;
|
||||
}
|
||||
|
||||
bool chasePointer() const;
|
||||
};
|
||||
|
||||
} // namespace oi::detail::type_graph
|
@ -200,6 +200,14 @@ drgn_type* DrgnExporter::visit(Pointer& p) {
|
||||
return drgnType;
|
||||
}
|
||||
|
||||
drgn_type* DrgnExporter::visit(Reference& p) {
|
||||
auto* drgnType =
|
||||
makeDrgnType(DRGN_TYPE_POINTER, false, DRGN_NOT_PRIMITIVE_TYPE, p);
|
||||
auto* pointeeType = accept(p.pointeeType());
|
||||
th_.pointerToTypeMap[drgnType] = pointeeType;
|
||||
return drgnType;
|
||||
}
|
||||
|
||||
drgn_type* DrgnExporter::visit(Dummy& d) {
|
||||
return makeDrgnType(DRGN_TYPE_VOID, false, DRGN_C_TYPE_VOID, d);
|
||||
}
|
||||
|
@ -54,6 +54,7 @@ class DrgnExporter : public Visitor<drgn_type*> {
|
||||
drgn_type* visit(Array&) override;
|
||||
drgn_type* visit(Typedef&) override;
|
||||
drgn_type* visit(Pointer&) override;
|
||||
drgn_type* visit(Reference&) override;
|
||||
drgn_type* visit(Dummy&) override;
|
||||
drgn_type* visit(DummyAllocator&) override;
|
||||
drgn_type* visit(CaptureKeys&) override;
|
||||
|
@ -200,6 +200,15 @@ void NameGen::visit(Pointer& p) {
|
||||
p.setInputName(inputName);
|
||||
}
|
||||
|
||||
void NameGen::visit(Reference& r) {
|
||||
RecursiveVisitor::visit(r);
|
||||
|
||||
r.regenerateName();
|
||||
std::string inputName{r.pointeeType().inputName()};
|
||||
inputName += '&';
|
||||
r.setInputName(inputName);
|
||||
}
|
||||
|
||||
void NameGen::visit(DummyAllocator& d) {
|
||||
RecursiveVisitor::visit(d);
|
||||
d.regenerateName();
|
||||
|
@ -46,6 +46,7 @@ class NameGen final : public RecursiveVisitor {
|
||||
void visit(Array& a) override;
|
||||
void visit(Typedef& td) override;
|
||||
void visit(Pointer& p) override;
|
||||
void visit(Reference& r) override;
|
||||
void visit(DummyAllocator& d) override;
|
||||
void visit(CaptureKeys& d) override;
|
||||
|
||||
|
@ -150,6 +150,17 @@ void Printer::visit(const Pointer& p) {
|
||||
print(p.pointeeType());
|
||||
}
|
||||
|
||||
void Printer::visit(const Reference& r) {
|
||||
if (prefix(r))
|
||||
return;
|
||||
|
||||
out_ << "Reference";
|
||||
if (auto inp = r.inputName(); !inp.empty())
|
||||
out_ << " [" << inp << "]";
|
||||
out_ << std::endl;
|
||||
print(r.pointeeType());
|
||||
}
|
||||
|
||||
void Printer::visit(const Dummy& d) {
|
||||
if (prefix(d))
|
||||
return;
|
||||
|
@ -40,6 +40,7 @@ class Printer : public ConstVisitor {
|
||||
void visit(const Array& a) override;
|
||||
void visit(const Typedef& td) override;
|
||||
void visit(const Pointer& p) override;
|
||||
void visit(const Reference& r) override;
|
||||
void visit(const Dummy& d) override;
|
||||
void visit(const DummyAllocator& d) override;
|
||||
void visit(const CaptureKeys& d) override;
|
||||
|
@ -42,4 +42,8 @@ void RemoveTopLevelPointer::visit(Pointer& p) {
|
||||
topLevelType_ = &p.pointeeType();
|
||||
}
|
||||
|
||||
void RemoveTopLevelPointer::visit(Reference& r) {
|
||||
topLevelType_ = &r.pointeeType();
|
||||
}
|
||||
|
||||
} // namespace oi::detail::type_graph
|
||||
|
@ -36,6 +36,7 @@ class RemoveTopLevelPointer : public LazyVisitor {
|
||||
|
||||
void removeTopLevelPointers(std::vector<std::reference_wrapper<Type>>& types);
|
||||
void visit(Pointer& p) override;
|
||||
void visit(Reference& r) override;
|
||||
|
||||
private:
|
||||
Type* topLevelType_ = nullptr;
|
||||
|
@ -49,6 +49,7 @@
|
||||
X(Array) \
|
||||
X(Typedef) \
|
||||
X(Pointer) \
|
||||
X(Reference) \
|
||||
X(Dummy) \
|
||||
X(DummyAllocator) \
|
||||
X(CaptureKeys)
|
||||
@ -734,6 +735,66 @@ class Pointer : public Type {
|
||||
std::string name_;
|
||||
};
|
||||
|
||||
class Reference : public Type {
|
||||
public:
|
||||
explicit Reference(NodeId id, Type& pointeeType)
|
||||
: pointeeType_(pointeeType), id_(id) {
|
||||
regenerateName();
|
||||
}
|
||||
|
||||
static inline constexpr bool has_node_id = true;
|
||||
|
||||
DECLARE_ACCEPT
|
||||
|
||||
virtual const std::string& name() const override {
|
||||
return name_;
|
||||
}
|
||||
|
||||
void regenerateName() {
|
||||
// Following a reference wouldn't trigger cycle checking, as it would look
|
||||
// like anything else we're sure is there. Generate as a pointer. It will be
|
||||
// followed regardless of `ChaseRawPointers` because that affects whether a
|
||||
// type becomes a `StubbedPointer` and not whether pointers are followed in
|
||||
// the generated code.
|
||||
name_ = pointeeType_.get().name() + "*";
|
||||
}
|
||||
|
||||
virtual std::string_view inputName() const override {
|
||||
return inputName_;
|
||||
}
|
||||
|
||||
void setInputName(std::string name) {
|
||||
inputName_ = std::move(name);
|
||||
}
|
||||
|
||||
virtual size_t size() const override {
|
||||
return sizeof(uintptr_t);
|
||||
}
|
||||
|
||||
virtual uint64_t align() const override {
|
||||
return size();
|
||||
}
|
||||
|
||||
virtual NodeId id() const override {
|
||||
return id_;
|
||||
}
|
||||
|
||||
Type& pointeeType() const {
|
||||
return pointeeType_;
|
||||
}
|
||||
|
||||
void setPointeeType(Type& type) {
|
||||
pointeeType_ = type;
|
||||
}
|
||||
|
||||
private:
|
||||
std::reference_wrapper<Type> pointeeType_;
|
||||
std::string inputName_;
|
||||
NodeId id_ = -1;
|
||||
|
||||
std::string name_;
|
||||
};
|
||||
|
||||
/*
|
||||
* Dummy
|
||||
*
|
||||
|
@ -108,6 +108,9 @@ class RecursiveVisitor : public Visitor<void> {
|
||||
virtual void visit(Pointer& p) {
|
||||
accept(p.pointeeType());
|
||||
}
|
||||
virtual void visit(Reference& r) {
|
||||
accept(r.pointeeType());
|
||||
}
|
||||
virtual void visit(Dummy&) {
|
||||
}
|
||||
virtual void visit(DummyAllocator& d) {
|
||||
@ -175,6 +178,10 @@ class RecursiveMutator : public Visitor<Type&> {
|
||||
p.setPointeeType(mutate(p.pointeeType()));
|
||||
return p;
|
||||
}
|
||||
virtual Type& visit(Reference& p) {
|
||||
p.setPointeeType(mutate(p.pointeeType()));
|
||||
return p;
|
||||
}
|
||||
virtual Type& visit(Dummy& d) {
|
||||
return d;
|
||||
}
|
||||
|
147
tools/OILGen.cpp
147
tools/OILGen.cpp
@ -14,131 +14,80 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <clang/Tooling/CommonOptionsParser.h>
|
||||
#include <gflags/gflags.h>
|
||||
#include <glog/logging.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <range/v3/range/conversion.hpp>
|
||||
#include <range/v3/view/transform.hpp>
|
||||
#include <vector>
|
||||
|
||||
#include "oi/OICodeGen.h"
|
||||
#include "oi/OIGenerator.h"
|
||||
#include "oi/OIOpts.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
using namespace oi::detail;
|
||||
|
||||
constexpr static OIOpts opts{
|
||||
OIOpt{'h', "help", no_argument, nullptr, "Print this message and exit."},
|
||||
OIOpt{'o',
|
||||
"output",
|
||||
required_argument,
|
||||
"<file>",
|
||||
"Write output(s) to file(s) with this prefix."},
|
||||
OIOpt{'c',
|
||||
"config-file",
|
||||
required_argument,
|
||||
"<oid.toml>",
|
||||
"Path to OI configuration file."},
|
||||
OIOpt{'d',
|
||||
"debug-level",
|
||||
required_argument,
|
||||
"<level>",
|
||||
"Verbose level for logging"},
|
||||
OIOpt{'j',
|
||||
"dump-jit",
|
||||
optional_argument,
|
||||
"<jit.cpp>",
|
||||
"Write generated code to a file (for debugging)."},
|
||||
OIOpt{'e',
|
||||
"exit-code",
|
||||
no_argument,
|
||||
nullptr,
|
||||
"Return a bad exit code if nothing is generated."},
|
||||
OIOpt{'p',
|
||||
"pic",
|
||||
no_argument,
|
||||
nullptr,
|
||||
"Generate position independent code."},
|
||||
};
|
||||
static llvm::cl::OptionCategory OilgenCategory("oilgen options");
|
||||
|
||||
void usage() {
|
||||
std::cerr << "usage: oilgen ARGS INPUT_OBJECT" << std::endl;
|
||||
std::cerr << opts;
|
||||
static llvm::cl::list<std::string> ConfigFiles(
|
||||
"config-file",
|
||||
llvm::cl::desc(R"(</path/to/oid.toml>)"),
|
||||
llvm::cl::cat(OilgenCategory));
|
||||
static llvm::cl::opt<std::string> OutputFile(
|
||||
"output",
|
||||
llvm::cl::desc(R"(Write output to this file.)"),
|
||||
llvm::cl::init("a.o"),
|
||||
llvm::cl::cat(OilgenCategory));
|
||||
static llvm::cl::opt<int> DebugLevel(
|
||||
"debug-level",
|
||||
llvm::cl::desc(R"(Verbose level for logging.)"),
|
||||
llvm::cl::init(-1),
|
||||
llvm::cl::cat(OilgenCategory));
|
||||
static llvm::cl::opt<std::string> DumpJit(
|
||||
"dump-jit",
|
||||
llvm::cl::desc(R"(Write the generated code to a file.)"),
|
||||
llvm::cl::init("jit.cpp"),
|
||||
llvm::cl::cat(OilgenCategory));
|
||||
|
||||
std::cerr << std::endl
|
||||
<< "You probably shouldn't be calling this application directly. "
|
||||
"It's meant to be"
|
||||
<< std::endl
|
||||
<< "called by a clang plugin automatically with BUCK." << std::endl;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
int main(int argc, const char* argv[]) {
|
||||
google::InitGoogleLogging(argv[0]);
|
||||
FLAGS_minloglevel = 0;
|
||||
FLAGS_stderrthreshold = 0;
|
||||
|
||||
fs::path outputPath = "a.o";
|
||||
std::vector<fs::path> configFilePaths;
|
||||
fs::path sourceFileDumpPath = "";
|
||||
bool exitCode = false;
|
||||
bool pic = false;
|
||||
|
||||
int c;
|
||||
while ((c = getopt_long(
|
||||
argc, argv, opts.shortOpts(), opts.longOpts(), nullptr)) != -1) {
|
||||
switch (c) {
|
||||
case 'h':
|
||||
usage();
|
||||
return EXIT_SUCCESS;
|
||||
case 'o':
|
||||
outputPath = optarg;
|
||||
break;
|
||||
case 'c':
|
||||
configFilePaths.emplace_back(optarg);
|
||||
break;
|
||||
case 'd':
|
||||
google::LogToStderr();
|
||||
google::SetStderrLogging(google::INFO);
|
||||
google::SetVLOGLevel("*", atoi(optarg));
|
||||
// Upstream glog defines `GLOG_INFO` as 0 https://fburl.com/ydjajhz0,
|
||||
// but internally it's defined as 1 https://fburl.com/code/9fwams75
|
||||
gflags::SetCommandLineOption("minloglevel", "0");
|
||||
break;
|
||||
case 'j':
|
||||
sourceFileDumpPath = optarg != nullptr ? optarg : "jit.cpp";
|
||||
break;
|
||||
case 'e':
|
||||
exitCode = true;
|
||||
break;
|
||||
case 'p':
|
||||
pic = true;
|
||||
break;
|
||||
}
|
||||
auto expectedParser =
|
||||
clang::tooling::CommonOptionsParser::create(argc, argv, OilgenCategory);
|
||||
if (!expectedParser) {
|
||||
llvm::errs() << expectedParser.takeError();
|
||||
return -1;
|
||||
}
|
||||
clang::tooling::CommonOptionsParser& options = expectedParser.get();
|
||||
auto& compilations = options.getCompilations();
|
||||
|
||||
if (optind >= argc) {
|
||||
usage();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
fs::path primaryObject = argv[optind];
|
||||
|
||||
if ((setenv("DRGN_ENABLE_TYPE_ITERATOR", "1", 1)) < 0) {
|
||||
LOG(ERROR) << "Failed to set environment variable\
|
||||
DRGN_ENABLE_TYPE_ITERATOR\n";
|
||||
exit(EXIT_FAILURE);
|
||||
if (DebugLevel.getNumOccurrences()) {
|
||||
google::LogToStderr();
|
||||
google::SetStderrLogging(google::INFO);
|
||||
google::SetVLOGLevel("*", DebugLevel);
|
||||
// Upstream glog defines `GLOG_INFO` as 0 https://fburl.com/ydjajhz0,
|
||||
// but internally it's defined as 1 https://fburl.com/code/9fwams75
|
||||
gflags::SetCommandLineOption("minloglevel", "0");
|
||||
}
|
||||
|
||||
OIGenerator oigen;
|
||||
|
||||
oigen.setOutputPath(std::move(outputPath));
|
||||
oigen.setConfigFilePaths(std::move(configFilePaths));
|
||||
oigen.setSourceFileDumpPath(sourceFileDumpPath);
|
||||
oigen.setFailIfNothingGenerated(exitCode);
|
||||
oigen.setUsePIC(pic);
|
||||
oigen.setConfigFilePaths(ConfigFiles |
|
||||
ranges::views::transform([](const auto& p) {
|
||||
return std::filesystem::path(p);
|
||||
}) |
|
||||
ranges::to<std::vector>());
|
||||
if (DumpJit.getNumOccurrences())
|
||||
oigen.setSourceFileDumpPath(DumpJit.getValue());
|
||||
|
||||
SymbolService symbols(primaryObject);
|
||||
oigen.setOutputPath(OutputFile.getValue());
|
||||
|
||||
return oigen.generate(primaryObject, symbols);
|
||||
oigen.setFailIfNothingGenerated(true);
|
||||
return oigen.generate(compilations, options.getSourcePathList());
|
||||
}
|
||||
|
@ -35,3 +35,34 @@ void getSizeType(const %1%<T, Traits, Allocator> &container, size_t& returnArg)
|
||||
);
|
||||
}
|
||||
"""
|
||||
|
||||
traversal_func = """
|
||||
bool sso = ((uintptr_t)container.data() <
|
||||
(uintptr_t)(&container + sizeof(std::__cxx11::basic_string<T0>))) &&
|
||||
((uintptr_t)container.data() >= (uintptr_t)&container);
|
||||
|
||||
return returnArg.write(container.capacity())
|
||||
.write(sso)
|
||||
.write(container.size());
|
||||
"""
|
||||
|
||||
[[codegen.processor]]
|
||||
type = "types::st::VarInt<DB>"
|
||||
func = """
|
||||
uint64_t capacity = std::get<ParsedData::VarInt>(d.val).value;
|
||||
el.container_stats.emplace(result::Element::ContainerStats { .capacity = capacity });
|
||||
"""
|
||||
|
||||
[[codegen.processor]]
|
||||
type = "types::st::VarInt<DB>"
|
||||
func = """
|
||||
bool sso = std::get<ParsedData::VarInt>(d.val).value;
|
||||
if (!sso)
|
||||
el.exclusive_size += el.container_stats->capacity * sizeof(T0);
|
||||
"""
|
||||
|
||||
[[codegen.processor]]
|
||||
type = "types::st::VarInt<DB>"
|
||||
func = """
|
||||
el.container_stats->length = std::get<ParsedData::VarInt>(d.val).value;
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user