mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 22:02:23 +01:00
The type `type` is now a `FacetType` inst with no constraints. This brings the model implemented in the toolchain into better alignment with the language design. The `SemIR::TypeType` struct remains as a scope for holding the `TypeInstId`, `ConstantId`, and `TypeId` constants, but is not an `InstKind` anymore. The `TypeType` inst looks a lot like singletons, but there are many `FacetType` insts so it doesn't quite fit that model. So we put it alongside singletons with a fixed inst id but refer to it as a more general "builtin" inst that is not a singleton. `Namespace::PackageInstId` is similar, and we group it with `TypeType` conceptually as another builtin instruction with a fixed id. No conversion is needed anymore to use a `type` as a facet, since types also have a `FacetType` type. This simplifies and removes a number of helpers and branches throughout the code. The `TypeType` inst is now part of the constant store, so we end up printing it in the constants block in every test. But it's also named `type` rather than `%type` to preserve the majority of existing formatting behaviour, though this does look different from other constants. Assisted-by: Opus 5 was used to generate a first draft and validate the refactoring. Though nearly everything non-trivial the tool wrote has been modified or rewritten.
1689 lines
69 KiB
C++
1689 lines
69 KiB
C++
// Part of the Carbon Language 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 "toolchain/check/cpp/export.h"
|
|
|
|
#include <optional>
|
|
#include <string_view>
|
|
|
|
#include "clang/AST/ASTConsumer.h"
|
|
#include "clang/Lex/Preprocessor.h"
|
|
#include "clang/Sema/EnterExpressionEvaluationContext.h"
|
|
#include "clang/Sema/Sema.h"
|
|
#include "llvm/Support/Casting.h"
|
|
#include "toolchain/check/cpp/access.h"
|
|
#include "toolchain/check/cpp/import.h"
|
|
#include "toolchain/check/cpp/location.h"
|
|
#include "toolchain/check/cpp/type_mapping.h"
|
|
#include "toolchain/check/facet_type.h"
|
|
#include "toolchain/check/function.h"
|
|
#include "toolchain/check/generic.h"
|
|
#include "toolchain/check/import_ref.h"
|
|
#include "toolchain/check/name_lookup.h"
|
|
#include "toolchain/check/pattern.h"
|
|
#include "toolchain/check/thunk.h"
|
|
#include "toolchain/check/type.h"
|
|
#include "toolchain/sem_ir/generic.h"
|
|
#include "toolchain/sem_ir/mangler.h"
|
|
#include "toolchain/sem_ir/pattern.h"
|
|
#include "toolchain/sem_ir/typed_insts.h"
|
|
#include "toolchain/sem_ir/vtable.h"
|
|
|
|
namespace Carbon::Check {
|
|
|
|
// If the given name scope was produced by importing a C++ declaration or has
|
|
// already been exported to C++, return the corresponding Clang decl context.
|
|
static auto GetClangDeclContextForScope(Context& context,
|
|
SemIR::NameScopeId scope_id)
|
|
-> clang::DeclContext* {
|
|
if (!scope_id.has_value()) {
|
|
return nullptr;
|
|
}
|
|
auto& scope = context.name_scopes().Get(scope_id);
|
|
auto clang_decl_context_id = scope.clang_decl_context_id();
|
|
if (!clang_decl_context_id.has_value()) {
|
|
return nullptr;
|
|
}
|
|
auto* decl = context.clang_decls().Get(clang_decl_context_id).decl();
|
|
return cast<clang::DeclContext>(decl);
|
|
}
|
|
|
|
// Exports a Carbon class into C++ as a class in the given `decl_context`.
|
|
//
|
|
// This does not check for an existing export of the class, nor does it add
|
|
// the class to `clang_decls()`.
|
|
//
|
|
// Returns nullptr if the class could not be exported and an error was
|
|
// diagnosed.
|
|
static auto ExportClassToCppInDeclContext(Context& context,
|
|
clang::DeclContext* decl_context,
|
|
const SemIR::Class& class_info,
|
|
const SemIR::SpecificId specific_id)
|
|
-> clang::TagDecl* {
|
|
SemIR::LocId loc_id(class_info.first_decl_id());
|
|
|
|
if (specific_id.has_value()) {
|
|
context.TODO(loc_id, "interop with specific class");
|
|
return nullptr;
|
|
}
|
|
|
|
auto* identifier_info = GetClangIdentifierInfo(context, class_info.name_id);
|
|
CARBON_CHECK(identifier_info, "non-identifier class name {0}",
|
|
class_info.name_id);
|
|
|
|
auto clang_loc = GetCppLocation(context, loc_id);
|
|
auto* record_decl = clang::CXXRecordDecl::Create(
|
|
context.ast_context(), clang::TagTypeKind::Class, decl_context, clang_loc,
|
|
clang_loc, identifier_info);
|
|
// If this is a member class, set its access.
|
|
if (isa<clang::CXXRecordDecl>(decl_context)) {
|
|
// TODO: Map Carbon access to C++ access.
|
|
record_decl->setAccess(clang::AS_public);
|
|
}
|
|
|
|
decl_context->addHiddenDecl(record_decl);
|
|
record_decl->setHasExternalLexicalStorage();
|
|
record_decl->setHasExternalVisibleStorage();
|
|
|
|
return record_decl;
|
|
}
|
|
|
|
auto ExportNameScopeToCpp(Context& context, SemIR::LocId loc_id,
|
|
SemIR::NameScopeId name_scope_id)
|
|
-> clang::DeclContext* {
|
|
llvm::SmallVector<SemIR::NameScopeId> name_scope_ids_to_create;
|
|
|
|
// Walk through the parent scopes, looking for one that's already mapped into
|
|
// C++. We already mapped the package scope to ::Carbon, so we must find one.
|
|
clang::DeclContext* decl_context = nullptr;
|
|
while (true) {
|
|
// If this name scope was produced by importing a C++ declaration or has
|
|
// already been exported to C++, return the corresponding Clang declaration.
|
|
if (auto* existing_decl_context =
|
|
GetClangDeclContextForScope(context, name_scope_id)) {
|
|
decl_context = existing_decl_context;
|
|
break;
|
|
}
|
|
|
|
// Otherwise, continue to the parent and create a scope for it first.
|
|
name_scope_ids_to_create.push_back(name_scope_id);
|
|
name_scope_id = context.name_scopes().Get(name_scope_id).parent_scope_id();
|
|
|
|
// TODO: What should happen if there's an intervening function scope?
|
|
CARBON_CHECK(
|
|
name_scope_id.has_value(),
|
|
"Reached the top level without finding a scope mapped into C++");
|
|
}
|
|
|
|
// Create the name scopes in order, starting from the outermost one.
|
|
while (!name_scope_ids_to_create.empty()) {
|
|
name_scope_id = name_scope_ids_to_create.pop_back_val();
|
|
|
|
auto& name_scope = context.name_scopes().Get(name_scope_id);
|
|
|
|
auto const_inst_id =
|
|
context.constant_values().GetConstantInstId(name_scope.inst_id());
|
|
if (context.insts().Is<SemIR::Namespace>(const_inst_id)) {
|
|
auto* identifier_info =
|
|
GetClangIdentifierInfo(context, name_scope.name_id());
|
|
if (!identifier_info) {
|
|
// TODO: Handle keyword package names like `Cpp` and `Core`. These can
|
|
// be named from C++ via an alias.
|
|
context.TODO(loc_id, "interop with non-identifier package name");
|
|
return nullptr;
|
|
}
|
|
|
|
// TODO: Provide a source location.
|
|
auto* namespace_decl = clang::NamespaceDecl::Create(
|
|
context.ast_context(), decl_context, false, clang::SourceLocation(),
|
|
clang::SourceLocation(), identifier_info, nullptr, false);
|
|
decl_context->addHiddenDecl(namespace_decl);
|
|
decl_context = namespace_decl;
|
|
} else if (auto class_type =
|
|
context.insts().TryGetAs<SemIR::ClassType>(const_inst_id)) {
|
|
const auto& class_info = context.classes().Get(class_type->class_id);
|
|
decl_context = ExportClassToCppInDeclContext(
|
|
context, decl_context, class_info, class_type->specific_id);
|
|
} else {
|
|
context.TODO(loc_id, "non-class non-namespace name scope");
|
|
return nullptr;
|
|
}
|
|
|
|
decl_context->setHasExternalVisibleStorage();
|
|
|
|
auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(
|
|
cast<clang::Decl>(decl_context));
|
|
auto clang_decl_id = context.clang_decls().Add(
|
|
{.key = key, .inst_id = name_scope.inst_id()});
|
|
name_scope.set_clang_decl_context_id(clang_decl_id, /*is_cpp_scope=*/false);
|
|
|
|
// Complete the type here to avoid hitting a clang assert later when
|
|
// adding methods.
|
|
if (auto* record_decl = llvm::dyn_cast<clang::RecordDecl>(decl_context)) {
|
|
context.ast_context().getExternalSource()->CompleteType(record_decl);
|
|
}
|
|
}
|
|
|
|
return decl_context;
|
|
}
|
|
|
|
static auto GetClassTypeInstId(Context& context, SemIR::ClassId class_id,
|
|
SemIR::SpecificId specific_id)
|
|
-> SemIR::TypeInstId {
|
|
auto type_id = GetClassType(context, class_id, specific_id);
|
|
return context.types().GetTypeInstId(type_id);
|
|
}
|
|
|
|
// Creates a `clang::ClassTemplateSpecializationDecl`, and registers it
|
|
// with the `ClassTemplateDecl` and `clang_decls`.
|
|
static auto CreateClassTemplateSpecializationDecl(
|
|
Context& context, clang::ClassTemplateDecl* class_template_decl,
|
|
llvm::ArrayRef<clang::TemplateArgument> template_args,
|
|
SemIR::TypeInstId class_type_inst_id)
|
|
-> clang::ClassTemplateSpecializationDecl* {
|
|
auto* class_template_specialization_decl =
|
|
clang::ClassTemplateSpecializationDecl::Create(
|
|
context.ast_context(),
|
|
class_template_decl->getTemplatedDecl()->getTagKind(),
|
|
class_template_decl->getDeclContext(),
|
|
class_template_decl->getTemplatedDecl()->getBeginLoc(),
|
|
class_template_decl->getLocation(), class_template_decl,
|
|
template_args,
|
|
/*StrictPackMatch=*/false,
|
|
/*PrevDecl=*/nullptr);
|
|
class_template_decl->AddSpecialization(
|
|
class_template_specialization_decl,
|
|
/*InsertPos=*/llvm::FoldingSetInsertToken());
|
|
class_template_specialization_decl->setHasExternalLexicalStorage();
|
|
class_template_specialization_decl->setHasExternalVisibleStorage();
|
|
|
|
// Create and store the `ClangDecl`.
|
|
auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(
|
|
class_template_specialization_decl);
|
|
context.clang_decls().Add({.key = key, .inst_id = class_type_inst_id});
|
|
|
|
return class_template_specialization_decl;
|
|
}
|
|
|
|
// Exports a specific Carbon class into C++ as a template class specialization.
|
|
//
|
|
// If the specific class has already been exported, returns the existing C++
|
|
// decl. Otherwise, creates a new C++ class template specialization and
|
|
// returns it. Returns nullptr if the class could not be exported and an error
|
|
// was diagnosed.
|
|
static auto ExportClassSpecificToCpp(Context& context, SemIR::LocId loc_id,
|
|
SemIR::ClassType class_type)
|
|
-> clang::ClassTemplateSpecializationDecl* {
|
|
CARBON_CHECK(class_type.specific_id.has_value());
|
|
|
|
// Use existing export if possible.
|
|
auto class_type_inst_id =
|
|
GetClassTypeInstId(context, class_type.class_id, class_type.specific_id);
|
|
if (const auto* clang_decl =
|
|
context.clang_decls().Lookup(class_type_inst_id)) {
|
|
return cast<clang::ClassTemplateSpecializationDecl>(clang_decl->decl());
|
|
}
|
|
|
|
// Ensure the generic class is exported, and get its `ClassTemplateDecl`.
|
|
auto generic_class_type_id = GetGenericClassType(context, class_type.class_id,
|
|
SemIR::SpecificId::None);
|
|
auto generic_class_type =
|
|
context.types().GetAs<SemIR::GenericClassType>(generic_class_type_id);
|
|
auto* class_template_decl =
|
|
ExportGenericClassToCpp(context, generic_class_type);
|
|
if (!class_template_decl) {
|
|
return nullptr;
|
|
}
|
|
|
|
llvm::SmallVector<clang::TemplateArgument> template_args;
|
|
const auto specific = context.specifics().Get(class_type.specific_id);
|
|
auto specific_args = context.inst_blocks().Get(specific.args_id);
|
|
for (auto specific_arg_inst_id : specific_args) {
|
|
// TODO: also handle non-type args. Such args can't happen here yet, since
|
|
// the `ExportGenericClassToCpp` call above already checks for them.
|
|
|
|
auto cpp_type = MapToCppType(
|
|
context, context.types().GetTypeIdForTypeInstId(specific_arg_inst_id));
|
|
if (cpp_type.isNull()) {
|
|
context.TODO(loc_id, "failed to map specific type arg to C++");
|
|
return nullptr;
|
|
}
|
|
|
|
template_args.push_back(cpp_type);
|
|
}
|
|
|
|
return CreateClassTemplateSpecializationDecl(
|
|
context, class_template_decl, template_args, class_type_inst_id);
|
|
}
|
|
|
|
auto ExportClassToCpp(Context& context, SemIR::ClassType class_type)
|
|
-> clang::TagDecl* {
|
|
const auto& class_info = context.classes().Get(class_type.class_id);
|
|
SemIR::LocId loc_id(class_info.first_decl_id());
|
|
|
|
if (class_type.specific_id.has_value()) {
|
|
return ExportClassSpecificToCpp(context, loc_id, class_type);
|
|
}
|
|
|
|
// If this class was produced by importing a C++ declaration or has
|
|
// already been exported to C++, return the corresponding Clang declaration.
|
|
// That could either be a CXXRecordDecl or an EnumDecl.
|
|
if (const auto* clang_decl =
|
|
context.clang_decls().Lookup(class_info.first_decl_id())) {
|
|
return cast<clang::TagDecl>(clang_decl->decl());
|
|
}
|
|
|
|
auto* decl_context =
|
|
ExportNameScopeToCpp(context, loc_id, class_info.parent_scope_id);
|
|
auto* record_decl = ExportClassToCppInDeclContext(
|
|
context, decl_context, class_info, class_type.specific_id);
|
|
|
|
auto key =
|
|
SemIR::ClangDeclKey::ForNonFunctionDecl(cast<clang::Decl>(record_decl));
|
|
auto clang_decl_id = context.clang_decls().Add(
|
|
{.key = key, .inst_id = class_info.first_decl_id()});
|
|
if (class_info.scope_id.has_value()) {
|
|
// TODO: Record the Carbon class -> clang declaration mapping for incomplete
|
|
// classes too.
|
|
context.name_scopes()
|
|
.Get(class_info.scope_id)
|
|
.set_clang_decl_context_id(clang_decl_id, /*is_cpp_scope=*/false);
|
|
}
|
|
return record_decl;
|
|
}
|
|
|
|
auto ExportAndCompleteClassToCpp(Context& context, SemIR::ClassType class_type)
|
|
-> clang::TagDecl* {
|
|
auto* tag_decl = ExportClassToCpp(context, class_type);
|
|
if (tag_decl && context.cpp_context() &&
|
|
context.ast_context().getExternalSource()) {
|
|
context.ast_context().getExternalSource()->CompleteType(tag_decl);
|
|
}
|
|
return tag_decl;
|
|
}
|
|
|
|
// Export the bindings in a generic as a `clang::TemplateParameterList`.
|
|
static auto ExportGenericBindings(Context& context, SemIR::LocId loc_id,
|
|
SemIR::GenericId generic_id,
|
|
clang::DeclContext* decl_context)
|
|
-> clang::TemplateParameterList* {
|
|
auto clang_loc = GetCppLocation(context, loc_id);
|
|
|
|
const auto& generic = context.generics().Get(generic_id);
|
|
auto bindings = context.inst_blocks().Get(generic.bindings_id);
|
|
llvm::SmallVector<clang::NamedDecl*> template_param_decls;
|
|
|
|
// Create `clang::TemplateTypeParmDecl`s for each of the generic's bindings.
|
|
//
|
|
// TODO: handle the case where the generic is within an enclosing generic,
|
|
// and only include the bindings introduced in the inner generic here. See
|
|
// `fail_todo_enclosing_generic.carbon`.
|
|
for (auto binding_inst_id : bindings) {
|
|
binding_inst_id =
|
|
context.constant_values().GetConstantInstId(binding_inst_id);
|
|
auto symbolic_binding =
|
|
context.insts().GetAs<SemIR::SymbolicBinding>(binding_inst_id);
|
|
|
|
const auto& entity_name =
|
|
context.entity_names().Get(symbolic_binding.entity_name_id);
|
|
|
|
auto* param_ident = GetClangIdentifierInfo(context, entity_name.name_id);
|
|
CARBON_CHECK(param_ident, "non-identifier param name {0}",
|
|
entity_name.name_id);
|
|
|
|
if (!context.types().Is<SemIR::FacetType>(symbolic_binding.type_id)) {
|
|
context.TODO(loc_id, "binding maps to a non-type template parameter");
|
|
return nullptr;
|
|
}
|
|
|
|
auto* param_decl = clang::TemplateTypeParmDecl::Create(
|
|
context.ast_context(), decl_context, /*KeyLoc=*/clang_loc,
|
|
/*NameLoc=*/clang_loc,
|
|
/*D=*/0, /*P=*/0, param_ident, /*Typename=*/true,
|
|
/*ParameterPack=*/false);
|
|
template_param_decls.push_back(param_decl);
|
|
|
|
// Store a mapping between the generic parameter's `TypeInstId` and
|
|
// the `clang::TemplateTypeParmDecl`.
|
|
auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(param_decl);
|
|
context.clang_decls().Add({.key = key, .inst_id = binding_inst_id});
|
|
}
|
|
|
|
return clang::TemplateParameterList::Create(context.ast_context(),
|
|
/*TemplateLoc=*/clang_loc,
|
|
/*LAngleLoc=*/clang_loc,
|
|
template_param_decls,
|
|
/*RAngleLoc=*/clang_loc,
|
|
/*RequiresClause=*/nullptr);
|
|
}
|
|
|
|
/// Create a Specific for the given generic using the given template args.
|
|
///
|
|
/// Returns `SemIR::SpecificId::None` if an error occurs.
|
|
static auto MakeSpecificForTemplateArgs(
|
|
Context& context, SemIR::LocId loc_id, SemIR::GenericId generic_id,
|
|
llvm::ArrayRef<clang::TemplateArgument> template_args)
|
|
-> SemIR::SpecificId {
|
|
const auto& generic = context.generics().Get(generic_id);
|
|
|
|
auto bindings = context.inst_blocks().Get(generic.bindings_id);
|
|
CARBON_CHECK(bindings.size() == template_args.size());
|
|
|
|
// Map the `clang::TemplateArgument`s into Carbon types suitable for
|
|
// passing into `MakeSpecific`.
|
|
llvm::SmallVector<SemIR::InstId> specific_arg_ids;
|
|
for (auto [binding_inst_id, clang_template_arg] :
|
|
llvm::zip(bindings, template_args)) {
|
|
auto type_expr =
|
|
ImportCppType(context, loc_id, clang_template_arg.getAsType());
|
|
if (type_expr.type_id == SemIR::ErrorInst::TypeId) {
|
|
return SemIR::SpecificId::None;
|
|
}
|
|
if (!type_expr.type_id.has_value()) {
|
|
context.TODO(loc_id, "failed to import C++ type");
|
|
return SemIR::SpecificId::None;
|
|
}
|
|
|
|
auto binding_const_inst_id =
|
|
context.constant_values().GetConstantInstId(binding_inst_id);
|
|
|
|
specific_arg_ids.push_back(ConvertToValueOfType(
|
|
context, loc_id, type_expr.inst_id,
|
|
context.insts().Get(binding_const_inst_id).type_id()));
|
|
}
|
|
|
|
return MakeSpecific(context, loc_id, generic_id, specific_arg_ids);
|
|
}
|
|
|
|
auto ExportGenericClassToCpp(Context& context,
|
|
SemIR::GenericClassType generic_class_type)
|
|
-> clang::ClassTemplateDecl* {
|
|
// Use existing export if possible.
|
|
const auto& class_info = context.classes().Get(generic_class_type.class_id);
|
|
auto decl_id = class_info.first_decl_id();
|
|
if (const auto* clang_decl = context.clang_decls().Lookup(decl_id)) {
|
|
return cast<clang::ClassTemplateDecl>(clang_decl->decl());
|
|
}
|
|
|
|
// Map the parent scope into the C++ AST.
|
|
SemIR::LocId loc_id(decl_id);
|
|
auto* decl_context =
|
|
ExportNameScopeToCpp(context, loc_id, class_info.parent_scope_id);
|
|
if (!decl_context) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto* template_param_list = ExportGenericBindings(
|
|
context, loc_id, class_info.generic_id, decl_context);
|
|
if (!template_param_list) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto clang_loc = GetCppLocation(context, loc_id);
|
|
auto* record_decl = ExportClassToCppInDeclContext(
|
|
context, decl_context, class_info, SemIR::SpecificId::None);
|
|
auto* class_template_decl = clang::ClassTemplateDecl::Create(
|
|
context.ast_context(), decl_context,
|
|
/*L=*/clang_loc, record_decl->getDeclName(), template_param_list,
|
|
record_decl);
|
|
|
|
auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(
|
|
cast<clang::Decl>(class_template_decl));
|
|
context.clang_decls().Add({.key = key, .inst_id = decl_id});
|
|
|
|
return class_template_decl;
|
|
}
|
|
|
|
auto ExportClassSpecializationToCpp(
|
|
Context& context, clang::ClassTemplateDecl* class_template_decl,
|
|
llvm::ArrayRef<clang::TemplateArgument> template_args) -> bool {
|
|
// Map from the `clang::ClassTemplateDecl` to the Carbon `ClassDecl`.
|
|
auto clang_decl_id =
|
|
context.clang_decls().LookupId(SemIR::ClangDeclKey(class_template_decl));
|
|
if (clang_decl_id == SemIR::ClangDeclId::None) {
|
|
return false;
|
|
}
|
|
const auto& clang_decl = context.clang_decls().Get(clang_decl_id);
|
|
if (clang_decl.is_imported) {
|
|
return false;
|
|
}
|
|
auto class_decl = context.insts().GetAs<SemIR::ClassDecl>(clang_decl.inst_id);
|
|
|
|
const auto& class_info = context.classes().Get(class_decl.class_id);
|
|
SemIR::LocId loc_id(class_info.first_decl_id());
|
|
|
|
auto specific_id = MakeSpecificForTemplateArgs(
|
|
context, loc_id, class_info.generic_id, template_args);
|
|
if (specific_id == SemIR::SpecificId::None) {
|
|
return false;
|
|
}
|
|
|
|
auto class_type_inst_id =
|
|
GetClassTypeInstId(context, class_decl.class_id, specific_id);
|
|
CreateClassTemplateSpecializationDecl(context, class_template_decl,
|
|
template_args, class_type_inst_id);
|
|
|
|
return true;
|
|
}
|
|
|
|
static auto SetCppClassMemberAccess(const SemIR::NameScope& class_scope,
|
|
SemIR::NameId member_name_id,
|
|
clang::Decl* member) -> void {
|
|
auto entry_id = class_scope.Lookup(member_name_id);
|
|
CARBON_CHECK(entry_id.has_value());
|
|
const auto& entry = class_scope.GetEntry(*entry_id);
|
|
member->setAccess(MapToCppAccess(entry.result.access_kind()));
|
|
}
|
|
|
|
// Creates a `clang::FieldDecl` for a Carbon class field. Returns
|
|
// nullptr if an error occurs.
|
|
static auto CreateCppFieldDecl(Context& context,
|
|
const SemIR::NameScope& class_scope,
|
|
clang::CXXRecordDecl* record_decl,
|
|
SemIR::InstId field_inst_id,
|
|
const SemIR::FieldDecl& field_decl,
|
|
SemIR::SpecificId specific_id)
|
|
-> clang::FieldDecl* {
|
|
// Get the field's C++ type.
|
|
auto unbound_element_type = context.types().GetAs<SemIR::UnboundElementType>(
|
|
SemIR::GetTypeOfInstInSpecific(context.sem_ir(), specific_id,
|
|
field_inst_id));
|
|
auto cpp_type =
|
|
MapToCppType(context, context.types().GetTypeIdForTypeInstId(
|
|
unbound_element_type.element_type_inst_id));
|
|
if (cpp_type.isNull()) {
|
|
context.TODO(field_inst_id, "failed to map Carbon type to C++");
|
|
return nullptr;
|
|
}
|
|
|
|
auto clang_loc = GetCppLocation(context, SemIR::LocId(field_inst_id));
|
|
bool invalid = false;
|
|
|
|
// The field is required to have a complete type in the Clang AST, not just in
|
|
// SemIR.
|
|
if (context.clang_sema().RequireCompleteSizedType(
|
|
clang_loc, cpp_type, clang::diag::err_field_incomplete_or_sizeless)) {
|
|
// Follow Clang in marking the class as invalid if it contains a field with
|
|
// an incomplete type. In Clang this prevents record layout; it's unclear if
|
|
// it's necessary for us.
|
|
record_decl->setInvalidDecl();
|
|
invalid = true;
|
|
}
|
|
|
|
// Get the field's C++ identifier.
|
|
const auto& field = context.fields().Get(field_decl.field_id);
|
|
auto* identifier_info = GetClangIdentifierInfo(context, field.name_id);
|
|
CARBON_CHECK(identifier_info, "field with non-identifier name {0}",
|
|
field.name_id);
|
|
|
|
// Create the `clang::FieldDecl`.
|
|
auto* cpp_field_decl = clang::FieldDecl::Create(
|
|
context.ast_context(), record_decl, /*StartLoc=*/clang_loc,
|
|
/*IdLoc=*/clang_loc, identifier_info, cpp_type, /*TInfo=*/nullptr,
|
|
/*BW=*/nullptr,
|
|
/*Mutable=*/true, clang::ICIS_NoInit);
|
|
cpp_field_decl->setInvalidDecl(invalid);
|
|
|
|
SetCppClassMemberAccess(class_scope, field.name_id, cpp_field_decl);
|
|
|
|
record_decl->addHiddenDecl(cpp_field_decl);
|
|
|
|
return cpp_field_decl;
|
|
}
|
|
|
|
// Create an invalid `clang::FieldDecl`. This is only used as an error marker
|
|
// to indicate that a Carbon field has already been unsuccessfully exported.
|
|
static auto CreateInvalidFieldDecl(Context& context,
|
|
clang::DeclContext* decl_context)
|
|
-> clang::FieldDecl* {
|
|
clang::SourceLocation clang_loc;
|
|
auto* identifier_info =
|
|
context.clang_sema().getPreprocessor().getIdentifierInfo("invalid_field");
|
|
auto cpp_type = context.ast_context().IntTy;
|
|
auto* field_decl = clang::FieldDecl::Create(
|
|
context.ast_context(), decl_context, /*StartLoc=*/clang_loc,
|
|
/*IdLoc=*/clang_loc, identifier_info, cpp_type, /*TInfo=*/nullptr,
|
|
/*BW=*/nullptr,
|
|
/*Mutable=*/true, clang::ICIS_NoInit);
|
|
field_decl->setInvalidDecl();
|
|
return field_decl;
|
|
}
|
|
|
|
auto ExportAllFieldsToCpp(Context& context,
|
|
SemIR::TypeInstId class_type_inst_id) -> void {
|
|
auto class_type = context.insts().GetAs<SemIR::ClassType>(class_type_inst_id);
|
|
auto& class_info = context.classes().Get(class_type.class_id);
|
|
|
|
const auto& class_scope = context.name_scopes().Get(class_info.scope_id);
|
|
|
|
for (const auto& struct_field : class_info.GetStructTypeFields(
|
|
context.sem_ir(), class_type.specific_id)) {
|
|
auto class_field = LookupClassFieldByStructField(context.sem_ir(),
|
|
class_scope, struct_field);
|
|
if (!class_field) {
|
|
continue;
|
|
}
|
|
|
|
// Return early if the field is already exported. Since fields are always
|
|
// exported as a group, this indicates all fields have been exported so
|
|
// there's no need to continue to the rest.
|
|
if (context.clang_decls().Lookup(class_field->inst_id,
|
|
class_type.specific_id)) {
|
|
return;
|
|
}
|
|
|
|
// Get the field's record decl.
|
|
auto lookup_key = class_type.specific_id == SemIR::SpecificId::None
|
|
? class_info.first_decl_id()
|
|
: class_type_inst_id;
|
|
const auto* clang_decl = context.clang_decls().Lookup(lookup_key);
|
|
auto* record_decl = llvm::cast<clang::CXXRecordDecl>(clang_decl->decl());
|
|
|
|
auto* cpp_field_decl = CreateCppFieldDecl(
|
|
context, class_scope, record_decl, class_field->inst_id,
|
|
class_field->inst, class_type.specific_id);
|
|
|
|
// If the field cannot be exported, create an invalid `FieldDecl` to store
|
|
// in `clang_decls`. This marks the field as unsuccessfully exported, so
|
|
// that we know not to attempt export again (which could create duplicate
|
|
// error diagnostics).
|
|
if (!cpp_field_decl) {
|
|
cpp_field_decl = CreateInvalidFieldDecl(context, record_decl);
|
|
}
|
|
|
|
// Create and store the `ClangDeclId`.
|
|
auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(cpp_field_decl);
|
|
context.clang_decls().Add({.key = key,
|
|
.inst_id = class_field->inst_id,
|
|
.specific_id = class_type.specific_id});
|
|
}
|
|
}
|
|
|
|
auto ExportFieldToCpp(Context& context, SemIR::InstId field_inst_id,
|
|
SemIR::FieldDecl field_decl,
|
|
SemIR::SpecificId specific_id) -> clang::FieldDecl* {
|
|
// Get the `SemIR::Class` that contains the `field_decl`.
|
|
auto unbound_element_type =
|
|
context.types().GetAs<SemIR::UnboundElementType>(field_decl.type_id);
|
|
SemIR::TypeId class_type_id = context.types().GetTypeIdForTypeInstId(
|
|
unbound_element_type.class_type_inst_id);
|
|
auto class_type = context.types().GetAs<SemIR::ClassType>(class_type_id);
|
|
|
|
// If the class's fields haven't already been exported, do so now.
|
|
auto class_type_inst_id =
|
|
GetClassTypeInstId(context, class_type.class_id, specific_id);
|
|
ExportAllFieldsToCpp(context, class_type_inst_id);
|
|
|
|
// Get the exported `clang::FieldDecl`.
|
|
if (const auto* clang_decl =
|
|
context.clang_decls().Lookup(field_inst_id, specific_id)) {
|
|
if (!clang_decl->decl()->isInvalidDecl()) {
|
|
return cast<clang::FieldDecl>(clang_decl->decl());
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
namespace {
|
|
struct FunctionInfo {
|
|
struct Param {
|
|
Param(Context& context, SemIR::InstId param_inst_id)
|
|
: pattern_inst_id(param_inst_id),
|
|
type_id(ExtractScrutineeType(
|
|
context.sem_ir(), context.insts().Get(param_inst_id).type_id())),
|
|
kind(GetParamPatternKind(context, param_inst_id)) {}
|
|
|
|
// The parameter's pattern type.
|
|
SemIR::InstId pattern_inst_id;
|
|
|
|
// Type of the parameter's scrutinee.
|
|
SemIR::TypeId type_id;
|
|
|
|
// Kind of the parameter pattern.
|
|
ParamPatternKind kind;
|
|
};
|
|
|
|
explicit FunctionInfo(Context& context, SemIR::FunctionId function_id,
|
|
const SemIR::Function& function,
|
|
clang::DeclContext* decl_context,
|
|
bool export_as_constructor)
|
|
: function_id(function_id),
|
|
function(function),
|
|
decl_context(decl_context),
|
|
return_type_id(function.GetDeclaredReturnType(context.sem_ir())),
|
|
export_as_constructor(export_as_constructor) {
|
|
auto function_params =
|
|
context.inst_blocks().Get(function.call_param_patterns_id);
|
|
const auto& ranges = function.call_param_ranges;
|
|
auto explicit_begin = ranges.explicit_begin().index;
|
|
|
|
// Get the function's `self` parameter, if present. `self` is the first
|
|
// explicit call parameter. (The lowered call parameters are leaf patterns
|
|
// without binding names, so we rely on `self_param_id` and the positional
|
|
// convention rather than inspecting the pattern.)
|
|
if (function.self_param_id.has_value()) {
|
|
CARBON_CHECK(explicit_begin != ranges.explicit_end().index);
|
|
self_param = Param(context, function_params[explicit_begin]);
|
|
++explicit_begin;
|
|
}
|
|
|
|
// The remaining explicit parameters are the caller-provided arguments.
|
|
for (auto i = explicit_begin; i != ranges.explicit_end().index; ++i) {
|
|
explicit_params.push_back(Param(context, function_params[i]));
|
|
}
|
|
}
|
|
|
|
// Get the `StorageClass` to use for `CXXMethodDecl`s.
|
|
auto GetStorageClass() const -> clang::StorageClass {
|
|
if (self_param) {
|
|
return clang::SC_None;
|
|
} else {
|
|
return clang::SC_Static;
|
|
}
|
|
}
|
|
|
|
// Get the `self` param type, or `None` if the function does not have
|
|
// a `self` param.
|
|
auto GetSelfTypeId() const -> SemIR::TypeId {
|
|
if (self_param) {
|
|
return self_param->type_id;
|
|
}
|
|
return SemIR::TypeId::None;
|
|
}
|
|
|
|
// Get the clang::DeclarationName of this function's C++ counterpart.
|
|
auto GetCppName(Context& context) const -> clang::DeclarationName {
|
|
if (export_as_constructor) {
|
|
auto* record_decl = cast<clang::CXXRecordDecl>(decl_context);
|
|
return context.ast_context().DeclarationNames.getCXXConstructorName(
|
|
context.ast_context().getCanonicalTagType(record_decl));
|
|
} else {
|
|
return &context.ast_context().Idents.get(
|
|
context.names().GetFormatted(function.name_id));
|
|
}
|
|
}
|
|
|
|
SemIR::FunctionId function_id;
|
|
const SemIR::Function& function;
|
|
|
|
// Parent scope in the C++ AST where a C++ thunk for this function can
|
|
// be created. If the function is a method or constructor, this will be a
|
|
// `CXXRecordDecl`.
|
|
clang::DeclContext* decl_context;
|
|
|
|
// For each of the function's explicit parameters, the scrutinee type
|
|
// and whether the parameter is a reference.
|
|
llvm::SmallVector<Param> explicit_params;
|
|
|
|
// Return type of the function.
|
|
SemIR::TypeId return_type_id;
|
|
|
|
// For methods, the type of `self` and whether it is a reference. If
|
|
// the function does not have a `self` parameter, this is `nullopt`.
|
|
std::optional<Param> self_param;
|
|
|
|
// Whether this function should be exported as a C++ constructor.
|
|
bool export_as_constructor;
|
|
};
|
|
} // namespace
|
|
|
|
// Converts a Carbon parameter type to the parameter type that should be used
|
|
// for the C++ declaration of the Carbon -> Carbon thunk. This is always a
|
|
// reference type.
|
|
static auto MapToCppThunkParamType(Context& context, SemIR::TypeId type_id)
|
|
-> clang::QualType {
|
|
auto cpp_type = MapToCppType(context, type_id);
|
|
if (cpp_type.isNull()) {
|
|
return clang::QualType();
|
|
}
|
|
// The function exposed to C++ may have a `const&` parameter type for a value
|
|
// parameter. Always use a const reference here so we accept the argument,
|
|
// even though we might not need the `const`.
|
|
return context.ast_context().getLValueReferenceType(
|
|
context.ast_context().getConstType(cpp_type));
|
|
}
|
|
|
|
// Build FunctionInfo for an export of the given Carbon function. Exports the
|
|
// name scope if necessary.
|
|
static auto BuildFunctionInfo(Context& context, SemIR::LocId loc_id,
|
|
SemIR::FunctionId callee_function_id)
|
|
-> std::optional<FunctionInfo> {
|
|
const SemIR::Function& callee = context.functions().Get(callee_function_id);
|
|
|
|
// Map the parent scope into the C++ AST.
|
|
auto* decl_context =
|
|
ExportNameScopeToCpp(context, loc_id, callee.parent_scope_id);
|
|
if (!decl_context) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
bool export_as_constructor = false;
|
|
const auto& parent_scope = context.name_scopes().Get(callee.parent_scope_id);
|
|
if (auto class_decl =
|
|
context.insts().TryGetAs<SemIR::ClassDecl>(parent_scope.inst_id())) {
|
|
auto& class_info = context.classes().Get(class_decl->class_id);
|
|
if (class_info.name_id == callee.name_id) {
|
|
// If the function's name matches the name of the enclosing class,
|
|
// we can't export it as an ordinary function, so from this point on
|
|
// if we can't export it as a constructor we can't export it at all.
|
|
//
|
|
// TODO: figure out a way to provide good diagnostics in this situation.
|
|
// Ideally we'd only diagnose if the user actually tries to call it,
|
|
// because it's perfectly valid as Carbon code, but it's not clear how
|
|
// to do that.
|
|
//
|
|
// TODO: some impl functions should also be exported as constructors
|
|
// (e.g. `Core.Copy.Op`). Figure out how to avoid colliding with those
|
|
// here.
|
|
if (callee.self_param_id != SemIR::InstId::None) {
|
|
return std::nullopt;
|
|
}
|
|
if (!context.insts().Is<SemIR::InitForm>(
|
|
callee.GetDeclaredReturnForm(context.sem_ir()))) {
|
|
return std::nullopt;
|
|
}
|
|
auto class_type_id =
|
|
GetClassType(context, class_decl->class_id, SemIR::SpecificId::None);
|
|
auto return_type_id =
|
|
context.types().GetTypeIdForTypeInstId(callee.return_type_inst_id);
|
|
if (class_type_id != return_type_id) {
|
|
return std::nullopt;
|
|
}
|
|
// TODO figure out how to deal with explicit generic parameters.
|
|
export_as_constructor = true;
|
|
}
|
|
}
|
|
|
|
return FunctionInfo(context, callee_function_id, callee, decl_context,
|
|
export_as_constructor);
|
|
}
|
|
|
|
// Create a `clang::FunctionDecl` with the given parameter types and
|
|
// return type.
|
|
//
|
|
// The function's name will match the one referenced by `function_name_id`,
|
|
// and the function will be added to the given `decl_context`.
|
|
static auto BuildCppFunctionDecl(Context& context,
|
|
clang::DeclContext* decl_context,
|
|
SemIR::LocId loc_id,
|
|
clang::DeclarationName declaration_name,
|
|
clang::ArrayRef<clang::QualType> param_types,
|
|
clang::QualType return_type,
|
|
bool export_as_constructor) {
|
|
auto clang_loc = GetCppLocation(context, loc_id);
|
|
|
|
auto cpp_function_type = context.ast_context().getFunctionType(
|
|
return_type, param_types, clang::FunctionProtoType::ExtProtoInfo());
|
|
|
|
auto* tinfo = context.ast_context().getTrivialTypeSourceInfo(
|
|
cpp_function_type, clang_loc);
|
|
clang::FunctionDecl* function_decl;
|
|
if (export_as_constructor) {
|
|
auto* record_decl = cast<clang::CXXRecordDecl>(decl_context);
|
|
function_decl = clang::CXXConstructorDecl::Create(
|
|
context.ast_context(), record_decl, /*StartLoc=*/clang_loc,
|
|
clang::DeclarationNameInfo{declaration_name, clang_loc},
|
|
cpp_function_type, tinfo,
|
|
clang::ExplicitSpecifier{nullptr,
|
|
clang::ExplicitSpecKind::ResolvedTrue},
|
|
/*UsesFPIntrin=*/false,
|
|
/*isInline=*/false, /*isImplicitlyDeclared=*/false,
|
|
clang::ConstexprSpecKind::Unspecified);
|
|
} else {
|
|
function_decl = clang::FunctionDecl::Create(
|
|
context.ast_context(), decl_context,
|
|
/*StartLoc=*/clang_loc, /*NLoc=*/clang_loc, declaration_name,
|
|
cpp_function_type, tinfo, clang::SC_Extern);
|
|
}
|
|
|
|
// Build parameter decls.
|
|
llvm::SmallVector<clang::ParmVarDecl*> param_var_decls;
|
|
for (auto [i, type] : llvm::enumerate(param_types)) {
|
|
auto* param_tinfo =
|
|
context.ast_context().getTrivialTypeSourceInfo(type, clang_loc);
|
|
clang::ParmVarDecl* param = clang::ParmVarDecl::Create(
|
|
context.ast_context(), function_decl, /*StartLoc=*/clang_loc,
|
|
/*IdLoc=*/clang_loc, /*Id=*/nullptr, type, param_tinfo, clang::SC_None,
|
|
/*DefArg=*/nullptr);
|
|
param_var_decls.push_back(param);
|
|
}
|
|
function_decl->setParams(param_var_decls);
|
|
|
|
return function_decl;
|
|
}
|
|
|
|
// Create a `clang::FunctionDecl` for the given Carbon function. This
|
|
// can be used to call the Carbon function from C++. The Carbon
|
|
// function's ABI must be compatible with C++.
|
|
//
|
|
// The resulting decl is used to allow a generated C++ function to call
|
|
// a generated Carbon function.
|
|
static auto BuildCppFunctionDeclForNonGenericCarbonFn(Context& context,
|
|
SemIR::LocId loc_id,
|
|
FunctionInfo target)
|
|
-> clang::FunctionDecl* {
|
|
CARBON_CHECK(!target.function.generic_id.has_value());
|
|
|
|
// Get parameters types.
|
|
llvm::SmallVector<clang::QualType> cpp_param_types;
|
|
if (target.self_param) {
|
|
auto cpp_type = MapToCppThunkParamType(context, target.self_param->type_id);
|
|
if (cpp_type.isNull()) {
|
|
context.TODO(loc_id, "failed to map Carbon self type to C++");
|
|
return nullptr;
|
|
}
|
|
cpp_param_types.push_back(cpp_type);
|
|
}
|
|
// For constructors, the first Carbon parameter is the object being
|
|
// constructed, which is not explicitly declared in C++.
|
|
llvm::ArrayRef<FunctionInfo::Param> params_to_map = target.explicit_params;
|
|
if (target.export_as_constructor) {
|
|
params_to_map = params_to_map.drop_front();
|
|
}
|
|
for (auto param : params_to_map) {
|
|
auto cpp_type = MapToCppThunkParamType(context, param.type_id);
|
|
if (cpp_type.isNull()) {
|
|
context.TODO(loc_id, "failed to map Carbon type to C++");
|
|
return nullptr;
|
|
}
|
|
cpp_param_types.push_back(cpp_type);
|
|
}
|
|
|
|
CARBON_CHECK(target.function.return_type_inst_id == SemIR::TypeInstId::None);
|
|
auto cpp_return_type = context.ast_context().VoidTy;
|
|
|
|
auto* decl_context = target.export_as_constructor
|
|
? target.decl_context
|
|
: context.ast_context().getTranslationUnitDecl();
|
|
auto* function_decl = BuildCppFunctionDecl(
|
|
context, decl_context, loc_id, target.GetCppName(context),
|
|
cpp_param_types, cpp_return_type, target.export_as_constructor);
|
|
|
|
// Mangle the function name and attach it to the `FunctionDecl`.
|
|
SemIR::Mangler m(context.sem_ir(), context.total_ir_count(),
|
|
context.mangle_string_fingerprint());
|
|
std::string mangled_name =
|
|
m.MangleWithPlatform(target.function_id, SemIR::SpecificId::None);
|
|
function_decl->addAttr(
|
|
clang::AsmLabelAttr::Create(context.ast_context(), mangled_name));
|
|
|
|
return function_decl;
|
|
}
|
|
|
|
// Create a `clang::FunctionDecl` for the given generic Carbon function.
|
|
//
|
|
// The `clang::FunctionDecl` created here is only used as a function template
|
|
// decl. Only specializations of this function template decl are called
|
|
// directly, so the ABI of this function decl is irrelevant.
|
|
static auto BuildCppFunctionDeclForGenericCarbonFn(Context& context,
|
|
SemIR::LocId loc_id,
|
|
FunctionInfo callee)
|
|
-> clang::FunctionDecl* {
|
|
CARBON_CHECK(callee.function.generic_id.has_value());
|
|
|
|
// Get parameters types.
|
|
//
|
|
// TODO: currently this matches the behavior of
|
|
// BuildCppFunctionDeclForNonGenericCarbonFn, but for templates the ABI is
|
|
// irrelevant, and the parameter should instead map to something that will
|
|
// guide C++ template argument deduction into doing the right thing.
|
|
llvm::SmallVector<clang::QualType> cpp_param_types;
|
|
if (callee.self_param) {
|
|
auto cpp_type = MapToCppThunkParamType(context, callee.self_param->type_id);
|
|
if (cpp_type.isNull()) {
|
|
context.TODO(loc_id, "failed to map Carbon self type to C++");
|
|
return nullptr;
|
|
}
|
|
cpp_param_types.push_back(cpp_type);
|
|
}
|
|
for (auto param : callee.explicit_params) {
|
|
auto cpp_type = MapToCppThunkParamType(context, param.type_id);
|
|
if (cpp_type.isNull()) {
|
|
context.TODO(loc_id, "failed to map Carbon type to C++");
|
|
return nullptr;
|
|
}
|
|
cpp_param_types.push_back(cpp_type);
|
|
}
|
|
|
|
clang::QualType cpp_return_type = context.ast_context().VoidTy;
|
|
if (callee.return_type_id.has_value()) {
|
|
cpp_return_type = MapToCppType(context, callee.return_type_id);
|
|
if (cpp_return_type.isNull()) {
|
|
context.TODO(loc_id, "failed to map Carbon return type to C++");
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
// TODO: provide the decl context corresponding to the Carbon generic
|
|
// function.
|
|
auto* decl_context = callee.export_as_constructor
|
|
? callee.decl_context
|
|
: context.ast_context().getTranslationUnitDecl();
|
|
return BuildCppFunctionDecl(context, decl_context, loc_id,
|
|
callee.GetCppName(context), cpp_param_types,
|
|
cpp_return_type, callee.export_as_constructor);
|
|
}
|
|
|
|
// Returns whether the given Carbon parameter should be passed as a C++ const
|
|
// reference.
|
|
static auto PassAsConstRef(Context& /*context*/,
|
|
const FunctionInfo::Param& param,
|
|
clang::QualType cpp_type) -> bool {
|
|
// Use pass-by-const-ref for value parameters of array type.
|
|
// TODO: Should we do this for value parameters of any type that uses a
|
|
// pointer value representation?
|
|
return param.kind == ParamPatternKind::Value && cpp_type->isArrayType();
|
|
}
|
|
|
|
// Converts a Carbon parameter type to the parameter type that should be exposed
|
|
// to C++ callers.
|
|
static auto MapToCppParamType(Context& context, SemIR::LocId loc_id,
|
|
const FunctionInfo::Param& param)
|
|
-> clang::QualType {
|
|
auto cpp_type = MapToCppType(context, param.type_id);
|
|
if (cpp_type.isNull()) {
|
|
return clang::QualType();
|
|
}
|
|
if (param.kind == Check::ParamPatternKind::Ref) {
|
|
cpp_type = context.ast_context().getLValueReferenceType(cpp_type);
|
|
} else if (PassAsConstRef(context, param, cpp_type)) {
|
|
cpp_type = context.ast_context().getLValueReferenceType(
|
|
context.ast_context().getConstType(cpp_type));
|
|
} else if (cpp_type->isArrayType()) {
|
|
// C++ doesn't support passing arrays by value.
|
|
context.TODO(loc_id, "by-var array parameter");
|
|
return clang::QualType();
|
|
}
|
|
return cpp_type;
|
|
}
|
|
|
|
// Returns the C++ function type (`clang::FunctionProtoType`) to use for a C++
|
|
// thunk calling a Carbon function.
|
|
static auto BuildCppToCarbonThunkFunctionType(Context& context,
|
|
SemIR::LocId loc_id,
|
|
const FunctionInfo& target)
|
|
-> const clang::FunctionProtoType* {
|
|
llvm::SmallVector<clang::QualType> thunk_param_types;
|
|
thunk_param_types.reserve(target.explicit_params.size());
|
|
for (auto param : target.explicit_params) {
|
|
auto cpp_type = MapToCppParamType(context, loc_id, param);
|
|
if (cpp_type.isNull()) {
|
|
context.TODO(loc_id, "failed to map C++ type to Carbon");
|
|
return nullptr;
|
|
}
|
|
thunk_param_types.push_back(cpp_type);
|
|
}
|
|
|
|
// Get the C++ return type (this corresponds to the return type of the
|
|
// target Carbon function).
|
|
clang::QualType cpp_return_type = context.ast_context().VoidTy;
|
|
if (!target.export_as_constructor &&
|
|
(target.return_type_id != SemIR::TypeId::None)) {
|
|
cpp_return_type = MapToCppType(context, target.return_type_id);
|
|
if (cpp_return_type.isNull()) {
|
|
context.TODO(loc_id, "failed to map Carbon return type to C++ type");
|
|
return nullptr;
|
|
}
|
|
if (cpp_return_type->isArrayType()) {
|
|
// C++ doesn't support returning arrays by value.
|
|
context.TODO(loc_id, "array return type");
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
auto ext_proto_info = clang::FunctionProtoType::ExtProtoInfo();
|
|
if (target.self_param) {
|
|
if (target.self_param->kind == ParamPatternKind::Ref) {
|
|
ext_proto_info.RefQualifier = clang::RQ_LValue;
|
|
} else {
|
|
// A method with `self` doesn't modify the object, so export it as
|
|
// `const`. Unlike `ref self`, `self` doesn't require a reference
|
|
// expression, so no ref-qualifier is added.
|
|
ext_proto_info.TypeQuals.addConst();
|
|
}
|
|
}
|
|
return context.ast_context()
|
|
.getFunctionType(cpp_return_type, thunk_param_types, ext_proto_info)
|
|
->getAs<clang::FunctionProtoType>();
|
|
}
|
|
|
|
// Create the declaration of the C++ thunk.
|
|
static auto BuildCppToCarbonThunkDecl(Context& context, SemIR::LocId loc_id,
|
|
const FunctionInfo& target,
|
|
clang::DeclarationName thunk_name)
|
|
-> clang::FunctionDecl* {
|
|
clang::ASTContext& ast_context = context.ast_context();
|
|
|
|
auto clang_loc = GetCppLocation(context, loc_id);
|
|
|
|
// If the signature was imported from C++, use that declaration to form the
|
|
// parameter types rather than (lossily) re-exporting the Carbon signature
|
|
// back to C++.
|
|
const clang::FunctionProtoType* thunk_function_type = nullptr;
|
|
if (auto thunk_id = target.function.thunk_id(); thunk_id.has_value()) {
|
|
const auto& thunk = context.thunks().Get(thunk_id);
|
|
const auto& thunk_signature = context.functions().Get(thunk.signature_id);
|
|
if (const auto* clang_decl =
|
|
context.clang_decls().Lookup(thunk_signature.first_decl_id())) {
|
|
thunk_function_type = cast<clang::FunctionDecl>(clang_decl->decl())
|
|
->getType()
|
|
->getAs<clang::FunctionProtoType>();
|
|
}
|
|
}
|
|
if (!thunk_function_type) {
|
|
thunk_function_type =
|
|
BuildCppToCarbonThunkFunctionType(context, loc_id, target);
|
|
if (!thunk_function_type) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
clang::DeclarationNameInfo name_info(thunk_name, clang_loc);
|
|
clang::QualType thunk_qual_type(thunk_function_type, 0);
|
|
auto* tinfo =
|
|
ast_context.getTrivialTypeSourceInfo(thunk_qual_type, clang_loc);
|
|
|
|
bool uses_fp_intrin = false;
|
|
bool inline_specified = true;
|
|
auto constexpr_kind = clang::ConstexprSpecKind::Unspecified;
|
|
auto trailing_requires_clause = clang::AssociatedConstraint();
|
|
|
|
clang::FunctionDecl* thunk_function_decl = nullptr;
|
|
if (auto* parent_class =
|
|
dyn_cast<clang::CXXRecordDecl>(target.decl_context)) {
|
|
if (target.export_as_constructor) {
|
|
thunk_function_decl = clang::CXXConstructorDecl::Create(
|
|
ast_context, parent_class, clang_loc, name_info, thunk_qual_type,
|
|
tinfo,
|
|
clang::ExplicitSpecifier{nullptr,
|
|
clang::ExplicitSpecKind::ResolvedTrue},
|
|
uses_fp_intrin, inline_specified, /* isImplicitlyDeclared= */ false,
|
|
constexpr_kind);
|
|
} else {
|
|
thunk_function_decl = clang::CXXMethodDecl::Create(
|
|
ast_context, parent_class, clang_loc, name_info, thunk_qual_type,
|
|
tinfo, target.GetStorageClass(), uses_fp_intrin, inline_specified,
|
|
constexpr_kind, clang_loc, trailing_requires_clause);
|
|
}
|
|
// TODO: Map Carbon access to C++ access.
|
|
thunk_function_decl->setAccess(clang::AS_public);
|
|
// Carbon overriders are non-virtual in C++; only the corresponding thunk is
|
|
// virtual.
|
|
thunk_function_decl->setVirtualAsWritten(
|
|
target.function.virtual_modifier !=
|
|
SemIR::Function::VirtualModifier::None &&
|
|
target.function.virtual_modifier !=
|
|
SemIR::Function::VirtualModifier::Override);
|
|
if (target.function.virtual_modifier ==
|
|
SemIR::Function::VirtualModifier::Abstract) {
|
|
cast<clang::CXXMethodDecl>(thunk_function_decl)->setIsPureVirtual(true);
|
|
}
|
|
} else {
|
|
thunk_function_decl = clang::FunctionDecl::Create(
|
|
ast_context, target.decl_context, clang_loc, name_info, thunk_qual_type,
|
|
tinfo, clang::SC_None, uses_fp_intrin, inline_specified,
|
|
/*hasWrittenPrototype=*/true, constexpr_kind, trailing_requires_clause);
|
|
}
|
|
|
|
llvm::SmallVector<clang::ParmVarDecl*> param_var_decls;
|
|
for (auto [i, type] : llvm::enumerate(thunk_function_type->param_types())) {
|
|
clang::ParmVarDecl* thunk_param = clang::ParmVarDecl::Create(
|
|
ast_context, thunk_function_decl, /*StartLoc=*/clang_loc,
|
|
/*IdLoc=*/clang_loc, /*Id=*/nullptr, type,
|
|
/*TInfo=*/nullptr, clang::SC_None, /*DefArg=*/nullptr);
|
|
param_var_decls.push_back(thunk_param);
|
|
}
|
|
thunk_function_decl->setParams(param_var_decls);
|
|
target.decl_context->addHiddenDecl(thunk_function_decl);
|
|
|
|
// Force the thunk to be inlined and discarded.
|
|
thunk_function_decl->addAttr(
|
|
clang::AlwaysInlineAttr::CreateImplicit(ast_context));
|
|
thunk_function_decl->addAttr(
|
|
clang::InternalLinkageAttr::CreateImplicit(ast_context));
|
|
|
|
return thunk_function_decl;
|
|
}
|
|
|
|
// Get an expr for accessing `this` in a method.
|
|
static auto GetThisArg(clang::Sema& sema, clang::SourceLocation clang_loc,
|
|
const clang::CXXMethodDecl* method_decl)
|
|
-> clang::Expr* {
|
|
// These pick up the method's `const` qualifier, if any.
|
|
clang::QualType class_type = method_decl->getFunctionObjectParameterType();
|
|
auto* this_expr = sema.BuildCXXThisExpr(clang_loc, method_decl->getThisType(),
|
|
/*IsImplicit=*/true);
|
|
return clang::UnaryOperator::Create(
|
|
sema.getASTContext(), this_expr, clang::UO_Deref, class_type,
|
|
clang::ExprValueKind::VK_LValue, clang::ExprObjectKind::OK_Ordinary,
|
|
clang_loc, /*CanOverflow=*/false, clang::FPOptionsOverride());
|
|
}
|
|
|
|
// Create the body of a C++ thunk that calls a Carbon thunk. The
|
|
// arguments are passed by reference to the callee.
|
|
static auto BuildCppToCarbonThunkBody(Context& context,
|
|
const FunctionInfo& target,
|
|
clang::FunctionDecl* function_decl,
|
|
clang::FunctionDecl* callee_function_decl)
|
|
-> clang::StmtResult {
|
|
clang::Sema& sema = context.clang_sema();
|
|
clang::SourceLocation clang_loc = function_decl->getLocation();
|
|
|
|
llvm::SmallVector<clang::Stmt*> stmts;
|
|
|
|
// Create return storage if the target function returns non-void.
|
|
const bool has_return_value = !function_decl->getReturnType()->isVoidType();
|
|
clang::VarDecl* return_storage_var_decl = nullptr;
|
|
clang::ExprResult return_storage_expr;
|
|
if (has_return_value) {
|
|
CARBON_CHECK(!target.export_as_constructor);
|
|
auto& return_storage_ident =
|
|
sema.getASTContext().Idents.get("return_storage");
|
|
return_storage_var_decl =
|
|
clang::VarDecl::Create(sema.getASTContext(), function_decl,
|
|
/*StartLoc=*/clang_loc,
|
|
/*IdLoc=*/clang_loc, &return_storage_ident,
|
|
function_decl->getReturnType(),
|
|
/*TInfo=*/nullptr, clang::SC_None);
|
|
return_storage_var_decl->setNRVOVariable(true);
|
|
return_storage_expr = sema.BuildDeclRefExpr(
|
|
return_storage_var_decl, return_storage_var_decl->getType(),
|
|
clang::VK_LValue, clang_loc);
|
|
|
|
auto decl_group_ref = clang::DeclGroupRef(return_storage_var_decl);
|
|
auto decl_stmt =
|
|
sema.ActOnDeclStmt(clang::Sema::DeclGroupPtrTy::make(decl_group_ref),
|
|
clang_loc, clang_loc);
|
|
stmts.push_back(decl_stmt.get());
|
|
}
|
|
|
|
llvm::SmallVector<clang::Expr*> call_args;
|
|
// For methods, pass the `this` pointer as the first argument to the callee.
|
|
if (target.self_param) {
|
|
call_args.push_back(
|
|
GetThisArg(sema, clang_loc, cast<clang::CXXMethodDecl>(function_decl)));
|
|
}
|
|
for (auto* param : function_decl->parameters()) {
|
|
clang::Expr* call_arg =
|
|
sema.BuildDeclRefExpr(param, param->getType().getNonReferenceType(),
|
|
clang::VK_LValue, clang_loc);
|
|
call_args.push_back(call_arg);
|
|
}
|
|
|
|
// If the target function returns non-void, the Carbon thunk takes an
|
|
// extra output parameter referencing the return storage.
|
|
if (has_return_value) {
|
|
call_args.push_back(return_storage_expr.get());
|
|
}
|
|
|
|
if (target.export_as_constructor) {
|
|
auto* class_decl = cast<clang::CXXRecordDecl>(target.decl_context);
|
|
clang::QualType class_type =
|
|
sema.getASTContext().getCanonicalTagType(class_decl);
|
|
auto* callee_ctor_decl =
|
|
llvm::cast<clang::CXXConstructorDecl>(callee_function_decl);
|
|
llvm::SmallVector<clang::Expr*> converted_args;
|
|
if (sema.CompleteConstructorCall(callee_ctor_decl, class_type, call_args,
|
|
clang_loc, converted_args,
|
|
/*AllowExplicit=*/true)) {
|
|
CARBON_FATAL("CompleteConstructorCall failed");
|
|
}
|
|
auto call = sema.BuildCXXConstructExpr(
|
|
clang_loc, class_type, callee_ctor_decl, /*Elidable=*/false,
|
|
converted_args,
|
|
/*HadMultipleCandidates=*/true, /*IsListInitialization=*/false,
|
|
/*IsStdInitListInitialization=*/false,
|
|
/*RequiresZeroInit=*/false, clang::CXXConstructionKind::Delegating,
|
|
clang::SourceRange(clang_loc, clang_loc));
|
|
auto* tinfo =
|
|
context.ast_context().getTrivialTypeSourceInfo(class_type, clang_loc);
|
|
auto* ctor_initializer =
|
|
new (context.ast_context()) clang::CXXCtorInitializer(
|
|
context.ast_context(), tinfo, clang_loc, call.get(), clang_loc);
|
|
CARBON_CHECK(call.isUsable());
|
|
sema.SetDelegatingInitializer(
|
|
llvm::cast<clang::CXXConstructorDecl>(function_decl), ctor_initializer);
|
|
} else {
|
|
clang::ExprResult callee = sema.BuildDeclRefExpr(
|
|
callee_function_decl, callee_function_decl->getType(),
|
|
clang::VK_PRValue, clang_loc);
|
|
clang::ExprResult call = sema.BuildCallExpr(
|
|
nullptr, callee.get(), clang_loc, call_args, clang_loc);
|
|
CARBON_CHECK(call.isUsable());
|
|
stmts.push_back(call.get());
|
|
|
|
if (has_return_value) {
|
|
auto* return_stmt = clang::ReturnStmt::Create(
|
|
sema.getASTContext(), clang_loc, return_storage_expr.get(),
|
|
return_storage_var_decl);
|
|
stmts.push_back(return_stmt);
|
|
}
|
|
}
|
|
return clang::CompoundStmt::Create(sema.getASTContext(), stmts,
|
|
clang::FPOptionsOverride(), clang_loc,
|
|
clang_loc);
|
|
}
|
|
|
|
// Create a Carbon thunk that calls `callee`. The thunk's parameters are
|
|
// all references to the callee parameter type.
|
|
//
|
|
// `extra_name` will be appended to the thunk name. This is used to
|
|
// disambiguate the names of specialized function thunks.
|
|
static auto BuildCarbonToCarbonThunk(Context& context, SemIR::LocId loc_id,
|
|
const FunctionInfo& target,
|
|
std::string_view extra_name = "")
|
|
-> FunctionInfo {
|
|
// Create the thunk's name.
|
|
llvm::SmallString<64> thunk_name =
|
|
context.names().GetFormatted(target.function.name_id);
|
|
thunk_name += "__carbon_thunk";
|
|
thunk_name += extra_name;
|
|
auto& ident = context.ast_context().Idents.get(thunk_name);
|
|
auto thunk_name_id =
|
|
SemIR::NameId::ForIdentifier(context.identifiers().Add(ident.getName()));
|
|
|
|
// Get the thunk's parameters. These match the callee parameters, with
|
|
// the addition of an output parameter for the callee's return value
|
|
// (if it has one).
|
|
llvm::SmallVector<SemIR::TypeId> thunk_param_type_ids;
|
|
for (const auto& param : target.explicit_params) {
|
|
thunk_param_type_ids.push_back(param.type_id);
|
|
}
|
|
if (target.return_type_id != SemIR::TypeId::None) {
|
|
thunk_param_type_ids.push_back(target.return_type_id);
|
|
}
|
|
|
|
// If this thunk will be exposed as a C++ constructor, we put the output
|
|
// parameter first to match the Itanium constructor ABI.
|
|
//
|
|
// TODO: use `clang::CodeGen::CGCXXABI::HasThisReturn` to determine if the
|
|
// constructor's `this` should be a return value instead of an output param.
|
|
if (target.export_as_constructor) {
|
|
CARBON_CHECK(target.return_type_id != SemIR::TypeId::None);
|
|
std::rotate(thunk_param_type_ids.begin(), thunk_param_type_ids.end() - 1,
|
|
thunk_param_type_ids.end());
|
|
}
|
|
|
|
llvm::SmallVector<ParamPatternKind> thunk_param_kinds(
|
|
thunk_param_type_ids.size(), ParamPatternKind::Ref);
|
|
auto carbon_thunk_function_id =
|
|
MakeGeneratedFunctionDecl(
|
|
context, loc_id,
|
|
{.parent_scope_id = target.function.parent_scope_id,
|
|
.name_id = thunk_name_id,
|
|
.self_type_id = target.GetSelfTypeId(),
|
|
.param_type_ids = thunk_param_type_ids,
|
|
.param_kinds = thunk_param_kinds})
|
|
.second;
|
|
|
|
BuildThunkDefinitionForExport(
|
|
context, carbon_thunk_function_id, target.function_id,
|
|
context.functions().Get(carbon_thunk_function_id).first_decl_id(),
|
|
target.function.first_decl_id(), target.export_as_constructor);
|
|
|
|
return FunctionInfo(context, carbon_thunk_function_id,
|
|
context.functions().Get(carbon_thunk_function_id),
|
|
target.decl_context, target.export_as_constructor);
|
|
}
|
|
|
|
static auto ExportNonGenericFunctionDeclToCpp(Context& context,
|
|
SemIR::LocId loc_id,
|
|
const FunctionInfo& target)
|
|
-> clang::FunctionDecl* {
|
|
return BuildCppToCarbonThunkDecl(context, loc_id, target,
|
|
target.GetCppName(context));
|
|
}
|
|
|
|
auto ExportVirtualFunctionDeclToCpp(Context& context, SemIR::LocId loc_id,
|
|
clang::CXXRecordDecl* parent,
|
|
SemIR::FunctionId function_id)
|
|
-> clang::CXXMethodDecl* {
|
|
FunctionInfo target(context, function_id,
|
|
context.functions().Get(function_id), parent,
|
|
/*export_as_constructor=*/false);
|
|
return cast_or_null<clang::CXXMethodDecl>(
|
|
ExportNonGenericFunctionDeclToCpp(context, loc_id, target));
|
|
}
|
|
|
|
static auto BuildCppToCarbonThunk(Context& context, SemIR::LocId loc_id,
|
|
const FunctionInfo& target,
|
|
clang::FunctionDecl* thunk_function_decl,
|
|
std::string_view extra_name) -> void {
|
|
// Create a Carbon thunk that calls the callee. The thunk's parameters
|
|
// are all references so that the ABI is compatible with C++ callers.
|
|
auto carbon_thunk_target =
|
|
BuildCarbonToCarbonThunk(context, loc_id, target, extra_name);
|
|
|
|
// Create a `clang::FunctionDecl` that can be used to call the Carbon thunk.
|
|
auto* carbon_function_decl = BuildCppFunctionDeclForNonGenericCarbonFn(
|
|
context, loc_id, carbon_thunk_target);
|
|
if (!carbon_function_decl) {
|
|
return;
|
|
}
|
|
|
|
// Build the thunk function body.
|
|
clang::Sema& sema = context.clang_sema();
|
|
clang::Sema::ContextRAII context_raii(sema, thunk_function_decl);
|
|
// Ensure that the evaluation context is not `Unevaluated`, as that
|
|
// would cause code generation to fail.
|
|
clang::EnterExpressionEvaluationContext evaluated(
|
|
sema, clang::Sema::ExpressionEvaluationContext::PotentiallyEvaluated);
|
|
sema.ActOnStartOfFunctionDef(nullptr, thunk_function_decl);
|
|
clang::StmtResult body = BuildCppToCarbonThunkBody(
|
|
context, target, thunk_function_decl, carbon_function_decl);
|
|
sema.ActOnFinishFunctionBody(thunk_function_decl, body.get());
|
|
CARBON_CHECK(!body.isInvalid());
|
|
|
|
context.clang_sema().getASTConsumer().HandleTopLevelDecl(
|
|
clang::DeclGroupRef(thunk_function_decl));
|
|
}
|
|
|
|
auto DefineExportedVirtualFunction(Context& context, SemIR::LocId loc_id,
|
|
SemIR::FunctionId callee_function_id,
|
|
clang::CXXMethodDecl* method_decl) -> void {
|
|
const SemIR::Function& callee = context.functions().Get(callee_function_id);
|
|
FunctionInfo target_function_info(context, callee_function_id, callee,
|
|
method_decl->getDeclContext(),
|
|
/*export_as_constructor=*/false);
|
|
BuildCppToCarbonThunk(context, loc_id, target_function_info, method_decl, "");
|
|
}
|
|
|
|
// Creates a `clang::FunctionDecl` that calls the Carbon function in
|
|
// `target`. The `extra_name` string is appended to the Carbon thunk's
|
|
// name.
|
|
//
|
|
// Returns nullptr if an error occurs.
|
|
auto ExportNonGenericFunctionToCpp(Context& context, SemIR::LocId loc_id,
|
|
const FunctionInfo& target,
|
|
std::string_view extra_name = "")
|
|
-> clang::FunctionDecl* {
|
|
auto* thunk_function_decl =
|
|
ExportNonGenericFunctionDeclToCpp(context, loc_id, target);
|
|
if (!thunk_function_decl) {
|
|
return nullptr;
|
|
}
|
|
|
|
BuildCppToCarbonThunk(context, loc_id, target, thunk_function_decl,
|
|
extra_name);
|
|
return thunk_function_decl;
|
|
}
|
|
|
|
auto ExportFunctionSpecializationToCpp(
|
|
Context& context, clang::FunctionTemplateDecl* function_template_decl,
|
|
llvm::ArrayRef<clang::TemplateArgument> template_args) -> bool {
|
|
// Map from the `clang::FunctionTemplateDecl` to the Carbon `FunctionDecl`.
|
|
auto clang_decl_id = context.clang_decls().LookupId(
|
|
SemIR::ClangDeclKey(function_template_decl));
|
|
if (clang_decl_id == SemIR::ClangDeclId::None) {
|
|
return false;
|
|
}
|
|
SemIR::InstId inst_id = context.clang_decls().Get(clang_decl_id).inst_id;
|
|
CARBON_CHECK(inst_id.has_value());
|
|
auto target_function_decl =
|
|
context.insts().GetAs<SemIR::FunctionDecl>(inst_id);
|
|
auto target_function =
|
|
context.functions().Get(target_function_decl.function_id);
|
|
|
|
auto* decl_context = function_template_decl->getDeclContext();
|
|
FunctionInfo target(context, target_function_decl.function_id,
|
|
target_function, decl_context,
|
|
llvm::isa<clang::CXXConstructorDecl>(
|
|
function_template_decl->getTemplatedDecl()));
|
|
SemIR::LocId loc_id(target.function.first_decl_id());
|
|
|
|
// Create a specific, and use that to convert return type and
|
|
// parameters with symbolic types to concrete types.
|
|
auto specific_id = MakeSpecificForTemplateArgs(
|
|
context, loc_id, target.function.generic_id, template_args);
|
|
if (specific_id == SemIR::SpecificId::None) {
|
|
return false;
|
|
}
|
|
// This name is appended to the thunk name to disambiguate between
|
|
// specializations.
|
|
SemIR::Mangler m(context.sem_ir(), context.total_ir_count(),
|
|
context.mangle_string_fingerprint());
|
|
auto extra_name = m.MangleSpecificId(specific_id);
|
|
target.return_type_id =
|
|
target.function.GetDeclaredReturnType(context.sem_ir(), specific_id);
|
|
for (auto& param : target.explicit_params) {
|
|
param.type_id =
|
|
GetScrutineeTypeInSpecific(context, param.pattern_inst_id, specific_id);
|
|
}
|
|
|
|
// Build the thunks. Mark the C++ thunk as a template specialization.
|
|
auto* function_decl =
|
|
ExportNonGenericFunctionToCpp(context, loc_id, target, extra_name);
|
|
if (!function_decl) {
|
|
return false;
|
|
}
|
|
auto* template_arg_list = clang::TemplateArgumentList::CreateCopy(
|
|
context.ast_context(), template_args);
|
|
function_decl->setFunctionTemplateSpecialization(
|
|
function_template_decl, template_arg_list,
|
|
/*InsertPos=*/llvm::FoldingSetInsertToken(),
|
|
clang::TSK_ExplicitSpecialization,
|
|
/*TemplateArgsAsWritten=*/nullptr,
|
|
/*PointOfInstantiation=*/clang::SourceLocation());
|
|
|
|
return true;
|
|
}
|
|
|
|
// Creates a `clang::FunctionTemplateDecl` for a generic Carbon function.
|
|
//
|
|
// Returns nullptr if an error occurs.
|
|
static auto ExportGenericFunctionToCpp(Context& context, SemIR::LocId loc_id,
|
|
const FunctionInfo& callee)
|
|
-> clang::FunctionTemplateDecl* {
|
|
auto clang_loc = GetCppLocation(context, loc_id);
|
|
|
|
auto* template_param_list = ExportGenericBindings(
|
|
context, loc_id, callee.function.generic_id, callee.decl_context);
|
|
if (!template_param_list) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto* function_decl =
|
|
BuildCppFunctionDeclForGenericCarbonFn(context, loc_id, callee);
|
|
if (!function_decl) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto* template_decl = clang::FunctionTemplateDecl::Create(
|
|
context.ast_context(), callee.decl_context, clang_loc,
|
|
function_decl->getDeclName(), template_param_list, function_decl);
|
|
function_decl->setDescribedFunctionTemplate(template_decl);
|
|
|
|
return template_decl;
|
|
}
|
|
|
|
auto ExportFunctionToCpp(Context& context, SemIR::LocId loc_id,
|
|
SemIR::FunctionId callee_function_id)
|
|
-> clang::NamedDecl* {
|
|
auto target = BuildFunctionInfo(context, loc_id, callee_function_id);
|
|
if (!target) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (target->function.generic_id.has_value()) {
|
|
if (target->export_as_constructor || target->self_param.has_value()) {
|
|
context.TODO(loc_id, "support exporting generic member functions");
|
|
return nullptr;
|
|
}
|
|
return ExportGenericFunctionToCpp(context, loc_id, *target);
|
|
}
|
|
|
|
return ExportNonGenericFunctionToCpp(context, loc_id, *target);
|
|
}
|
|
|
|
// Returns whether the given class has any abstract methods.
|
|
static auto HasAnyAbstractMethods(Context& context,
|
|
const SemIR::Class& class_info,
|
|
SemIR::SpecificId class_specific_id) -> bool {
|
|
if (class_info.vtable_decl_id == SemIR::InstId::None) {
|
|
return false;
|
|
}
|
|
|
|
LoadImportRef(context, class_info.vtable_decl_id);
|
|
auto vtable_decl_const_id = GetConstantValueInSpecific(
|
|
context.sem_ir(), class_specific_id, class_info.vtable_decl_id);
|
|
if (vtable_decl_const_id == SemIR::ErrorInst::ConstantId) {
|
|
return false;
|
|
}
|
|
auto vtable_id = context.constant_values()
|
|
.GetInstAs<SemIR::VtableDecl>(vtable_decl_const_id)
|
|
.vtable_id;
|
|
const auto& vtable = context.vtables().Get(vtable_id);
|
|
for (auto virtual_fn_id :
|
|
context.inst_blocks().Get(vtable.virtual_functions_id)) {
|
|
auto virtual_fn = DecomposeVirtualFunction(context.sem_ir(), virtual_fn_id,
|
|
class_specific_id);
|
|
if (context.functions().Get(virtual_fn.function_id).virtual_modifier ==
|
|
SemIR::Function::VirtualModifier::Abstract) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
auto ExportDestructorToCpp(Context& context, const SemIR::Class& class_info,
|
|
clang::CXXRecordDecl* record_decl)
|
|
-> clang::CXXDestructorDecl* {
|
|
SemIR::LocId loc_id(class_info.first_decl_id());
|
|
auto clang_loc = record_decl->getLocation();
|
|
|
|
// TODO: Add support for exporting specific classes.
|
|
const auto specific_id = SemIR::SpecificId::None;
|
|
|
|
// Create C++ destructor decl.
|
|
auto class_type = context.ast_context().getCanonicalTagType(record_decl);
|
|
auto name =
|
|
context.ast_context().DeclarationNames.getCXXDestructorName(class_type);
|
|
clang::DeclarationNameInfo name_info(name, clang_loc);
|
|
clang::QualType type = context.ast_context().getFunctionType(
|
|
context.ast_context().VoidTy, llvm::ArrayRef<clang::QualType>(),
|
|
clang::FunctionProtoType::ExtProtoInfo().withExceptionSpec(
|
|
clang::EST_BasicNoexcept));
|
|
auto* cpp_destructor_decl = clang::CXXDestructorDecl::Create(
|
|
context.ast_context(), record_decl,
|
|
/*StartLoc=*/clang_loc, name_info, type, /*TInfo=*/nullptr,
|
|
/*UsesFPIntrin=*/false, /*isInline=*/true, /*isImplicitlyDeclared=*/true,
|
|
clang::ConstexprSpecKind::Unspecified);
|
|
cpp_destructor_decl->setAccess(clang::AS_public);
|
|
|
|
clang::Sema& sema = context.clang_sema();
|
|
|
|
// Find and register any base class virtual destructors that this destructor
|
|
// overrides. This marks the destructor as implicitly virtual if needed.
|
|
sema.AddOverriddenMethods(record_decl, cpp_destructor_decl);
|
|
|
|
// If the class is abstract and has no abstract methods, we need to mark the
|
|
// destructor as pure virtual.
|
|
if (class_info.inheritance_kind == SemIR::Class::InheritanceKind::Abstract &&
|
|
!HasAnyAbstractMethods(context, class_info, specific_id)) {
|
|
if (cpp_destructor_decl->isVirtual()) {
|
|
cpp_destructor_decl->setIsPureVirtual(true);
|
|
} else {
|
|
context.TODO(class_info.definition_id,
|
|
"exporting abstract class with no abstract methods and "
|
|
"non-virtual destructor to C++");
|
|
}
|
|
}
|
|
|
|
// Create Carbon thunk that destroys the object, and get a C++
|
|
// function decl for calling it.
|
|
// TODO: Once we support exporting specific classes, export the specific
|
|
// destructor here rather than a generic one.
|
|
auto thunk_function_id = BuildDestroyThunk(context, loc_id, class_info);
|
|
FunctionInfo thunk_target(context, thunk_function_id,
|
|
context.functions().Get(thunk_function_id),
|
|
record_decl, /*export_as_constructor=*/false);
|
|
auto* cpp_function_decl =
|
|
BuildCppFunctionDeclForNonGenericCarbonFn(context, loc_id, thunk_target);
|
|
if (!cpp_function_decl) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Build the destructor body.
|
|
clang::Sema::ContextRAII context_raii(sema, cpp_destructor_decl);
|
|
sema.ActOnStartOfFunctionDef(nullptr, cpp_destructor_decl);
|
|
|
|
// Create a clang call expr to call the Carbon thunk.
|
|
clang::ExprResult callee =
|
|
sema.BuildDeclRefExpr(cpp_function_decl, cpp_function_decl->getType(),
|
|
clang::VK_PRValue, clang_loc);
|
|
llvm::SmallVector<clang::Expr*> call_args;
|
|
call_args.push_back(GetThisArg(sema, clang_loc, cpp_destructor_decl));
|
|
clang::ExprResult call = sema.BuildCallExpr(nullptr, callee.get(), clang_loc,
|
|
call_args, clang_loc);
|
|
|
|
sema.ActOnFinishFunctionBody(cpp_destructor_decl, call.get());
|
|
|
|
return cpp_destructor_decl;
|
|
}
|
|
|
|
auto ExportVarToCpp(Context& context, SemIR::InstId inst_id,
|
|
SemIR::VarStorage var_storage) -> clang::VarDecl* {
|
|
// Check if the variable was already exported and return the existing
|
|
// `VarDecl` if so. Note that the `pattern_id` is used as the key
|
|
// rather than the `InstId` for the `VarStorage`.
|
|
if (const auto* clang_decl =
|
|
context.clang_decls().Lookup(var_storage.pattern_id)) {
|
|
return cast<clang::VarDecl>(clang_decl->decl());
|
|
}
|
|
|
|
// Look up the entity name and check the scope.
|
|
auto entity_name_id = GetFirstBindingNameFromPatternId(
|
|
context.sem_ir(), var_storage.pattern_id);
|
|
const auto& entity_name = context.entity_names().Get(entity_name_id);
|
|
const auto& name_scope =
|
|
context.name_scopes().Get(entity_name.parent_scope_id);
|
|
auto scope_inst = context.insts().Get(name_scope.inst_id());
|
|
CARBON_CHECK(scope_inst.Is<SemIR::Namespace>() ||
|
|
scope_inst.Is<SemIR::ClassDecl>());
|
|
|
|
// Map the parent scope into the C++ AST.
|
|
SemIR::LocId loc_id(inst_id);
|
|
auto* decl_context =
|
|
ExportNameScopeToCpp(context, loc_id, entity_name.parent_scope_id);
|
|
if (!decl_context) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Map the type.
|
|
auto cpp_type = MapToCppType(context, var_storage.type_id);
|
|
if (cpp_type.isNull()) {
|
|
context.TODO(loc_id, "failed to map Carbon type to C++");
|
|
return nullptr;
|
|
}
|
|
|
|
// Create the `clang::VarDecl` and add it to `clang_decls()`.
|
|
auto clang_loc = GetCppLocation(context, loc_id);
|
|
auto* identifier_info = GetClangIdentifierInfo(context, entity_name.name_id);
|
|
auto* var_decl = clang::VarDecl::Create(
|
|
context.ast_context(), decl_context,
|
|
/*StartLoc=*/clang_loc, /*IdLoc=*/clang_loc, identifier_info, cpp_type,
|
|
/*TInfo=*/nullptr, clang::SC_Extern);
|
|
context.clang_decls().Add(
|
|
{.key = SemIR::ClangDeclKey::ForNonFunctionDecl(var_decl),
|
|
.inst_id = var_storage.pattern_id,
|
|
.var_storage_inst_id = inst_id});
|
|
|
|
if (scope_inst.Is<SemIR::ClassDecl>()) {
|
|
SetCppClassMemberAccess(name_scope, entity_name.name_id, var_decl);
|
|
}
|
|
|
|
// Set the Carbon mangled variable name.
|
|
// TODO: do we need to apply the platform mangling, like we do for exported
|
|
// functions?
|
|
SemIR::Mangler m(context.sem_ir(), context.total_ir_count(),
|
|
context.mangle_string_fingerprint());
|
|
std::string mangled_name = m.MangleGlobalVariable(var_storage.pattern_id);
|
|
var_decl->addAttr(
|
|
clang::AsmLabelAttr::Create(context.ast_context(), mangled_name));
|
|
|
|
return var_decl;
|
|
}
|
|
|
|
} // namespace Carbon::Check
|