// 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 "toolchain/check/cpp/import.h" #include "toolchain/check/cpp/location.h" #include "toolchain/check/cpp/type_mapping.h" #include "toolchain/check/function.h" #include "toolchain/check/thunk.h" #include "toolchain/check/type.h" #include "toolchain/sem_ir/mangler.h" namespace Carbon::Check { // 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 BuildCppFunctionDeclForCarbonFn(Context& context, SemIR::LocId loc_id, SemIR::FunctionId function_id) -> clang::FunctionDecl* { auto clang_loc = GetCppLocation(context, loc_id); const SemIR::Function& function = context.functions().Get(function_id); // Get parameters types. auto carbon_function_params = context.inst_blocks().Get(function.call_param_patterns_id); llvm::SmallVector cpp_param_types; for (auto param_inst_id : carbon_function_params) { auto scrutinee_type_id = ExtractScrutineeType( context.sem_ir(), context.insts().Get(param_inst_id).type_id()); auto cpp_type = MapToCppType(context, scrutinee_type_id); if (cpp_type.isNull()) { context.TODO(loc_id, "failed to map C++ type to Carbon"); return nullptr; } auto ref_type = context.ast_context().getLValueReferenceType(cpp_type); cpp_param_types.push_back(ref_type); } CARBON_CHECK(function.return_type_inst_id == SemIR::TypeInstId::None); auto cpp_return_type = context.ast_context().VoidTy; auto cpp_function_type = context.ast_context().getFunctionType( cpp_return_type, cpp_param_types, clang::FunctionProtoType::ExtProtoInfo()); auto* identifier_info = GetClangIdentifierInfo(context, function.name_id); CARBON_CHECK(identifier_info, "function with non-identifier name {0}", function.name_id); auto* 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); // Build parameter decls. llvm::SmallVector param_var_decls; for (auto [i, type] : llvm::enumerate(cpp_param_types)) { 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); param_var_decls.push_back(param); } function_decl->setParams(param_var_decls); // Mangle the function name and attach it to the `FunctionDecl`. SemIR::Mangler m(context.sem_ir(), context.total_ir_count()); std::string mangled_name = m.Mangle(function_id, SemIR::SpecificId::None); function_decl->addAttr( clang::AsmLabelAttr::Create(context.ast_context(), mangled_name)); return function_decl; } // Create the declaration of the C++ thunk. static auto BuildCppToCarbonThunkDecl( Context& context, SemIR::LocId loc_id, clang::DeclContext* decl_context, clang::DeclarationName thunk_name, llvm::ArrayRef thunk_param_types) -> clang::FunctionDecl* { clang::ASTContext& ast_context = context.ast_context(); auto clang_loc = GetCppLocation(context, loc_id); clang::DeclarationNameInfo name_info(thunk_name, clang_loc); auto ext_proto_info = clang::FunctionProtoType::ExtProtoInfo(); clang::QualType thunk_function_type = ast_context.getFunctionType( ast_context.VoidTy, thunk_param_types, ext_proto_info); auto* tinfo = ast_context.getTrivialTypeSourceInfo(thunk_function_type, clang_loc); const bool uses_fp_intrin = false; const bool inline_specified = true; const auto constexpr_kind = clang::ConstexprSpecKind::Unspecified; const auto trailing_requires_clause = clang::AssociatedConstraint(); clang::FunctionDecl* thunk_function_decl = nullptr; if (auto* parent_class = dyn_cast(decl_context)) { // TODO: Support non-static methods. thunk_function_decl = clang::CXXMethodDecl::Create( ast_context, parent_class, clang_loc, name_info, thunk_function_type, tinfo, clang::SC_Static, 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); } else { thunk_function_decl = clang::FunctionDecl::Create( ast_context, decl_context, clang_loc, name_info, thunk_function_type, tinfo, clang::SC_None, uses_fp_intrin, inline_specified, /*hasWrittenPrototype=*/true, constexpr_kind, trailing_requires_clause); } decl_context->addDecl(thunk_function_decl); llvm::SmallVector param_var_decls; for (auto [i, type] : llvm::enumerate(thunk_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); // 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; } // Create the body of a C++ thunk that calls a Carbon thunk. The // arguments are passed by reference to the callee. static auto BuildCppToCarbonThunkBody(clang::Sema& sema, clang::FunctionDecl* function_decl, clang::FunctionDecl* callee_function_decl) -> clang::StmtResult { clang::SourceLocation clang_loc = function_decl->getLocation(); clang::ExprResult callee = sema.BuildDeclRefExpr( callee_function_decl, callee_function_decl->getType(), clang::VK_PRValue, clang_loc); llvm::SmallVector call_args; 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); } clang::ExprResult call = sema.BuildCallExpr(nullptr, callee.get(), clang_loc, call_args, clang_loc); CARBON_CHECK(call.isUsable()); return call.get(); } // Create a C++ thunk that calls the Carbon thunk. The C++ thunk's // parameter types are mapped from the parameters of the target function // with `MapToCppType`. (Note that the target function here is the // callee of the Carbon thunk.) static auto BuildCppToCarbonThunk( Context& context, SemIR::LocId loc_id, clang::DeclContext* decl_context, llvm::StringRef base_name, clang::FunctionDecl* carbon_function_decl, llvm::ArrayRef callee_param_type_ids) -> clang::FunctionDecl* { // Create the thunk's name. llvm::SmallString<64> thunk_name = base_name; thunk_name += "__cpp_thunk"; auto& thunk_ident = context.ast_context().Idents.get(thunk_name); llvm::SmallVector param_types; for (auto type_id : callee_param_type_ids) { auto cpp_type = MapToCppType(context, type_id); if (cpp_type.isNull()) { context.TODO(loc_id, "failed to map C++ type to Carbon"); return nullptr; } param_types.push_back(cpp_type); } auto* thunk_function_decl = BuildCppToCarbonThunkDecl( context, loc_id, decl_context, &thunk_ident, param_types); // Build the thunk function body. clang::Sema& sema = context.clang_sema(); clang::Sema::ContextRAII context_raii(sema, thunk_function_decl); sema.ActOnStartOfFunctionDef(nullptr, thunk_function_decl); clang::StmtResult body = BuildCppToCarbonThunkBody(sema, 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)); return thunk_function_decl; } // Create a Carbon thunk that calls `callee`. The thunk's parameters are // all references to the callee parameter type. static auto BuildCarbonToCarbonThunk( Context& context, SemIR::LocId loc_id, const SemIR::Function& callee, llvm::ArrayRef callee_param_type_ids) -> SemIR::FunctionId { // Create the thunk's name. llvm::SmallString<64> thunk_name = context.names().GetFormatted(callee.name_id); thunk_name += "__carbon_thunk"; auto& ident = context.ast_context().Idents.get(thunk_name); auto thunk_name_id = SemIR::NameId::ForIdentifier(context.identifiers().Add(ident.getName())); auto carbon_thunk_function_id = MakeGeneratedFunctionDecl(context, loc_id, {.parent_scope_id = callee.parent_scope_id, .name_id = thunk_name_id, .param_type_ids = callee_param_type_ids, .params_are_refs = true}) .second; BuildThunkDefinition(context, carbon_thunk_function_id, carbon_thunk_function_id, callee.first_decl_id(), callee.first_decl_id()); return carbon_thunk_function_id; } auto GetReverseInteropFunctionDecl(Context& context, SemIR::LocId loc_id, clang::DeclContext& decl_context, SemIR::FunctionId callee_function_id) -> clang::FunctionDecl* { const SemIR::Function& callee = context.functions().Get(callee_function_id); if (callee.return_type_inst_id != SemIR::TypeInstId::None) { context.TODO(loc_id, "unsupported: C++ calling a Carbon function with " "return type other than `()`"); return nullptr; } if (callee.generic_id.has_value()) { context.TODO(loc_id, "unsupported: C++ calling a Carbon function with " "generic parameters"); return nullptr; } if (callee.call_param_ranges.implicit_size() != 0) { context.TODO(loc_id, "unsupported: C++ calling a Carbon function with " "an implicit parameter"); return nullptr; } // Get the parameter types of the Carbon function being called. auto callee_function_params = context.inst_blocks().Get(callee.call_param_patterns_id); llvm::SmallVector callee_param_type_ids; for (auto callee_param_inst_id : callee_function_params) { auto scrutinee_type_id = ExtractScrutineeType( context.sem_ir(), context.insts().Get(callee_param_inst_id).type_id()); callee_param_type_ids.push_back(scrutinee_type_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, callee, callee_param_type_ids); // 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, &decl_context, context.names().GetFormatted(callee.name_id), carbon_function_decl, callee_param_type_ids); } } // namespace Carbon::Check