export.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. // Part of the Carbon Language project, under the Apache License v2.0 with LLVM
  2. // Exceptions. See /LICENSE for license information.
  3. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  4. #include "toolchain/check/cpp/export.h"
  5. #include "toolchain/check/cpp/import.h"
  6. #include "toolchain/check/cpp/location.h"
  7. #include "toolchain/check/cpp/type_mapping.h"
  8. #include "toolchain/check/function.h"
  9. #include "toolchain/check/thunk.h"
  10. #include "toolchain/check/type.h"
  11. #include "toolchain/sem_ir/mangler.h"
  12. namespace Carbon::Check {
  13. // If the given name scope was produced by importing a C++ declaration or has
  14. // already been exported to C++, return the corresponding Clang decl context.
  15. static auto GetClangDeclContextForScope(Context& context,
  16. SemIR::NameScopeId scope_id)
  17. -> clang::DeclContext* {
  18. if (!scope_id.has_value()) {
  19. return nullptr;
  20. }
  21. auto& scope = context.name_scopes().Get(scope_id);
  22. auto clang_decl_context_id = scope.clang_decl_context_id();
  23. if (!clang_decl_context_id.has_value()) {
  24. return nullptr;
  25. }
  26. auto* decl = context.clang_decls().Get(clang_decl_context_id).key.decl;
  27. return cast<clang::DeclContext>(decl);
  28. }
  29. auto ExportNameScopeToCpp(Context& context, SemIR::LocId loc_id,
  30. SemIR::NameScopeId name_scope_id)
  31. -> clang::DeclContext* {
  32. llvm::SmallVector<SemIR::NameScopeId> name_scope_ids_to_create;
  33. // Walk through the parent scopes, looking for one that's already mapped into
  34. // C++. We already mapped the package scope to ::Carbon, so we must find one.
  35. clang::DeclContext* decl_context = nullptr;
  36. while (true) {
  37. // If this name scope was produced by importing a C++ declaration or has
  38. // already been exported to C++, return the corresponding Clang declaration.
  39. if (auto* existing_decl_context =
  40. GetClangDeclContextForScope(context, name_scope_id)) {
  41. decl_context = existing_decl_context;
  42. break;
  43. }
  44. // Otherwise, continue to the parent and create a scope for it first.
  45. name_scope_ids_to_create.push_back(name_scope_id);
  46. name_scope_id = context.name_scopes().Get(name_scope_id).parent_scope_id();
  47. // TODO: What should happen if there's an intervening function scope?
  48. CARBON_CHECK(
  49. name_scope_id.has_value(),
  50. "Reached the top level without finding a scope mapped into C++");
  51. }
  52. // Create the name scopes in order, starting from the outermost one.
  53. while (!name_scope_ids_to_create.empty()) {
  54. name_scope_id = name_scope_ids_to_create.pop_back_val();
  55. auto& name_scope = context.name_scopes().Get(name_scope_id);
  56. auto* identifier_info =
  57. GetClangIdentifierInfo(context, name_scope.name_id());
  58. if (!identifier_info) {
  59. // TODO: Handle keyword package names like `Cpp` and `Core`. These can
  60. // be named from C++ via an alias.
  61. context.TODO(loc_id, "interop with non-identifier package name");
  62. return nullptr;
  63. }
  64. auto inst = context.insts().Get(name_scope.inst_id());
  65. if (inst.Is<SemIR::Namespace>()) {
  66. // TODO: Provide a source location.
  67. auto* namespace_decl = clang::NamespaceDecl::Create(
  68. context.ast_context(), decl_context, false, clang::SourceLocation(),
  69. clang::SourceLocation(), identifier_info, nullptr, false);
  70. decl_context = namespace_decl;
  71. } else if (inst.Is<SemIR::ClassDecl>()) {
  72. // TODO: Provide a source location.
  73. auto* record_decl = clang::CXXRecordDecl::Create(
  74. context.ast_context(), clang::TagTypeKind::Class, decl_context,
  75. clang::SourceLocation(), clang::SourceLocation(), identifier_info);
  76. // If this is a member class, set its access.
  77. if (isa<clang::CXXRecordDecl>(decl_context)) {
  78. // TODO: Map Carbon access to C++ access.
  79. record_decl->setAccess(clang::AS_public);
  80. }
  81. decl_context = record_decl;
  82. decl_context->setHasExternalLexicalStorage();
  83. } else {
  84. context.TODO(loc_id, "non-class non-namespace name scope");
  85. return nullptr;
  86. }
  87. decl_context->setHasExternalVisibleStorage();
  88. auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(
  89. cast<clang::Decl>(decl_context));
  90. auto clang_decl_id = context.clang_decls().Add(
  91. {.key = key, .inst_id = name_scope.inst_id()});
  92. name_scope.set_clang_decl_context_id(clang_decl_id, /*is_cpp_scope=*/false);
  93. }
  94. return decl_context;
  95. }
  96. auto ExportClassToCpp(Context& context, SemIR::LocId loc_id,
  97. SemIR::InstId class_inst_id, SemIR::ClassType class_type)
  98. -> clang::TagDecl* {
  99. // TODO: A lot of logic in this function is shared with ExportNameScopeToCpp.
  100. // This should be refactored.
  101. if (class_type.specific_id.has_value()) {
  102. context.TODO(loc_id, "interop with specific class");
  103. return nullptr;
  104. }
  105. const auto& class_info = context.classes().Get(class_type.class_id);
  106. // If this class was produced by importing a C++ declaration or has
  107. // already been exported to C++, return the corresponding Clang declaration.
  108. // That could either be a CXXRecordDecl or an EnumDecl.
  109. if (auto* decl_context =
  110. GetClangDeclContextForScope(context, class_info.scope_id)) {
  111. return cast<clang::TagDecl>(decl_context);
  112. }
  113. auto* identifier_info = GetClangIdentifierInfo(context, class_info.name_id);
  114. CARBON_CHECK(identifier_info, "non-identifier class name {0}",
  115. class_info.name_id);
  116. auto* decl_context =
  117. ExportNameScopeToCpp(context, loc_id, class_info.parent_scope_id);
  118. // TODO: Provide a source location.
  119. auto* record_decl = clang::CXXRecordDecl::Create(
  120. context.ast_context(), clang::TagTypeKind::Class, decl_context,
  121. clang::SourceLocation(), clang::SourceLocation(), identifier_info);
  122. // If this is a member class, set its access.
  123. if (isa<clang::CXXRecordDecl>(decl_context)) {
  124. // TODO: Map Carbon access to C++ access.
  125. record_decl->setAccess(clang::AS_public);
  126. }
  127. record_decl->setHasExternalLexicalStorage();
  128. record_decl->setHasExternalVisibleStorage();
  129. auto key =
  130. SemIR::ClangDeclKey::ForNonFunctionDecl(cast<clang::Decl>(record_decl));
  131. auto clang_decl_id =
  132. context.clang_decls().Add({.key = key, .inst_id = class_inst_id});
  133. if (class_info.scope_id.has_value()) {
  134. // TODO: Record the Carbon class -> clang declaration mapping for incomplete
  135. // classes too.
  136. context.name_scopes()
  137. .Get(class_info.scope_id)
  138. .set_clang_decl_context_id(clang_decl_id, /*is_cpp_scope=*/false);
  139. }
  140. return record_decl;
  141. }
  142. // Create a `clang::FunctionDecl` for the given Carbon function. This
  143. // can be used to call the Carbon function from C++. The Carbon
  144. // function's ABI must be compatible with C++.
  145. //
  146. // The resulting decl is used to allow a generated C++ function to call
  147. // a generated Carbon function.
  148. static auto BuildCppFunctionDeclForCarbonFn(Context& context,
  149. SemIR::LocId loc_id,
  150. SemIR::FunctionId function_id)
  151. -> clang::FunctionDecl* {
  152. auto clang_loc = GetCppLocation(context, loc_id);
  153. const SemIR::Function& function = context.functions().Get(function_id);
  154. // Get parameters types.
  155. auto carbon_function_params =
  156. context.inst_blocks().Get(function.call_param_patterns_id);
  157. llvm::SmallVector<clang::QualType> cpp_param_types;
  158. for (auto param_inst_id : carbon_function_params) {
  159. auto scrutinee_type_id = ExtractScrutineeType(
  160. context.sem_ir(), context.insts().Get(param_inst_id).type_id());
  161. auto cpp_type = MapToCppType(context, scrutinee_type_id);
  162. if (cpp_type.isNull()) {
  163. context.TODO(loc_id, "failed to map C++ type to Carbon");
  164. return nullptr;
  165. }
  166. auto ref_type = context.ast_context().getLValueReferenceType(cpp_type);
  167. cpp_param_types.push_back(ref_type);
  168. }
  169. CARBON_CHECK(function.return_type_inst_id == SemIR::TypeInstId::None);
  170. auto cpp_return_type = context.ast_context().VoidTy;
  171. auto cpp_function_type = context.ast_context().getFunctionType(
  172. cpp_return_type, cpp_param_types,
  173. clang::FunctionProtoType::ExtProtoInfo());
  174. auto* identifier_info = GetClangIdentifierInfo(context, function.name_id);
  175. CARBON_CHECK(identifier_info, "function with non-identifier name {0}",
  176. function.name_id);
  177. auto* function_decl = clang::FunctionDecl::Create(
  178. context.ast_context(), context.ast_context().getTranslationUnitDecl(),
  179. /*StartLoc=*/clang_loc, /*NLoc=*/clang_loc, identifier_info,
  180. cpp_function_type, /*TInfo=*/nullptr, clang::SC_Extern);
  181. // Build parameter decls.
  182. llvm::SmallVector<clang::ParmVarDecl*> param_var_decls;
  183. for (auto [i, type] : llvm::enumerate(cpp_param_types)) {
  184. clang::ParmVarDecl* param = clang::ParmVarDecl::Create(
  185. context.ast_context(), function_decl, /*StartLoc=*/clang_loc,
  186. /*IdLoc=*/clang_loc, /*Id=*/nullptr, type, /*TInfo=*/nullptr,
  187. clang::SC_None, /*DefArg=*/nullptr);
  188. param_var_decls.push_back(param);
  189. }
  190. function_decl->setParams(param_var_decls);
  191. // Mangle the function name and attach it to the `FunctionDecl`.
  192. SemIR::Mangler m(context.sem_ir(), context.total_ir_count());
  193. std::string mangled_name = m.Mangle(function_id, SemIR::SpecificId::None);
  194. function_decl->addAttr(
  195. clang::AsmLabelAttr::Create(context.ast_context(), mangled_name));
  196. return function_decl;
  197. }
  198. // Create the declaration of the C++ thunk.
  199. static auto BuildCppToCarbonThunkDecl(
  200. Context& context, SemIR::LocId loc_id, clang::DeclContext* decl_context,
  201. clang::DeclarationName thunk_name,
  202. llvm::ArrayRef<clang::QualType> thunk_param_types) -> clang::FunctionDecl* {
  203. clang::ASTContext& ast_context = context.ast_context();
  204. auto clang_loc = GetCppLocation(context, loc_id);
  205. clang::DeclarationNameInfo name_info(thunk_name, clang_loc);
  206. auto ext_proto_info = clang::FunctionProtoType::ExtProtoInfo();
  207. clang::QualType thunk_function_type = ast_context.getFunctionType(
  208. ast_context.VoidTy, thunk_param_types, ext_proto_info);
  209. auto* tinfo =
  210. ast_context.getTrivialTypeSourceInfo(thunk_function_type, clang_loc);
  211. const bool uses_fp_intrin = false;
  212. const bool inline_specified = true;
  213. const auto constexpr_kind = clang::ConstexprSpecKind::Unspecified;
  214. const auto trailing_requires_clause = clang::AssociatedConstraint();
  215. clang::FunctionDecl* thunk_function_decl = nullptr;
  216. if (auto* parent_class = dyn_cast<clang::CXXRecordDecl>(decl_context)) {
  217. // TODO: Support non-static methods.
  218. thunk_function_decl = clang::CXXMethodDecl::Create(
  219. ast_context, parent_class, clang_loc, name_info, thunk_function_type,
  220. tinfo, clang::SC_Static, uses_fp_intrin, inline_specified,
  221. constexpr_kind, clang_loc, trailing_requires_clause);
  222. // TODO: Map Carbon access to C++ access.
  223. thunk_function_decl->setAccess(clang::AS_public);
  224. } else {
  225. thunk_function_decl = clang::FunctionDecl::Create(
  226. ast_context, decl_context, clang_loc, name_info, thunk_function_type,
  227. tinfo, clang::SC_None, uses_fp_intrin, inline_specified,
  228. /*hasWrittenPrototype=*/true, constexpr_kind, trailing_requires_clause);
  229. }
  230. decl_context->addDecl(thunk_function_decl);
  231. llvm::SmallVector<clang::ParmVarDecl*> param_var_decls;
  232. for (auto [i, type] : llvm::enumerate(thunk_param_types)) {
  233. clang::ParmVarDecl* thunk_param = clang::ParmVarDecl::Create(
  234. ast_context, thunk_function_decl, /*StartLoc=*/clang_loc,
  235. /*IdLoc=*/clang_loc, /*Id=*/nullptr, type,
  236. /*TInfo=*/nullptr, clang::SC_None, /*DefArg=*/nullptr);
  237. param_var_decls.push_back(thunk_param);
  238. }
  239. thunk_function_decl->setParams(param_var_decls);
  240. // Force the thunk to be inlined and discarded.
  241. thunk_function_decl->addAttr(
  242. clang::AlwaysInlineAttr::CreateImplicit(ast_context));
  243. thunk_function_decl->addAttr(
  244. clang::InternalLinkageAttr::CreateImplicit(ast_context));
  245. return thunk_function_decl;
  246. }
  247. // Create the body of a C++ thunk that calls a Carbon thunk. The
  248. // arguments are passed by reference to the callee.
  249. static auto BuildCppToCarbonThunkBody(clang::Sema& sema,
  250. clang::FunctionDecl* function_decl,
  251. clang::FunctionDecl* callee_function_decl)
  252. -> clang::StmtResult {
  253. clang::SourceLocation clang_loc = function_decl->getLocation();
  254. clang::ExprResult callee = sema.BuildDeclRefExpr(
  255. callee_function_decl, callee_function_decl->getType(), clang::VK_PRValue,
  256. clang_loc);
  257. llvm::SmallVector<clang::Expr*> call_args;
  258. for (auto* param : function_decl->parameters()) {
  259. clang::Expr* call_arg =
  260. sema.BuildDeclRefExpr(param, param->getType().getNonReferenceType(),
  261. clang::VK_LValue, clang_loc);
  262. call_args.push_back(call_arg);
  263. }
  264. clang::ExprResult call = sema.BuildCallExpr(nullptr, callee.get(), clang_loc,
  265. call_args, clang_loc);
  266. CARBON_CHECK(call.isUsable());
  267. return call.get();
  268. }
  269. // Create a C++ thunk that calls the Carbon thunk. The C++ thunk's
  270. // parameter types are mapped from the parameters of the target function
  271. // with `MapToCppType`. (Note that the target function here is the
  272. // callee of the Carbon thunk.)
  273. static auto BuildCppToCarbonThunk(
  274. Context& context, SemIR::LocId loc_id, clang::DeclContext* decl_context,
  275. llvm::StringRef base_name, clang::FunctionDecl* carbon_function_decl,
  276. llvm::ArrayRef<SemIR::TypeId> callee_param_type_ids)
  277. -> clang::FunctionDecl* {
  278. // Create the thunk's name.
  279. llvm::SmallString<64> thunk_name = base_name;
  280. thunk_name += "__cpp_thunk";
  281. auto& thunk_ident = context.ast_context().Idents.get(thunk_name);
  282. llvm::SmallVector<clang::QualType> param_types;
  283. for (auto type_id : callee_param_type_ids) {
  284. auto cpp_type = MapToCppType(context, type_id);
  285. if (cpp_type.isNull()) {
  286. context.TODO(loc_id, "failed to map C++ type to Carbon");
  287. return nullptr;
  288. }
  289. param_types.push_back(cpp_type);
  290. }
  291. auto* thunk_function_decl = BuildCppToCarbonThunkDecl(
  292. context, loc_id, decl_context, &thunk_ident, param_types);
  293. // Build the thunk function body.
  294. clang::Sema& sema = context.clang_sema();
  295. clang::Sema::ContextRAII context_raii(sema, thunk_function_decl);
  296. sema.ActOnStartOfFunctionDef(nullptr, thunk_function_decl);
  297. clang::StmtResult body = BuildCppToCarbonThunkBody(sema, thunk_function_decl,
  298. carbon_function_decl);
  299. sema.ActOnFinishFunctionBody(thunk_function_decl, body.get());
  300. CARBON_CHECK(!body.isInvalid());
  301. context.clang_sema().getASTConsumer().HandleTopLevelDecl(
  302. clang::DeclGroupRef(thunk_function_decl));
  303. return thunk_function_decl;
  304. }
  305. // Create a Carbon thunk that calls `callee`. The thunk's parameters are
  306. // all references to the callee parameter type.
  307. static auto BuildCarbonToCarbonThunk(
  308. Context& context, SemIR::LocId loc_id, const SemIR::Function& callee,
  309. llvm::ArrayRef<SemIR::TypeId> callee_param_type_ids) -> SemIR::FunctionId {
  310. // Create the thunk's name.
  311. llvm::SmallString<64> thunk_name =
  312. context.names().GetFormatted(callee.name_id);
  313. thunk_name += "__carbon_thunk";
  314. auto& ident = context.ast_context().Idents.get(thunk_name);
  315. auto thunk_name_id =
  316. SemIR::NameId::ForIdentifier(context.identifiers().Add(ident.getName()));
  317. auto carbon_thunk_function_id =
  318. MakeGeneratedFunctionDecl(context, loc_id,
  319. {.parent_scope_id = callee.parent_scope_id,
  320. .name_id = thunk_name_id,
  321. .param_type_ids = callee_param_type_ids,
  322. .params_are_refs = true})
  323. .second;
  324. BuildThunkDefinition(context, carbon_thunk_function_id,
  325. carbon_thunk_function_id, callee.first_decl_id(),
  326. callee.first_decl_id());
  327. return carbon_thunk_function_id;
  328. }
  329. auto ExportFunctionToCpp(Context& context, SemIR::LocId loc_id,
  330. SemIR::FunctionId callee_function_id)
  331. -> clang::FunctionDecl* {
  332. const SemIR::Function& callee = context.functions().Get(callee_function_id);
  333. if (callee.return_type_inst_id != SemIR::TypeInstId::None) {
  334. context.TODO(loc_id,
  335. "unsupported: C++ calling a Carbon function with "
  336. "return type other than `()`");
  337. return nullptr;
  338. }
  339. if (callee.generic_id.has_value()) {
  340. context.TODO(loc_id,
  341. "unsupported: C++ calling a Carbon function with "
  342. "generic parameters");
  343. return nullptr;
  344. }
  345. if (callee.call_param_ranges.implicit_size() != 0) {
  346. context.TODO(loc_id,
  347. "unsupported: C++ calling a Carbon function with "
  348. "an implicit parameter");
  349. return nullptr;
  350. }
  351. // Map the parent scope into the C++ AST.
  352. auto* decl_context =
  353. ExportNameScopeToCpp(context, loc_id, callee.parent_scope_id);
  354. if (!decl_context) {
  355. return nullptr;
  356. }
  357. // Get the parameter types of the Carbon function being called.
  358. auto callee_function_params =
  359. context.inst_blocks().Get(callee.call_param_patterns_id);
  360. llvm::SmallVector<SemIR::TypeId> callee_param_type_ids;
  361. for (auto callee_param_inst_id : callee_function_params) {
  362. auto scrutinee_type_id = ExtractScrutineeType(
  363. context.sem_ir(), context.insts().Get(callee_param_inst_id).type_id());
  364. callee_param_type_ids.push_back(scrutinee_type_id);
  365. }
  366. // Create a Carbon thunk that calls the callee. The thunk's parameters
  367. // are all references so that the ABI is compatible with C++ callers.
  368. auto carbon_thunk_function_id =
  369. BuildCarbonToCarbonThunk(context, loc_id, callee, callee_param_type_ids);
  370. // Create a `clang::FunctionDecl` that can be used to call the Carbon thunk.
  371. auto* carbon_function_decl = BuildCppFunctionDeclForCarbonFn(
  372. context, loc_id, carbon_thunk_function_id);
  373. if (!carbon_function_decl) {
  374. return nullptr;
  375. }
  376. // Create a C++ thunk that calls the Carbon thunk.
  377. return BuildCppToCarbonThunk(context, loc_id, decl_context,
  378. context.names().GetFormatted(callee.name_id),
  379. carbon_function_decl, callee_param_type_ids);
  380. }
  381. } // namespace Carbon::Check