Patrick Kelley 8fd444092b initial
2025-05-07 15:35:15 -04:00

177 lines
6.1 KiB
C++

// Copyright (c) 2020-now by the Zeek Project. See LICENSE for details.
#pragma once
#include <iostream>
#include <list>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include <hilti/rt/filesystem.h>
#include <hilti/rt/types/reference.h>
#include <hilti/ast/ctor.h>
#include <hilti/ast/declarations/module.h>
#include <hilti/ast/expression.h>
#include <hilti/ast/operator.h>
#include <hilti/ast/statement.h>
#include <hilti/ast/type.h>
#include <hilti/ast/types/function.h>
#include <hilti/base/result.h>
#include <hilti/compiler/context.h>
#include <hilti/compiler/detail/cxx/elements.h>
#include <hilti/compiler/detail/cxx/formatter.h>
namespace hilti::detail::cxx {
class Linker;
namespace linker {
/**
* Function joined by the linker.
*
* The HILTI linker will generate a C++ function `<id>` that calls all
* `callee` function registered for that ID.
*/
struct Join {
cxx::ID id; /**< name of externally visible function */
cxx::declaration::Function callee; /**< callee function to execute through linker function */
std::list<cxx::declaration::Type>
aux_types; /**< additional types the linker needs to declare for external prototype to work */
int64_t priority =
0; /**< Priority determining the order between callees; higher priority callees will be called first */
bool declare_only = false; /**< only declare the joined C++ function, don't generate the implementation */
bool operator<(const Join& other) const {
return std::make_tuple(id, priority, callee.id) < std::make_tuple(other.id, other.priority, other.callee.id);
}
};
struct MetaData {
ID module;
ID namespace_;
hilti::rt::filesystem::path path;
std::set<Join> joins;
cxx::declaration::Constant globals_index;
};
} // namespace linker
/** One C++ code unit. */
class Unit {
public:
Unit(const std::shared_ptr<Context>& context, hilti::declaration::Module* module);
auto* module() const {
assert(_module); // available only if module was passed to constructor
return _module;
};
const auto& cxxModuleID() const { return _module_id; }
cxx::ID cxxInternalNamespace() const;
cxx::ID cxxExternalNamespace() const;
void setUsesGlobals() { _uses_globals = true; }
template<typename Declaration,
typename std::enable_if_t<std::is_base_of_v<declaration::DeclarationBase, Declaration>>* = nullptr>
void add(const Declaration& d, const Meta& m = Meta()); // add C++ declaration
void add(std::string_view stmt, const Meta& m = Meta()); // add generic top-level item
void add(const linker::Join& f); // add linker joined function
void addComment(std::string_view comment);
void addInitialization(cxx::Block block) { _init_module.appendFromBlock(std::move(block)); }
void addPreInitialization(cxx::Block block) { _preinit_module.appendFromBlock(std::move(block)); }
// @param include_all_implementations if true, do not filter out function
// implementations that aren't within the module's own namespace.
Result<Nothing> finalize(bool include_all_implementations = false);
Result<Nothing> print(std::ostream& out) const; // only after finalize
Result<Nothing> createPrototypes(std::ostream& out); // only after finalize
Result<linker::MetaData> linkerMetaData() const; // only after finalize
std::shared_ptr<Context> context() const { return _context.lock(); }
protected:
friend class Linker;
Unit(const std::shared_ptr<Context>& context, cxx::ID module_id, const std::string& cxx_code = {});
private:
enum class Phase {
Includes,
Forwards,
Enums,
Types,
PublicAliases,
Constants,
Globals,
Functions,
TypeInfos,
Implementations
};
using cxxDeclaration = std::variant<declaration::IncludeFile, declaration::Global, declaration::Constant,
declaration::Type, declaration::Function>;
void _generateCode(Formatter& f, bool prototypes_only, bool include_all_implementations);
void _emitDeclarations(const cxxDeclaration& decl, Formatter& f, Phase phase, bool prototypes_only,
bool include_all_implementations);
void _addHeader(Formatter& f);
void _addModuleInitFunction();
std::weak_ptr<Context> _context;
hilti::declaration::Module* _module = nullptr;
cxx::ID _module_id;
hilti::rt::filesystem::path _module_path;
bool _no_linker_meta_data = false;
bool _uses_globals = false;
std::optional<std::string> _cxx_code;
std::vector<std::pair<ID, cxxDeclaration>> _declarations; // maintains order of insertion
std::multimap<ID, cxxDeclaration> _declarations_by_id; // index into declarations by their ID
std::vector<std::string> _comments;
std::vector<std::string> _statements;
std::set<linker::Join> _linker_joins; // set to keep sorted.
cxx::Block _init_module;
cxx::Block _preinit_module;
cxx::Block _init_globals;
};
template<typename Declaration, typename std::enable_if_t<std::is_base_of_v<declaration::DeclarationBase, Declaration>>*>
void Unit::add(const Declaration& d, const Meta& m) {
static_assert(std::is_base_of_v<declaration::DeclarationBase, Declaration>,
"Declaration must be derived from DeclarationBase");
// Check if the same declaration has already been added.
auto decls = _declarations_by_id.equal_range(d.id);
for ( auto i = decls.first; i != decls.second; i++ ) {
auto* other = std::get_if<Declaration>(&i->second);
if ( ! other ) {
logger().internalError(
util::fmt("mismatched declaration types in cxx::Unit::add for ID %s: got a %s, but already have a %s",
d.id, typeid(d).name(), typeid(i->second).name()));
}
if ( *other == d )
// Have it already, nothing to do.
return;
}
_declarations.emplace_back(d.id, d);
if ( d.id )
_declarations_by_id.emplace(d.id, d);
}
} // namespace hilti::detail::cxx