blob: 6666735c2b234edd93f6009054b80d11eb97fe8b [file] [log] [blame]
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "rs_bindings_from_cc/importer.h"
#include <stdint.h>
#include <algorithm>
#include <cstddef>
#include <memory>
#include <optional>
#include <string>
#include <tuple>
#include <utility>
#include <variant>
#include <vector>
#include "third_party/absl/container/flat_hash_map.h"
#include "third_party/absl/container/flat_hash_set.h"
#include "third_party/absl/status/status.h"
#include "third_party/absl/status/statusor.h"
#include "third_party/absl/strings/cord.h"
#include "third_party/absl/strings/str_cat.h"
#include "third_party/absl/strings/str_join.h"
#include "third_party/absl/strings/string_view.h"
#include "third_party/absl/strings/substitute.h"
#include "lifetime_annotations/type_lifetimes.h"
#include "rs_bindings_from_cc/ast_convert.h"
#include "rs_bindings_from_cc/bazel_types.h"
#include "rs_bindings_from_cc/ir.h"
#include "rs_bindings_from_cc/util/check.h"
#include "rs_bindings_from_cc/util/status_macros.h"
#include "third_party/llvm/llvm-project/clang/include/clang/AST/ASTContext.h"
#include "third_party/llvm/llvm-project/clang/include/clang/AST/Attrs.inc"
#include "third_party/llvm/llvm-project/clang/include/clang/AST/CXXInheritance.h"
#include "third_party/llvm/llvm-project/clang/include/clang/AST/Decl.h"
#include "third_party/llvm/llvm-project/clang/include/clang/AST/DeclCXX.h"
#include "third_party/llvm/llvm-project/clang/include/clang/AST/DeclTemplate.h"
#include "third_party/llvm/llvm-project/clang/include/clang/AST/Mangle.h"
#include "third_party/llvm/llvm-project/clang/include/clang/AST/RawCommentList.h"
#include "third_party/llvm/llvm-project/clang/include/clang/AST/RecordLayout.h"
#include "third_party/llvm/llvm-project/clang/include/clang/AST/Type.h"
#include "third_party/llvm/llvm-project/clang/include/clang/Basic/FileManager.h"
#include "third_party/llvm/llvm-project/clang/include/clang/Basic/OperatorKinds.h"
#include "third_party/llvm/llvm-project/clang/include/clang/Basic/SourceLocation.h"
#include "third_party/llvm/llvm-project/clang/include/clang/Basic/SourceManager.h"
#include "third_party/llvm/llvm-project/clang/include/clang/Basic/Specifiers.h"
#include "third_party/llvm/llvm-project/clang/include/clang/Sema/Sema.h"
#include "third_party/llvm/llvm-project/llvm/include/llvm/ADT/Optional.h"
#include "third_party/llvm/llvm-project/llvm/include/llvm/ADT/SmallPtrSet.h"
#include "third_party/llvm/llvm-project/llvm/include/llvm/Support/Casting.h"
#include "third_party/llvm/llvm-project/llvm/include/llvm/Support/ErrorHandling.h"
#include "third_party/llvm/llvm-project/llvm/include/llvm/Support/FormatVariadic.h"
#include "third_party/llvm/llvm-project/llvm/include/llvm/Support/Regex.h"
namespace rs_bindings_from_cc {
namespace {
constexpr absl::string_view kTypeStatusPayloadUrl =
"type.googleapis.com/devtools.rust.cc_interop.rs_binding_from_cc.type";
// A mapping of C++ standard types to their equivalent Rust types.
// To produce more idiomatic results, these types receive special handling
// instead of using the generic type mapping mechanism.
std::optional<absl::string_view> MapKnownCcTypeToRsType(
absl::string_view cc_type) {
static const auto* const kWellKnownTypes =
new absl::flat_hash_map<absl::string_view, absl::string_view>({
{"ptrdiff_t", "isize"},
{"intptr_t", "isize"},
{"size_t", "usize"},
{"uintptr_t", "usize"},
{"std::ptrdiff_t", "isize"},
{"std::intptr_t", "isize"},
{"std::size_t", "usize"},
{"std::uintptr_t", "usize"},
{"int8_t", "i8"},
{"int16_t", "i16"},
{"int32_t", "i32"},
{"int64_t", "i64"},
{"std::int8_t", "i8"},
{"std::int16_t", "i16"},
{"std::int32_t", "i32"},
{"std::int64_t", "i64"},
{"uint8_t", "u8"},
{"uint16_t", "u16"},
{"uint32_t", "u32"},
{"uint64_t", "u64"},
{"std::uint8_t", "u8"},
{"std::uint16_t", "u16"},
{"std::uint32_t", "u32"},
{"std::uint64_t", "u64"},
{"char16_t", "u16"},
{"char32_t", "u32"},
{"wchar_t", "i32"},
});
auto it = kWellKnownTypes->find(cc_type);
if (it == kWellKnownTypes->end()) return std::nullopt;
return it->second;
}
ItemId GenerateItemId(const clang::Decl* decl) {
return ItemId(reinterpret_cast<uintptr_t>(decl->getCanonicalDecl()));
}
ItemId GenerateItemId(const clang::RawComment* comment) {
return ItemId(reinterpret_cast<uintptr_t>(comment));
}
// Converts clang::CallingConv enum [1] into an equivalent Rust Abi [2, 3, 4].
// [1]
// https://github.com/llvm/llvm-project/blob/c6a3225bb03b6afc2b63fbf13db3c100406b32ce/clang/include/clang/Basic/Specifiers.h#L262-L283
// [2] https://doc.rust-lang.org/reference/types/function-pointer.html
// [3]
// https://doc.rust-lang.org/reference/items/functions.html#extern-function-qualifier
// [4]
// https://github.com/rust-lang/rust/blob/b27ccbc7e1e6a04d749e244a3c13f72ca38e80e7/compiler/rustc_target/src/spec/abi.rs#L49
absl::StatusOr<absl::string_view> ConvertCcCallConvIntoRsAbi(
clang::CallingConv cc_call_conv) {
switch (cc_call_conv) {
case clang::CC_C: // __attribute__((cdecl))
// https://doc.rust-lang.org/reference/items/external-blocks.html#abi says
// that:
// - `extern "C"` [...] whatever the default your C compiler supports.
// - `extern "cdecl"` -- The default for x86_32 C code.
//
// We don't support C++ exceptions and therefore we use "C" (rather than
// "C-unwind") - we have no need for unwinding across the FFI boundary -
// e.g. from C++ into Rust frames (or vice versa).
return "C";
case clang::CC_X86FastCall: // __attribute__((fastcall))
// https://doc.rust-lang.org/reference/items/external-blocks.html#abi says
// that the fastcall ABI -- corresponds to MSVC's __fastcall and GCC and
// clang's __attribute__((fastcall)).
return "fastcall";
case clang::CC_X86VectorCall: // __attribute__((vectorcall))
// https://doc.rust-lang.org/reference/items/external-blocks.html#abi says
// that the vectorcall ABI -- corresponds to MSVC's __vectorcall and
// clang's __attribute__((vectorcall)).
return "vectorcall";
case clang::CC_X86ThisCall: // __attribute__((thiscall))
// We don't support C++ exceptions and therefore we use "thiscall" (rather
// than "thiscall-unwind") - we have no need for unwinding across the FFI
// boundary - e.g. from C++ into Rust frames (or vice versa).
return "thiscall";
case clang::CC_X86StdCall: // __attribute__((stdcall))
// https://doc.rust-lang.org/reference/items/external-blocks.html#abi says
// extern "stdcall" -- The default for the Win32 API on x86_32.
//
// We don't support C++ exceptions and therefore we use "stdcall" (rather
// than "stdcall-unwind") - we have no need for unwinding across the FFI
// boundary - e.g. from C++ into Rust frames (or vice versa).
return "stdcall";
case clang::CC_Win64: // __attribute__((ms_abi))
// https://doc.rust-lang.org/reference/items/external-blocks.html#abi says
// extern "win64" -- The default for C code on x86_64 Windows.
return "win64";
case clang::CC_AAPCS: // __attribute__((pcs("aapcs")))
case clang::CC_AAPCS_VFP: // __attribute__((pcs("aapcs-vfp")))
// TODO(lukasza): Should both map to "aapcs"?
break;
case clang::CC_X86_64SysV: // __attribute__((sysv_abi))
// TODO(lukasza): Maybe this is "sysv64"?
break;
case clang::CC_X86Pascal: // __attribute__((pascal))
case clang::CC_X86RegCall: // __attribute__((regcall))
case clang::CC_IntelOclBicc: // __attribute__((intel_ocl_bicc))
case clang::CC_SpirFunction: // default for OpenCL functions on SPIR target
case clang::CC_OpenCLKernel: // inferred for OpenCL kernels
case clang::CC_Swift: // __attribute__((swiftcall))
case clang::CC_SwiftAsync: // __attribute__((swiftasynccall))
case clang::CC_PreserveMost: // __attribute__((preserve_most))
case clang::CC_PreserveAll: // __attribute__((preserve_all))
case clang::CC_AArch64VectorCall: // __attribute__((aarch64_vector_pcs))
// These don't seem to have any Rust equivalents.
break;
}
return absl::UnimplementedError(
absl::StrCat("Unsupported calling convention: ",
absl::string_view(
clang::FunctionType::getNameForCallConv(cc_call_conv))));
}
} // namespace
std::vector<clang::RawComment*> Importer::ImportFreeComments() {
clang::SourceManager& sm = ctx_.getSourceManager();
// We put all comments into an ordered set in source order. Later we'll remove
// the comments that we don't want or that we get by other means.
auto source_order = [&sm](const clang::SourceLocation& a,
const clang::SourceLocation& b) {
return b.isValid() && (a.isInvalid() || sm.isBeforeInTranslationUnit(a, b));
};
auto ordered_comments = std::map<clang::SourceLocation, clang::RawComment*,
decltype(source_order)>(source_order);
// We start off by getting the comments from all entry header files...
for (const auto& header : invocation_.entry_headers_) {
if (auto file = sm.getFileManager().getFile(header.IncludePath())) {
if (auto comments = ctx_.Comments.getCommentsInFile(
sm.getOrCreateFileID(*file, clang::SrcMgr::C_User))) {
for (const auto& [_, comment] : *comments) {
ordered_comments.insert({comment->getBeginLoc(), comment});
}
}
}
}
// ... and then we remove those that "conflict" with an IR item.
for (const auto& [decl, result] : import_cache_) {
if (result.has_value()) {
// Remove doc comments of imported items.
if (auto raw_comment = ctx_.getRawCommentForDeclNoCache(decl)) {
ordered_comments.erase(raw_comment->getBeginLoc());
}
// Remove comments that are within a visited decl.
// TODO(forster): We should retain floating comments in decls like
// records and namespaces.
ordered_comments.erase(ordered_comments.lower_bound(decl->getBeginLoc()),
ordered_comments.upper_bound(decl->getEndLoc()));
}
}
// Return the remaining comments as a `std::vector`.
std::vector<clang::RawComment*> result;
result.reserve(ordered_comments.size());
for (auto& [_, comment] : ordered_comments) {
result.push_back(comment);
}
return result;
}
// Multiple IR items can be associated with the same source location (e.g. the
// implicitly defined constructors and assignment operators). To produce
// deterministic output, we order such items based on GetDeclOrder. The order
// is somewhat arbitrary, but we still try to make it aesthetically pleasing
// (e.g. constructors go before assignment operators; default constructor goes
// first, etc.).
static int GetDeclOrder(const clang::Decl* decl) {
if (clang::isa<clang::RecordDecl>(decl)) {
return decl->getDeclContext()->isRecord() ? 101 : 100;
}
if (auto* ctor = clang::dyn_cast<clang::CXXConstructorDecl>(decl)) {
return ctor->isDefaultConstructor() ? 202
: ctor->isCopyConstructor() ? 203
: ctor->isMoveConstructor() ? 204
: 299;
}
if (clang::isa<clang::CXXDestructorDecl>(decl)) {
return 306;
}
if (auto* method = clang::dyn_cast<clang::CXXMethodDecl>(decl)) {
return method->isCopyAssignmentOperator() ? 401
: method->isMoveAssignmentOperator() ? 402
: 499;
}
return 999;
}
void Importer::Import(clang::TranslationUnitDecl* translation_unit_decl) {
ImportDeclsFromDeclContext(translation_unit_decl);
using OrderedItem = std::tuple<clang::SourceRange, int, IR::Item>;
clang::SourceManager& sm = ctx_.getSourceManager();
auto is_less_than = [&sm](const OrderedItem& a, const OrderedItem& b) {
auto a_range = std::get<0>(a);
auto b_range = std::get<0>(b);
if (!a_range.isValid() || !b_range.isValid()) {
if (a_range.isValid() != b_range.isValid())
return !a_range.isValid() && b_range.isValid();
} else {
if (a_range.getBegin() != b_range.getBegin()) {
return sm.isBeforeInTranslationUnit(a_range.getBegin(),
b_range.getBegin());
}
if (a_range.getEnd() != b_range.getEnd()) {
return sm.isBeforeInTranslationUnit(a_range.getEnd(), b_range.getEnd());
}
}
auto a_decl_order = std::get<1>(a);
auto b_decl_order = std::get<1>(b);
if (a_decl_order != b_decl_order) return a_decl_order < b_decl_order;
// A single FunctionDecl can be associated with multiple UnsupportedItems.
// Comparing the fields allows deterministic order between items like:
// Non-trivial_abi type '...' is not supported by value as a parameter.
// Non-trivial_abi type '...' is not supported by value as a return type.
const auto& a_variant = std::get<2>(a);
const auto& b_variant = std::get<2>(b);
const auto* a_unsupported = std::get_if<UnsupportedItem>(&a_variant);
const auto* b_unsupported = std::get_if<UnsupportedItem>(&b_variant);
if (a_unsupported && b_unsupported) {
if (a_unsupported->name != b_unsupported->name)
return a_unsupported->name < b_unsupported->name;
return a_unsupported->message < b_unsupported->message;
}
return false;
};
auto are_equal = [&is_less_than](const OrderedItem& a, const OrderedItem& b) {
return !is_less_than(a, b) && !is_less_than(b, a);
};
// We emit IR items in the order of the decls they were generated for.
// For decls that emit multiple items we use a stable, but arbitrary order.
std::vector<OrderedItem> items;
for (const auto& [decl, item] : import_cache_) {
if (item.has_value()) {
if (std::holds_alternative<UnsupportedItem>(*item) &&
!IsFromCurrentTarget(decl)) {
continue;
}
items.push_back(
std::make_tuple(decl->getSourceRange(), GetDeclOrder(decl), *item));
}
}
for (auto comment : ImportFreeComments()) {
items.push_back(std::make_tuple(
comment->getSourceRange(), 0 /* decl_order */,
Comment{.text = comment->getFormattedText(sm, sm.getDiagnostics()),
.id = GenerateItemId(comment)}));
}
std::sort(items.begin(), items.end(), is_less_than);
for (size_t i = 0; i < items.size(); i++) {
const auto& item = items[i];
if (i > 0) {
const auto& prev = items[i - 1];
if (are_equal(item, prev)) {
llvm::json::Value prev_json = std::visit(
[&](auto&& item) { return item.ToJson(); }, std::get<2>(prev));
llvm::json::Value curr_json = std::visit(
[&](auto&& item) { return item.ToJson(); }, std::get<2>(item));
if (prev_json != curr_json) {
llvm::report_fatal_error(
llvm::formatv("Non-deterministic order of IR items: {0} -VS- {1}",
prev_json, curr_json));
} else {
// TODO(lukasza): Avoid generating duplicate IR items. Currently
// known example: UnsupportedItem: name=std::signbit; message=
// Items contained in namespaces are not supported yet.
continue;
}
}
}
invocation_.ir_.items.push_back(std::get<2>(item));
}
}
void Importer::ImportDeclsFromDeclContext(
const clang::DeclContext* decl_context) {
for (auto decl : decl_context->decls()) {
ImportDeclIfNeeded(decl->getCanonicalDecl());
}
}
void Importer::ImportDeclIfNeeded(clang::Decl* decl) {
// TODO: Move `decl->getCanonicalDecl()` from callers into here.
if (!import_cache_.contains(decl)) {
import_cache_.insert({decl, ImportDecl(decl)});
}
}
std::optional<IR::Item> Importer::ImportDecl(clang::Decl* decl) {
if (auto* namespace_decl = clang::dyn_cast<clang::NamespaceDecl>(decl)) {
return ImportUnsupportedItem(decl, "Namespaces are not supported yet");
} else if (auto* function_decl = clang::dyn_cast<clang::FunctionDecl>(decl)) {
return ImportFunction(function_decl);
} else if (auto* function_template_decl =
clang::dyn_cast<clang::FunctionTemplateDecl>(decl)) {
return ImportFunction(function_template_decl->getTemplatedDecl());
} else if (auto* record_decl = clang::dyn_cast<clang::CXXRecordDecl>(decl)) {
auto result = ImportRecord(record_decl);
// TODO(forster): Should we even visit the nested decl if we couldn't
// import the parent? For now we have tests that check that we generate
// error messages for those decls, so we're visiting.
ImportDeclsFromDeclContext(record_decl);
return result;
} else if (auto* enum_decl = clang::dyn_cast<clang::EnumDecl>(decl)) {
return ImportEnum(enum_decl);
} else if (auto* typedef_name_decl =
clang::dyn_cast<clang::TypedefNameDecl>(decl)) {
return ImportTypedefName(typedef_name_decl);
} else if (clang::isa<clang::ClassTemplateDecl>(decl)) {
return ImportUnsupportedItem(decl, "Class templates are not supported yet");
} else {
return std::nullopt;
}
}
std::optional<IR::Item> Importer::ImportFunction(
clang::FunctionDecl* function_decl) {
if (!IsFromCurrentTarget(function_decl)) return std::nullopt;
if (function_decl->isDeleted()) return std::nullopt;
if (function_decl->isTemplated()) {
return ImportUnsupportedItem(function_decl,
"Function templates are not supported yet");
}
devtools_rust::LifetimeSymbolTable lifetime_symbol_table;
llvm::Expected<devtools_rust::FunctionLifetimes> lifetimes =
devtools_rust::GetLifetimeAnnotations(function_decl,
*invocation_.lifetime_context_,
&lifetime_symbol_table);
std::vector<FuncParam> params;
std::set<std::string> errors;
auto add_error = [&errors, function_decl](std::string msg) {
auto result = errors.insert(std::move(msg));
CRUBIT_CHECK(result.second && "Duplicated error message");
};
if (auto* method_decl =
clang::dyn_cast<clang::CXXMethodDecl>(function_decl)) {
if (!known_type_decls_.contains(
method_decl->getParent()->getCanonicalDecl())) {
return ImportUnsupportedItem(function_decl, "Couldn't import the parent");
}
// non-static member functions receive an implicit `this` parameter.
if (method_decl->isInstance()) {
std::optional<devtools_rust::ValueLifetimes> this_lifetimes;
if (lifetimes) {
this_lifetimes = lifetimes->GetThisLifetimes();
}
auto param_type =
ConvertQualType(method_decl->getThisType(), this_lifetimes,
/*nullable=*/false);
if (!param_type.ok()) {
add_error(absl::StrCat("`this` parameter is not supported: ",
param_type.status().message()));
} else {
params.push_back({*std::move(param_type), Identifier("__this")});
}
}
}
if (lifetimes) {
CRUBIT_CHECK(lifetimes->IsValidForDecl(function_decl));
}
for (unsigned i = 0; i < function_decl->getNumParams(); ++i) {
const clang::ParmVarDecl* param = function_decl->getParamDecl(i);
std::optional<devtools_rust::ValueLifetimes> param_lifetimes;
if (lifetimes) {
param_lifetimes = lifetimes->GetParamLifetimes(i);
}
auto param_type = ConvertQualType(param->getType(), param_lifetimes);
if (!param_type.ok()) {
add_error(absl::Substitute("Parameter #$0 is not supported: $1", i,
param_type.status().message()));
continue;
}
if (const clang::RecordType* record_type =
clang::dyn_cast<clang::RecordType>(param->getType())) {
if (clang::RecordDecl* record_decl =
clang::dyn_cast<clang::RecordDecl>(record_type->getDecl())) {
// TODO(b/200067242): non-trivial_abi structs, when passed by value,
// have a different representation which needs special support. We
// currently do not support it.
if (!record_decl->canPassInRegisters()) {
add_error(
absl::Substitute("Non-trivial_abi type '$0' is not "
"supported by value as parameter #$1",
param->getType().getAsString(), i));
}
}
}
std::optional<Identifier> param_name = GetTranslatedIdentifier(param);
CRUBIT_CHECK(param_name.has_value()); // No known failure cases.
params.push_back({*param_type, *std::move(param_name)});
}
if (const clang::RecordType* record_return_type =
clang::dyn_cast<clang::RecordType>(function_decl->getReturnType())) {
if (clang::RecordDecl* record_decl =
clang::dyn_cast<clang::RecordDecl>(record_return_type->getDecl())) {
// TODO(b/200067242): non-trivial_abi structs, when passed by value,
// have a different representation which needs special support. We
// currently do not support it.
if (!record_decl->canPassInRegisters()) {
add_error(
absl::Substitute("Non-trivial_abi type '$0' is not supported "
"by value as a return type",
function_decl->getReturnType().getAsString()));
}
}
}
std::optional<devtools_rust::ValueLifetimes> return_lifetimes;
if (lifetimes) {
return_lifetimes = lifetimes->GetReturnLifetimes();
}
auto return_type =
ConvertQualType(function_decl->getReturnType(), return_lifetimes);
if (!return_type.ok()) {
add_error(absl::StrCat("Return type is not supported: ",
return_type.status().message()));
}
llvm::DenseSet<devtools_rust::Lifetime> all_lifetimes;
if (lifetimes) {
lifetimes->Traverse(
[&all_lifetimes](devtools_rust::Lifetime l, devtools_rust::Variance) {
all_lifetimes.insert(l);
});
}
std::vector<Lifetime> lifetime_params;
for (devtools_rust::Lifetime lifetime : all_lifetimes) {
std::optional<llvm::StringRef> name =
lifetime_symbol_table.LookupLifetime(lifetime);
CRUBIT_CHECK(name.has_value());
lifetime_params.push_back(
{.name = name->str(), .id = LifetimeId(lifetime.Id())});
}
std::sort(
lifetime_params.begin(), lifetime_params.end(),
[](const Lifetime& l1, const Lifetime& l2) { return l1.name < l2.name; });
llvm::Optional<MemberFuncMetadata> member_func_metadata;
if (auto* method_decl =
clang::dyn_cast<clang::CXXMethodDecl>(function_decl)) {
switch (method_decl->getAccess()) {
case clang::AS_public:
break;
case clang::AS_protected:
case clang::AS_private:
case clang::AS_none:
// No need for IR to include Func representing private methods.
// TODO(lukasza): Revisit this for protected methods.
return std::nullopt;
}
llvm::Optional<MemberFuncMetadata::InstanceMethodMetadata>
instance_metadata;
if (method_decl->isInstance()) {
MemberFuncMetadata::ReferenceQualification reference;
switch (method_decl->getRefQualifier()) {
case clang::RQ_LValue:
reference = MemberFuncMetadata::kLValue;
break;
case clang::RQ_RValue:
reference = MemberFuncMetadata::kRValue;
break;
case clang::RQ_None:
reference = MemberFuncMetadata::kUnqualified;
break;
}
instance_metadata = MemberFuncMetadata::InstanceMethodMetadata{
.reference = reference,
.is_const = method_decl->isConst(),
.is_virtual = method_decl->isVirtual(),
.is_explicit_ctor = false,
};
if (auto* ctor_decl =
clang::dyn_cast<clang::CXXConstructorDecl>(function_decl)) {
instance_metadata->is_explicit_ctor = ctor_decl->isExplicit();
}
}
member_func_metadata = MemberFuncMetadata{
.record_id = GenerateItemId(method_decl->getParent()),
.instance_method_metadata = instance_metadata};
}
if (!errors.empty()) {
return ImportUnsupportedItem(function_decl, errors);
}
bool has_c_calling_convention =
function_decl->getType()->getAs<clang::FunctionType>()->getCallConv() ==
clang::CC_C;
std::optional<UnqualifiedIdentifier> translated_name =
GetTranslatedName(function_decl);
// Silence ClangTidy, checked above: calling `add_error` if
// `!return_type.ok()` and returning early if `!errors.empty()`.
CRUBIT_CHECK(return_type.ok());
if (translated_name.has_value()) {
return Func{
.name = *translated_name,
.owning_target = GetOwningTarget(function_decl),
.doc_comment = GetComment(function_decl),
.mangled_name = GetMangledName(function_decl),
.return_type = *return_type,
.params = std::move(params),
.lifetime_params = std::move(lifetime_params),
.is_inline = function_decl->isInlined(),
.member_func_metadata = std::move(member_func_metadata),
.has_c_calling_convention = has_c_calling_convention,
.source_loc = ConvertSourceLocation(function_decl->getBeginLoc()),
.id = GenerateItemId(function_decl),
};
}
return std::nullopt;
}
BazelLabel Importer::GetOwningTarget(const clang::Decl* decl) const {
clang::SourceManager& source_manager = ctx_.getSourceManager();
auto source_location = decl->getLocation();
// If the header this decl comes from is not associated with a target we
// consider it a textual header. In that case we go up the include stack
// until we find a header that has an owning target.
while (source_location.isValid()) {
if (source_location.isMacroID()) {
source_location = source_manager.getExpansionLoc(source_location);
}
auto id = source_manager.getFileID(source_location);
llvm::Optional<llvm::StringRef> filename =
source_manager.getNonBuiltinFilenameForID(id);
if (!filename) {
return BazelLabel("//:builtin");
}
if (filename->startswith("./")) {
filename = filename->substr(2);
}
if (auto target = invocation_.header_target(HeaderName(filename->str()))) {
return *target;
}
source_location = source_manager.getIncludeLoc(id);
}
return BazelLabel("//:virtual_clang_resource_dir_target");
}
bool Importer::IsFromCurrentTarget(const clang::Decl* decl) const {
return invocation_.target_ == GetOwningTarget(decl);
}
std::optional<IR::Item> Importer::ImportRecord(
clang::CXXRecordDecl* record_decl) {
const clang::DeclContext* decl_context = record_decl->getDeclContext();
if (decl_context->isFunctionOrMethod()) {
return std::nullopt;
}
if (record_decl->isInjectedClassName()) {
return std::nullopt;
}
if (decl_context->isRecord()) {
return ImportUnsupportedItem(record_decl,
"Nested classes are not supported yet");
}
if (record_decl->isUnion()) {
return ImportUnsupportedItem(record_decl, "Unions are not supported yet");
}
// Make sure the record has a definition that we'll be able to call
// ASTContext::getASTRecordLayout() on.
record_decl = record_decl->getDefinition();
if (!record_decl || record_decl->isInvalidDecl() ||
!record_decl->isCompleteDefinition()) {
return std::nullopt;
}
// To compute the memory layout of the record, it needs to be a concrete type,
// not a template.
if (record_decl->getDescribedClassTemplate() ||
clang::isa<clang::ClassTemplateSpecializationDecl>(record_decl)) {
return ImportUnsupportedItem(record_decl,
"Class templates are not supported yet");
}
sema_.ForceDeclarationOfImplicitMembers(record_decl);
const clang::ASTRecordLayout& layout = ctx_.getASTRecordLayout(record_decl);
llvm::Optional<size_t> base_size;
bool override_alignment = record_decl->hasAttr<clang::AlignedAttr>();
if (record_decl->getNumBases() != 0) {
// The size of the base class subobjects is easy to compute, so long as we
// know that fields start after the base class subobjects. (This is not
// guaranteed by the standard, but is true on the ABIs we work with.)
base_size = layout.getFieldCount() == 0
? static_cast<size_t>(layout.getDataSize().getQuantity())
: layout.getFieldOffset(0) / 8;
// Ideally, we'd only include an alignment adjustment if one of the base
// classes is more-aligned than any of the fields, but it is simpler do it
// whenever there are any base classes at all.
override_alignment = true;
}
std::optional<Identifier> record_name = GetTranslatedIdentifier(record_decl);
if (!record_name.has_value()) {
return std::nullopt;
}
// Provisionally assume that we know this RecordDecl so that we'll be able
// to import fields whose type contains the record itself.
known_type_decls_.insert(record_decl);
absl::StatusOr<std::vector<Field>> fields = ImportFields(record_decl);
if (!fields.ok()) {
// Importing a field failed, so note that we didn't import this RecordDecl
// after all.
known_type_decls_.erase(record_decl);
return ImportUnsupportedItem(record_decl, fields.status().ToString());
}
for (const Field& field : *fields) {
if (field.is_no_unique_address) {
override_alignment = true;
break;
}
}
return Record{
.rs_name = std::string(record_name->Ident()),
.cc_name = std::string(record_name->Ident()),
.id = GenerateItemId(record_decl),
.owning_target = GetOwningTarget(record_decl),
.doc_comment = GetComment(record_decl),
.unambiguous_public_bases = GetUnambiguousPublicBases(*record_decl),
.fields = *std::move(fields),
.size = layout.getSize().getQuantity(),
.alignment = layout.getAlignment().getQuantity(),
.base_size = base_size,
.override_alignment = override_alignment,
.copy_constructor = GetCopyCtorSpecialMemberFunc(*record_decl),
.move_constructor = GetMoveCtorSpecialMemberFunc(*record_decl),
.destructor = GetDestructorSpecialMemberFunc(*record_decl),
.is_trivial_abi = record_decl->canPassInRegisters(),
.is_final = record_decl->isEffectivelyFinal()};
}
std::optional<IR::Item> Importer::ImportEnum(clang::EnumDecl* enum_decl) {
std::optional<Identifier> enum_name = GetTranslatedIdentifier(enum_decl);
if (!enum_name.has_value()) {
// TODO(b/208945197): This corresponds to an unnamed enum declaration like
// `enum { kFoo = 1 }`, which only exists to provide constants into the
// surrounding scope and doesn't actually introduce an enum namespace. It
// seems like it should probably be handled with other constants.
return ImportUnsupportedItem(enum_decl,
"Unnamed enums are not supported yet");
}
clang::QualType cc_type = enum_decl->getIntegerType();
if (cc_type.isNull()) {
// According to https://clang.llvm.org/doxygen/classclang_1_1EnumDecl.html,
// getIntegerType "returns a null QualType for an enum forward definition
// with no fixed underlying type." The same page implies that this can't
// occur in C++ nor in standard C, but clang supports enums like this
// in C "as an extension".
return ImportUnsupportedItem(
enum_decl,
"Forward declared enums without type specifiers are not supported");
}
std::optional<devtools_rust::ValueLifetimes> no_lifetimes;
absl::StatusOr<MappedType> type = ConvertQualType(cc_type, no_lifetimes);
if (!type.ok()) {
return ImportUnsupportedItem(enum_decl, type.status().ToString());
}
std::vector<Enumerator> enumerators;
enumerators.reserve(std::distance(enum_decl->enumerators().begin(),
enum_decl->enumerators().end()));
for (clang::EnumConstantDecl* enumerator : enum_decl->enumerators()) {
std::optional<Identifier> enumerator_name =
GetTranslatedIdentifier(enumerator);
if (!enumerator_name.has_value()) {
// It's not clear that this case is possible
return ImportUnsupportedItem(
enum_decl, "importing enum failed: missing enumerator name");
}
enumerators.push_back(Enumerator{
.identifier = *enumerator_name,
.value = IntegerConstant(enumerator->getInitVal()),
});
}
return Enum{
.identifier = *enum_name,
.id = GenerateItemId(enum_decl),
.owning_target = GetOwningTarget(enum_decl),
.underlying_type = *std::move(type),
.enumerators = enumerators,
};
}
IR::Item Importer::ImportUnsupportedItem(const clang::Decl* decl,
std::string error) {
std::string name = "unnamed";
if (const auto* named_decl = clang::dyn_cast<clang::NamedDecl>(decl)) {
name = named_decl->getQualifiedNameAsString();
}
SourceLoc source_loc = ConvertSourceLocation(decl->getBeginLoc());
return UnsupportedItem{.name = name,
.message = error,
.source_loc = source_loc,
.id = GenerateItemId(decl)};
}
IR::Item Importer::ImportUnsupportedItem(const clang::Decl* decl,
std::set<std::string> errors) {
return ImportUnsupportedItem(decl, absl::StrJoin(errors, "\n\n"));
}
std::optional<IR::Item> Importer::ImportTypedefName(
clang::TypedefNameDecl* typedef_name_decl) {
const clang::DeclContext* decl_context = typedef_name_decl->getDeclContext();
if (decl_context) {
if (decl_context->isFunctionOrMethod()) {
return std::nullopt;
}
if (decl_context->isRecord()) {
return ImportUnsupportedItem(
typedef_name_decl,
"Typedefs nested in classes are not supported yet");
}
}
clang::QualType type =
typedef_name_decl->getASTContext().getTypedefType(typedef_name_decl);
if (MapKnownCcTypeToRsType(type.getAsString()).has_value()) {
return std::nullopt;
}
std::optional<Identifier> identifier =
GetTranslatedIdentifier(typedef_name_decl);
CRUBIT_CHECK(identifier.has_value()); // This should never happen.
std::optional<devtools_rust::ValueLifetimes> no_lifetimes;
absl::StatusOr<MappedType> underlying_type =
ConvertQualType(typedef_name_decl->getUnderlyingType(), no_lifetimes);
if (underlying_type.ok()) {
known_type_decls_.insert(typedef_name_decl);
return TypeAlias{.identifier = *identifier,
.id = GenerateItemId(typedef_name_decl),
.owning_target = GetOwningTarget(typedef_name_decl),
.doc_comment = GetComment(typedef_name_decl),
.underlying_type = *underlying_type};
} else {
return ImportUnsupportedItem(
typedef_name_decl, std::string(underlying_type.status().message()));
}
}
static bool ShouldKeepCommentLine(absl::string_view line) {
// Based on https://clang.llvm.org/extra/clang-tidy/:
llvm::Regex patterns_to_ignore(
"^[[:space:]/]*" // Whitespace, or extra //
"(NOLINT|NOLINTNEXTLINE|NOLINTBEGIN|NOLINTEND)"
"(\\([^)[:space:]]*\\)?)?" // Optional (...)
"[[:space:]]*$"); // Whitespace
return !patterns_to_ignore.match(line);
}
llvm::Optional<std::string> Importer::GetComment(
const clang::Decl* decl) const {
// This does currently not distinguish between different types of comments.
// In general it is not possible in C++ to reliably only extract doc comments.
// This is going to be a heuristic that needs to be tuned over time.
clang::SourceManager& sm = ctx_.getSourceManager();
clang::RawComment* raw_comment = ctx_.getRawCommentForDeclNoCache(decl);
if (raw_comment == nullptr) {
return {};
}
std::string raw_comment_text =
raw_comment->getFormattedText(sm, sm.getDiagnostics());
std::string cleaned_comment_text = absl::StrJoin(
absl::StrSplit(raw_comment_text, '\n', ShouldKeepCommentLine), "\n");
if (cleaned_comment_text.empty()) return {};
return cleaned_comment_text;
}
SourceLoc Importer::ConvertSourceLocation(clang::SourceLocation loc) const {
auto& sm = ctx_.getSourceManager();
clang::StringRef filename = sm.getFilename(loc);
if (filename.startswith("./")) {
filename = filename.substr(2);
}
return SourceLoc{.filename = filename.str(),
.line = sm.getSpellingLineNumber(loc),
.column = sm.getSpellingColumnNumber(loc)};
}
absl::StatusOr<MappedType> Importer::ConvertTypeDecl(
const clang::TypeDecl* decl) const {
if (!known_type_decls_.contains(decl)) {
return absl::NotFoundError(absl::Substitute(
"No generated bindings found for '$0'", decl->getNameAsString()));
}
ItemId decl_id = GenerateItemId(decl);
return MappedType::WithDeclId(decl_id);
}
absl::StatusOr<MappedType> Importer::ConvertType(
const clang::Type* type,
std::optional<devtools_rust::ValueLifetimes>& lifetimes,
bool nullable) const {
// Qualifiers are handled separately in ConvertQualType().
std::string type_string = clang::QualType(type, 0).getAsString();
if (auto maybe_mapped_type = MapKnownCcTypeToRsType(type_string);
maybe_mapped_type.has_value()) {
return MappedType::Simple(std::string(*maybe_mapped_type), type_string);
} else if (type->isPointerType() || type->isLValueReferenceType() ||
type->isRValueReferenceType()) {
clang::QualType pointee_type = type->getPointeeType();
std::optional<LifetimeId> lifetime;
if (lifetimes.has_value()) {
lifetime =
LifetimeId(lifetimes->GetPointeeLifetimes().GetLifetime().Id());
lifetimes = lifetimes->GetPointeeLifetimes().GetValueLifetimes();
}
if (const auto* func_type =
pointee_type->getAs<clang::FunctionProtoType>()) {
if (lifetime.has_value() &&
lifetime->value() != devtools_rust::Lifetime::Static().Id()) {
return absl::UnimplementedError(
absl::StrCat("Function pointers with non-'static lifetimes are "
"not supported: ",
type_string));
}
clang::StringRef cc_call_conv =
clang::FunctionType::getNameForCallConv(func_type->getCallConv());
CRUBIT_ASSIGN_OR_RETURN(
absl::string_view rs_abi,
ConvertCcCallConvIntoRsAbi(func_type->getCallConv()));
CRUBIT_ASSIGN_OR_RETURN(
MappedType mapped_return_type,
ConvertQualType(func_type->getReturnType(), lifetimes));
std::vector<MappedType> mapped_param_types;
for (const clang::QualType& param_type : func_type->getParamTypes()) {
CRUBIT_ASSIGN_OR_RETURN(MappedType mapped_param_type,
ConvertQualType(param_type, lifetimes));
mapped_param_types.push_back(std::move(mapped_param_type));
}
if (type->isPointerType()) {
return MappedType::FuncPtr(cc_call_conv, rs_abi, lifetime,
std::move(mapped_return_type),
std::move(mapped_param_types));
} else {
CRUBIT_CHECK(type->isLValueReferenceType());
return MappedType::FuncRef(cc_call_conv, rs_abi, lifetime,
std::move(mapped_return_type),
std::move(mapped_param_types));
}
}
CRUBIT_ASSIGN_OR_RETURN(MappedType mapped_pointee_type,
ConvertQualType(pointee_type, lifetimes));
if (type->isPointerType()) {
return MappedType::PointerTo(std::move(mapped_pointee_type), lifetime,
nullable);
} else if (type->isLValueReferenceType()) {
return MappedType::LValueReferenceTo(std::move(mapped_pointee_type),
lifetime);
} else {
CRUBIT_CHECK(type->isRValueReferenceType());
if (!lifetime.has_value()) {
return absl::UnimplementedError(
"Unsupported type: && without lifetime");
}
return MappedType::RValueReferenceTo(std::move(mapped_pointee_type),
*lifetime);
}
} else if (const auto* builtin_type =
// Use getAsAdjusted instead of getAs so we don't desugar
// typedefs.
type->getAsAdjusted<clang::BuiltinType>()) {
switch (builtin_type->getKind()) {
case clang::BuiltinType::Bool:
return MappedType::Simple("bool", "bool");
break;
case clang::BuiltinType::Float:
return MappedType::Simple("f32", "float");
break;
case clang::BuiltinType::Double:
return MappedType::Simple("f64", "double");
break;
case clang::BuiltinType::Void:
return MappedType::Void();
break;
default:
if (builtin_type->isIntegerType()) {
auto size = ctx_.getTypeSize(builtin_type);
if (size == 8 || size == 16 || size == 32 || size == 64) {
return MappedType::Simple(
absl::Substitute(
"$0$1", builtin_type->isSignedInteger() ? 'i' : 'u', size),
type_string);
}
}
}
} else if (const auto* tag_type = type->getAsAdjusted<clang::TagType>()) {
return ConvertTypeDecl(tag_type->getDecl());
} else if (const auto* typedef_type =
type->getAsAdjusted<clang::TypedefType>()) {
return ConvertTypeDecl(typedef_type->getDecl());
}
return absl::UnimplementedError(absl::StrCat(
"Unsupported clang::Type class '", type->getTypeClassName(), "'"));
}
absl::StatusOr<MappedType> Importer::ConvertQualType(
clang::QualType qual_type,
std::optional<devtools_rust::ValueLifetimes>& lifetimes,
bool nullable) const {
std::string type_string = qual_type.getAsString();
absl::StatusOr<MappedType> type =
ConvertType(qual_type.getTypePtr(), lifetimes, nullable);
if (!type.ok()) {
absl::Status error = absl::UnimplementedError(absl::Substitute(
"Unsupported type '$0': $1", type_string, type.status().message()));
error.SetPayload(kTypeStatusPayloadUrl, absl::Cord(type_string));
return error;
}
// Handle cv-qualification.
type->cc_type.is_const = qual_type.isConstQualified();
if (qual_type.isVolatileQualified()) {
return absl::UnimplementedError(
absl::StrCat("Unsupported `volatile` qualifier: ", type_string));
}
return type;
}
absl::StatusOr<std::vector<Field>> Importer::ImportFields(
clang::CXXRecordDecl* record_decl) {
clang::AccessSpecifier default_access =
record_decl->isClass() ? clang::AS_private : clang::AS_public;
std::vector<Field> fields;
const clang::ASTRecordLayout& layout = ctx_.getASTRecordLayout(record_decl);
for (const clang::FieldDecl* field_decl : record_decl->fields()) {
std::optional<devtools_rust::ValueLifetimes> no_lifetimes;
auto type = ConvertQualType(field_decl->getType(), no_lifetimes);
if (!type.ok()) {
return absl::UnimplementedError(absl::Substitute(
"Type of field '$0' is not supported: $1",
field_decl->getNameAsString(), type.status().message()));
}
clang::AccessSpecifier access = field_decl->getAccess();
if (access == clang::AS_none) {
access = default_access;
}
std::optional<Identifier> field_name = GetTranslatedIdentifier(field_decl);
if (!field_name.has_value()) {
return absl::UnimplementedError(
absl::Substitute("Cannot translate name for field '$0'",
field_decl->getNameAsString()));
}
fields.push_back(
{.identifier = *std::move(field_name),
.doc_comment = GetComment(field_decl),
.type = *type,
.access = TranslateAccessSpecifier(access),
.offset = layout.getFieldOffset(field_decl->getFieldIndex()),
.is_no_unique_address =
field_decl->hasAttr<clang::NoUniqueAddressAttr>()});
}
return fields;
}
std::string Importer::GetMangledName(const clang::NamedDecl* named_decl) const {
clang::GlobalDecl decl;
// There are only three named decl types that don't work with the GlobalDecl
// unary constructor: GPU kernels (which do not exist in standard C++, so we
// ignore), constructors, and destructors. GlobalDecl does not support
// constructors and destructors from the unary constructor because there is
// more than one global declaration for a given constructor or destructor!
//
// * (Ctor|Dtor)_Complete is a function which constructs / destroys the
// entire object. This is what we want. :)
// * Dtor_Deleting is a function which additionally calls operator delete.
// * (Ctor|Dtor)_Base is a function which constructs/destroys the object but
// NOT including virtual base class subobjects.
// * (Ctor|Dtor)_Comdat: I *believe* this is the identifier used to
// deduplicate inline functions, and is not callable.
// * Dtor_(Copying|Default)Closure: These only exist in the MSVC++ ABI,
// which we don't support for now. I don't know when they are used.
//
// It was hard to piece this together, so writing it down here to explain why
// we magically picked the *_Complete variants.
if (auto dtor = clang::dyn_cast<clang::CXXDestructorDecl>(named_decl)) {
decl = clang::GlobalDecl(dtor, clang::CXXDtorType::Dtor_Complete);
} else if (auto ctor =
clang::dyn_cast<clang::CXXConstructorDecl>(named_decl)) {
decl = clang::GlobalDecl(ctor, clang::CXXCtorType::Ctor_Complete);
} else {
decl = clang::GlobalDecl(named_decl);
}
std::string name;
llvm::raw_string_ostream stream(name);
mangler_->mangleName(decl, stream);
stream.flush();
return name;
}
std::optional<UnqualifiedIdentifier> Importer::GetTranslatedName(
const clang::NamedDecl* named_decl) const {
switch (named_decl->getDeclName().getNameKind()) {
case clang::DeclarationName::Identifier: {
auto name = std::string(named_decl->getName());
if (name.empty()) {
if (const clang::ParmVarDecl* param_decl =
clang::dyn_cast<clang::ParmVarDecl>(named_decl)) {
int param_pos = param_decl->getFunctionScopeIndex();
return {Identifier(absl::StrCat("__param_", param_pos))};
}
// TODO(lukasza): Handle anonymous structs (probably this won't be an
// issue until nested types are handled - b/200067824).
return std::nullopt;
}
return {Identifier(std::move(name))};
}
case clang::DeclarationName::CXXConstructorName:
return {SpecialName::kConstructor};
case clang::DeclarationName::CXXDestructorName:
return {SpecialName::kDestructor};
case clang::DeclarationName::CXXOperatorName:
switch (named_decl->getDeclName().getCXXOverloadedOperator()) {
case clang::OO_None:
CRUBIT_CHECK(false &&
"No OO_None expected under CXXOperatorName branch");
return std::nullopt;
case clang::NUM_OVERLOADED_OPERATORS:
CRUBIT_CHECK(false &&
"No NUM_OVERLOADED_OPERATORS expected at runtime");
return std::nullopt;
// clang-format off
#define OVERLOADED_OPERATOR(name, spelling, ...) \
case clang::OO_##name: { \
return {Operator(spelling)}; \
}
#include "third_party/llvm/llvm-project/clang/include/clang/Basic/OperatorKinds.def"
#undef OVERLOADED_OPERATOR
// clang-format on
}
CRUBIT_CHECK(false && "The `switch` above should handle all cases");
return std::nullopt;
default:
// To be implemented later: CXXConversionFunctionName.
// There are also e.g. literal operators, deduction guides, etc., but
// we might not need to implement them at all. Full list at:
// https://clang.llvm.org/doxygen/classclang_1_1DeclarationName.html#a9ab322d434446b43379d39e41af5cbe3
return std::nullopt;
}
}
std::vector<BaseClass> Importer::GetUnambiguousPublicBases(
const clang::CXXRecordDecl& record_decl) const {
// This function is unfortunate: the only way to correctly get information
// about the bases is lookupInBases. It runs a complex O(N^3) algorithm for
// e.g. correctly determining virtual base paths, etc.
//
// However, lookupInBases does not recurse into a class once it's found.
// So we need to call lookupInBases once per class, making this O(N^4).
llvm::SmallPtrSet<const clang::CXXRecordDecl*, 4> seen;
std::vector<BaseClass> bases;
clang::CXXBasePaths paths;
// the const cast is a common pattern, apparently, see e.g.
// https://clang.llvm.org/doxygen/CXXInheritance_8cpp_source.html#l00074
paths.setOrigin(const_cast<clang::CXXRecordDecl*>(&record_decl));
auto next_class = [&]() {
const clang::CXXRecordDecl* found = nullptr;
// Matches the first new class it encounters (and adds it to `seen`, so
// that future runs don't rediscover it.)
auto is_new_class = [&](const clang::CXXBaseSpecifier* base_specifier,
clang::CXXBasePath&) {
const auto* record_decl = base_specifier->getType()->getAsCXXRecordDecl();
if (found) {
return record_decl == found;
}
if (record_decl && seen.insert(record_decl).second) {
found = record_decl;
return true;
}
return false;
};
return record_decl.lookupInBases(is_new_class, paths);
};
for (; next_class(); paths.clear()) {
for (const clang::CXXBasePath& path : paths) {
if (path.Access != clang::AS_public) {
continue;
}
const clang::CXXBaseSpecifier& base_specifier =
*path[path.size() - 1].Base;
const clang::QualType& base = base_specifier.getType();
if (paths.isAmbiguous(ctx_.getCanonicalType(base))) {
continue;
}
clang::CXXRecordDecl* base_record_decl =
CRUBIT_DIE_IF_NULL(base_specifier.getType()->getAsCXXRecordDecl());
if (!ConvertTypeDecl(base_record_decl).status().ok()) {
continue;
}
llvm::Optional<int64_t> offset = {0};
for (const clang::CXXBasePathElement& base_path_element : path) {
if (base_path_element.Base->isVirtual()) {
offset.reset();
break;
}
*offset +=
{ctx_.getASTRecordLayout(base_path_element.Class)
.getBaseClassOffset(CRUBIT_DIE_IF_NULL(
base_path_element.Base->getType()->getAsCXXRecordDecl()))
.getQuantity()};
}
CRUBIT_CHECK((!offset.hasValue() || *offset >= 0) &&
"Concrete base classes should have non-negative offsets.");
bases.push_back(
BaseClass{.base_record_id = GenerateItemId(base_record_decl),
.offset = offset});
break;
}
}
return bases;
}
} // namespace rs_bindings_from_cc