mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 22:02:23 +01:00
Add initial support for exporting generic Carbon functions to C++ (#7462)
This allows C++ to call Carbon functions with generic type parameters,
with some conditions. Example:
```carbon
interface I {
fn Doit(self);
}
class A {
impl as I { fn Doit(unused self) {} }
}
class B {
impl as I { fn Doit(unused self) {} }
}
fn F[T:! I](t: T) {
t.Doit();
}
inline Cpp '''
void G() {
Carbon::A a;
Carbon::B b;
Carbon::F(a);
Carbon::F(b);
}
''';
```
The initial support is limited; only explicit parameters are handled
currently.
`CarbonExternalASTSource::GetOrExportFunctionToCpp` now generates a
`clang::FunctionTemplateDecl` for generic Carbon functions. If C++ code
attempts to call that templated function,
`CarbonExternalASTSource::LoadExternalSpecializations` will be called
with the template argument types of that call site. Then we can generate
a specialized thunk for those argument types for C++ to call.
This commit is contained in:
+215
-25
@@ -5,8 +5,10 @@
|
||||
#include "toolchain/check/cpp/export.h"
|
||||
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
#include "clang/AST/ASTConsumer.h"
|
||||
#include "clang/Sema/EnterExpressionEvaluationContext.h"
|
||||
#include "clang/Sema/Sema.h"
|
||||
#include "llvm/Support/Casting.h"
|
||||
#include "toolchain/check/cpp/access.h"
|
||||
@@ -421,18 +423,22 @@ static auto BuildCppFunctionDeclForCarbonFn(Context& context,
|
||||
CARBON_CHECK(identifier_info, "function with non-identifier name {0}",
|
||||
function.name_id);
|
||||
|
||||
auto* tinfo = context.ast_context().getTrivialTypeSourceInfo(
|
||||
cpp_function_type, clang_loc);
|
||||
clang::FunctionDecl* function_decl = clang::FunctionDecl::Create(
|
||||
context.ast_context(), context.ast_context().getTranslationUnitDecl(),
|
||||
/*StartLoc=*/clang_loc, /*NLoc=*/clang_loc, identifier_info,
|
||||
cpp_function_type, /*TInfo=*/nullptr, clang::SC_Extern);
|
||||
cpp_function_type, tinfo, clang::SC_Extern);
|
||||
|
||||
// Build parameter decls.
|
||||
llvm::SmallVector<clang::ParmVarDecl*> param_var_decls;
|
||||
for (auto [i, type] : llvm::enumerate(cpp_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, /*TInfo=*/nullptr,
|
||||
clang::SC_None, /*DefArg=*/nullptr);
|
||||
/*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);
|
||||
@@ -683,6 +689,10 @@ static auto BuildCppToCarbonThunk(Context& context, SemIR::LocId loc_id,
|
||||
// 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(
|
||||
sema, target, thunk_function_decl, carbon_function_decl);
|
||||
@@ -696,13 +706,18 @@ static auto BuildCppToCarbonThunk(Context& context, SemIR::LocId loc_id,
|
||||
|
||||
// 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)
|
||||
const FunctionInfo& target,
|
||||
std::string_view extra_name = "")
|
||||
-> SemIR::FunctionId {
|
||||
// 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()));
|
||||
@@ -738,18 +753,204 @@ static auto BuildCarbonToCarbonThunk(Context& context, SemIR::LocId loc_id,
|
||||
return carbon_thunk_function_id;
|
||||
}
|
||||
|
||||
auto ExportFunctionToCpp(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::FunctionId callee_function_id)
|
||||
// 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* {
|
||||
const SemIR::Function& callee = context.functions().Get(callee_function_id);
|
||||
// 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_function_id =
|
||||
BuildCarbonToCarbonThunk(context, loc_id, target, extra_name);
|
||||
|
||||
if (callee.generic_id.has_value()) {
|
||||
context.TODO(loc_id,
|
||||
"unsupported: C++ calling a Carbon function with "
|
||||
"generic parameters");
|
||||
// Create a `clang::FunctionDecl` that can be used to call the Carbon thunk.
|
||||
auto* carbon_function_decl = BuildCppFunctionDeclForCarbonFn(
|
||||
context, loc_id, carbon_thunk_function_id);
|
||||
if (!carbon_function_decl) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Create a C++ thunk that calls the Carbon thunk.
|
||||
return BuildCppToCarbonThunk(
|
||||
context, loc_id, target,
|
||||
context.names().GetFormatted(target.function.name_id),
|
||||
carbon_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);
|
||||
SemIR::LocId loc_id(target.function.first_decl_id());
|
||||
|
||||
const auto& generic = context.generics().Get(target.function.generic_id);
|
||||
auto bindings = context.inst_blocks().Get(generic.bindings_id);
|
||||
CARBON_CHECK(bindings.size() == template_args.size());
|
||||
|
||||
// This name will be appended to the thunk name to disambiguate
|
||||
// between specializations.
|
||||
std::string extra_name;
|
||||
|
||||
// Create a mapping from Carbon generic parameters to the
|
||||
// corresponding C++ type in `template_args`.
|
||||
Map<SemIR::InstId, SemIR::TypeId> symbolic_to_actual;
|
||||
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 false;
|
||||
}
|
||||
if (!type_expr.type_id.has_value()) {
|
||||
context.TODO(loc_id, "failed to import C++ type");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto binding_const_inst_id =
|
||||
context.constant_values().GetConstantInstId(binding_inst_id);
|
||||
symbolic_to_actual.Insert(binding_const_inst_id, type_expr.type_id);
|
||||
|
||||
// TODO: this generates a pretty ugly name.
|
||||
extra_name += std::string(llvm::formatv("{}", type_expr.inst_id));
|
||||
}
|
||||
|
||||
// Replace symbolic explicit parameters with a concrete Carbon type.
|
||||
//
|
||||
// This will only handle simple cases like `x: T`, and not things like
|
||||
// `x: T*`. Ultimately what we should be doing here is producing a Specific
|
||||
// for the generic function. See `fail_todo_generic_pointer.carbon`.
|
||||
for (auto& param : target.explicit_params) {
|
||||
auto param_type_inst_id = context.types().GetTypeInstId(param.type_id);
|
||||
SemIR::InstId symbolic_inst_id = SemIR::InstId::None;
|
||||
if (auto symbolic_binding =
|
||||
context.insts().TryGetAs<SemIR::SymbolicBinding>(
|
||||
param_type_inst_id)) {
|
||||
symbolic_inst_id = param_type_inst_id;
|
||||
} else if (auto facet_access_type =
|
||||
context.insts().TryGetAs<SemIR::FacetAccessType>(
|
||||
param_type_inst_id)) {
|
||||
symbolic_inst_id = facet_access_type->facet_value_inst_id;
|
||||
}
|
||||
|
||||
if (symbolic_inst_id.has_value()) {
|
||||
if (auto lookup = symbolic_to_actual.Lookup(symbolic_inst_id)) {
|
||||
param.type_id = lookup.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: handle generic return type.
|
||||
|
||||
// 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=*/nullptr, clang::TSK_ExplicitSpecialization);
|
||||
|
||||
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);
|
||||
|
||||
const auto& generic = context.generics().Get(callee.function.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 function's
|
||||
// symbolic parameters.
|
||||
//
|
||||
// TODO: handle the case where the function is within an enclosing generic,
|
||||
// and only include the bindings introduced in the inner function 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 (symbolic_binding.type_id != SemIR::TypeType::TypeId &&
|
||||
!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(), callee.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});
|
||||
}
|
||||
|
||||
auto* template_param_list = clang::TemplateParameterList::Create(
|
||||
context.ast_context(),
|
||||
/*TemplateLoc=*/clang_loc,
|
||||
/*LAngleLoc=*/clang_loc, template_param_decls,
|
||||
/*RAngleLoc=*/clang_loc,
|
||||
/*RequiresClause=*/nullptr);
|
||||
|
||||
auto* function_decl =
|
||||
BuildCppFunctionDeclForCarbonFn(context, loc_id, callee.function_id);
|
||||
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* {
|
||||
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);
|
||||
@@ -760,22 +961,11 @@ auto ExportFunctionToCpp(Context& context, SemIR::LocId loc_id,
|
||||
FunctionInfo target_function_info(context, callee_function_id, callee,
|
||||
decl_context);
|
||||
|
||||
// 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_function_id =
|
||||
BuildCarbonToCarbonThunk(context, loc_id, target_function_info);
|
||||
|
||||
// Create a `clang::FunctionDecl` that can be used to call the Carbon thunk.
|
||||
auto* carbon_function_decl = BuildCppFunctionDeclForCarbonFn(
|
||||
context, loc_id, carbon_thunk_function_id);
|
||||
if (!carbon_function_decl) {
|
||||
return nullptr;
|
||||
if (callee.generic_id.has_value()) {
|
||||
return ExportGenericFunctionToCpp(context, loc_id, target_function_info);
|
||||
}
|
||||
|
||||
// Create a C++ thunk that calls the Carbon thunk.
|
||||
return BuildCppToCarbonThunk(context, loc_id, target_function_info,
|
||||
context.names().GetFormatted(callee.name_id),
|
||||
carbon_function_decl);
|
||||
return ExportNonGenericFunctionToCpp(context, loc_id, target_function_info);
|
||||
}
|
||||
|
||||
// Returns whether the given class has any abstract methods.
|
||||
|
||||
Reference in New Issue
Block a user