mirror of
https://github.com/JakeHillion/object-introspection.git
synced 2024-11-09 21:24:14 +00:00
55989a9156
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
281 lines
8.8 KiB
C++
281 lines
8.8 KiB
C++
/*
|
|
* 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 "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 <fstream>
|
|
#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/Headers.h"
|
|
#include "oi/type_graph/ClangTypeParser.h"
|
|
#include "oi/type_graph/TypeGraph.h"
|
|
#include "oi/type_graph/Types.h"
|
|
|
|
namespace oi::detail {
|
|
namespace {
|
|
|
|
class ConsumerContext;
|
|
|
|
class CreateTypeGraphConsumer;
|
|
class CreateTypeGraphAction : public clang::ASTFrontendAction {
|
|
public:
|
|
CreateTypeGraphAction(ConsumerContext& ctx_) : ctx{ctx_} {
|
|
}
|
|
|
|
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_} {
|
|
}
|
|
|
|
std::unique_ptr<clang::FrontendAction> create() override {
|
|
return std::make_unique<CreateTypeGraphAction>(ctx);
|
|
}
|
|
|
|
private:
|
|
ConsumerContext& ctx;
|
|
};
|
|
|
|
class ConsumerContext {
|
|
public:
|
|
ConsumerContext(const std::vector<std::unique_ptr<ContainerInfo>>& cis)
|
|
: containerInfos{cis} {
|
|
}
|
|
|
|
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;
|
|
}
|
|
containerInfos.emplace_back(std::move(info));
|
|
}
|
|
} catch (const ContainerInfoError& err) {
|
|
LOG(ERROR) << "Error reading container TOML file " << err.what();
|
|
return -1;
|
|
}
|
|
|
|
ConsumerContext ctx{containerInfos};
|
|
CreateTypeGraphActionFactory factory{ctx};
|
|
|
|
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;
|
|
codegen.generate(ctx.typeGraph, code, CodeGen::ExactName{linkageName});
|
|
|
|
std::string sourcePath = sourceFileDumpPath;
|
|
if (sourceFileDumpPath.empty()) {
|
|
// This is the path Clang acts as if it has compiled from e.g. for debug
|
|
// information. It does not need to exist.
|
|
sourcePath = "oil_jit.cpp";
|
|
} else {
|
|
std::ofstream outputFile(sourcePath);
|
|
outputFile << code;
|
|
}
|
|
|
|
OICompiler compiler{{}, compilerConfig};
|
|
return compiler.compile(code, sourcePath, outputPath) ? 0 : -1;
|
|
}
|
|
|
|
namespace {
|
|
|
|
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 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;
|
|
}
|
|
|
|
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);
|
|
|
|
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);
|
|
}
|
|
};
|
|
|
|
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
|