cpp_thunk.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  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_thunk.h"
  5. #include "clang/AST/GlobalDecl.h"
  6. #include "clang/AST/Mangle.h"
  7. #include "clang/Sema/Sema.h"
  8. #include "toolchain/check/call.h"
  9. #include "toolchain/check/context.h"
  10. #include "toolchain/check/control_flow.h"
  11. #include "toolchain/check/literal.h"
  12. #include "toolchain/check/type.h"
  13. #include "toolchain/check/type_completion.h"
  14. #include "toolchain/sem_ir/ids.h"
  15. #include "toolchain/sem_ir/typed_insts.h"
  16. namespace Carbon::Check {
  17. // Returns the C++ thunk mangled name given the callee function.
  18. static auto GenerateThunkMangledName(
  19. clang::ASTContext& ast_context,
  20. const clang::FunctionDecl& callee_function_decl) -> std::string {
  21. RawStringOstream mangled_name_stream;
  22. {
  23. // TODO: Create `MangleContext` once.
  24. std::unique_ptr<clang::MangleContext> mangle_context(
  25. ast_context.createMangleContext());
  26. mangle_context->mangleName(clang::GlobalDecl(&callee_function_decl),
  27. mangled_name_stream);
  28. }
  29. mangled_name_stream << ".carbon_thunk";
  30. return mangled_name_stream.TakeStr();
  31. }
  32. // Returns true if a C++ thunk is required for the given type. A C++ thunk is
  33. // required for any type except for void, pointer types and signed 32-bit and
  34. // 64-bit integers.
  35. static auto IsThunkRequiredForType(Context& context, SemIR::TypeId type_id)
  36. -> bool {
  37. if (!type_id.has_value() || type_id == SemIR::ErrorInst::TypeId) {
  38. return false;
  39. }
  40. type_id = context.types().GetUnqualifiedType(type_id);
  41. switch (context.types().GetAsInst(type_id).kind()) {
  42. case SemIR::PointerType::Kind: {
  43. return false;
  44. }
  45. case SemIR::ClassType::Kind: {
  46. if (!context.types().IsComplete(type_id)) {
  47. // Signed integers of 32 or 64 bits should be completed when imported.
  48. return true;
  49. }
  50. if (!context.types().IsSignedInt(type_id)) {
  51. return true;
  52. }
  53. llvm::APInt bit_width =
  54. context.ints().Get(context.types().GetIntTypeInfo(type_id).bit_width);
  55. return bit_width != 32 && bit_width != 64;
  56. }
  57. default:
  58. return true;
  59. }
  60. }
  61. auto IsCppThunkRequired(Context& context, const SemIR::Function& function)
  62. -> bool {
  63. if (!function.clang_decl_id.has_value()) {
  64. return false;
  65. }
  66. if (function.self_param_id.has_value()) {
  67. // TODO: Support member methods.
  68. return false;
  69. }
  70. SemIR::TypeId return_type_id =
  71. function.GetDeclaredReturnType(context.sem_ir());
  72. if (return_type_id.has_value()) {
  73. // TODO: Support non-void return values.
  74. return false;
  75. }
  76. bool thunk_required_for_param = false;
  77. for (auto param_id :
  78. context.inst_blocks().GetOrEmpty(function.call_params_id)) {
  79. if (param_id == SemIR::ErrorInst::InstId) {
  80. return false;
  81. }
  82. if (!thunk_required_for_param &&
  83. IsThunkRequiredForType(
  84. context,
  85. context.insts().GetAs<SemIR::AnyParam>(param_id).type_id)) {
  86. thunk_required_for_param = true;
  87. }
  88. }
  89. return thunk_required_for_param;
  90. }
  91. // Returns whether the type is a pointer or a signed int of 32 or 64 bits.
  92. static auto IsSimpleAbiType(clang::ASTContext& ast_context,
  93. clang::QualType type) -> bool {
  94. if (type->isPointerType()) {
  95. return true;
  96. }
  97. if (const auto* builtin_type = type->getAs<clang::BuiltinType>()) {
  98. if (builtin_type->isSignedInteger()) {
  99. uint64_t type_size = ast_context.getIntWidth(type);
  100. return type_size == 32 || type_size == 64;
  101. }
  102. }
  103. return false;
  104. }
  105. // Creates the thunk parameter types given the callee function. Also returns for
  106. // each type whether it is different from the matching callee function parameter
  107. // type.
  108. static auto BuildThunkParameterTypes(
  109. clang::ASTContext& ast_context,
  110. const clang::FunctionDecl& callee_function_decl)
  111. -> std::tuple<llvm::SmallVector<clang::QualType>, llvm::SmallVector<bool>> {
  112. std::tuple<llvm::SmallVector<clang::QualType>, llvm::SmallVector<bool>>
  113. result;
  114. auto& [thunk_param_types, param_type_changed] = result;
  115. unsigned num_params = callee_function_decl.getNumParams();
  116. thunk_param_types.reserve(num_params);
  117. param_type_changed.reserve(num_params);
  118. for (const clang::ParmVarDecl* callee_param :
  119. callee_function_decl.parameters()) {
  120. clang::QualType param_type = callee_param->getType();
  121. bool is_simple_abi_type = IsSimpleAbiType(ast_context, param_type);
  122. if (!is_simple_abi_type) {
  123. clang::QualType pointer_type = ast_context.getPointerType(param_type);
  124. param_type = ast_context.getAttributedType(
  125. clang::NullabilityKind::NonNull, pointer_type, pointer_type);
  126. }
  127. param_type_changed.push_back(!is_simple_abi_type);
  128. thunk_param_types.push_back(param_type);
  129. }
  130. return result;
  131. }
  132. // Returns the thunk parameters using the callee function parameter identifiers.
  133. static auto BuildThunkParameters(
  134. clang::ASTContext& ast_context,
  135. const clang::FunctionDecl& callee_function_decl,
  136. clang::FunctionDecl* thunk_function_decl)
  137. -> llvm::SmallVector<clang::ParmVarDecl*> {
  138. clang::SourceLocation clang_loc = callee_function_decl.getLocation();
  139. unsigned num_params = thunk_function_decl->getNumParams();
  140. CARBON_CHECK(callee_function_decl.getNumParams() == num_params);
  141. const auto* thunk_function_proto_type =
  142. thunk_function_decl->getFunctionType()->getAs<clang::FunctionProtoType>();
  143. llvm::SmallVector<clang::ParmVarDecl*> thunk_params;
  144. thunk_params.reserve(num_params);
  145. for (unsigned i = 0; i < num_params; ++i) {
  146. clang::ParmVarDecl* thunk_param = clang::ParmVarDecl::Create(
  147. ast_context, thunk_function_decl, clang_loc, clang_loc,
  148. callee_function_decl.getParamDecl(i)->getIdentifier(),
  149. thunk_function_proto_type->getParamType(i), nullptr, clang::SC_None,
  150. nullptr);
  151. thunk_params.push_back(thunk_param);
  152. }
  153. return thunk_params;
  154. }
  155. // Returns the thunk function declaration given the callee function and the
  156. // thunk parameter types.
  157. static auto CreateThunkFunctionDecl(
  158. clang::ASTContext& ast_context,
  159. const clang::FunctionDecl& callee_function_decl,
  160. llvm::ArrayRef<clang::QualType> thunk_param_types) -> clang::FunctionDecl* {
  161. clang::SourceLocation clang_loc = callee_function_decl.getLocation();
  162. clang::IdentifierInfo& identifier_info = ast_context.Idents.get(
  163. callee_function_decl.getNameAsString() + "__carbon_thunk");
  164. const auto* callee_function_type = callee_function_decl.getFunctionType()
  165. ->castAs<clang::FunctionProtoType>();
  166. // TODO: Check whether we need to modify `ExtParameterInfo` in `ExtProtoInfo`.
  167. clang::QualType thunk_function_type = ast_context.getFunctionType(
  168. callee_function_decl.getReturnType(), thunk_param_types,
  169. callee_function_type->getExtProtoInfo());
  170. // TODO: Thunks should not have external linkage, consider using `SC_Static`.
  171. clang::FunctionDecl* thunk_function_decl = clang::FunctionDecl::Create(
  172. ast_context, ast_context.getTranslationUnitDecl(), clang_loc, clang_loc,
  173. clang::DeclarationName(&identifier_info), thunk_function_type,
  174. /*TInfo=*/nullptr, clang::SC_Extern);
  175. thunk_function_decl->setParams(BuildThunkParameters(
  176. ast_context, callee_function_decl, thunk_function_decl));
  177. // Set always_inline.
  178. thunk_function_decl->addAttr(
  179. clang::AlwaysInlineAttr::CreateImplicit(ast_context));
  180. // Set asm("<callee function mangled name>.carbon_thunk").
  181. thunk_function_decl->addAttr(clang::AsmLabelAttr::CreateImplicit(
  182. ast_context, GenerateThunkMangledName(ast_context, callee_function_decl),
  183. clang_loc));
  184. return thunk_function_decl;
  185. }
  186. // Takes the thunk function parameters and for each one creates an arg for the
  187. // callee function which is the thunk parameter or its address.
  188. static auto BuildCalleeArgs(clang::Sema& sema,
  189. clang::FunctionDecl* thunk_function_decl,
  190. llvm::ArrayRef<bool> param_type_changed)
  191. -> llvm::SmallVector<clang::Expr*> {
  192. llvm::SmallVector<clang::Expr*> call_args;
  193. size_t num_params = thunk_function_decl->getNumParams();
  194. CARBON_CHECK(param_type_changed.size() == num_params);
  195. call_args.reserve(num_params);
  196. for (unsigned i = 0; i < num_params; ++i) {
  197. clang::ParmVarDecl* thunk_param = thunk_function_decl->getParamDecl(i);
  198. clang::SourceLocation clang_loc = thunk_param->getLocation();
  199. clang::Expr* call_arg = sema.BuildDeclRefExpr(
  200. thunk_param, thunk_param->getType(), clang::VK_LValue, clang_loc);
  201. if (param_type_changed[i]) {
  202. // TODO: Insert a cast to an rvalue.
  203. clang::ExprResult deref_result =
  204. sema.BuildUnaryOp(nullptr, clang_loc, clang::UO_Deref, call_arg);
  205. CARBON_CHECK(deref_result.isUsable());
  206. call_arg = deref_result.get();
  207. }
  208. call_args.push_back(call_arg);
  209. }
  210. return call_args;
  211. }
  212. // Builds the thunk function body which calls the callee function using the call
  213. // args and returns the callee function return value. Returns nullptr on
  214. // failure.
  215. static auto BuildThunkBody(clang::Sema& sema,
  216. clang::FunctionDecl* callee_function_decl,
  217. llvm::MutableArrayRef<clang::Expr*> call_args)
  218. -> clang::Stmt* {
  219. clang::SourceLocation clang_loc = callee_function_decl->getLocation();
  220. clang::DeclRefExpr* callee_function_ref = sema.BuildDeclRefExpr(
  221. callee_function_decl, callee_function_decl->getType(), clang::VK_PRValue,
  222. clang_loc);
  223. clang::ExprResult call_result = sema.BuildCallExpr(
  224. nullptr, callee_function_ref, clang_loc, call_args, clang_loc);
  225. if (!call_result.isUsable()) {
  226. return nullptr;
  227. }
  228. clang::Expr* call = call_result.get();
  229. clang::StmtResult return_result = sema.BuildReturnStmt(clang_loc, call);
  230. CARBON_CHECK(return_result.isUsable());
  231. return return_result.get();
  232. }
  233. auto BuildCppThunk(Context& context, const SemIR::Function& callee_function)
  234. -> clang::FunctionDecl* {
  235. clang::ASTContext& ast_context = context.ast_context();
  236. clang::FunctionDecl* callee_function_decl =
  237. context.sem_ir()
  238. .clang_decls()
  239. .Get(callee_function.clang_decl_id)
  240. .decl->getAsFunction();
  241. CARBON_CHECK(callee_function_decl);
  242. // Build the thunk function declaration.
  243. auto [thunk_param_types, param_type_changed] =
  244. BuildThunkParameterTypes(ast_context, *callee_function_decl);
  245. clang::FunctionDecl* thunk_function_decl = CreateThunkFunctionDecl(
  246. ast_context, *callee_function_decl, thunk_param_types);
  247. // Build the thunk function body.
  248. clang::Sema& sema = context.sem_ir().cpp_ast()->getSema();
  249. clang::Sema::ContextRAII context_raii(sema, thunk_function_decl);
  250. sema.ActOnStartOfFunctionDef(nullptr, thunk_function_decl);
  251. llvm::SmallVector<clang::Expr*> call_args =
  252. BuildCalleeArgs(sema, thunk_function_decl, param_type_changed);
  253. clang::Stmt* body = BuildThunkBody(sema, callee_function_decl, call_args);
  254. sema.ActOnFinishFunctionBody(thunk_function_decl, body);
  255. if (!body) {
  256. return nullptr;
  257. }
  258. return thunk_function_decl;
  259. }
  260. auto PerformCppThunkCall(Context& context, SemIR::LocId loc_id,
  261. SemIR::FunctionId callee_function_id,
  262. llvm::ArrayRef<SemIR::InstId> callee_arg_ids,
  263. SemIR::InstId thunk_callee_id) -> SemIR::InstId {
  264. llvm::ArrayRef<SemIR::InstId> callee_function_params =
  265. context.inst_blocks().GetOrEmpty(
  266. context.functions().Get(callee_function_id).call_params_id);
  267. llvm::ArrayRef<SemIR::InstId> thunk_function_params =
  268. context.inst_blocks().GetOrEmpty(
  269. context.functions()
  270. .Get(GetCalleeFunction(context.sem_ir(), thunk_callee_id)
  271. .function_id)
  272. .call_params_id);
  273. size_t num_params = callee_function_params.size();
  274. CARBON_CHECK(thunk_function_params.size() == num_params);
  275. CARBON_CHECK(callee_arg_ids.size() == num_params);
  276. llvm::SmallVector<SemIR::InstId> thunk_arg_ids;
  277. thunk_arg_ids.reserve(callee_arg_ids.size());
  278. for (size_t i = 0; i < callee_function_params.size(); ++i) {
  279. SemIR::TypeId callee_param_type_id =
  280. context.insts()
  281. .GetAs<SemIR::AnyParam>(callee_function_params[i])
  282. .type_id;
  283. SemIR::TypeId thunk_param_type_id =
  284. context.insts()
  285. .GetAs<SemIR::AnyParam>(thunk_function_params[i])
  286. .type_id;
  287. SemIR::InstId arg_id = callee_arg_ids[i];
  288. if (callee_param_type_id != thunk_param_type_id) {
  289. CARBON_CHECK(thunk_param_type_id ==
  290. GetPointerType(context, context.types().GetInstId(
  291. callee_param_type_id)));
  292. // TODO: Don't create storage if it's already in a storage (depends on
  293. // expression category).
  294. SemIR::InstId temporary_storage_inst_id = AddInstWithCleanup(
  295. context, loc_id,
  296. SemIR::TemporaryStorage{.type_id = callee_param_type_id});
  297. AddInst(context, loc_id,
  298. SemIR::InitializeFrom{.type_id = callee_param_type_id,
  299. .src_id = arg_id,
  300. .dest_id = temporary_storage_inst_id});
  301. // TODO: Do not use `InitializeFrom` directly. Use the `Initialize`
  302. // machinery. See
  303. // https://github.com/carbon-language/carbon-lang/pull/5850/files#r2249030529.
  304. arg_id = AddInst(context, loc_id,
  305. SemIR::AddrOf{.type_id = thunk_param_type_id,
  306. .lvalue_id = temporary_storage_inst_id});
  307. }
  308. thunk_arg_ids.push_back(arg_id);
  309. }
  310. return PerformCall(context, loc_id, thunk_callee_id, thunk_arg_ids);
  311. }
  312. } // namespace Carbon::Check